cd /news/ai-agents/your-mcp-tool-schema-is-silently-rej… · home topics ai-agents article
[ARTICLE · art-136735] src=dev.to ↗ pub= topic=ai-agents verified=true sentiment=· neutral

Your MCP Tool Schema Is Silently Rejecting Valid Calls

A developer building an MCP server that exposes roughly two dozen Amazon data endpoints as tools found that encoding an incomplete marketplace list as a JSON Schema enum in each tool's inputSchema caused the MCP SDK's AjvJsonSchemaValidator to silently reject valid calls before any handler ran. Because validation occurs before dispatch, the rejections left no server-side logs, and models often retried with a nearby enum value, returning plausible but wrong data. The team now splits glossary tables into closed sets, safe to emit as enums, and described-only sets documented in prose but enforced nowhere, promoting a table to closed only when its value set is corroborated by multiple independent sources.

by read4 min views1 publishedSep 22, 2026

We ship an MCP server that exposes about two dozen Amazon data endpoints as

tools. Each tool's inputSchema is the endpoint's request JSON Schema, passed

through untouched — the API contract is the tool contract. That design felt

obviously correct for about three weeks.

Then a user reported that querying a marketplace we support returned an error

saying the marketplace didn't exist. It did exist. We had shipped it for months.

The MCP SDK validates tool arguments against inputSchema before your handler

ever runs. With @modelcontextprotocol/sdk, registering a tool wires up an

AjvJsonSchemaValidator that checks the incoming arguments. If validation

fails, the handler is never called — you get a generic error back to the model,

and your code sees nothing.

The mechanism is fine. The problem was what we put in the schema.

Our API accepts a set of marketplaces. Upstream documentation lists nine of

them, and we'd published that list into our glossary, so we encoded it as an

enum in the request schema. Except our API supported four more marketplaces

than the upstream docs listed. The moment that enum reached inputSchema, it

stopped being documentation and became a gate:

// Before: the enum looked like helpful, self-documenting validation
inputSchema: {
  type: 'object',
  properties: {
    marketplace: { type: 'string', enum: ['US', 'UK', 'DE', /* ...nine total */] }
  }
}

A perfectly valid request for MX now fails in the SDK's validator. Not in our

gateway. Not in our upstream adapter. In a layer we don't control, before any

of our code runs.

Why it took so long to notice

Three things had to go wrong at once, and all three did.

The rejection is unobservable from the server side. Validation happens

before dispatch. Our request log is written after dispatch. Every rejected call

leaves zero trace — no row, no counter, no metric. Our logs showed a healthy

success rate the entire time, because the failures never reached them.

MCP calls are indistinguishable from REST calls in our telemetry. Both get

logged against the same endpoint path, so we couldn't even segment "MCP traffic

vs direct API traffic" to see the discrepancy. Searching our logs for MCP-shaped

rows returns nothing, because there is no such thing.

The model often doesn't surface the error. This is the part that still

bothers me. Asked for MX, a model that hits a rejected argument may quietly

retry with a nearby value from the enum — US, say — and continue. The caller

gets a normal-looking 200 with data for the wrong marketplace. That's worse than

a crash: a crash gets reported, a plausible wrong answer gets shipped.

What we do now

We split the concept in two. A glossary table can be closed — the value set is

genuinely exhaustive, safe to emit as a JSON Schema enum — or described-only,

which lists the values in prose and human-readable columns but never emits an

enum.

Of our nineteen glossary tables, four are closed. The other fifteen are

described in full, with every accepted value documented, and enforced nowhere.

That last part is the counterintuitive bit: an AI agent reading the description

sees exactly the same value list it would have seen in an enum. It just isn't

gated by it. The agent gets to try a value; the API gets to be the thing that

answers. Putting the check where the knowledge actually lives costs you nothing

in accuracy and buys back the ability to see what's happening.

Deciding when an enum is safe

We only promote a table to closed when the value set is corroborated by more

than one independent source. The one we felt best about is a weight-unit field:

// Four canonical units, listed in the appendix and independently in this

// endpoint's own field description, and the only values ever seen in

// production are g and lb — so the set is corroborated three ways rather

// than resting on the appendix alone.

enumPolicy: 'closed',

Three independent confirmations — spec, endpoint docs, observed production

values — for four values that are physically defined. That's the bar. An

upstream doc listing values without claiming the list is exhaustive does not

clear it, and we write down that reasoning next to the table so the next person

doesn't "fix" it back to an enum.

We also keep a kill switch — one boolean that stops emitting any glossary enums

at all — because releasing a bad constraint should not require a deploy.

The general lesson

If your API's schema is also your agent's tool schema, remember that they have

different failure modes. A JSON Schema for a REST API is documentation: clients

read it, and the server is the authority. The same schema as a tool

inputSchema is an enforcement point in a layer you can't instrument.

The asymmetry is brutal. A schema that's too loose costs you a clear validation

error you can see, log, and fix. A schema that's too tight costs you silent

rejections and confident wrong answers.

Write the values down. Enforce only what you'd bet on.

I work on Ecommerce Data API, which exposes

Amazon product, keyword and market data as a REST API and a remote MCP server —

the catalog described above. Our glossary tables are public if you want to see

which four we marked closed.

── more in #ai-agents 4 stories · sorted by recency
── more on @amazon 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/your-mcp-tool-schema…] indexed:0 read:4min 2026-09-22 ·