MCP TypeScript SDK v2: Fix zod-to-json-schema Errors After the 2026-07-28 Breaking Migration
MCP TypeScript SDK v2 breaks zod-to-json-schema in three ways: transforms lost, draft-07 vs 2020-12 mismatch, and Zod 4.0–4.1 .describe() fallback drops descriptions. Step-by-step fix guide with TypeScript code.
Have broken JSON right now? Fix it free in under 1 second — no signup.
Fix My JSON →The MCP TypeScript SDK v2 shipped alongside the 2026-07-28 stable spec, and it quietly broke three things that worked fine in v1.x — all of them in the zod-to-json-schema pipeline that turns your Zod schemas into MCP wire format.
If your MCP server passes tools/list cleanly but clients throw 400 errors or your tool descriptions are mysteriously blank, this guide is for you.
What Changed in SDK v2
1. Package split — @modelcontextprotocol/sdk is retired
The monolithic @modelcontextprotocol/sdk package is now split into focused packages:
| Old (v1) | New (v2) |
|---|---|
@modelcontextprotocol/sdk | @modelcontextprotocol/server — server-side only |
@modelcontextprotocol/client — client-side only | |
@modelcontextprotocol/core — shared types/protocol | |
@modelcontextprotocol/node — Node.js transport adapter | |
@modelcontextprotocol/hono — Hono adapter | |
@modelcontextprotocol/fastify — Fastify adapter | |
@modelcontextprotocol/server-legacy — backwards-compat shim |
Install only what you need:
# Server-only MCP endpoint
npm install @modelcontextprotocol/server @modelcontextprotocol/node
# Client that calls MCP servers
npm install @modelcontextprotocol/client @modelcontextprotocol/node
2. Standard Schema replaces Zod-specific APIs
SDK v2 adopts Standard Schema — a cross-library schema interface. You can now use Zod v4, Valibot, ArkType, or any Standard Schema-compliant library to define tool inputs and outputs.
Under the hood, the SDK calls schema['~standard'].jsonSchema to serialize your schema to JSON Schema for the MCP wire format.
The practical implication: which Zod version you install determines the JSON Schema output, and getting this wrong silently breaks your server.
The Three zod-to-json-schema Bugs
Bug 1 — Zod transform functions are stripped from the JSON Schema output
Symptom: A union type likez.union([z.array(z.string()), z.string()]).transform(parseArray) generates correct TypeScript types but the serialized JSON Schema loses the union options entirely. Clients see a schema with no valid input types and reject calls.
Root cause: zod-to-json-schema does not know how to represent .transform() in JSON Schema (JSON Schema has no concept of runtime transformations). It drops the entire branch containing the transform.
Fix: Separate your parsing transform from the schema declaration:
import { z } from 'zod'
// ❌ Before — transform attached to the schema
const inputSchema = z.union([
z.array(z.string()),
z.string(),
]).transform((val) => (typeof val === 'string' ? val.split(',') : val))
// ✅ After — schema for validation, transform applied separately
const inputSchema = z.union([
z.array(z.string()),
z.string(),
])
// Apply the transform in your tool handler, not in the schema
server.tool('parse-tags', inputSchema, async (input) => {
const tags = typeof input === 'string' ? input.split(',') : input
return { tags }
})
This lets the SDK serialize the union to a valid JSON Schema while you keep the runtime transformation in the handler.
Bug 2 — SDK generates JSON Schema draft-07, but modern MCP clients require draft-2020-12
Symptom:tools/list returns 200. The first actual tool call returns a 400 error with a body like "JSON Schema validation failed: $schema mismatch" or -32602 Invalid params depending on the client.
Root cause: The SDK internally bundles [email protected], which targets JSON Schema draft-07 by default. The MCP 2026 spec mandates JSON Schema 2020-12 on the wire. Modern MCP clients enforce this.
Checking your output directly:
import { zodToJsonSchema } from 'zod-to-json-schema'
import { z } from 'zod'
const schema = z.object({ query: z.string(), limit: z.number().optional() })
console.log(JSON.stringify(zodToJsonSchema(schema), null, 2))
// Output includes: "$schema": "http://json-schema.org/draft-07/schema#"
// ← This is wrong for MCP 2026 clients
Fix option A — upgrade to Zod ≥4.2.0 (recommended):
npm install zod@^4.2.0
With Zod ≥4.2.0, the SDK uses z.toJSONSchema() via ~standard.jsonSchema, which outputs JSON Schema 2020-12 by default. The $schema field will read "https://json-schema.org/draft/2020-12/schema".
target explicitly if you must stay on an older zod-to-json-schema:
import { zodToJsonSchema } from 'zod-to-json-schema'
import { z } from 'zod'
const schema = z.object({ query: z.string() })
const jsonSchema = zodToJsonSchema(schema, {
target: 'jsonSchema2020', // ← force 2020-12
$refStrategy: 'none',
})
You can paste your generated JSON Schema into the AI JSONMedic validator to confirm the $schema field before deploying.
Bug 3 — Zod 4.0–4.1 fallback drops .describe() field descriptions
Symptom: Tool parameters that use.describe('...') show blank descriptions in Claude Desktop / Cursor's tool inspector. The description field is missing from the JSON Schema.
Root cause: Zod 4.0–4.1 lacks the ~standard.jsonSchema property that the SDK needs. The SDK falls back to its bundled z.toJSONSchema() with a console warning — but the fallback implementation does not propagate .describe() annotations to the JSON Schema output.
// ❌ On Zod 4.0–4.1 — .describe() is silently dropped
const schema = z.object({
query: z.string().describe('The search query to execute'),
limit: z.number().optional().describe('Max results to return'),
})
// Resulting JSON Schema has properties but NO "description" fields
Fix: Upgrade to Zod ≥4.2.0:
npm install zod@^4.2.0
On Zod ≥4.2.0 with the v2 SDK, .describe() flows through correctly:
import { z } from 'zod'
// ✅ On Zod ≥4.2.0 — descriptions preserved
const schema = z.object({
query: z.string().describe('The search query to execute'),
limit: z.number().optional().describe('Max results to return'),
})
// Resulting JSON Schema:
// {
// "type": "object",
// "properties": {
// "query": { "type": "string", "description": "The search query to execute" },
// "limit": { "type": "number", "description": "Max results to return" }
// }
// }
The Silent Failure: Zod v3 in a v2 Project
There is a particularly nasty failure mode that trips up incremental migrations:
SDK v2 declares peerDependencies: { zod: "^3.25 || ^4.0" }. A Zod version that satisfied the v1 peer (e.g., [email protected]) will install and typecheck without errors under v2. The server starts normally.
The failure only appears when a client makes the first tools/list call — the schema registration swallows the conversion failure silently, then the SDK throws on serialization at request time.
# Installed: [email protected] (below 3.25 minimum)
npm install [email protected] @modelcontextprotocol/server@^2.0.0
// No TypeScript errors. Server starts. First tools/list → throws.
import { McpServer } from '@modelcontextprotocol/server'
import { z } from 'zod'
const server = new McpServer({ name: 'my-server', version: '1.0.0' })
server.tool('search', z.object({ q: z.string() }), async (input) => ({
result: `Searched: ${input.q}`,
}))
// tools/list response: 500 or malformed schema
Detect it:
node -e "const { z } = require('zod'); console.log(z.ZodType.prototype['~standard'] ? 'OK' : 'BROKEN')"
If it prints BROKEN, your Zod version is below the minimum. Upgrade:
npm install zod@^3.25.0
# Or jump to v4 for full 2020-12 support:
npm install zod@^4.2.0
Step-by-Step Migration: v1 to v2
Use this incremental approach to avoid breaking a running server:
Step 1 — Install v2 packages alongside v1npm install @modelcontextprotocol/server@^2.0.0 \
@modelcontextprotocol/client@^2.0.0 \
@modelcontextprotocol/node@^2.0.0 \
zod@^4.2.0
# Keep @modelcontextprotocol/sdk for now — don't remove yet
Step 2 — Update imports incrementally
// ❌ Old v1 import
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
// ✅ New v2 import
import { McpServer } from '@modelcontextprotocol/server'
import { StdioTransport } from '@modelcontextprotocol/node'
Step 3 — Validate your JSON Schema output
Before removing v1, verify your tool schemas serialize correctly. You can use the AI JSONMedic JSON Schema Generator to inspect your schema structure, or pipe to the validator:
import { z } from 'zod'
const mySchema = z.object({
query: z.string().describe('Search query'),
filters: z.array(z.string()).optional().describe('Tag filters'),
})
// Check what the SDK will serialize
const jsonSchema = mySchema['~standard'].jsonSchema
console.log(JSON.stringify(jsonSchema, null, 2))
// Should output $schema: "https://json-schema.org/draft/2020-12/schema"
Paste the output into the JSON Schema Validator to confirm it's valid 2020-12.
Step 4 — Remove the v1 dependencynpm uninstall @modelcontextprotocol/sdk
Only do this after confirming all imports use v2 packages.
v1 vs v2 Comparison Summary
| Area | v1 (@modelcontextprotocol/sdk) | v2 (split packages) |
|---|---|---|
| Install | Single package | Install only server or client |
| Schema library | Zod-specific | Standard Schema (Zod, Valibot, ArkType) |
| JSON Schema version | draft-07 via zod-to-json-schema | 2020-12 via ~standard.jsonSchema (Zod ≥4.2) |
.describe() support | Always worked | Requires Zod ≥4.2.0 |
| Transform handling | Transparent (no wire format) | Must separate from schema declaration |
| Node.js transport | Bundled | @modelcontextprotocol/node |
| Silent Zod version failures | No (explicit peer range) | Yes — Zod <3.25 installs clean, fails at runtime |
FAQ
Q: Why does my MCP server start but fail on the firsttools/list call after upgrading to SDK v2?
Most likely you have Zod v3 below 3.25 installed. SDK v2 requires zod@^3.25 || ^4.0. Older Zod versions install without TypeScript errors but fail at runtime when the SDK tries to serialize your schema. Run npm install zod@^4.2.0 to fix it.
You are likely running Zod 4.0 or 4.1, which lacks the ~standard.jsonSchema interface. The SDK falls back to an internal serializer that drops .describe() annotations. Upgrade to zod@^4.2.0.
Upgrade to Zod ≥4.2.0. The SDK will then use z.toJSONSchema() which outputs JSON Schema 2020-12 by default — matching the MCP 2026 spec requirement.
Yes, but only Zod ≥3.25. You'll be stuck on JSON Schema draft-07 output. For full 2020-12 support and working .describe() annotations, upgrade to Zod ≥4.2.0.
.transform() in MCP tool schemas?
Transforms are not representable in JSON Schema and are dropped during serialization. Define your input schema without transforms, and apply data transformations inside your tool handler function instead.
Q: Is@modelcontextprotocol/sdk still maintained?
Active development is in the split packages. A @modelcontextprotocol/server-legacy shim exists for backwards compatibility, but new projects should use @modelcontextprotocol/server and @modelcontextprotocol/client directly.
Related Guides
- MCP 2026 JSON Schema 2020-12 migration guide — the spec-level changes (not SDK-specific)
- MCP Python SDK v2 beta migration — same spec, Python side
- MCP JSON Schema validation errors — runtime schema error patterns
- Validate your generated JSON Schema — paste and check before deploying
- JSON Schema Generator — generate a valid schema from a JSON sample
Still dealing with broken JSON?
Paste it in and get it fixed in under 1 second — free, no signup, no install. Works with ChatGPT, Claude, n8n, and any AI output.
Fix My JSON Free →Related Articles