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

Open
#2,464 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug fix proposed P2 ready for work v1 v2
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" + render ZodDate as { type: "string", format: "date-time" } (matches serialization and the v3 path),
  • drop defaulted fields from required in output conversion,
  • drop additionalProperties: false in 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.