You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Docs: passing z.object() as inputSchema on v1 fails silently (empty schema) or cryptically (tools/list crash) — not covered in troubleshooting or migration guide #2627
On SDK v1 (still latest on npm as of this writing — 1.30.0), passing a ZodObject (z.object({...})) where the API expects a Zod raw shape ({ field: z.string() }) produces failure modes that are either silent or cryptic, depending on the version and the API used. None of them point the developer at the actual mistake, and neither the troubleshooting page nor the v1→v2 migration guide mentions these symptoms.
I hit this while building scorm-mcp-server and lost the better part of a day on it: the server started fine, tools/list looked plausible, but every client silently stripped my tool arguments. I've since tested the behavior across v1 versions — full matrix below.
Reproduction matrix
Minimal repro: register one tool with inputSchema: z.object({ name: z.string() }) (instead of the raw shape { name: z.string() }), connect an in-memory client, call tools/list.
⚠️Silent: publishes an empty schema ({"type":"object"}, no properties) — clients strip all arguments; handler receives no args
1.22.0 → 1.26.0
✅ normalized, works
⚠️ Silent empty schema (same as above)
1.28.0 → 1.30.0
✅ normalized, works
✅ throws a clear error at registration
Repro script (run with any of the versions above)
import{McpServer}from'@modelcontextprotocol/sdk/server/mcp.js';import{Client}from'@modelcontextprotocol/sdk/client/index.js';import{InMemoryTransport}from'@modelcontextprotocol/sdk/inMemory.js';import{z}from'zod';constserver=newMcpServer({name: 't',version: '1.0.0'});// WRONG on v1 (correct on v2): ZodObject instead of raw shapeserver.tool('greet','desc',z.object({name: z.string()}),async(args)=>({content: [{type: 'text',text: JSON.stringify(args)}]}));const[ct,st]=InMemoryTransport.createLinkedPair();constclient=newClient({name: 'c',version: '1.0.0'});awaitPromise.all([server.connect(st),client.connect(ct)]);const{ tools }=awaitclient.listTools();console.log(JSON.stringify(tools[0].inputSchema));// 1.12.0–1.26.0 → {"type":"object"} (empty — no properties, no error anywhere)
Why this still matters even though recent versions behave better
Older 1.x versions are massively deployed. Plenty of published servers pin ^1.x ranges resolved months ago; every tutorial written before mid-v1 shows raw shapes, while the v2 docs and README now show z.object() — so developers moving between examples get bitten in both directions.
The failure modes never mention the cause. An empty {"type":"object"} schema looks like a client bug ("my client strips arguments"), and Cannot read properties of null (reading '_def') looks like an SDK internals bug. Neither error string is searchable back to "you passed a ZodObject where a raw shape was expected".
Troubleshooting page: add an entry keyed on the two observable symptoms — "my tool's inputSchema is empty / clients don't send arguments" and "tools/list fails with reading '_def'" — explaining the raw-shape vs ZodObject distinction on v1 and which versions normalize it.
Migration guide: the v1→v2 guide already documents the API shift (raw shape → Standard Schema), but a one-line warning that doing it backwards on v1 ≤1.21 crashes tools/list, and ≤1.26 silently empties the schema via server.tool(), would save people real debugging time.
I'm happy to open a PR with the docs wording if maintainers agree this belongs in the docs.
Environment used for the matrix: Node v22, zod 3.23.8, in-memory transport, each SDK version installed clean from npm.
Summary
On SDK v1 (still
lateston npm as of this writing — 1.30.0), passing aZodObject(z.object({...})) where the API expects a Zod raw shape ({ field: z.string() }) produces failure modes that are either silent or cryptic, depending on the version and the API used. None of them point the developer at the actual mistake, and neither the troubleshooting page nor the v1→v2 migration guide mentions these symptoms.I hit this while building scorm-mcp-server and lost the better part of a day on it: the server started fine,
tools/listlooked plausible, but every client silently stripped my tool arguments. I've since tested the behavior across v1 versions — full matrix below.Reproduction matrix
Minimal repro: register one tool with
inputSchema: z.object({ name: z.string() })(instead of the raw shape{ name: z.string() }), connect an in-memory client, calltools/list.registerTool+z.object()server.tool()(positional) +z.object()tools/listcrashes:MCP error -32603: Cannot read properties of null (reading '_def'){"type":"object"}, no properties) — clients strip all arguments; handler receives no argsRepro script (run with any of the versions above)
Why this still matters even though recent versions behave better
^1.xranges resolved months ago; every tutorial written before mid-v1 shows raw shapes, while the v2 docs and README now showz.object()— so developers moving between examples get bitten in both directions.{"type":"object"}schema looks like a client bug ("my client strips arguments"), andCannot read properties of null (reading '_def')looks like an SDK internals bug. Neither error string is searchable back to "you passed a ZodObject where a raw shape was expected".z.object()still doesn't cover other Zod types — see registerTool() silently drops inputSchema for z.discriminatedUnion() — normalizeObjectSchema only passes z.object() #1643 (z.discriminatedUnion()silently dropped), so the general class of "wrong schema kind fails silently" is still live onlatest.Suggested fix (happy to PR either)
inputSchemais empty / clients don't send arguments" and "tools/listfails withreading '_def'" — explaining the raw-shape vsZodObjectdistinction on v1 and which versions normalize it.tools/list, and ≤1.26 silently empties the schema viaserver.tool(), would save people real debugging time.I'm happy to open a PR with the docs wording if maintainers agree this belongs in the docs.
Environment used for the matrix: Node v22, zod 3.23.8, in-memory transport, each SDK version installed clean from npm.