Skip to content

[SEP-1613 regression] tools/list still emits draft-07 in 1.29.0 — target option missing from mcp.js toJsonSchemaCompat calls #2084

@rsalus

Description

@rsalus

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

Environment

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions