Shopkeeper MCP
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Shopkeeper MCPRefund order 1042 if it arrived late."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Shopkeeper MCP
An MCP server that lets Claude run a small online store's back office (orders, refunds, invoices, sales reports, inventory), with server-side guardrails on every write and an eval harness that checks the agent's behaviour against the real database.
TypeScript · official MCP SDK (McpServer.registerTool) · zod · SQLite (node:sqlite, no native build) · vitest · Anthropic SDK (evals only)
Tools | 8 (5 read-only, 3 write), with MCP annotations, input/output schemas and typed errors |
Guardrails | refund caps, required idempotency keys with replay, an order state machine, duplicate-invoice refusal, audit log |
Tests | 92 vitest tests: unit tests per tool, protocol tests through the MCP |
Evals | 15 scenarios; oracle 15/15, naive agent 6/15, no-op 0/15; live Claude: pending (needs an API key), see EVAL_REPORT.md |
All store data is fictional: "Kettle & Crema" is an invented coffee & tea gear shop, customers use
@example.comaddresses.
What it does
Point Claude Desktop, Claude Code or any MCP host at this server and it can answer and act on requests like:
"Refund order 1042 if it arrived late." It checks the promised and actual delivery dates, then refunds once with an idempotency key.
"Refund $500 on order 1010." The server refuses, because the order total is $58.95.
"Mark order 1180 as delivered." Refused: the order is unpaid, and the state machine won't let it skip steps.
"What were net sales by month in Q2?" / "Which products need reordering?" These are read-only, and no write tool is needed.
"Cancel Morgan Ellis's pending order." Two customers share that name; the search tool flags the ambiguity so the agent asks which one.
Related MCP server: mcp-sql-server
Tools
Tool | Kind | What it does | Guardrails / behaviour | Annotations |
| read | Exact, partial and fuzzy (typo-tolerant, accent-insensitive) search by name or email | Returns | readOnly, idempotent |
| read | Items, customer, totals, refunds, refundable remainder, invoice, delivery timing (promised vs actual, days late), allowed next statuses | Meant to be called before any write | readOnly, idempotent |
| read | Filters: status(es), customer, placed date range, min/max total, | Unknown customer gives | readOnly, idempotent |
| read | Gross, refunds and net by day / ISO week / month / product / customer | Excludes pending and cancelled orders; the product view allocates refunds pro-rata to the cent | readOnly, idempotent |
| read | Products strictly below their reorder threshold, most urgent first | Suggested quantity refills to target stock, rounded up to the supplier case pack | readOnly, idempotent |
| write |
| Any other move gives | destructive, idempotent |
| write | Refund part or all of a paid order |
| destructive, idempotent |
| write | Invoice for the order total (paid orders are due today; unpaid orders get payment terms) | One invoice per order: a duplicate gives | non-destructive, idempotent |
All tools set openWorldHint: false. Inputs are strict: an unknown argument such as dry_run: true is rejected rather than silently ignored.
Errors come back as isError: true with a machine-readable envelope:
{"error":{"code":"GUARDRAIL_VIOLATION","reason":"REFUND_EXCEEDS_REFUNDABLE","message":"Requested $500.00 exceeds the refundable amount $58.95 on order 1010 (total $58.95, already refunded $0.00). No refund was issued.","details":{"order_id":1010,"refundable_cents":5895,"requested_cents":50000,"...":"..."}}}code is one of NOT_FOUND, VALIDATION_ERROR, GUARDRAIL_VIOLATION, CONFLICT (plus INTERNAL_ERROR, which never leaks internals). reason names the exact rule. Pure schema violations (wrong type, missing field, unknown key) are rejected by the MCP SDK before any handler runs; they also come back as isError: true, but with the SDK's Input validation error: … text instead of the envelope (the eval harness classifies those as VALIDATION_ERROR). Successful calls return a short text summary plus the same JSON in structuredContent, validated against each tool's outputSchema.
Architecture
flowchart LR
subgraph host["MCP host"]
claude["Claude Desktop / Claude Code / MCP Inspector"]
end
subgraph server["shopkeeper-mcp (Node process)"]
mcp["McpServer<br/>registerTool + zod schemas<br/>annotations, instructions"]
tools["Tool layer<br/>guardrails, state machine,<br/>idempotency, audit log"]
errors["Typed errors<br/>to isError envelope"]
db[("SQLite via node:sqlite<br/>integer cents")]
mcp --> tools --> db
tools --> errors
end
claude <-->|"JSON-RPC over stdio<br/>(or Streamable HTTP)"| mcp
subgraph evals["Eval harness (evals/)"]
runner["run.ts"] --> agent{"Agent:<br/>Claude / oracle / baseline"}
agent -->|"MCP Client over stdio"| fresh["fresh server process<br/>+ fresh copy of seeded DB"]
runner --> graders["Graders:<br/>DB diff, tool trace, answer"]
graders --> report["EVAL_REPORT.md"]
endQuickstart
Requires Node ≥ 22.13 (built-in node:sqlite). Developed on Node 26.
npm install && npm run seed && npm run build
npm test # 92 tests
npm run smoke # spawn the stdio server, list tools, call a read and a guarded write
npm run inspect # open the MCP Inspector web UI against the servernpm run seed (re)creates data/shop.db deterministically. Re-run it to reset the store between demos. If the database is missing, the server creates and seeds it on first start.
Configuration
Setting | CLI flag | Env var | Default |
Database file |
|
|
|
Store clock |
|
| wall-clock time |
The seed data runs through 2026-09-29, so pin --now 2026-09-30T18:00:00Z for reproducible "late / overdue" answers (the snippets below do).
Claude Desktop
Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"shopkeeper": {
"command": "node",
"args": ["/ABSOLUTE/PATH/TO/shopkeeper-mcp/dist/index.js", "--now", "2026-09-30T18:00:00Z"]
}
}
}Restart Claude Desktop. If you use nvm, set "command" to the absolute path from which node.
Claude Code
claude mcp add shopkeeper -- node /ABSOLUTE/PATH/TO/shopkeeper-mcp/dist/index.js --now 2026-09-30T18:00:00Zor commit a project-scoped .mcp.json:
{
"mcpServers": {
"shopkeeper": {
"command": "node",
"args": ["/ABSOLUTE/PATH/TO/shopkeeper-mcp/dist/index.js", "--now", "2026-09-30T18:00:00Z"]
}
}
}Streamable HTTP (optional)
npm run start:http # POST http://127.0.0.1:3333/mcp (PORT to change)Stateless mode, bound to loopback, rejects non-local Host/Origin headers. It has no authentication, so put an auth layer in front before exposing it anywhere.
Example
Real output from npm run smoke (the built server over stdio, MCP SDK client):
tools/list -> 8 tools
search_customers read destructive=false idempotent=true
get_order read destructive=false idempotent=true
list_orders read destructive=false idempotent=true
sales_report read destructive=false idempotent=true
low_stock_alerts read destructive=false idempotent=true
update_order_status WRITE destructive=true idempotent=true
issue_refund WRITE destructive=true idempotent=true
create_invoice WRITE destructive=false idempotent=true
get_order(1042):
Order 1042 - status: delivered; delivered 3 day(s) LATE (promised 2026-03-30, delivered 2026-04-02).
Customer: Marcus Reid <marcus.reid@example.com> (#34).
Total $105.00 (shipping $0.00); refunded $0.00; refundable $105.00.
2 line item(s). Invoice: INV-2026-1042. Refunds on file: 0.
issue_refund(1010, $500.00) -> isError=true
{"error":{"code":"GUARDRAIL_VIOLATION","reason":"REFUND_EXCEEDS_REFUNDABLE","message":"Requested $500.00 exceeds the refundable amount $58.95 on order 1010 (total $58.95, already refunded $0.00). No refund was issued.", ...}}Illustrative conversation. This is not a recorded Claude run (no live run has been made yet). The tool calls and tool results are what this server returns for these requests; Claude's wording is a sketch.
User: Refund order 1042 if it arrived late.
→
get_order({"order_id": 1042}): "delivered 3 day(s) LATE (promised 2026-03-30, delivered 2026-04-02) … refundable $105.00" →issue_refund({"order_id": 1042, "amount_cents": 10500, "reason": "Late delivery: 3 days after promised date", "idempotency_key": "refund-1042-late-delivery"}): "Refunded $105.00 on order 1042 (refund #16). The order is now fully refunded."Claude: Order 1042 was delivered on April 2, three days after the promised March 30 date, so I refunded the full $105.00. The order is now marked refunded.
User: Also refund $500 on order 1010.
→
get_order({"order_id": 1010}): "Total $58.95 … refundable $58.95"Claude: I can't refund $500 on order 1010: the order total is $58.95, which is the most that can be refunded. Do you want a full $58.95 refund instead?
Screenshots
Screenshots coming soon: Inspector tool list, a guardrail refusal, Claude Desktop handling a refund, and the eval matrix.
Evals
Agents that call write tools need more than unit tests. The harness in evals/ runs 15 natural-language scenarios end to end against the real server and grades each one deterministically, with no LLM judge.
Isolation: every attempt gets a fresh copy of the seeded DB and its own
node dist/index.jsprocess, driven over stdio by the MCP SDKClient. The clock is pinned.Graders check three independent things:
Final DB state, diffed table by table against the untouched template. Examples: exactly one new refund for 1042 equal to the order total; no new refund rows on a retried idempotent refund; every table unchanged on read-only questions.
Tool-call trace: required tools, forbidden tools (any write tool on a read-only question, a second refund attempt on a refunded order, marking an unpaid order
paidto forcedelivered), ordering (get_orderbeforeissue_refund), and idempotency-key reuse.Final answer, with light, tolerant checks: mentions the right amount (±$1), the invoice number, every late order ID.
Expected values are computed with SQL on the database, never copied from tool output, so a buggy tool can't grade itself.
Three modes:
Command | Agent | Needs key |
| Claude via the Anthropic SDK: MCP tools converted to Anthropic tool definitions, manual tool-use loop (max 12 turns), default | yes |
| Scripted known-good tool sequence per scenario, run through the real server and graded by the same graders | no |
| No-op agent and a naive keyword agent | no |
Current results (from EVAL_REPORT.md):
Agent | Scenarios passed |
Claude (live) | pending: run |
Oracle (scripted known-good) | 15/15 |
Naive keyword agent | 6/15 |
No-op agent | 0/15 |
The oracle at 15/15 shows the server and graders agree. The baselines show the graders discriminate. The naive agent passes only literal one-step requests and cases where the server's guardrails stopped it. It fails everything that needs judgement: it refunds an on-time order, double-refunds a retried refund, and turns a read-only question into a write.
Running the live eval
cp .env.example .env # set ANTHROPIC_API_KEY=...
npm run eval # all 15 scenarios, claude-sonnet-5-5
npm run eval -- --model claude-haiku-5-5 --trials 3 # cheaper model, 3 trials each (pass^3)
npm run eval -- --only idempotent-retry,ambiguous-customer --effort lowWithout a key, npm run eval exits immediately with instructions. Every run writes evals/results/<agent>.json (full trace, final answer, usage, cost) and regenerates EVAL_REPORT.md.
Design decisions
Guardrails live in the server, not the prompt. Refund caps, the state machine and duplicate checks are enforced in a transaction no matter which client calls. The baseline results show the effect: even a careless agent cannot over-refund or skip from
pendingtodelivered.Idempotent writes.
issue_refundrequires anidempotency_key(unique in the DB). Replaying the same key returns the original refund withreplayed: trueand changes nothing; reusing a key for a different amount or order is aCONFLICT. Agents can retry after timeouts without paying twice. That is exactly what theidempotent-retryscenario tests.Money is integer cents end to end.
CHECKconstraints keeptotal = subtotal + shippingandrefund > 0. Formatting to dollars happens only in summaries, and pro-rata refund allocation uses largest-remainder so no cent is lost or invented.Honest annotations. Reads are
readOnlyHint: true; writes declaredestructiveHint/idempotentHint, so hosts can gate confirmations.openWorldHint: falseeverywhere: the server touches only its own database.Strict schemas, structured output. zod input schemas reject unknown keys; every tool has an
outputSchemaand returnsstructuredContent. Error results deliberately omitstructuredContent, because MCP clients validate it against the output schema.State machine over free-form status edits. Cancel is only allowed before payment; paid orders are unwound through refunds, and
refundedis set automatically when refunds reach the total, so money and status can't disagree.Everything audited. Each successful write appends to
audit_logwith actor, action and details; replays and refusals write nothing.Deterministic by construction. A fixed-seed PRNG plus pinned fixtures (late order 1042, refunded order 1077, two customers named "Morgan Ellis", stock below threshold, …) and an injectable clock. Tests and evals are reproducible to the byte.
Small runtime footprint. Runtime dependencies are only
@modelcontextprotocol/sdkandzod. The Anthropic SDK and dotenv are dev-only, used by the eval harness. stdout carries JSON-RPC only; all logs go to stderr.
Project layout
src/
index.ts stdio entrypoint (auto-seeds a missing DB)
http.ts optional Streamable HTTP entrypoint (stateless, loopback)
server.ts McpServer + tool registration + error mapping
tools/ one file per tool (zod schemas, handler, annotations)
domain/ state machine, delivery/refund rules, fuzzy matching
db/ schema, connection/transactions, deterministic seed
errors.ts money.ts clock.ts config.ts
tests/ vitest: per-tool, protocol (in-memory Client), stdio integration, seed, graders
evals/
scenarios.ts 15 scenarios: prompt, oracle, graders
graders.ts assertion library (DB diff, trace, answer)
harness.ts spawns the real server per attempt, traces tool calls
agents/ claude.ts (live), scripted.ts (oracle, naive, no-op)
run.ts report.ts CLI + EVAL_REPORT.md generator
results/ latest results per agent (JSON)
scripts/smoke.ts end-to-end smoke testScripts
Script | What it does |
| Compile (tsc strict) / run the stdio server |
| Recreate |
| Tests / typecheck everything + tests |
| Spawn the built server and exercise it with the MCP SDK client |
| MCP Inspector web UI / list tools from the terminal |
| Evals (see above) |
License
MIT © 2026 Shubham Garje. Demo data is fictional; any resemblance to real people or businesses is coincidental.
Available Tools
8 toolscreate_invoiceCreate invoiceAIdempotent
Issue the invoice for an order (amount = order total). Each order can have only one invoice: a second request is refused with the existing invoice number (CONFLICT). Cancelled and refunded orders cannot be invoiced.
| Name | Required | Description | Default |
|---|---|---|---|
| notes | No | Optional note printed on the invoice. | |
| order_id | Yes | Numeric order ID, e.g. 1042. | |
| due_in_days | No | Payment terms for unpaid orders (default 14). Paid orders are invoiced as due today. |
Output Schema
| Name | Required | Description |
|---|---|---|
| invoice | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the annotations (readOnlyHint=false, destructiveHint=false, idempotentHint=true), the description discloses the duplicate-request outcome: a second call is refused with the existing invoice number as a CONFLICT. That is genuinely useful error semantics an agent cannot infer from structured fields, though there is mild tension worth noting — idempotentHint=true while repeats are rejected rather than silently succeeding (the net state is still one invoice, so this is not a contradiction).
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three short sentences, zero filler, with the core action and its derived amount front-loaded and the failure modes packed into the follow-up clauses.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists and all three parameters are documented, so the description need not explain return values; it covers the key constraints (uniqueness, conflict, order-state gating). It could still say what happens to the invoice after issuance (e.g., whether it is sent or merely created), which is the main residual gap.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so order_id, notes, and due_in_days are already fully documented in the schema, and this establishes a baseline of 3. The description adds only that the amount equals the order total (i.e., it is not a settable parameter), which is minor added meaning.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ("Issue the invoice for an order") and immediately pins the derived amount to the order total, so the agent knows exactly what is created. This is clearly distinct from siblings like issue_refund and update_order_status; no overlap is possible.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly gives the preconditions for use: one invoice per order, and cancelled/refunded orders cannot be invoiced. It does not name an alternative tool or explain what to do instead when refused, but no sibling tool is a real substitute, so the guidance is close to complete.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_orderGet orderARead-onlyIdempotent
Full detail for one order: status (and which statuses it may move to next), customer, line items, totals in cents, refunds already issued, refundable remainder, invoice, and delivery timing (promised vs actual date, days late). Call this before any write on an order.
| Name | Required | Description | Default |
|---|---|---|---|
| order_id | Yes | Numeric order ID, e.g. 1042. |
Output Schema
| Name | Required | Description |
|---|---|---|
| items | Yes | |
| order | Yes | |
| invoice | Yes | |
| refunds | Yes | |
| customer | Yes | |
| delivery | Yes | |
| financials | Yes | |
| refund_eligibility | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly, idempotent, non-destructive, closed-world, so the safety bar is largely met structurally. The description adds value by revealing that the response includes derived state — allowed next statuses, refundable remainder, days late — which tells the agent this call is a decision substrate for writes. It does not mention error behavior for a missing order.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
One dense sentence enumerating the payload followed by a single imperative directive; the operational rule is front-loaded at the end with zero filler. Nothing repeats the name or title.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present the description need not explain return values, yet it still orients the agent on what matters. For a simple one-param read it is near-complete; only failure modes for an invalid or nonexistent order_id are unaddressed.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the single order_id parameter is already documented with an example in the schema. The description adds no format, range, or lookup guidance beyond it, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Full detail for one order') and immediately scopes it against the sibling list_orders by emphasizing a single order with full detail. The enumeration of what 'full detail' means (status, customer, line items, totals, refunds, invoice, delivery timing) removes any ambiguity about the payload.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
'Call this before any write on an order' gives a concrete precondition that ties it to update_order_status and issue_refund. It stops short of an explicit negative case (e.g. use list_orders to browse), so it is strong but not fully routing.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
issue_refundIssue refundADestructiveIdempotent
Refund money on a paid order. Guardrails: amount must be > 0 and within the remaining refundable amount; unpaid, cancelled and fully-refunded orders are refused; a reason and an idempotency_key are required, and repeating a key replays the original refund instead of paying twice. When refunds reach the order total the order becomes 'refunded'. Every refund is written to the audit log.
| Name | Required | Description | Default |
|---|---|---|---|
| reason | Yes | Why the refund is issued; stored in the audit log. | |
| order_id | Yes | Numeric order ID, e.g. 1042. | |
| amount_cents | Yes | Refund amount in integer cents (1050 = $10.50). Must be > 0 and <= the order's remaining refundable amount (get_order -> financials.refundable_cents). | |
| idempotency_key | Yes | Unique key for THIS refund, e.g. 'refund-1042-late-delivery'. Retrying with the same key returns the original refund instead of paying twice. Use a new key only for a genuinely new refund. |
Output Schema
| Name | Required | Description |
|---|---|---|
| order | Yes | |
| refund | Yes | |
| replayed | Yes | True when this key was already used for the same refund: nothing new was refunded. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare destructiveHint=true and idempotentHint=true, and the description adds substantive behavioral context beyond that: amount limits, refusal cases, idempotency replay semantics, order state transition to 'refunded', and audit logging. This is rich transparency for a mutation tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is dense but well-structured, front-loading the purpose before listing guardrails, idempotency behavior, and side effects. Every sentence carries useful information and none is wasted.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a destructive, idempotent mutation with an output schema and full annotation coverage, the description supplies everything an agent needs: purpose, eligibility, refusal cases, idempotency behavior, and side effects. No important behavioral gap remains.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so all four parameters are already documented in the schema. The description reinforces the amount guardrail and idempotency-key behavior, but much of that is already present in the schema descriptions, so this is baseline rather than added semantic value.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Refund money on a paid order') and clearly scopes the operation to paid orders. An agent can distinguish this from sibling tools like get_order or update_order_status without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives clear when-to-use conditions and explicit when-not cases: unpaid, cancelled, and fully-refunded orders are refused. It does not name sibling alternatives such as using get_order to check the remaining refundable amount, but the guardrails are otherwise strong.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_ordersList ordersARead-onlyIdempotent
Search orders with filters (status, customer, placed date range, total range, late-only) and pagination. Each row includes total, amount refunded, and delivery timing (promised vs delivered date, days late).
| Name | Required | Description | Default |
|---|---|---|---|
| sort | No | placed_at_desc | |
| limit | No | Page size, 1-100 (default 20). | |
| offset | No | Rows to skip; use next_offset from the previous page. | |
| status | No | Only orders in this status or any of these statuses, e.g. "paid" or ["paid","shipped"]. | |
| late_only | No | Only late orders: delivered after the promised date, or paid/shipped and already past it (overdue). Check each row's delivery_status to tell them apart. | |
| placed_to | No | Placed on or before this date (YYYY-MM-DD, inclusive). | |
| customer_id | No | Only orders for this customer (see search_customers). | |
| placed_from | No | Placed on or after this date (YYYY-MM-DD, inclusive). | |
| max_total_cents | No | Order total <= this many cents. | |
| min_total_cents | No | Order total >= this many cents. |
Output Schema
| Name | Required | Description |
|---|---|---|
| limit | Yes | |
| offset | Yes | |
| orders | Yes | |
| next_offset | Yes | Pass as offset to fetch the next page; null on the last page. |
| total_count | Yes | Rows matching the filters across all pages. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, idempotentHint=true, destructiveHint=false and openWorldHint=false, so safety needs no restating. The description adds that results are paginated and that late_only can mean two different things resolved via a per-row delivery_status — useful behavioral context — but the sentence about row contents largely restates fields the output schema already exposes.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, front-loaded with the verb and filter scope, with no filler. The second sentence about row contents is slightly redundant against the output schema, which is the only thing keeping it from a 5.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present, the description need not explain return values, and the annotations cover the safety profile. Given 10 optional parameters at 90% schema coverage, the description supplies what is left: that results are paginated and that late-only spans delivered-late and overdue orders.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 90%, so nearly every parameter is self-documenting in the schema and baseline 3 applies. The description's parenthetical filter list mirrors the schema rather than adding format, defaults, or edge-case behavior, and it never mentions the sort parameter or limit caps.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Says "Search orders" with a concrete enumeration of the filter dimensions (status, customer, placed date range, total range, late-only) plus pagination, so the verb+resource+scope are unambiguous. It stops short of naming siblings such as get_order or search_customers, so the distinction from a single-order fetch must be inferred from the word "Search".
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is only implied: the filter list tells the agent this is the filtered/multi-row retrieval path, but there is no explicit "use this when / prefer get_order when you already have an id" statement. The sibling set (get_order, sales_report, search_customers) is common enough that an agent can guess, but the description does not route it.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
low_stock_alertsLow stock alertsARead-onlyIdempotent
Products whose stock is strictly below their reorder threshold, most urgent first, each with a suggested reorder quantity (refill to target stock, rounded up to the supplier case pack).
| Name | Required | Description | Default |
|---|---|---|---|
| category | No | Only this product category. |
Output Schema
| Name | Required | Description |
|---|---|---|
| count | Yes | |
| alerts | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, openWorldHint=false and destructiveHint=false, so safety is covered. The description goes beyond that by disclosing the boundary condition ('strictly below', so at-threshold items are excluded), the sort order, and the exact derivation of the suggested reorder quantity (refill to target, rounded up to the supplier case pack) — real behavioral value the agent could not infer from annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
One sentence with no wasted words, and the most decision-relevant facts — the filter condition and the ordering — are front-loaded. Parenthetical glosses for the derived field are compact and unambiguous.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists and annotations are rich, so the description need not explain return values or safety. It covers the qualifying condition, ordering, and derived-quantity logic. Minor gaps remain: 'most urgent' is not defined (e.g. largest absolute vs. relative shortfall) and there is no mention of result limits or the empty-result case.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the single optional parameter is an enumerated category, so the schema fully documents the input. The description does not mention the category filter or clarify whether it is optional, adding nothing beyond the schema. Baseline 3 applies when the schema does the heavy lifting.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names a specific resource (products below their reorder threshold) and adds scope-defining detail — 'strictly below' and 'most urgent first'. It reads as a return-value spec rather than a verb-led statement ('List products...'), but the semantics are unambiguous. None of the sibling tools operate on inventory at all, so no sibling differentiation is required.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no explicit statement of when to call this versus alternatives, but the definition of the qualifying condition ('stock strictly below their reorder threshold') implicitly tells an agent the tool answers a restocking question. With no inventory-related siblings, there is little ambiguity to resolve, so the omission is minor rather than damaging.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
sales_reportSales reportARead-onlyIdempotent
Sales for a date range (by order placed date), grouped by day, ISO week, month, product, or customer. Reports gross, refunds and net (gross minus refunds issued against those orders), all in cents. Pending and cancelled orders are excluded because no money was captured.
| Name | Required | Description | Default |
|---|---|---|---|
| to | Yes | Last day included (YYYY-MM-DD). | |
| from | Yes | First day included (YYYY-MM-DD, by order placed date). | |
| group_by | No | day | week (ISO, Monday start) | month | product | customer | month |
Output Schema
| Name | Required | Description |
|---|---|---|
| to | Yes | |
| from | Yes | |
| rows | Yes | |
| basis | Yes | order_totals: gross = order totals incl. shipping. line_items (product view): gross = item revenue excl. shipping, refunds allocated pro-rata. |
| totals | Yes | |
| group_by | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the annotations (readOnlyHint=true), the description details the complex calculation logic (net = gross minus refunds), the unit of measurement (all in cents), and the specific filtering behavior (excluding pending/cancelled orders).
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is highly dense but structured logically, moving from what the tool generates to its calculations, and finally its limitations. Every sentence adds unique, essential information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the complexity of multiple grouping options and specific financial calculations, the description covers all critical behavioral details—including what is excluded and how data is processed—making it maximally complete for an AI agent.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Since schema coverage is 100%, the description's primary value is reinforcing the context of the grouping parameters. It specifies that the grouping (day, week, month, etc.) operates on the 'order placed date,' adding vital context to the input constraints.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly defines the tool's function: generating a 'Sales report.' It specifies the resource (sales data) and the scope (date range), making its purpose unambiguous relative to other sibling tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It provides critical constraints on how the tool behaves, specifying that it aggregates data based on the 'order placed date' and explicitly excluding 'Pending and cancelled orders.' This gives the agent precise boundaries on appropriate usage.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_customersSearch customersARead-onlyIdempotent
Find customers by name or email (exact, partial, or fuzzy/misspelled). Returns contact details, order count and lifetime spend for each match. If ambiguous is true, several customers fit equally well: ask the user which one they mean (e.g. by email or city) before acting.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Maximum matches to return (default 10). | |
| query | Yes | Name, email, or part of either. Case/accent-insensitive and tolerant of typos (e.g. 'jon benett'). |
Output Schema
| Name | Required | Description |
|---|---|---|
| note | Yes | |
| query | Yes | |
| matches | Yes | |
| ambiguous | Yes | True when more than one customer matches equally well - confirm with the user before acting. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare read-only, idempotent, closed-world behavior, so the safety profile is covered. The description adds real value beyond that: it lists what each match contains (contact details, order count, lifetime spend) and defines an ambiguity-handling protocol keyed on the `ambiguous` flag.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three short sentences, front-loaded with what the tool does, then result shape, then the disambiguation branch. No filler or repeated annotation information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present, return structure needn't be spelled out, and the ambiguity case is handled explicitly. Minor gaps remain around zero-match behavior and truncated results at the limit cap, but nothing essential for a correct call is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the schema already documents length bounds, defaults, the 25 maximum, and case/accent-insensitive typo tolerance. The description's mention of partial and fuzzy matching largely restates that, adding no format or syntax detail beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Find customers') plus the searchable fields (name or email) and matching modes (exact, partial, fuzzy). No sibling tool overlaps with customer lookup, so the scope is unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explains the search modes available and gives an explicit follow-up rule when `ambiguous` is true: ask the user for a disambiguating attribute before acting. It stops short of naming when to prefer a different tool, but no sibling performs customer lookup.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
update_order_statusUpdate order statusADestructiveIdempotent
Move an order through its lifecycle: pending->paid|cancelled, paid->shipped, shipped->delivered. Any other transition is rejected (CONFLICT) with the allowed next statuses. 'refunded' is never set directly (use issue_refund). Setting the current status again is a no-op. Changes are audit-logged.
| Name | Required | Description | Default |
|---|---|---|---|
| note | No | Optional context stored in the audit log (e.g. tracking number). | |
| status | Yes | Target status. Allowed moves: pending->paid|cancelled, paid->shipped, shipped->delivered. | |
| order_id | Yes | Numeric order ID, e.g. 1042. |
Output Schema
| Name | Required | Description |
|---|---|---|
| status | Yes | |
| changed | Yes | False when the order was already in the requested status (no-op). |
| order_id | Yes | |
| updated_at | Yes | |
| previous_status | Yes | |
| allowed_next_statuses | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare destructiveHint=true and idempotentHint=true; the description corroborates and enriches this by explaining the rejection behavior (409 CONFLICT with the allowed next statuses in the response), the no-op on same-status writes, and that changes are audit-logged. These are exactly the failure modes and side effects an agent needs before invoking a mutation.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Four short sentences, front-loaded with the transition table, followed by the failure mode, the exclusion, and the idempotency nuance. No filler; each clause carries an operational rule.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 3-parameter mutation tool with full schema coverage, rich annotations, and an output schema, the description covers everything an agent needs: valid inputs, rejection semantics, the excluded value, and side effects. Nothing material is delegated to guesswork.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline would be 3, but the description adds real meaning the schema does not: 'refunded' appears in the enum yet must never be set directly, which materially changes how the status parameter should be used. The note parameter's audit-log purpose is left to the schema, which already documents it well.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource and immediately defines the exact state machine it operates on (pending->paid|cancelled, paid->shipped, shipped->delivered). It also names the sibling tool to use instead for 'refunded', so an agent can separate this from issue_refund without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly enumerates valid transitions, states that other transitions are rejected with CONFLICT plus the allowed next statuses, and routes 'refunded' to issue_refund. It also pre-empts the redundant-call case by noting that re-setting the current status is a no-op.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
8 tool updates
v0.1.0- First observed
create_invoice - First observed
get_order - First observed
issue_refund - First observed
list_orders - First observed
low_stock_alerts - First observed
sales_report - First observed
search_customers - First observed
update_order_status
TDQS
Scored across 8 tools
Each tool targets a distinct resource and action: customer search, order detail/list, invoice creation, reporting, stock alerts, status transitions, and refunds. Overlaps such as get_order vs list_orders and update_order_status vs issue_refund are clearly separated by scope and guardrails.
Most tools use a predictable verb_noun pattern (search_customers, get_order, create_invoice, update_order_status, issue_refund), with only minor noun-phrase deviations like sales_report and low_stock_alerts.
Eight tools is well-scoped for a shop operations server, covering the main back-office workflows without feeling bloated or thin.
Core order read/update/refund and invoicing operations are present, but there are notable gaps: no order creation, no customer creation/update, no product catalog or stock adjustment, and no invoice retrieval/listing. These omissions could block common shopkeeper workflows.
Maintenance
Related MCP Connectors
Intelligence for your AI agents: what they need to know, at the moment they decide. NEXUS connects Claude Code, Claude, ChatGPT, Cursor and OpenClaw to one shared record of your decisions, rules and past incidents, so an agent doesn't repeat a costly mistake or undo what was already decided. What one agent learns, the others already know. Every item is graded (measured, reported or inferred), outdated facts are marked as superseded, and contradictions are flagged. Before an important action, agents can call antes_de_actuar to get the rules that apply. Remote server with OAuth. Free plan, no credit card: https://nexus.eblas.link
Warehouse ops from Claude: orders, stock, forecasts, parcel rates, labels you buy in the console
Database for your AI agent. Turn its output into data, docs, skills, and apps you can actually use.
Build and manage AI-native customer support agents from Claude or any MCP client.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables LLMs to interact with a SQLite e-commerce database via safe, typed MCP tools with read-only guards and auth-gated mutations, plus a Claude agent for answering business questions.1MIT
- AlicenseAqualityDmaintenanceAn MCP server that lets Claude query a mock business SQL database in plain language through read-only tools, with server-side guardrails that enforce SELECT-only queries and block access to sensitive payment data.3MIT
- FlicenseNot gradedqualityCmaintenanceEnables AI agents to safely mutate business state by demonstrating phase-gating, validation-before-mutation, and structured audit logging in a toy inventory and purchase order system.-
- FlicenseAqualityCmaintenanceEnables Claude to manage customers and support tickets through database-backed CRUD tools, contextual customer and knowledge resources, and a reusable ticket-triage prompt.13-