commerce-operations-mcp
by mukundjha-mj
README.md
# Commerce Operations MCP
A focused MCP v2 service that helps a commerce operations analyst investigate a stuck synthetic order, explain the blocker from stored facts, and create an auditable human-review escalation without changing fulfillment state.
The repository was initialized with the official Turborepo generator and then reduced to three TypeScript workspaces:
- `apps/mcp-server`: authenticated stateless Streamable HTTP MCP and health endpoints
- `packages/domain`: deterministic diagnosis, validation schemas, and domain errors
- `packages/database`: Drizzle schema/migrations, stable fixtures, queries, and serializable escalation transaction
## Product scope
The MCP is deliberately read-heavy and exposes exactly one bounded write. It has three tools:
- `get_order_context` reads the order, latest payment, required inventory lines, and latest fulfillment attempt.
- `diagnose_order_blocker` selects exactly one diagnosis using documented deterministic precedence. It never mutates state.
- `create_order_escalation` creates only a human-review escalation, and only with `confirmed: true`, the diagnosed order version, and a stable idempotency key. It never changes the order or fulfillment attempts.
No frontend, real commerce integration, automated remediation, arbitrary mutation, customer PII, or production payment/provider action is included. Retry, requeue, reroute, cancellation, and address modification are explicitly prohibited.
## Architecture
```text
MCP client -> HTTPS/Caddy -> TypeScript MCP v2 server -> Drizzle/pg -> Neon PostgreSQL
|
+-> deterministic domain policy
```
The domain policy is separate from MCP transport and persistence. Repository reads, joins, inserts, conflict handling, row locks, updates, transactions, fixture writes, and test assertions use Drizzle ORM against the schema definitions. The write transaction reserves the idempotency key, locks and rechecks the order and diagnosis, creates the escalation and audit event, and stores the replay result under `SERIALIZABLE` isolation. It contains no fulfillment-attempt insertion or order update. A failed transaction leaves no durable `PENDING` request. Raw SQL is limited to generated/custom migrations and PostgreSQL/operator controls such as `SET LOCAL statement_timeout`, transaction commands in CLI scripts, and schema teardown.
## Prerequisites and setup
- Node.js 22+ locally; the production image uses the maintained Node.js 24 Bookworm-slim tag
- npm 10.9.8
- A PostgreSQL/Neon connection containing synthetic data only
Oxlint provides the real TypeScript-aware lint stage. It was selected because current stable `typescript-eslint` does not yet declare compatibility with the pinned stable TypeScript 7 compiler; type correctness remains a separate `npm run typecheck` gate.
```bash
npm install
cp .env.example .env
npm run db:migrate
npm run db:seed
npm test
npm run test:integration
npm run test:mcp
npm run build
npm run dev
```
The `.env` values must include a random demo token of at least 32 characters and a UTC expiry no more than seven days ahead. For Neon, use `sslmode=verify-full` in `DATABASE_URL`. Never commit or publish the token.
Operator-only database commands:
- `npm run db:reset` deterministically restores the five fixtures.
- `npm run db:fresh` drops only this application's seven synthetic tables and Drizzle metadata, reapplies migrations, and seeds. It is destructive and is never exposed as an MCP tool.
## HTTP and MCP usage
Local endpoint: `POST http://127.0.0.1:3000/mcp`
Hosted endpoint: `POST https://mcp.mukundjha.dev/mcp`
Source repository: <https://github.com/mukundjha-mj/commerce-operations-mcp>
The bearer token is delivered out of band and expires within seven days.
Every MCP POST requires:
- `Authorization: Bearer <demo-token>`
- `MCP-Protocol-Version: 2026-07-28`
- `Mcp-Method` exactly matching the JSON-RPC method
- `Mcp-Name` exactly matching `params.name` for `tools/call`, and omitted for discovery/listing
For Postman, import [`postman/commerce-operations-mcp.postman_collection.json`](postman/commerce-operations-mcp.postman_collection.json), leave `baseUrl` as `https://mcp.mukundjha.dev`, and set its `demoToken` collection variable to the short-lived bearer token provided privately. The collection contains no token and is safe to commit.
Example tool listing:
```bash
curl -X POST http://127.0.0.1:3000/mcp \
-H "Authorization: Bearer $DEMO_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "MCP-Protocol-Version: 2026-07-28" \
-H "Mcp-Method: tools/list" \
--data '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}'
```
Health checks:
- `GET /health/live` proves the process is running.
- `GET /health/ready` runs a minimal database query and exposes no internals.
## Stable fixtures
| Order | Expected diagnosis | Escalation |
| --- | --- | --- |
| `ord_timeout_001` | `FULFILLMENT_PROVIDER_FAILURE` | Allowed after confirmation |
| `ord_address_002` | `FULFILLMENT_NON_RETRYABLE_FAILURE` | Allowed after confirmation |
| `ord_payment_003` | `PAYMENT_NOT_CAPTURED` | Allowed after confirmation |
| `ord_done_004` | `FULFILLMENT_ALREADY_COMPLETE` | Refused |
| `ord_inventory_005` | `INVENTORY_NOT_RESERVED` | Allowed after confirmation |
## Safety and operational behavior
- Strict Zod objects reject malformed and unknown fields.
- Required inventory comes from order items and left-joins reservations, so a missing reservation cannot disappear from the evidence.
- Diagnosis never implies authorization. `confirmed: true` is caller attestation, not proof of a human identity.
- Version checking refuses stale diagnoses; idempotency replay returns the original escalation and audit IDs.
- Escalation is the only mutation. Tests assert that order version/status and fulfillment-attempt count remain unchanged.
- Origin allow-listing, expiring bearer token, 64 KiB body limit, 10-second request limit, eight-request concurrency cap, and in-memory source-IP/auth throttles protect the demo boundary. Forwarded client addresses are trusted only when `TRUST_PROXY=true`; Compose enables it because the app port is private behind Caddy, while direct local runs default to the socket peer.
- Pino logs include correlation IDs, timing, tool outcome, and structured error codes while redacting credentials. MCP tool failures are recorded as errors even though the protocol transports them in HTTP 200 responses. Tool errors never return SQL, stack traces, or connection details.
- The app binds to loopback locally. In Compose it is private behind Caddy; only ports 80/443 are published.
Token rotation: replace `DEMO_TOKEN` and `DEMO_TOKEN_EXPIRES_AT`, then recreate the app container. Emergency shutdown: stop the Compose stack or revoke its inbound 443 rule. Neither procedure logs the token.
## Verification
```bash
npm run typecheck
npm run lint
npm test
npm run test:integration
npm run test:mcp
npm run build
```
Unit/HTTP tests cover diagnosis precedence, inventory precedence, strict inputs including forbidden retry actions, configuration expiry, authentication, origins, limits, health, MCP metadata, mandatory server discovery, and the three-tool listing. The Neon repository suite verifies context, all fixture diagnoses, atomic escalation/audit creation, replay, concurrent replay, idempotency conflict, stale version, refusal, unchanged operational state, and absence of durable pending rows. The separate MCP suite exercises the complete workflow through the real HTTP handler and Neon.
## Deployment
`Dockerfile`, `compose.yaml`, and `Caddyfile` provide a reproducible single-host deployment using Neon as requested. Set `MCP_HOSTNAME` to the public hostname and make `ALLOWED_ORIGINS` match approved browser origins. Keep the app port private and expose only Caddy on 80/443.
For the requested Ubuntu 24.04 EC2 deployment in Mumbai (`ap-south-1`), follow the [AWS HTTPS deployment runbook](docs/AWS_HTTPS_DEPLOYMENT.md). It includes Elastic IP/`sslip.io`, security-group, secret, smoke-test, rotation, rollback, and emergency-shutdown steps.
See [AI_WORKLOG.md](AI_WORKLOG.md) for the AI-assisted implementation approach, corrections, verification, and limitations.
## Assumptions, exclusions, and limitations
- Neon replaces the PRD's single-host PostgreSQL container by explicit project direction.
- The shared token identifies a demo client, not an individual approver.
- In-memory rate limits are per process and are not a distributed production control.
- There is no queue, tenant isolation, real provider retry, user management, or high-availability/DR design.
- The demo video was recorded and its link shared with the reviewer as part of the final submission email.
- Remote Neon latency makes integration tests slower than local unit tests.
- `npm audit --omit=dev` reports the pinned MCP Node adapter through its Hono dependency because Hono's Windows static-file helper has a path-traversal advisory. This service never mounts or calls that static-file helper, runs in Linux, and no compatible upstream MCP dependency update is currently available. The remaining audit findings are development-only Drizzle Kit/esbuild tooling and are pruned from the runtime image.
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues