If your Claude Desktop app updated itself overnight and your local MCP servers stopped registering the next morning, this is probably why. It's not your config, and it's not your server code. A newer bundled MCP client started rejecting your tool schemas over a version string in the schema.
The error
On Windows, the MSIX build of Claude Desktop auto-updated to 1.32352.1.0. After that, my filesystem MCP server refused to register in Cowork sessions:
Tool 'list_directory' has an invalid outputSchema: JSON Schema declares an unsupported
dialect ("$schema": "http://json-schema.org/draft-07/schema#"). The default validator
supports JSON Schema 2020-12 only
Re-adding the server in settings didn't help. Another user reproduced the exact same failure with an n8n MCP server, 33 tools, same error. It wasn't one bad server.
What actually changed
Claude Desktop ships its own copy of the MCP TypeScript SDK. The updated build bundles @modelcontextprotocol/client 2.0.0-beta.4, and its default validator (Ajv) throws on any tool schema that declares a $schema dialect other than 2020-12.
The part that stings: the MCP spec (SEP-1613) says 2020-12 is only the default when $schema is absent. Declaring draft-07 is legal. The beta client was stricter than the spec.
The other half of the story: if you built an MCP server with the 1.x TypeScript SDK, zod-to-json-schema stamped draft-07 on every inputSchema and outputSchema it generated. So a client-side update broke servers that never changed a line.
The upstream fix has actually landed. modelcontextprotocol/typescript-sdk PR #2534 merged and shipped in client 2.0.0, which validates whatever dialect you declare instead of rejecting everything but 2020-12. Claude Desktop just still bundles the older beta client. Fun detail: the Claude Code CLI already bundles the fixed client, same schemas, same day, no error. The lag is desktop-specific, and since MSIX auto-updates, you can't really pin an older desktop version.
The fix that works today
Option 1: a tiny stdio proxy that cleans up $schema
The proxy spawns your real server, passes messages through unchanged, and rewrites tools/list responses so no tool declares a rejected dialect. Two people have confirmed this works, 14 of 14 and 33 of 33 tools registering, with real tool calls succeeding after the rewrite.
// mcp-dialect-fix.mjs
import { spawn } from "node:child_process";
import readline from "node:readline";
// spawn the real server
const server = spawn("npx", ["-y", "@modelcontextprotocol/server-filesystem", "C:/data"], {
stdio: ["pipe", "pipe", "inherit"],
});
const rl = readline.createInterface({ input: server.stdout });
rl.on("line", (line) => {
try {
const msg = JSON.parse(line);
// tools/list responses carry result.tools but no method field
if (msg.result && Array.isArray(msg.result.tools)) {
for (const tool of msg.result.tools) {
// delete the dialect claim, or set it to the 2020-12 URI
for (const key of ["inputSchema", "outputSchema"]) {
if (tool[key]?.$schema) delete tool[key].$schema;
}
}
}
process.stdout.write(JSON.stringify(msg) + "\n");
} catch {
process.stdout.write(line + "\n");
}
});
process.stdin.pipe(server.stdin);
Deleting the key instead of rewriting it is safe in most cases: schemas produced by zod-to-json-schema are already 2020-12-compatible in structure. The client only chokes on the declared dialect string, so removing it lets the validator fall back to its default. If your schemas rely on draft-07-only keywords, set the 2020-12 URI explicitly instead and check nothing breaks.
Then point your MCP client config at the proxy instead of the server:
{
"mcpServers": {
"filesystem": {
"command": "node",
"args": ["C:\\path\\to\\mcp-dialect-fix.mjs"]
}
}
}
Two gotchas, both annoying:
- The config is read when the app launches. Quit Claude Desktop completely, tray icon included, before editing it, or your change gets silently overwritten.
- Servers register at session start. Open a brand-new session to test. Existing sessions keep showing the failure until they're restarted anyway.
Option 2: fix your own server instead
If you control the server code, don't declare draft-07 at all. Omit $schema entirely (2020-12 is the default), or set it to the 2020-12 URI explicitly. If you're on the 1.x TS SDK, check what zod-to-json-schema stamps onto your tool schemas and override the dialect.
Option 3: wait it out
The client-side fix is merged and real. This goes away when a desktop build bundles client 2.0.0 or newer. Until then, the proxy is a five-minute bridge, and it beats losing your local tools every time the app updates itself.
Sources: anthropics/claude-code issue #87633 (where two users verified the proxy fix) and the upstream SDK fix, PR #2534.
Top comments (1)
Thanks for writing this up — "a client update broke servers that never changed a line" is the worst kind of bug, because your first instinct is to audit your own diff. The zod-to-json-schema stamping draft-07 on everything is the detail that makes it hit a whole population at once instead of one bad server.
The stdio proxy rewriting tools/list is a clean interim fix. Two things I'd be careful with: if the validator also touches tools/call responses, cleaning $schema on the list side alone may not be enough for servers that echo a schema back. And rewriting $schema to 2020-12 when the body still uses draft-07-only keywords is a latent trap — safer to drop the $schema line and let the client apply its default than to declare a dialect the schema doesn't honor.
The MSIX-can't-pin part stings most: auto-update with no rollback removes the "wait for the fixed build" option, so a proxy or an SDK fork is the only user-side lever. Same class of skew bites me on the CDP side — a client that ships ahead of the spec starts rejecting perfectly valid inputs, and strictness above the spec always punishes the compliant party. Did you file against the desktop bundle's SDK pin so there's a tracking issue, or is it only the merged 2.0.0 fix?