# mcp-znet

Read-only MCP server + Catalog Data Layer for Znet's Magento 2 store, built for ChatGPT.
Design and decisions: **[ARCHITECTURE.md](ARCHITECTURE.md)**.

```
Magento ──► apps/sync ──► PostgreSQL ──► apps/catalog-api ──► apps/mcp-server ──► ChatGPT
   ▲                                                              │
   └────────────── live orders / salable stock ───────────────────┘
```

| Path | What |
|---|---|
| `packages/shared` | env loading (zod), logger, errors |
| `packages/magento` | Magento REST/GraphQL client (OAuth1 or bearer, retries, pagination) |
| `packages/catalog-db` | Drizzle schema + migrations, writes (sync), reads + quality checks (API) |
| `apps/sync` | pg-boss worker: reference, incremental, inventory, nightly full + reconciliation |
| `apps/catalog-api` | Internal Fastify API over the catalog |
| `apps/mcp-server` | Streamable HTTP MCP server, OAuth resource server, audit log |

## Run locally against Znet staging

1. Create the read-only Integration on staging and fill `MAGENTO_*` in `.env` — **[docs/magento-setup.md](docs/magento-setup.md)**.
2. `pnpm magento:check` — every row ✅.
3. Load and serve:
   ```bash
   pnpm db:migrate
   pnpm sync:once sync-reference
   pnpm sync:once sync-products-full
   pnpm dev:api      # terminal 1
   pnpm dev:mcp      # terminal 2
   pnpm dev:sync     # terminal 3 — keeps it fresh (2 min / hourly)
   ```
4. Try it: Claude Code `claude mcp add --transport http znet http://localhost:4200/mcp --header "Authorization: Bearer <token from MCP_STATIC_TOKENS>"`,
   or MCP Inspector. ChatGPT cannot reach `localhost`; it is tested after the server deploy.

## Local development

Requires Node 22, pnpm 11, PostgreSQL 16.

```bash
pnpm install
cp .env.example .env          # fill MAGENTO_* and CATALOG_API_TOKEN
createdb mcp_znet
pnpm db:migrate
pnpm sync:once sync-reference # then: sync-products-full, sync-inventory
pnpm dev:api                  # :4100
pnpm dev:mcp                  # :4200/mcp  (AUTH_MODE=static for local)
pnpm dev:sync                 # scheduler (optional locally)
```

Try it with MCP Inspector (`npx @modelcontextprotocol/inspector`), transport *Streamable HTTP*,
URL `http://127.0.0.1:4200/mcp`, header `Authorization: Bearer <token from MCP_STATIC_TOKENS>`.

## Tests

```bash
pnpm test     # unit + end-to-end (mock Magento → sync → Postgres → API → MCP process → MCP client)
```

```bash
pnpm perf 50000   # synthetic 50k-product catalog: sync timings, memory, API latency
```

No Magento handy? `pnpm exec tsx test/run-mock.ts 4900` serves the test fixtures as a fake Magento
(token `magento-test-token`) for running sync/MCP locally.

The e2e suite recreates `mcp_znet_test` on the local Postgres (`TEST_DATABASE_URL` to override).

## Schema changes

Edit `packages/catalog-db/src/schema.ts`, then:

```bash
pnpm db:generate --name <change>   # writes SQL to packages/catalog-db/migrations
pnpm db:migrate
```

Services also apply pending migrations on start (sync, sync:once).

## Deploy

`docker compose up -d --build` with `.env` plus `POSTGRES_PASSWORD`, `SYNC_DB_PASSWORD`,
`API_DB_PASSWORD`, `SERVER_DB_PASSWORD` and `MCP_DOMAIN` set. A one-shot `migrate` service applies
migrations and grants; each app then connects as its own least-privilege role. Only Caddy
(80/443) is public; it forwards `/mcp` and the OAuth well-known paths to the MCP server.
Production uses `AUTH_MODE=magento` with `MCP_AUTH_SECRET` and `MCP_USER_SCOPES` set (ARCHITECTURE.md §6.1).

## Authentication

| Hop | Method |
|---|---|
| MCP/sync → Magento | **OAuth 1.0a** Magento Integration (read-only ACL). Set all four `MAGENTO_*` key/secret values. Don't use an admin username/password token: it expires in ~4 h and needs 2FA. |
| ChatGPT user → MCP | **Magento admin sign-in**: username, password and Google Authenticator code on our login page, checked against Magento, never stored. Scopes per user from `MCP_USER_SCOPES`. |

```bash
pnpm auth:sessions           # who is connected
pnpm auth:revoke <username>  # sign someone out everywhere, now
```

## Connecting ChatGPT

ChatGPT › Settings › Apps & Connectors › Advanced › Developer mode → *Create* connector with
URL `https://<MCP_DOMAIN>/mcp`, authentication **OAuth**. ChatGPT registers itself, then opens our
sign-in page; staff log in with their Magento admin account.
