modelcontextprotocol / modelcontextprotocol/typescript-sdk
zod v4: toJSONSchema options are hardcoded — z.date() breaks tools/list; output schemas advertise required/closed shapes the raw structuredContent can't satisfy
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 13.4k
- Forks
- 2.2k
- Avg merge
- 3d 15h
- Merged PRs (30d)
- 4
Description
Summary
When tools use zod v4 schemas, the SDK's zod→JSON Schema conversion (server/zod-json-schema-compat.ts, toJsonSchemaCompat) calls zod's native toJSONSchema with hardcoded options and no passthrough, producing three behaviors that break real servers. All three stem from the same asymmetry: the SDK converts and validates with the tool's zod schema, but sends the tool's raw structuredContent object (validation does not replace/serialize the payload), so the advertised schema must describe the serialized form of a raw object.
Versions: @modelcontextprotocol/sdk 1.29.0, zod 4.4.3.
1. z.date() anywhere in a tool schema kills tools/list
zod v4's toJSONSchema defaults to unrepresentable: "throw", so listing tools throws:
McpError: MCP error -32603: Date cannot be represented in JSON Schema
The zod v3 path (vendored zod-to-json-schema) rendered z.date() as { "type": "string", "format": "date-time" } — which is also what JSON.stringify actually puts on the wire for a Date. Servers migrating v3→v4 with Date-valued fields (e.g. ORM entities) lose tools/list entirely. Switching schemas to z.iso.datetime() is not a workaround: server-side output validation then rejects the tools' raw Date values before serialization (isError: true, "expected string").
2. Defaulted output-schema fields are advertised as required
With io: "output" semantics, zod v4 lists .default() fields in required. But the server ships the tool's raw return value — zod never fills defaults into the response — so a tool that omits a defaulted field violates its own advertised schema, and spec-compliant validating clients reject the response. (zod-to-json-schema kept defaulted fields optional.)
3. additionalProperties: false on output objects can't be honored
Plain z.object() converts with additionalProperties: false on outputs, while zod validation tolerates and passes through unknown keys on the raw object. Any tool returning extra keys (very common when returning spread ORM/service objects) produces responses that validating clients reject. We caught four production tools failing this way once we added client-side ajv validation of serialized results.
Repro (issues 1 & 2 in one tool)
const server = new McpServer({ name: "repro", version: "1.0.0" });
server.registerTool(
"t",
{
description: "repro",
inputSchema: { when: z.date() }, // (1) tools/list throws
outputSchema: { counted: z.number().default(0), // (2) advertised as required
name: z.string() },
},
async () => ({ content: [], structuredContent: { name: "x" } }) // omits `counted` → strict clients reject
);
Proposal
Either (preferred, minimal) expose conversion options on registerTool/server config — at least unrepresentable and override passthrough to toJSONSchema — or make the defaults wire-truthful:
unrepresentable: "any"+ renderZodDateas{ type: "string", format: "date-time" }(matches serialization and the v3 path),- drop defaulted fields from
requiredin output conversion, - drop
additionalProperties: falsein output conversion (or strip unknown keys during output validation so the promise is actually kept).
We currently carry exactly these three adjustments as a ~30-line pnpm patch to zod-json-schema-compat (both dists), with contract tests pinning the behaviors — happy to send a PR if maintainers agree on the direction.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in server/zod-json-schema-compat.ts at toJsonSchemaCompat and trace how registerTool/server configuration supplies conversion options for zod v4 input and output schemas. Reproduce the z.date(), defaulted-field, and extra-key cases, then add or update the contract tests mentioned in the issue so the selected wire-truthful behavior is covered.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, backend-api-design
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100