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

Open Beginner friendly
#2,084 6 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

bug fix proposed P1 ready for work
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

  1. Install @modelcontextprotocol/sdk@1.29.0 and zod@^4.
  2. Register a tool with any Zod outputSchema.
  3. Call tools/list.
  4. Observe $schema: "http://json-schema.org/draft-07/schema#" on every tool's inputSchema and outputSchema (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-18mapMiniTarget(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:

  1. tools[].inputSchema.$schema === 'https://json-schema.org/draft/2020-12/schema'
  2. tools[].outputSchema.$schema === 'https://json-schema.org/draft/2020-12/schema' (when registered)
  3. Tuple-array fields emit prefixItems (not items: [...])

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-package in the interim.

Environment

  • @modelcontextprotocol/sdk@1.29.0
  • zod@^4.4.3 (also reproduces on v3)
  • Node 20+

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 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.