Skip to content
KnightkollaPublic

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Commerce Ops MCP

A remotely hosted MCP server that lets a frontline operations person diagnose stalled e-commerce orders and route the fix to the right team — without an engineer, and without the model ever deciding what's actually wrong. Diagnosis is deterministic TypeScript against a database snapshot; the model's job is to translate that diagnosis into a conversation and, where the action class allows it, into a routed escalation. All data is synthetic. Built as a paid take-home assessment.

Full design docs live in docs/: DATA_MODEL.md (entities and invariants), BUILD_SPEC.md (implementation spec), SYSTEM_DESIGN.md (the full design narrative), DEMO_SCRIPT.md (rehearsed walkthrough with live-confirmed outputs), AI_WORKLOG.md (how this was built, phase by phase).

Two non-negotiable principles

  1. The model never determines correctness. The same order produces the same findings every call. There is no LLM call anywhere in the diagnosis path.
  2. Diagnosis never confers authority. Whether an action may execute is decided by its action class alone, independent of how well-evidenced the finding is. A conclusive diagnosis does not authorize a write.

Hosted server

https://commerce-ops-mcp-production-d2b0.up.railway.app
  • GET /health — public, no auth, reports {status, db, seeded_orders, version}.
  • POST /mcp — the MCP endpoint (Streamable HTTP, stateless). Requires Authorization: Bearer <MCP_ACCESS_TOKEN>. The production token is not in this repo — retrieve it from wherever the person running this assessment shared it with you, or from the Railway project's variables if you have access.

Connect with the MCP inspector

npx @modelcontextprotocol/inspector@latest --cli \
  https://commerce-ops-mcp-production-d2b0.up.railway.app/mcp \
  --transport http \
  --header "Authorization: Bearer $MCP_ACCESS_TOKEN" \
  --method tools/list

Connect with Claude Desktop

Add to Claude Desktop's MCP config (Settings → Developer → Edit Config), substituting the real token:

{
  "mcpServers": {
    "commerce-ops": {
      "url": "https://commerce-ops-mcp-production-d2b0.up.railway.app/mcp",
      "headers": {
        "Authorization": "Bearer <MCP_ACCESS_TOKEN>"
      }
    }
  }
}

Restart Claude Desktop after editing. If your Claude Desktop version's config format for remote HTTP servers differs from the above, its own MCP settings UI will show the field names it expects — the two pieces of information you need are the URL and the bearer header.

Run it locally

Requires Node 22+ and a local Postgres. Copy .env.example to .env and fill in a real DATABASE_URL (the other four values can stay as generated placeholders for local dev, or generate your own with openssl rand -hex 32).

npm install
npm run migrate && npm run seed   # schema + deterministic 40-clean/10-dirty seed
npm run dev                        # http://localhost:3000

curl localhost:3000/health should show db: ok and seeded_orders: 50. Run the test suite with npm test (against whatever Postgres your local .env points at).

Reproducible test run (no local setup)

npm run test:isolated

One command, no .env, no local Postgres, no manual steps. It brings up a throwaway Postgres 16 container (docker-compose.test.yml, port 5433, tmpfs storage so nothing touches disk and nothing persists between runs, literal placeholder credentials), migrates it, seeds it, runs the full suite against it, and tears the container down afterward whether the suite passed or failed. This is the path a fresh clone or CI should use — it can't be pointed at the wrong database because it never reads your .env at all; see scripts/test-isolated.sh. Requires Docker running locally.

The six tools

Tool Writes Purpose
list_open_exceptions No Worklist of every order currently tripping a rule, bounded, severity/age-filterable
find_order No Look up an order by id, email, or phone
diagnose_order No Evidence-backed findings for one order, naming what can/can't be fixed and by whom
preview_action No Compute the exact diff one action would make for one finding, and mint a signed token for it
apply_action Yes — the only one Writes, only with a valid unexpired token bound to the exact diff previewed, idempotency-keyed
get_audit_trail No Every past write and review case for one order

Intended chain: list_open_exceptions → diagnose_order → (if executable) preview_action → apply_action → get_audit_trail. find_order substitutes for list_open_exceptions when you already have a customer identifier instead of a worklist.

The authority model

Whether an action may execute is decided by its class, never by how well-evidenced the finding is:

Class Executable Covers
EXTERNAL_EFFECT Never Any call to a processor, carrier, or warehouse system
FINANCIAL_STATE Never Financial status, captures, voids, refunds, holds
PHYSICAL_STATE Never Stock allocation/reservation, fulfillment requeue
POLICY_DECISION Never Address correction, fraud clearance
INTERNAL_RECORD Yes, gated by order value Review cases, internal diagnosis notes

Gate order is always class first, value second — a restricted class is refused at any order value; the value threshold (POLICY_MAX_ORDER_VALUE_PAISE) only ever applies inside INTERNAL_RECORD. Exactly two actions are executable: a_create_review_case and a_attach_diagnosis. Every other resolving action — reconciling payment, reallocating stock, requeuing fulfillment, correcting an address, clearing a fraud hold — escalates by design, no matter how conclusive the diagnosis. The authority gate runs twice on every write: once at preview_action, again at apply_action, because a token is never trusted alone.

Explicitly out of scope

Not built, on purpose: a frontend, authentication or user management, a complete commerce backend, CI/CD, a product catalogue, returns or refunds, a webhook receiver, carrier integration, multi-tenancy, or a review-case approval workflow (cases are created and routed; nothing in this system works or resolves them). audit_log.actor and review_cases.requested_by are caller-supplied strings by design — there is no auth layer to derive identity from.

Known limitations

  • No webhook event log. A missed fulfillment acknowledgement is inferred from the absence of a status transition past its tolerance window, not proven from a delivery record.
  • Caller-supplied identity. actor and requested_by are trusted strings from the MCP caller, not an authenticated session — there is no user/auth layer in scope.
  • Review cases are terminal at open. They're created and correctly routed; nothing in this system works, approves, or resolves them — that's a human process outside this system's boundary.
  • Inventory has no movement ledger. A discrepancy between order_lines.allocated_qty and inventory is detected, but the system can't explain how it arose, only that it exists.
  • Seed timestamps are relative to now, not fixed calendar dates. The tolerance-window rules (30 min / 4 h / 24 h) are evaluated against real elapsed time, so the seed data's "healthy" edge cases (an order seeded as "10 minutes past a capture, within the 30-minute window") will drift stale — and start reading as violations — if too much real time passes between seeding and use. Re-run npm run seed (locally) or railway ssh --service commerce-ops-mcp -- npm run seed (hosted) before a demo or test run if it's been more than a few hours since the last seed. This is inherent to testing tolerance-window logic against real time, not a bug.
  • Idempotency is check-then-act for a brand-new key under true simultaneous double-submission (the database's own unique constraint is the backstop for that narrow race, not independently load-tested here).

Rejected alternatives

Each considered and rejected for a stated reason — recorded here so they aren't quietly reintroduced later:

  • Materialised view for the worklist — staleness plus a refresh schedule, for a workload that needs neither.
  • A separate exceptions database — findings are silences, not caught errors; there's no write-time moment to log one, and the existing read/write table split already marks where a replica would go.
  • A reasoning agent inside the server — the client is already an agent; a second one doubles latency, splits retry ownership, and hides its reasoning from the operator.
  • Agent-first diagnosis with a deterministic fallback — runs the slow, non-reproducible path first and the fast, testable one as backup. The correct order is rules first, model on the residue.
  • A combined fix_order tool that diagnoses and writes in one call — collapses preview and apply, making confirmation cosmetic instead of real.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages