modelcontextprotocol / modelcontextprotocol/typescript-sdk
[SEP-1613 regression] tools/list still emits draft-07 in 1.29.0 — `target` option missing from mcp.js `toJsonSchemaCompat` calls
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 13.4k
- Forks
- 2.2k
- Avg merge
- 3d 15h
- Merged PRs (30d)
- 4
Description
Describe the bug
SEP-1613 (tracked in #1057, closed 2025-11-21) was supposed to make JSON Schema 2020-12 the default dialect for tool inputSchema / outputSchema emission in tools/list. In @modelcontextprotocol/sdk@1.29.0, tools/list still emits draft-07 because mcp.js calls toJsonSchemaCompat(...) without passing a target option, and toJsonSchemaCompat's mapMiniTarget(undefined) falls back to 'draft-7'.
This appears to be a regression / partial implementation of SEP-1613; the SDK has the wiring for target: 'draft-2020-12' in zod-json-schema-compat.js, but the call sites in mcp.js don't pass it.
Impact
Every server built on @modelcontextprotocol/sdk since SEP-1613 closed advertises draft-07 schemas on tools/list, contrary to the MCP 2025-11-25 spec which mandates 2020-12. Strict validating clients reject those servers outright (#745 reported Claude Code returning 400s against Mapbox MCP for exactly this reason). For most simple schemas the break is silent (draft-07 is a structural subset of 2020-12), but tuple/composition-heavy schemas lose 2020-12-only keywords like prefixItems.
To Reproduce
- Install
@modelcontextprotocol/sdk@1.29.0andzod@^4. - Register a tool with any Zod
outputSchema. - Call
tools/list. - Observe
$schema: "http://json-schema.org/draft-07/schema#"on every tool'sinputSchemaandoutputSchema(or absent — falls back).
Expected behavior
Per SEP-1613, tools/list advertises $schema: "https://json-schema.org/draft/2020-12/schema" by default.
Code path evidence (1.29.0)
dist/esm/server/mcp.js:76-95 — both inputSchema and outputSchema emission paths call toJsonSchemaCompat with NO target option:
inputSchema: (() => {
const obj = normalizeObjectSchema(tool.inputSchema);
return obj
? toJsonSchemaCompat(obj, {
strictUnions: true,
pipeStrategy: 'input'
})
: EMPTY_OBJECT_JSON_SCHEMA;
})(),
...
if (tool.outputSchema) {
const obj = normalizeObjectSchema(tool.outputSchema);
if (obj) {
toolDefinition.outputSchema = toJsonSchemaCompat(obj, {
strictUnions: true,
pipeStrategy: 'output'
});
}
}
dist/esm/server/zod-json-schema-compat.js:9-18 — mapMiniTarget(undefined) returns 'draft-7':
function mapMiniTarget(t) {
if (!t)
return 'draft-7';
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
}
So mcp.js → toJsonSchemaCompat(obj, { strictUnions, pipeStrategy }) resolves to mapMiniTarget(undefined) === 'draft-7'.
Suggested fix
Pass target: 'draft-2020-12' in both mcp.js call sites (line 78 for inputSchema, line 91 for outputSchema):
inputSchema: (() => {
const obj = normalizeObjectSchema(tool.inputSchema);
return obj
? toJsonSchemaCompat(obj, {
+ target: 'draft-2020-12',
strictUnions: true,
pipeStrategy: 'input'
})
: EMPTY_OBJECT_JSON_SCHEMA;
})(),
if (tool.outputSchema) {
const obj = normalizeObjectSchema(tool.outputSchema);
if (obj) {
toolDefinition.outputSchema = toJsonSchemaCompat(obj, {
+ target: 'draft-2020-12',
strictUnions: true,
pipeStrategy: 'output'
});
}
}
Alternatively, change mapMiniTarget(undefined) to default to 'draft-2020-12' per SEP-1613's intent, but explicit passing at the call sites is safer for callers that might rely on the legacy draft-7 default.
Test to verify
Register a tool with a non-trivial Zod v4 schema (including a tuple, for prefixItems-evidence), call tools/list, and assert:
tools[].inputSchema.$schema === 'https://json-schema.org/draft/2020-12/schema'tools[].outputSchema.$schema === 'https://json-schema.org/draft/2020-12/schema'(when registered)- Tuple-array fields emit
prefixItems(notitems: [...])
Related
- #1057 (SEP-1613 tracking issue, closed 2025-11-21) — likely incomplete; this regression suggests the call-site update was missed
- #745 (original draft-07 → 2020-12 bug report, closed 2025-12-05 — possibly closed in anticipation of #1057's fix)
- Affected real-world consumer: lvlup-sw/exarchos#1366 — migration to Zod v4 to unlock native 2020-12 is blocked by this regression at the SDK boundary. We're patching locally via
patch-packagein the interim.
Environment
@modelcontextprotocol/sdk@1.29.0zod@^4.4.3(also reproduces on v3)- Node 20+
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 with the two toJsonSchemaCompat call sites in dist/esm/server/mcp.js and read mapMiniTarget in dist/esm/server/zod-json-schema-compat.js to confirm the default behavior. Reproduce tools/list with a Zod v4 tuple schema, then verify both inputSchema and outputSchema advertise the 2020-12 schema and tuple fields use prefixItems.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, backend-api-design
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100