Case Study: NEXUM-004 IdempotencyMissing on a real MCP spec — Nexum
CASE STUDY<br>May 17, 2026
NEXUM-004 on a real spec: how a public MCP storefront closed an IdempotencyMissing finding in a day
This is not a demo spec. Fabian Williams (@fabianwilliams) built a public, MCP-callable storefront — a real purchase flow, live on fabswill.com. When Nexum's rules were walked against his spec by hand, one of them failed: NEXUM-004. Here's what happened, in his own words and his own pull request.
The spec
Fabian's storefront serves one MCP-callable endpoint that produces an identical, protocol-neutral<br>audit-trail receipt regardless of caller — he tested it against both hosted Claude Desktop and a<br>fully-local Qwen3.6 27B running offline on his laptop, same six runtime checks, same result. He<br>posted it as a thread on X<br>on May 16, 2026.
Mehdi replied the next day: the runtime checks looked solid, but what did the spec look like<br>before it reached the client? The receipt-URL pattern — one endpoint satisfying both<br>the security audit and the finance billing view — was interesting from a blast-radius angle: an<br>endpoint that serves two consumers is also one endpoint an agent can call for both.
Fabian took the offer. The Nexum scanner binary wasn't public yet, so he walked his<br>purchase_free_bundle spec against the manifest's five enumerated rules by hand.<br>Four passed or didn't apply. One failed.
The finding — NEXUM-004 IdempotencyMissing
HIGH<br>purchase_free_bundle is a mutating MCP tool: it mints a receipt, upserts a contact in<br>Brevo, mints a JOSE-signed download token, and dispatches a fulfillment email. The tool's<br>inputSchema exposed no Idempotency-Key. Any agent that retries on<br>timeout — the default retry behavior in most LLM-SDK client policies — would double-issue the<br>bundle and double-fire the email.
Why the runtime guards didn't catch it
Fabian's storefront already had runtime governance in place: a 5-requests-per-hour rate limit per<br>IP, and a $5/day cost ceiling. Neither one fires on this bug. Two retries inside the same hour<br>both land comfortably under the rate-limit cap on first try. A duplicate free-bundle issue costs<br>nowhere near the daily ceiling. As Fabian put it, that's exactly the defense-in-depth gap Nexum's<br>framing predicts: runtime guards and static spec review catch different classes of failure. A<br>spec-level check on the schema itself catches what request-volume and cost-based guards<br>structurally cannot see.
The fix — PR #1
Fabian shipped the fix the same day, merged May 17, 2026:
Schema. Added idempotency_key (optional, 8–128 chars, pattern [A-Za-z0-9._-]) to the MCP tool's inputSchema, with the tool description updated to recommend it for production agents.
HTTP header. Both /api/a2a/mcp and /api/a2a/purchase also accept a standard Idempotency-Key request header — the tool argument wins if both are present.
Storage. A new idempotency.ts module backed by Azure Table Storage. PartitionKey and RowKey are hashed values — no raw key or email is stored.
Replay semantics, Stripe-style. On every request with a valid key, the table is checked before input validation runs. On a hit, the original receipt is loaded from Blob storage and returned exactly as-is.
Only successes are cached. Failure receipts are never stored, so a transient downstream failure (a Brevo blip, for example) doesn't get poisoned by a cached error — the agent's retry re-runs the flow as intended.
Fail open. A missing cache hit or a failed write are both swallowed rather than surfaced as errors. Idempotency is a safety belt here, not a new failure mode.
Known v1 limitations
Fabian documented these in the PR itself, rather than glossing over them:
Concurrent requests with the same key can both miss the cache and both run the flow — last-write-wins on the table, and two receipts get written to Blob. A Stripe-style in-progress lock (set state to processing under optimistic concurrency, return 409 on collision) is the planned v2 hardening.
No table TTL yet — mappings live until manually reaped. Negligible cost at demo volume; flagged for revisit past 10k+ rows.
Fabian Williams<br>@fabianwilliams
“Credit where it is due @MBelckadi — your trust-manifest is a clean static-analysis schema.”
May 17, 2026
Why this matters
Runtime guards — rate limits, cost ceilings — govern behavior: how often and how much.<br>They say nothing about whether a single call, executed exactly once as designed, is safe to retry.<br>A static read of the spec catches that class of risk before the first agent ever calls the tool —<br>defense-in-depth on top of runtime governance, not a replacement for it. That's the whole premise<br>behind the Nexum Cert.
Scan your API
Is your MCP server agent-safe?
Upload your OpenAPI spec and get a Nexum Cert + PDF report in seconds.<br>Free. No account required.
Scan your API free →<br>View the registry