Skip to content

fix(core): inline reused Zod subschemas in tools/list JSON Schema#2119

Open
abdulmunimj wants to merge 1 commit into
modelcontextprotocol:mainfrom
abdulmunimj:fix/tools-list-zod-refs
Open

fix(core): inline reused Zod subschemas in tools/list JSON Schema#2119
abdulmunimj wants to merge 1 commit into
modelcontextprotocol:mainfrom
abdulmunimj:fix/tools-list-zod-refs

Conversation

@abdulmunimj
Copy link
Copy Markdown

Fixes #2100.

Problem

McpServer.registerTool() accepts a Zod input schema and advertises it under
tools/list via standardSchemaToJsonSchema() in packages/core/src/util/standardSchema.ts.
When that schema reuses a subschema (the same z.object({...}) referenced in two
places, or once inside an array), zod's JSON Schema converter may dedupe the
reused instance into a $ref pointer. Strict MCP clients (issue #2100 reports
kimi) reject any $ref form other than #/$defs/..., so tools/list fails
with errors like references must start with #/$defs/.

Zod's to-json-schema exposes a reused option whose value flipped between
'ref' and 'inline' across 4.x minors; the SDK pins zod ^4.2.0, so users on
older 4.2.x are exposed to the buggy default. Even on the current default,
relying on zod's choice is fragile.

Fix

Pass reused: 'inline' from standardSchemaToJsonSchema on both code paths:

  • Primary path (line ~181) — schemas implementing StandardJSONSchemaV1
    (zod >= 4.2.0, ArkType, Valibot via toStandardJsonSchema). The option is
    threaded through libraryOptions, which zod's standard-schema entrypoint
    forwards to the converter (verified against
    zod/v4/core/to-json-schema.ts:createStandardJSONSchemaMethod). The
    SDK's StandardJSONSchemaV1.Options already permits
    libraryOptions?: Record<string, unknown>, so the change is type-safe.
  • Fallback path (line ~200) — zod 4.0/4.1 (no ~standard.jsonSchema).
    Pass reused: 'inline' directly to z.toJSONSchema. This matches the
    exact site the issue text points at.

Editing only the fallback path (as the issue text suggests) would have zero
effect for the SDK's stated minimum zod (^4.2.0), where the primary path is
the live one. The maintainer-affiliated commenter on #2100 outlined the same
approach.

Tests

packages/core/test/util/standardSchema.test.ts — new describe('$ref behavior for reused schemas (issue #2100)') block:

  • Reused object referenced by two sibling fields — assert zero $ref keys.
  • Reused object inside an array — assert zero $ref keys.
  • Reused object inside a discriminated union — assert zero $ref keys.
  • Control case: call the underlying ~standard.jsonSchema with
    libraryOptions.reused: 'ref' directly to witness that ref-mode is
    reachable (proving zod still honours the knob), then call the SDK and
    assert it still inlines. This defends against zod silently dropping the
    option or flipping its default in either direction.

packages/core/test/util/standardSchema.zodFallback.test.ts — adds an inline
assertion for the zod 4.0/4.1 fallback path, by stripping ~standard.jsonSchema
from a real zod schema and asserting $ref keys are absent in the result.

All 557 core tests pass; full repo pnpm test:all is green except an unrelated,
environment-flaky integration test (test/server/cloudflareWorkers.test.ts
fails with spawnSync /bin/sh ETIMEDOUT running a nested npm install,
reproducible on clean main).

Changeset

.changeset/inline-reused-zod-schemas.md (@modelcontextprotocol/core: patch).

Out of scope

Per-tool override knob for reused mode — can land in a follow-up if anyone
ever needs ref-mode output for a non-strict client.

@abdulmunimj abdulmunimj requested a review from a team as a code owner May 18, 2026 14:06
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 18, 2026

🦋 Changeset detected

Latest commit: 9150754

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@modelcontextprotocol/core Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 18, 2026

Open in StackBlitz

@modelcontextprotocol/client

npm i https://pkg.pr.new/@modelcontextprotocol/client@2119

@modelcontextprotocol/server

npm i https://pkg.pr.new/@modelcontextprotocol/server@2119

@modelcontextprotocol/express

npm i https://pkg.pr.new/@modelcontextprotocol/express@2119

@modelcontextprotocol/fastify

npm i https://pkg.pr.new/@modelcontextprotocol/fastify@2119

@modelcontextprotocol/hono

npm i https://pkg.pr.new/@modelcontextprotocol/hono@2119

@modelcontextprotocol/node

npm i https://pkg.pr.new/@modelcontextprotocol/node@2119

commit: 9150754

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tool inputSchema generation emits $ref for reused Zod instances · breaks strict MCP clients (kimi)

2 participants