Skip to content

fix: default Zod v4 JSON Schema target to draft-2020-12 - #2653

Open
amitfin wants to merge 1 commit into
modelcontextprotocol:v1.xfrom
amitfin:fix/default-json-schema-dialect-2020-12
Open

fix: default Zod v4 JSON Schema target to draft-2020-12#2653
amitfin wants to merge 1 commit into
modelcontextprotocol:v1.xfrom
amitfin:fix/default-json-schema-dialect-2020-12

Conversation

@amitfin

@amitfin amitfin commented Aug 12, 2026

Copy link
Copy Markdown

Summary

mapMiniTarget in src/server/zod-json-schema-compat.ts defaults to 'draft-7' when no target is supplied. Neither call site in src/server/mcp.ts passes one (lines 151 and 165 pass only strictUnions/pipeStrategy), and target is never exposed through registerTool. So every tool schema generated from a Zod v4 schema advertises:

"$schema": "http://json-schema.org/draft-07/schema#"

This change makes the default 'draft-2020-12', and updates the unrecognised-target fallback on the last line to match — it also returned 'draft-7', so the two now agree:

 function mapMiniTarget(t: CommonOpts['target'] | undefined): 'draft-7' | 'draft-2020-12' {
-    if (!t) return 'draft-7';
+    if (!t) return 'draft-2020-12';
     if (t === 'jsonSchema7' || t === 'draft-7') return 'draft-7';
     if (t === 'jsonSchema2019-09' || t === 'draft-2020-12') return 'draft-2020-12';
-    return 'draft-7'; // fallback
+    return 'draft-2020-12'; // fallback
 }

The fallback is unreachable through the CommonOpts type — all four accepted values are handled by the two explicit branches — but it is reachable from JavaScript callers passing an off-type value, where silently downgrading the dialect is the same surprise this PR is fixing.

Why 2020-12 is the right default

  1. The spec says so. src/spec.types.ts, on Tool.outputSchema:

    Defaults to JSON Schema 2020-12 when no explicit $schema is provided.

    The SDK currently contradicts the spec types vendored in the same tree.

  2. Zod already defaults to it. Zod v4's toJSONSchema default target is draft-2020-12 — verified on zod 4.4.3:

    z4mini.toJSONSchema(s)                              -> "https://json-schema.org/draft/2020-12/schema"
    z4mini.toJSONSchema(s, {target:'draft-7'})          -> "http://json-schema.org/draft-07/schema#"
    

    The compat layer was overriding Zod's own default to an older dialect.

  3. v2 already made this call. On main, packages/core-internal/src/util/standardSchema.ts:170:

    export const JSON_SCHEMA_CONVERSION_TARGET = 'draft-2020-12';

    This aligns v1.x with a decision already taken for v2.

Real-world impact

This surfaced as a client compatibility break. Claude Desktop began rejecting draft-07 outputSchema documents outright:

Tool 'count' has an invalid outputSchema: JSON Schema declares an unsupported dialect
("$schema": "http://json-schema.org/draft-07/schema#"). The default validator supports
JSON Schema 2020-12 only

The failure happens at tool registration, so every tool on an affected server becomes unusable, and the server process is never contacted. mongodb-mcp-server is one instance (mongodb-js/mongodb-mcp-server#1427, anthropics/claude-code#86142), but because draft-07 comes from this SDK's default rather than from server code, any SDK-based server on Zod v4 declaring outputSchema is affected, and no server-side option exists to opt out.

To be clear: that client is too strict — draft-07 is valid JSON Schema, and the v2 SDK's own validator accepts draft-07, draft-06, 2019-09 and 2020-12. This PR isn't a workaround for that bug; it's aligning the default with the spec, Zod, and v2. It happens to also unblock affected users.

Scope and compatibility

  • Explicit targets are unchanged. target: 'draft-7' and 'jsonSchema7' still produce draft-07; the mapping is untouched.
  • Zod v3 is unchanged and still emits draft-07. The v3 branch delegates to zod-to-json-schema, whose targets are jsonSchema7 / jsonSchema2019-09 / openApi3 — there is no 2020-12 target available. A test documents this v3/v4 asymmetry so it reads as intentional.
  • Behavior change note: consumers who relied on the implicit draft-07 default for Zod v4 schemas will now see 2020-12. For the 2019-09→2020-12 delta involved, clients validating draft-07 documents generally accept 2020-12; and per the spec text above, 2020-12 is what clients should assume when $schema is absent.

If you'd prefer a narrower change, the alternative is passing target: 'draft-2020-12' only at the outputSchema call site (src/server/mcp.ts:165), leaving inputSchema on draft-07. I went with the shared default since the asymmetry seemed harder to justify than the alignment. Happy to switch.

Testing

New file test/server/zod-json-schema-compat.test.ts (6 tests):

  • Zod v4 with no target → 2020-12 (the fix)
  • explicit 'draft-7' / 'jsonSchema7' → draft-07 (opt-in preserved)
  • explicit 'draft-2020-12' / 'jsonSchema2019-09' → 2020-12
  • unrecognised target (cast from JS) → 2020-12 (the fallback)
  • Zod v3 with no target → draft-07 (documents the vendored-converter limit)
  • end-to-end tools/list over InMemoryTransport asserting both inputSchema.$schema and outputSchema.$schema are 2020-12

Full suite: 1645 tests / 53 files passing, npm run lint clean. No existing test asserted the draft-07 dialect.

@amitfin
amitfin requested a review from a team as a code owner August 12, 2026 15:59
@changeset-bot

changeset-bot Bot commented Aug 12, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: cdc0b89

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

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

pkg-pr-new Bot commented Aug 12, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@modelcontextprotocol/sdk@2653

commit: cdc0b89

mapMiniTarget defaulted to 'draft-7' when no target was supplied, so every
tool schema generated from a Zod v4 schema advertised
"$schema": "http://json-schema.org/draft-07/schema#". The unrecognised-target
fallback returned 'draft-7' as well; both now return 'draft-2020-12' so the
two agree.

Three things say 2020-12 is the right default:

- spec.types.ts documents Tool.outputSchema as defaulting to JSON Schema
  2020-12 when no explicit $schema is provided
- Zod v4's own toJSONSchema default target is draft-2020-12; the compat
  layer was overriding it to an older dialect
- the v2 SDK already uses draft-2020-12
  (JSON_SCHEMA_CONVERSION_TARGET in packages/core-internal)

Explicit targets, including 'draft-7', are unaffected. The Zod v3 branch
still emits draft-07 because the vendored zod-to-json-schema has no
2020-12 target; a test documents that asymmetry.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

2 participants