<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Cloudy</title>
    <description>The latest articles on DEV Community by Cloudy (@cloudy_c97250d0fc460db760).</description>
    <link>https://dev.to/cloudy_c97250d0fc460db760</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3721957%2F5471a2a9-f340-49e8-b351-8cb6a2652684.jpg</url>
      <title>DEV Community: Cloudy</title>
      <link>https://dev.to/cloudy_c97250d0fc460db760</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cloudy_c97250d0fc460db760"/>
    <language>en</language>
    <item>
      <title>Your MCP Tool Schema Is Silently Rejecting Valid Calls</title>
      <dc:creator>Cloudy</dc:creator>
      <pubDate>Tue, 22 Sep 2026 07:16:01 +0000</pubDate>
      <link>https://dev.to/cloudy_c97250d0fc460db760/your-mcp-tool-schema-is-silently-rejecting-valid-calls-1495</link>
      <guid>https://dev.to/cloudy_c97250d0fc460db760/your-mcp-tool-schema-is-silently-rejecting-valid-calls-1495</guid>
      <description>&lt;p&gt;We ship an MCP server that exposes about two dozen Amazon data endpoints as&lt;br&gt;
tools. Each tool's &lt;code&gt;inputSchema&lt;/code&gt; is the endpoint's request JSON Schema, passed&lt;br&gt;
through untouched — the API contract &lt;em&gt;is&lt;/em&gt; the tool contract. That design felt&lt;br&gt;
obviously correct for about three weeks.&lt;/p&gt;

&lt;p&gt;Then a user reported that querying a marketplace we support returned an error&lt;br&gt;
saying the marketplace didn't exist. It did exist. We had shipped it for months.&lt;/p&gt;
&lt;h2&gt;
  
  
  What was actually happening
&lt;/h2&gt;

&lt;p&gt;The MCP SDK validates tool arguments against &lt;code&gt;inputSchema&lt;/code&gt; before your handler&lt;br&gt;
ever runs. With &lt;code&gt;@modelcontextprotocol/sdk&lt;/code&gt;, registering a tool wires up an&lt;br&gt;
&lt;code&gt;AjvJsonSchemaValidator&lt;/code&gt; that checks the incoming arguments. If validation&lt;br&gt;
fails, the handler is never called — you get a generic error back to the model,&lt;br&gt;
and your code sees nothing.&lt;/p&gt;

&lt;p&gt;The mechanism is fine. The problem was what we put &lt;em&gt;in&lt;/em&gt; the schema.&lt;/p&gt;

&lt;p&gt;Our API accepts a set of marketplaces. Upstream documentation lists nine of&lt;br&gt;
them, and we'd published that list into our glossary, so we encoded it as an&lt;br&gt;
&lt;code&gt;enum&lt;/code&gt; in the request schema. Except our API supported four more marketplaces&lt;br&gt;
than the upstream docs listed. The moment that &lt;code&gt;enum&lt;/code&gt; reached &lt;code&gt;inputSchema&lt;/code&gt;, it&lt;br&gt;
stopped being documentation and became a gate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before: the enum looked like helpful, self-documenting validation&lt;/span&gt;
&lt;span class="nx"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;marketplace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UK&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* ...nine total */&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A perfectly valid request for MX now fails in the SDK's validator. Not in our&lt;br&gt;
gateway. Not in our upstream adapter. In a layer we don't control, before any&lt;br&gt;
of our code runs.&lt;/p&gt;

&lt;p&gt;Why it took so long to notice&lt;/p&gt;

&lt;p&gt;Three things had to go wrong at once, and all three did.&lt;/p&gt;

&lt;p&gt;The rejection is unobservable from the server side. Validation happens&lt;br&gt;
before dispatch. Our request log is written after dispatch. Every rejected call&lt;br&gt;
leaves zero trace — no row, no counter, no metric. Our logs showed a healthy&lt;br&gt;
success rate the entire time, because the failures never reached them.&lt;/p&gt;

&lt;p&gt;MCP calls are indistinguishable from REST calls in our telemetry. Both get&lt;br&gt;
logged against the same endpoint path, so we couldn't even segment "MCP traffic&lt;br&gt;
vs direct API traffic" to see the discrepancy. Searching our logs for MCP-shaped&lt;br&gt;
rows returns nothing, because there is no such thing.&lt;/p&gt;

&lt;p&gt;The model often doesn't surface the error. This is the part that still&lt;br&gt;
bothers me. Asked for MX, a model that hits a rejected argument may quietly&lt;br&gt;
retry with a nearby value from the enum — US, say — and continue. The caller&lt;br&gt;
gets a normal-looking 200 with data for the wrong marketplace. That's worse than&lt;br&gt;
a crash: a crash gets reported, a plausible wrong answer gets shipped.&lt;/p&gt;

&lt;p&gt;What we do now&lt;/p&gt;

&lt;p&gt;We split the concept in two. A glossary table can be closed — the value set is&lt;br&gt;
genuinely exhaustive, safe to emit as a JSON Schema enum — or described-only,&lt;br&gt;
which lists the values in prose and human-readable columns but never emits an&lt;br&gt;
enum.&lt;/p&gt;

&lt;p&gt;Of our nineteen glossary tables, four are closed. The other fifteen are&lt;br&gt;
described in full, with every accepted value documented, and enforced nowhere.&lt;/p&gt;

&lt;p&gt;That last part is the counterintuitive bit: an AI agent reading the description&lt;br&gt;
sees exactly the same value list it would have seen in an enum. It just isn't&lt;br&gt;
gated by it. The agent gets to try a value; the API gets to be the thing that&lt;br&gt;
answers. Putting the check where the knowledge actually lives costs you nothing&lt;br&gt;
in accuracy and buys back the ability to see what's happening.&lt;/p&gt;

&lt;p&gt;Deciding when an enum is safe&lt;/p&gt;

&lt;p&gt;We only promote a table to closed when the value set is corroborated by more&lt;br&gt;
than one independent source. The one we felt best about is a weight-unit field:&lt;/p&gt;

&lt;p&gt;// Four canonical units, listed in the appendix and independently in this&lt;br&gt;
// endpoint's own field description, and the only values ever seen in&lt;br&gt;
// production are g and lb — so the set is corroborated three ways rather&lt;br&gt;
// than resting on the appendix alone.&lt;br&gt;
enumPolicy: 'closed',&lt;/p&gt;

&lt;p&gt;Three independent confirmations — spec, endpoint docs, observed production&lt;br&gt;
values — for four values that are physically defined. That's the bar. An&lt;br&gt;
upstream doc listing values without claiming the list is exhaustive does not&lt;br&gt;
clear it, and we write down that reasoning next to the table so the next person&lt;br&gt;
doesn't "fix" it back to an enum.&lt;/p&gt;

&lt;p&gt;We also keep a kill switch — one boolean that stops emitting any glossary enums&lt;br&gt;
at all — because releasing a bad constraint should not require a deploy.&lt;/p&gt;

&lt;p&gt;The general lesson&lt;/p&gt;

&lt;p&gt;If your API's schema is also your agent's tool schema, remember that they have&lt;br&gt;
different failure modes. A JSON Schema for a REST API is documentation: clients&lt;br&gt;
read it, and the server is the authority. The same schema as a tool&lt;br&gt;
inputSchema is an enforcement point in a layer you can't instrument.&lt;/p&gt;

&lt;p&gt;The asymmetry is brutal. A schema that's too loose costs you a clear validation&lt;br&gt;
error you can see, log, and fix. A schema that's too tight costs you silent&lt;br&gt;
rejections and confident wrong answers.&lt;/p&gt;

&lt;p&gt;Write the values down. Enforce only what you'd bet on.&lt;/p&gt;




&lt;p&gt;I work on &lt;a href="https://ecommercedataapi.com" rel="noopener noreferrer"&gt;Ecommerce Data API&lt;/a&gt;, which exposes&lt;br&gt;
Amazon product, keyword and market data as a REST API and a remote MCP server —&lt;br&gt;
the catalog described above. Our glossary tables are public if you want to see&lt;br&gt;
which four we marked closed.&lt;/p&gt;

</description>
      <category>api</category>
      <category>debugging</category>
      <category>mcp</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Website Traffic Checker</title>
      <dc:creator>Cloudy</dc:creator>
      <pubDate>Tue, 20 Jan 2026 14:44:33 +0000</pubDate>
      <link>https://dev.to/cloudy_c97250d0fc460db760/website-traffic-checker-2amk</link>
      <guid>https://dev.to/cloudy_c97250d0fc460db760/website-traffic-checker-2amk</guid>
      <description>&lt;p&gt;This free traffic checker sounds super useful! I like that it gives instant results and analyzes so many sites. Checking competitors and getting hidden insights could really help with my online projects. &lt;a href="https://websitetrafficchecker.net" rel="noopener noreferrer"&gt;https://websitetrafficchecker.net&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
