fix: default Zod v4 JSON Schema target to draft-2020-12 - #2653
Conversation
🦋 Changeset detectedLatest 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 |
commit: |
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>
2e712c3 to
cdc0b89
Compare
|
+1 on this, with a concrete downstream impact report in case it helps prioritise. Two independent users of my server ( Reproduced on Claude Desktop 1.28929.0 (Code tab) — every tool call fails at the same validation step, so the server is unusable there. Notably the same server works fine in the regular Desktop chat client, so this only surfaces where the client actually validates the declared dialect. Worth noting there's no downstream escape hatch on 1.30.0: See also #2085, which targets the same branch. Glad to test a build against the affected client. |
Summary
mapMiniTargetinsrc/server/zod-json-schema-compat.tsdefaults to'draft-7'when notargetis supplied. Neither call site insrc/server/mcp.tspasses one (lines 151 and 165 pass onlystrictUnions/pipeStrategy), andtargetis never exposed throughregisterTool. So every tool schema generated from a Zod v4 schema advertises: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
CommonOptstype — 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
The spec says so.
src/spec.types.ts, onTool.outputSchema:The SDK currently contradicts the spec types vendored in the same tree.
Zod already defaults to it. Zod v4's
toJSONSchemadefault target isdraft-2020-12— verified on zod 4.4.3:The compat layer was overriding Zod's own default to an older dialect.
v2 already made this call. On
main,packages/core-internal/src/util/standardSchema.ts:170:This aligns
v1.xwith a decision already taken for v2.Real-world impact
This surfaced as a client compatibility break. Claude Desktop began rejecting draft-07
outputSchemadocuments outright:The failure happens at tool registration, so every tool on an affected server becomes unusable, and the server process is never contacted.
mongodb-mcp-serveris 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 declaringoutputSchemais 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
target: 'draft-7'and'jsonSchema7'still produce draft-07; the mapping is untouched.zod-to-json-schema, whose targets arejsonSchema7/jsonSchema2019-09/openApi3— there is no 2020-12 target available. A test documents this v3/v4 asymmetry so it reads as intentional.$schemais absent.If you'd prefer a narrower change, the alternative is passing
target: 'draft-2020-12'only at theoutputSchemacall site (src/server/mcp.ts:165), leavinginputSchemaon 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):'draft-7'/'jsonSchema7'→ draft-07 (opt-in preserved)'draft-2020-12'/'jsonSchema2019-09'→ 2020-12tools/listoverInMemoryTransportasserting bothinputSchema.$schemaandoutputSchema.$schemaare 2020-12Full suite: 1645 tests / 53 files passing,
npm run lintclean. No existing test asserted the draft-07 dialect.