Commerce Ops MCP

Commerce Ops MCP

Enables commerce operations teams to investigate missing-delivery complaints and resolve them through natural language, with policy-enforced refunds and escalations.

Category
访问服务器

README

Commerce Ops MCP — Missing-Delivery Investigation

An MCP server that lets a commerce operations team investigate "my order never arrived" complaints and resolve them without pulling in an engineer.

The operator works through an AI client in plain language. The AI gathers evidence through read-only tools, then either issues a refund or files a human-review escalation. Every policy decision is made and enforced by this server, not by the AI client.

  • Hosted MCP endpoint: https://commerce-ops-mcp-production-c298.up.railway.app/mcp
  • Source: https://github.com/7vignesh/commerce-ops-mcp

The workflow

One bounded path, end to end:

Customer reports a missing delivery
  │
  ├─ lookup_order              find the order from an email, phone, or order ID
  ├─ get_order_details         order state, amounts, line items, prior actions
  ├─ check_shipment_status     carrier evidence — is there a real exception?
  ├─ get_customer_history      risk score and refund history
  │
  └─ issue_refund
       ├─ all six conditions pass  ──▶  refund applied, logged, idempotent
       └─ any condition fails      ──▶  manager-approval escalation

cancel_order and reship_order are deliberately escalation-only — see Safety model.


Safety model

The MCP consumer is treated as untrusted. An AI client can be confused by a persuasive customer, retry a call after a dropped connection, or simply hallucinate an argument. None of that can move money here, because the server re-derives every decision from the database.

Refund eligibility

issue_refund applies a refund only when all six conditions hold:

# Condition Threshold
1 Refund amount within cap ≤ $150
2 Amount does not exceed what remains refundable ≤ paid − already refunded
3 Order is recent ≤ 30 days old
4 Customer risk is acceptable risk score < 70
5 Carrier exception verified shipment is lost or returned
6 No existing refund on the order refunded amount = 0

Plus a payment sanity check: the payment must have actually succeeded.

If any condition fails, the refund is not applied. The server files a pending_approval escalation recording every failed condition as evidence, so the reviewer sees why the automation declined. Callers cannot override a threshold, and the AI is never asked to evaluate one — it only supplies an order ID, amount, and reason.

Condition 5 is the one that carries the most weight in practice: a customer saying a parcel never arrived is not evidence. The carrier marking it lost or returned is. A parcel that is merely late stays in in_transit and will not qualify for an automatic refund.

Actions that never execute automatically

cancel_order and reship_order always escalate and never mutate order or shipment state. Both gather evidence, assign a priority from the order's current state, and hand off to a human. Cancelling a shipped order or re-dispatching goods has physical, hard-to-reverse consequences, and unlike a capped refund there is no bounded blast radius that makes automating it safe.

They still earn their place as tools: they turn a vague "customer wants to cancel" into a structured, triaged case with the evidence already attached.

Idempotency

Every mutation derives an idempotency key and is wrapped in a transaction.

Two things matter here, and the second is easy to miss:

  1. The replay check runs before the eligibility check. Otherwise a retried request would see the refund the first call recorded, conclude the order was already refunded, and escalate a false duplicate to a manager.
  2. processRefund takes a FOR UPDATE lock on the payment row, so genuinely concurrent refunds for one order serialise instead of racing that check. A UNIQUE constraint on idempotency_key is the final backstop.

A retried refund returns the original outcome and leaves the refunded amount unchanged. Both paths are covered by tests.

Audit trail

Every mutation and escalation writes to action_log with a reason (minimum 10 characters, enforced at the schema level), structured evidence, priority, and status. get_order_details and get_customer_history read it back, so an operator picking up a case sees what was already tried.


Tools

Read-only

Tool Purpose
lookup_order Find orders by order ID, customer email, or phone number
get_order_details Full order state: items, amounts, payment, shipment, action history
check_payment_status Payment state and failure reason. Evidence only — never mutates
check_shipment_status Carrier status, delay calculation, and hasCarrierException
get_customer_history Orders, spend, refund count, risk score, recent actions

Mutating

Tool Behaviour
issue_refund Refunds if all six conditions pass, otherwise escalates. Idempotent
cancel_order Always escalates. Never cancels
reship_order Always escalates. Never reships
escalate_to_human Files a durable escalation with priority, reason, and evidence

Tool descriptions state these constraints inline, so a client can reason about what will happen before calling. The server does not depend on it having done so.


Try the workflow

Against the hosted server, ask the AI client:

"A customer emailed about order ORD-1019 — they say it never arrived. Can you look into it?"

Expect the client to look up the order, find the carrier marked the shipment lost, check the customer's risk, and refund. Then contrast:

Ask about Amount Risk Age Outcome
ORD-1019 $73.97 15 10d Auto-refund — all conditions pass
ORD-1051 $229.98 18 6d Escalates — refund cap only
ORD-1024 $23.47 12 36d Escalates — order age only
ORD-1021 $20.48 72 12d Escalates — customer risk only
ORD-1012 No refund — parcel still in transit, no carrier exception

Each escalating order fails exactly one condition, so the reason in the response maps to a single policy rule. Amounts other than ORD-1051 shift a little between seed runs, since most line items are randomised.

Retry an identical issue_refund call to see the idempotent path: the response reports the original action and the refunded amount does not move.


Running locally

Requires Node.js 20+ and a PostgreSQL database.

npm install
cp .env.example .env     # then set DATABASE_URL
npm run seed             # creates schema and loads synthetic data
npm run dev              # server on http://localhost:3000
Script
npm run dev Start with hot reload
npm run seed Reset schema and reload synthetic data
npm test Run the safety and idempotency suite
npm run build Compile to dist/

Verify it is up:

curl http://localhost:3000/health

Connecting an MCP client

Transport is Streamable HTTP at POST /mcp. For a client that reads a config file, pointing at the hosted server:

{
  "mcpServers": {
    "commerce-ops": {
      "url": "https://commerce-ops-mcp-production-c298.up.railway.app/mcp"
    }
  }
}

Substitute http://localhost:3000/mcp to run against a local instance.

Deployment note

The database host must be reachable over IPv4. Supabase's direct connection hostname (db.<ref>.supabase.co) resolves to IPv6 only, and Railway containers have no IPv6 egress — the connection hangs rather than failing cleanly. Use the connection pooler host (aws-0-<region>.pooler.supabase.com), which has IPv4 records. Note the pooler username is postgres.<project-ref> rather than postgres.

If the database is unreachable the server still binds its port and /health returns 503 with the underlying error, so the cause is visible rather than surfacing as a generic platform 502.


Tests

npm test

22 tests run against real PostgreSQL — the guarantees under test are transactional (row locks, unique constraints, FOR UPDATE), and a mocked database would verify the mocks rather than the behaviour.

Coverage:

  • Each of the six refund conditions failing in isolation, so one guardrail cannot mask another
  • The eligible path, including partial refunds
  • Retried and concurrent refund requests
  • cancel_order and reship_order leaving state untouched — asserted by re-reading the order and shipment rows after the call
  • Escalations persisting priority, reason, and evidence

Each test creates fixtures under a unique ID and tears them down afterwards, so the suite does not disturb the demo data.


Data model

Synthetic data only. No real customer data or production credentials.

customers ──< orders ──< payments      (1:1, unique order_id)
                   │
                   ├──< shipments
                   └──< action_log     (unique idempotency_key)

Invariants enforced in the schema: refunded_amount never exceeds amount (enforced in the update predicate); one payment per order; idempotency_key is unique; status values are constrained by CHECK; risk_score is 0–100.

Statuses. Orders: pending, confirmed, shipped, delivered, cancelled, failed. Payments: success, failed, pending, refunded. Shipments: processing, in_transit, out_for_delivery, delivered, lost, returned. Only lost and returned count as carrier exceptions.

The seed covers 51 orders across every status, with 7 lost and 2 returned shipments, and a risk-score spread that includes three high-risk customers. Coverage of meaningful states was the goal rather than volume.


Decisions and assumptions

Scope. One complaint path — missing delivery, ending in refund or escalation. Payment failures, item quality, and address changes are out of scope. A narrow path exercised properly says more about the design than four half-built ones.

Refund caps over approval queues. Bounding what automation may do is simpler to reason about than modelling multi-step approval, and the escalation record is where a real approval queue would attach.

Risk score is stored, not computed. A real system would derive it from chargebacks and claim frequency. Modelling that is a separate problem, so the score is seeded per customer and the server treats it as input.

No authentication. Out of scope per the brief and confirmed with the client. See Limitations.

PostgreSQL over a lighter store. The safety guarantees are transactional, so they need real transactions, row-level locks, and unique constraints.

Escalations are records, not notifications. No email or Slack delivery — a pending_approval row is the durable handoff, and a notifier would read from it.


Limitations

The /mcp endpoint is unauthenticated and publicly reachable, and it exposes mutating tools. Anyone with the URL can call issue_refund. This is deliberate for the assignment and confirmed with the client; all data is synthetic and no production credentials are involved. It would be the first thing to fix for real use — bearer-token auth on the transport, then an operator identity threaded into action_log so the audit trail records who acted rather than just what happened.

Refunds are recorded, not settled. issue_refund updates the payment row; it does not call a payment provider. A real integration needs the provider call and the local write to agree, which means an outbox or reconciliation job — the idempotency key is the seam that would hook into.

Sessions are in memory. A restart drops active MCP sessions and clients must reinitialise. Fine for one instance; horizontal scaling needs shared session state.

No approval workflow. Escalations are queryable rows. Nothing yet lets a manager approve or reject one, which is the obvious next increment.

Thresholds are compile-time constants. The cap, age limit, and risk threshold live in src/guards/safety.ts. Real operations would want them configurable without a redeploy.

get_customer_history takes a customer ID, which the AI has to obtain from an order lookup first. Accepting an email directly would remove a step.


Layout

src/
  server.ts          MCP server, Streamable HTTP transport, session handling
  db/index.ts        Pool, schema, transaction helper
  data/seed.ts       Synthetic data
  guards/safety.ts   Eligibility rules, refund application, escalation
  tools/             One file per tool
tests/
  safety.test.ts     Safety and idempotency verification

Policy lives in guards/safety.ts rather than in the tool handlers, so the rules are readable in one place and cannot drift between call sites.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选