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. 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 https://ecommercedataapi.com , 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.