Skip to main content
Glama

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 Client, and a stdio integration test

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.com addresses.


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

search_customers

read

Exact, partial and fuzzy (typo-tolerant, accent-insensitive) search by name or email

Returns ambiguous: true and a note when several customers match equally

readOnly, idempotent

get_order

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

list_orders

read

Filters: status(es), customer, placed date range, min/max total, late_only; pagination with total_count / next_offset

Unknown customer gives NOT_FOUND, not an empty list; inverted ranges give VALIDATION_ERROR

readOnly, idempotent

sales_report

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

low_stock_alerts

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

update_order_status

write

pending→paid/cancelled, paid→shipped, shipped→delivered

Any other move gives CONFLICT with the allowed next statuses; refunded is only reachable through refunds; a repeat of the same status is a no-op; audited

destructive, idempotent

issue_refund

write

Refund part or all of a paid order

amount > 0 and ≤ refundable remainder; refuses unpaid, cancelled and fully refunded orders; reason required; idempotency_key required: the same key replays the original refund, and a different payload gives CONFLICT; runs in a BEGIN IMMEDIATE transaction; audited; order flips to refunded at 100%

destructive, idempotent

create_invoice

write

Invoice for the order total (paid orders are due today; unpaid orders get payment terms)

One invoice per order: a duplicate gives CONFLICT with the existing number; cancelled and refunded orders are refused; audited

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"]
  end

Quickstart

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 server

npm 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

--db <path>

SHOPKEEPER_DB

<repo>/data/shop.db (resolved from the repo, not the cwd)

Store clock

--now <iso>

SHOPKEEPER_NOW

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:00Z

or 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.js process, driven over stdio by the MCP SDK Client. The clock is pinned.

  • Graders check three independent things:

    1. 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.

    2. 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 paid to force delivered), ordering (get_order before issue_refund), and idempotency-key reuse.

    3. 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

npm run eval

Claude via the Anthropic SDK: MCP tools converted to Anthropic tool definitions, manual tool-use loop (max 12 turns), default claude-sonnet-5-5 at effort medium, server-side refusal fallback, token usage and cost tracked

yes

npm run eval:oracle

Scripted known-good tool sequence per scenario, run through the real server and graded by the same graders

no

npm run eval:baseline

No-op agent and a naive keyword agent

no

Current results (from EVAL_REPORT.md):

Agent

Scenarios passed

Claude (live)

pending: run npm run eval with ANTHROPIC_API_KEY set

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 low

Without 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 pending to delivered.

  • Idempotent writes. issue_refund requires an idempotency_key (unique in the DB). Replaying the same key returns the original refund with replayed: true and changes nothing; reusing a key for a different amount or order is a CONFLICT. Agents can retry after timeouts without paying twice. That is exactly what the idempotent-retry scenario tests.

  • Money is integer cents end to end. CHECK constraints keep total = subtotal + shipping and refund > 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 declare destructiveHint/idempotentHint, so hosts can gate confirmations. openWorldHint: false everywhere: the server touches only its own database.

  • Strict schemas, structured output. zod input schemas reject unknown keys; every tool has an outputSchema and returns structuredContent. Error results deliberately omit structuredContent, 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 refunded is set automatically when refunds reach the total, so money and status can't disagree.

  • Everything audited. Each successful write appends to audit_log with 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/sdk and zod. 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 test

Scripts

Script

What it does

npm run build / npm start

Compile (tsc strict) / run the stdio server

npm run seed

Recreate data/shop.db deterministically

npm test / npm run check

Tests / typecheck everything + tests

npm run smoke

Spawn the built server and exercise it with the MCP SDK client

npm run inspect / npm run inspect:cli

MCP Inspector web UI / list tools from the terminal

npm run eval / eval:oracle / eval:baseline / eval:report

Evals (see above)

License

MIT © 2026 Shubham Garje. Demo data is fictional; any resemblance to real people or businesses is coincidental.

Available Tools

8 tools
create_invoiceCreate invoiceA
Idempotent

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.

NameRequiredDescriptionDefault
notesNoOptional note printed on the invoice.
order_idYesNumeric order ID, e.g. 1042.
due_in_daysNoPayment terms for unpaid orders (default 14). Paid orders are invoiced as due today.

Output Schema

NameRequiredDescription
invoiceYes

TDQS

A4.2/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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 orderA
Read-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.

NameRequiredDescriptionDefault
order_idYesNumeric order ID, e.g. 1042.

Output Schema

NameRequiredDescription
itemsYes
orderYes
invoiceYes
refundsYes
customerYes
deliveryYes
financialsYes
refund_eligibilityYes

TDQS

A4.2/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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 refundA
DestructiveIdempotent

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.

NameRequiredDescriptionDefault
reasonYesWhy the refund is issued; stored in the audit log.
order_idYesNumeric order ID, e.g. 1042.
amount_centsYesRefund amount in integer cents (1050 = $10.50). Must be > 0 and <= the order's remaining refundable amount (get_order -> financials.refundable_cents).
idempotency_keyYesUnique 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

NameRequiredDescription
orderYes
refundYes
replayedYesTrue when this key was already used for the same refund: nothing new was refunded.

TDQS

A4.5/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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 ordersA
Read-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).

NameRequiredDescriptionDefault
sortNoplaced_at_desc
limitNoPage size, 1-100 (default 20).
offsetNoRows to skip; use next_offset from the previous page.
statusNoOnly orders in this status or any of these statuses, e.g. "paid" or ["paid","shipped"].
late_onlyNoOnly 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_toNoPlaced on or before this date (YYYY-MM-DD, inclusive).
customer_idNoOnly orders for this customer (see search_customers).
placed_fromNoPlaced on or after this date (YYYY-MM-DD, inclusive).
max_total_centsNoOrder total <= this many cents.
min_total_centsNoOrder total >= this many cents.

Output Schema

NameRequiredDescription
limitYes
offsetYes
ordersYes
next_offsetYesPass as offset to fetch the next page; null on the last page.
total_countYesRows matching the filters across all pages.

TDQS

A3.6/5.0
Behavior3/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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 alertsA
Read-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).

NameRequiredDescriptionDefault
categoryNoOnly this product category.

Output Schema

NameRequiredDescription
countYes
alertsYes

TDQS

A3.8/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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 reportA
Read-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.

NameRequiredDescriptionDefault
toYesLast day included (YYYY-MM-DD).
fromYesFirst day included (YYYY-MM-DD, by order placed date).
group_byNoday | week (ISO, Monday start) | month | product | customermonth

Output Schema

NameRequiredDescription
toYes
fromYes
rowsYes
basisYesorder_totals: gross = order totals incl. shipping. line_items (product view): gross = item revenue excl. shipping, refunds allocated pro-rata.
totalsYes
group_byYes

TDQS

A4.9/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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 customersA
Read-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.

NameRequiredDescriptionDefault
limitNoMaximum matches to return (default 10).
queryYesName, email, or part of either. Case/accent-insensitive and tolerant of typos (e.g. 'jon benett').

Output Schema

NameRequiredDescription
noteYes
queryYes
matchesYes
ambiguousYesTrue when more than one customer matches equally well - confirm with the user before acting.

TDQS

A4.2/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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 statusA
DestructiveIdempotent

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.

NameRequiredDescriptionDefault
noteNoOptional context stored in the audit log (e.g. tracking number).
statusYesTarget status. Allowed moves: pending->paid|cancelled, paid->shipped, shipped->delivered.
order_idYesNumeric order ID, e.g. 1042.

Output Schema

NameRequiredDescription
statusYes
changedYesFalse when the order was already in the requested status (no-op).
order_idYes
updated_atYes
previous_statusYes
allowed_next_statusesYes

TDQS

A4.9/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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.

  1. 8 tool updatesv0.1.0
    • First observedcreate_invoice
    • First observedget_order
    • First observedissue_refund
    • First observedlist_orders
    • First observedlow_stock_alerts
    • First observedsales_report
    • First observedsearch_customers
    • First observedupdate_order_status

TDQS

A4.1/5.0

Scored across 8 tools

Disambiguation5/5

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.

Naming Consistency4/5

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.

Tool Count5/5

Eight tools is well-scoped for a shop operations server, covering the main back-office workflows without feeling bloated or thin.

Completeness3/5

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

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables 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.
    1
    MIT
  • A
    license
    A
    quality
    D
    maintenance
    An 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.
    3
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables 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.
    -
  • F
    license
    A
    quality
    C
    maintenance
    Enables Claude to manage customers and support tickets through database-backed CRUD tools, contextual customer and knowledge resources, and a reusable ticket-triage prompt.
    13
    -