Skip to content

Conversation

@felixweinberger
Copy link
Contributor

@felixweinberger felixweinberger commented Nov 18, 2025

Summary

Restores .catchall() on inputSchema and outputSchema in ToolSchema to properly support JSON Schema 2020-12 keywords as required by SEP-1613.

Motivation and Context

SEP-1613 establishes JSON Schema 2020-12 as the default dialect for MCP embedded schemas. This requires the SDK to preserve all JSON Schema properties (like $schema, additionalProperties, $defs, allOf, etc.) rather than stripping them.

The Problem: PR #1086 removed .passthrough() from ToolSchema, causing the SDK to strip valid JSON Schema properties. This was reported in #1057 where $schema was being stripped, breaking SEP-1613's backwards compatibility mechanism (servers need $schema to specify alternative dialects).

Why passthrough is correct here:

  1. Spec compliance: The MCP spec's JSON Schema for inputSchema does NOT have additionalProperties: false, meaning additional properties are allowed by default
  2. inputSchema is an external spec: Unlike MCP protocol fields, inputSchema/outputSchema are JSON Schema objects - the SDK should transport them, not validate their internal structure
  3. SEP-1613 backwards compatibility: Servers must be able to specify $schema to use dialects other than 2020-12
  4. Restores original behavior: The SDK had passthrough before PR SEP-1319: Decouple Request Payloads, Remove passthrough iteration, Typecheck fixes #1086

Changes:

  • Add .catchall() to inputSchema and outputSchema in ToolSchema
  • Preserve existing typed properties (type, properties, required) for TypeScript backwards compatibility
  • Add JSDoc documenting SEP-1613/2020-12 default dialect
  • Add comprehensive tests for JSON Schema 2020-12 keyword acceptance

How Has This Been Tested?

  • Added unit tests ensuring required fields are passed through.

Breaking Changes

None. This restores the ability to use JSON Schema properties that were being silently stripped since PR #1125.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

@felixweinberger felixweinberger requested a review from a team as a code owner November 18, 2025 23:18
Add .passthrough() to inputSchema and outputSchema to accept all JSON Schema
2020-12 keywords. This is the correct approach because:

- inputSchema/outputSchema are embedded external specs (JSON Schema), not
  MCP protocol fields that should be strictly validated
- The SDK's role is to transport schemas, not validate JSON Schema structure
- Enumeration approach would silently drop unrecognized keywords (data loss)

Changes:
- Add .passthrough() to inputSchema and outputSchema in ToolSchema
- Update JSDoc to document SEP-1613/2020-12 as default dialect
- Add comprehensive tests for JSON Schema 2020-12 keyword support

Backwards compatible: existing typed properties (type, properties, required)
remain explicitly typed for TypeScript autocomplete.
@pkg-pr-new
Copy link

pkg-pr-new bot commented Nov 18, 2025

Open in StackBlitz

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/sdk@1135

commit: 6116af3

@KKonstantinov
Copy link
Contributor

Hello,

There should be a way to achieve it without using .passthrough() and without necessarily listing all the possible fields.

.catchall(z.unknown) should achieve the same effect:

 inputSchema: z
        .object({
            type: z.literal('object'),
            properties: z.record(z.string(), AssertObjectSchema).optional(),
            required: z.array(z.string()).optional()
        })
        .catchall(z.unknown()),

or alternatively an intersection with a Record<string, unknown>

 inputSchema: z.intersection(z
        .object({
            type: z.literal('object'),
            properties: z.record(z.string(), AssertObjectSchema).optional(),
            required: z.array(z.string()).optional()
        }), z.record(z.string(), z.unknown()))

More semantically correct for JSON Schema objects where additional
properties are expected but not validated by Zod.
@felixweinberger
Copy link
Contributor Author

Hello,

There should be a way to achieve it without using .passthrough() and without necessarily listing all the possible fields.

.catchall(z.unknown) should achieve the same effect:

 inputSchema: z
        .object({
            type: z.literal('object'),
            properties: z.record(z.string(), AssertObjectSchema).optional(),
            required: z.array(z.string()).optional()
        })
        .catchall(z.unknown()),

or alternatively an intersection with a Record<string, unknown>

 inputSchema: z.intersection(z
        .object({
            type: z.literal('object'),
            properties: z.record(z.string(), AssertObjectSchema).optional(),
            required: z.array(z.string()).optional()
        }), z.record(z.string(), z.unknown()))

Done!

@felixweinberger felixweinberger requested review from KKonstantinov and removed request for KKonstantinov November 20, 2025 14:43
@felixweinberger felixweinberger changed the title SEP-1613: Restore passthrough for inputSchema/outputSchema to support JSON Schema 2020-12 SEP-1613: use.catchall() on inputSchema/outputSchema to support JSON Schema 2020-12 Nov 20, 2025
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.

Implement SEP-1613: JSON Schema 2020-12 as Default Dialect

3 participants