Skip to content

Commit 32f8a66

Browse files
Merge branch 'main' into fweinberger/structured-clone-fallback
2 parents dcd3011 + 8e1d2e9 commit 32f8a66

112 files changed

Lines changed: 9344 additions & 6570 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@modelcontextprotocol/core-internal': patch
3+
'@modelcontextprotocol/client': patch
4+
'@modelcontextprotocol/core': patch
5+
'@modelcontextprotocol/server': patch
6+
---
7+
8+
Restore the v1 parse tolerance for `CallToolResult.content`: an inbound legacy-era `tools/call` result without `content` defaults to `[]` instead of failing validation. Deployed servers — accepted by SDK v1 for years — return `structuredContent`-only (or otherwise content-less) results, and the strict parse turned every such call into an `INVALID_RESULT` error before application code could run.
9+
10+
The silent-empty-success hazard the strictness guarded is preserved where it matters: the 2025 era's wire-seam schema refuses to default `content` for a body carrying another result family's vocabulary (`task`, `inputRequests`, `requestState` — the era is frozen, so the list is complete), and the 2026-era wire schemas stay strict — modern-revision servers have no legacy excuse. Task interop through an explicit result schema is untouched (including bodies that also stamp a foreign `resultType`), and the server-side authoring normalization refuses the same foreign-family vocabulary.
11+
12+
Server-side authoring is era-independent: a handler result without `content` (dynamic/JS callers — the TypeScript surface requires it) is normalized to `content: []` before era validation on every leg, reaching the wire spec-valid.
13+
14+
Conscious call: the nested sampling `ToolResultContentSchema` stays spec-strict — v1 had defaulted its `content` too, but it is params-side (tool results a caller authors into a sampling message), deliberately not restored.

.changeset/config.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@
22
"$schema": "https://unpkg.com/@changesets/config@3.1.2/schema.json",
33
"changelog": ["@changesets/changelog-github", { "repo": "modelcontextprotocol/typescript-sdk" }],
44
"commit": false,
5-
"fixed": [],
5+
"fixed": [
6+
[
7+
"@modelcontextprotocol/core",
8+
"@modelcontextprotocol/client",
9+
"@modelcontextprotocol/server",
10+
"@modelcontextprotocol/server-legacy"
11+
]
12+
],
613
"linked": [],
714
"access": "public",
815
"baseBranch": "main",
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@modelcontextprotocol/client': patch
3+
---
4+
5+
The Streamable HTTP client transport no longer attaches a session ID to a POST containing an `initialize` request — a new session starts "without a session ID attached" (2025-11-25 transports §Session Management) — and it only captures the `mcp-session-id` response header from a successful initialize response, since the spec assigns the session ID "at initialization time … on the HTTP response containing the InitializeResult". Previously the transport stored the header from any response, so a legacy server answering a protocol-version probe with an error that happened to carry a session ID would poison the fallback initialize, which then went out with a session ID it should not have had. A stale session ID from a previous connection is likewise no longer leaked onto the initialize handshake, and a successful initialize response that carries no session ID now clears any stale ID the transport was holding — clients include only an ID "returned by the server during initialization", so an ID the server never returned this session is outside the session model. Ignoring `mcp-session-id` headers mid-session is the complement of the spec's one actual rotation mechanism: a server that wants a new session terminates the old one (it "MAY terminate the session at any time") and answers 404, after which the client "MUST start a new session by sending a new InitializeRequest without a session ID attached". Rotation exists as session replacement via 404 + re-initialize, never as a header swap on a live session, so a server that rotates per the spec's own flow is handled correctly by this transport.

.changeset/lazy-ajv-engine.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@modelcontextprotocol/client': patch
3+
'@modelcontextprotocol/server': patch
4+
---
5+
6+
Construct the default Ajv validation engine lazily on first validation. Creating a `Client` or `Server` no longer pays the ajv + ajv-formats instantiation cost at startup when no JSON Schema validation ever runs.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@modelcontextprotocol/client': patch
3+
'@modelcontextprotocol/server': patch
4+
'@modelcontextprotocol/server-legacy': patch
5+
---
6+
7+
Build protocol-revision wire schemas lazily on first validation instead of at import. Each revision's schema set is now constructed by a module-level memoized factory, so importing the client or server package no longer pays the construction cost of both frozen wire-schema graphs up front. Method membership in the revision registries stays static, the schemas themselves are unchanged, and registry lookups keep returning reference-identical schema objects.

.changeset/pre.json

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,22 @@
2626
},
2727
"changesets": [
2828
"beta-release",
29+
"calltoolresult-content-default",
30+
"cjs-ajv-validator-subpath",
2931
"cjs-support-v2-packages",
3032
"codemod-iterations-5",
31-
"post-dispatch-32021-http-400"
33+
"codemod-versions-from-manifests",
34+
"content-type-media-type-validation",
35+
"cross-bundle-error-instanceof",
36+
"examples-protected-wiring",
37+
"initialize-session-hygiene",
38+
"malformed-resource-uri-invalid-params",
39+
"post-dispatch-32021-http-400",
40+
"probe-window-handler-restore",
41+
"silent-validators-wave",
42+
"standard-header-ows",
43+
"standard-schema-elicitation",
44+
"web-standard-bearer-auth",
45+
"web-standard-oauth-metadata"
3246
]
3347
}

.changeset/schemas-source-home.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@modelcontextprotocol/core': minor
3+
'@modelcontextprotocol/client': minor
4+
'@modelcontextprotocol/server': minor
5+
'@modelcontextprotocol/server-legacy': minor
6+
---
7+
8+
Move the schema source modules (spec schemas, OAuth schemas, protocol constants) into `@modelcontextprotocol/core` and resolve them from there as a regular runtime dependency instead of bundling a private copy into each package. An application importing more than one of the packages now evaluates a single shared schema graph with shared object identity. `@modelcontextprotocol/core` gains a `./internal` subpath (SDK-internal contract; may change in any release) and the four packages now version together.

.changeset/standard-schema-elicitation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
'@modelcontextprotocol/server': minor
44
---
55

6-
Allow `inputRequired.elicit()` to accept a Standard Schema such as a Zod object for `requestedSchema`. The builder converts it to MCP's restricted form-elicitation JSON Schema, while the same schema can validate and type the response through `acceptedContent()` on handler re-entry.
6+
Allow `inputRequired.elicit()` to accept a Standard Schema such as a Zod object for `requestedSchema`. The builder converts it to MCP's restricted form-elicitation JSON Schema, while the same schema can validate and type the response through `acceptedContent()` on handler re-entry. Zod formats mapping to `email`, `uri`, `date`, and `date-time` are supported. Shapes the restricted schema cannot express reject before anything is sent — nested objects, `.regex()` and customized zod format patterns, exclusive number bounds (`.positive()`/`.gt()`), literal unions (use `z.enum` or `z.literal(['a', 'b'])`), and non-spec root keywords like `z.strictObject()`'s `additionalProperties`.

CLAUDE.md

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ Include what changed, why, and how to migrate. Search for related sections and g
4444

4545
### JSDoc `@example` Code Snippets
4646

47-
JSDoc `@example` tags should pull type-checked code from companion `.examples.ts` files (e.g., `client.ts``client.examples.ts`). Use `` ```ts source="./file.examples.ts#regionName" `` fences referencing `//#region regionName` blocks; region names follow `exportedName_variant` or `ClassName_methodName_variant` pattern (e.g., `applyMiddlewares_basicUsage`, `Client_connect_basicUsage`). For whole-file inclusion (any file type), omit the `#regionName`.
47+
JSDoc `@example` tags should pull type-checked code from companion `.examples.ts` files (e.g., `client.ts``client.examples.ts`). Use ` ```ts source="./file.examples.ts#regionName" ` fences referencing `//#region regionName` blocks; region names follow `exportedName_variant` or `ClassName_methodName_variant` pattern (e.g., `applyMiddlewares_basicUsage`, `Client_connect_basicUsage`). For whole-file inclusion (any file type), omit the `#regionName`.
4848

4949
Run `pnpm sync:snippets` to sync example content into JSDoc comments and markdown files.
5050

@@ -70,9 +70,10 @@ The SDK separates internal code from the public API surface:
7070
- **`@modelcontextprotocol/core-internal`** (main entry, `packages/core-internal/src/index.ts`) — Internal barrel. Exports everything (including Zod schemas, Protocol class, stdio utils). Only consumed by sibling packages within the monorepo (`private: true`).
7171
- **`@modelcontextprotocol/core-internal/public`** (`packages/core-internal/src/exports/public/index.ts`) — Curated public API. Exports only TypeScript types, error classes, constants, and guards. Re-exported by client and server packages.
7272
- **`@modelcontextprotocol/client`** and **`@modelcontextprotocol/server`** (`packages/*/src/index.ts`) — Final public surface. Package-specific exports (named explicitly) plus re-exports from `core-internal/public`.
73-
- **`@modelcontextprotocol/core`** (`packages/core/src/index.ts`) — Public Zod-schema package. Re-exports **only** the `*Schema` Zod constants (MCP spec + OAuth/OpenID), bundled from `core-internal` at build time. The published home for raw runtime validation (`CallToolResultSchema.parse(...)`); runtime-neutral (`zod` is its only dependency). Not consumed by the sibling packages`client`/`server` keep their own bundled schema copies and stay Zod-free in their public surface.
73+
- **`@modelcontextprotocol/core`** (`packages/core/src/index.ts`) — Public Zod-schema package and the canonical home of the schema source modules (`src/schemas.ts`, `src/auth.ts`, `src/constants.ts`). The root entry re-exports **only** the `*Schema` Zod constants (MCP spec + OAuth/OpenID) — the published home for raw runtime validation (`CallToolResultSchema.parse(...)`); runtime-neutral (`zod` is its only dependency). The `./internal` subpath re-exports the schema modules wholesale for the sibling packages: `core-internal` re-exports them at the old module paths, and the `client`/`server`/`server-legacy` bundles resolve `@modelcontextprotocol/core/internal` as a real external dependency instead of carrying their own schema copies (their public surfaces stay Zod-free).
7474

7575
When modifying exports:
76+
7677
- Use explicit named exports, not `export *`, in package `index.ts` files and `core-internal/public`.
7778
- Adding a symbol to a package `index.ts` makes it public API — do so intentionally.
7879
- Internal helpers should stay in the core internal barrel and not be added to `core-internal/public` or package index files.
@@ -197,14 +198,14 @@ The `ctx` parameter in handlers provides a structured context:
197198

198199
- `sessionId?`: Transport session identifier
199200
- `mcpReq`: Request-level concerns
200-
- `id`: JSON-RPC message ID
201-
- `method`: Request method string (e.g., 'tools/call')
202-
- `_meta?`: Request metadata
203-
- `signal`: AbortSignal for cancellation
204-
- `send(request, schema, options?)`: Send related request (for bidirectional flows)
205-
- `notify(notification)`: Send related notification back
201+
- `id`: JSON-RPC message ID
202+
- `method`: Request method string (e.g., 'tools/call')
203+
- `_meta?`: Request metadata
204+
- `signal`: AbortSignal for cancellation
205+
- `send(request, schema, options?)`: Send related request (for bidirectional flows)
206+
- `notify(notification)`: Send related notification back
206207
- `http?`: HTTP transport info (undefined for stdio)
207-
- `authInfo?`: Validated auth token info
208+
- `authInfo?`: Validated auth token info
208209

209210
**`ServerContext`** extends `BaseContext.mcpReq` and `BaseContext.http?` via type intersection:
210211

docs/advanced/wire-schemas.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
---
22
shape: how-to
33
---
4+
45
# Wire schemas
56

67
`@modelcontextprotocol/core` exports the **wire schemas** — the exact Zod constants the SDK validates protocol and OAuth payloads against — for code that holds raw JSON instead of SDK objects.
@@ -56,7 +57,7 @@ These are the `*Schema` constants v1 exported from `@modelcontextprotocol/sdk/ty
5657

5758
If you build with `McpServer` or `Client`, skip this package: [tools](../servers/tools.md) arrive in your handler already validated, and [tool calls](../clients/calling.md) come back as typed results. Reach for `@modelcontextprotocol/core` when nothing stands between you and the JSON — gateways, proxies, test harnesses, [worker fleets](./gateway.md).
5859

59-
Install it separately (`npm install @modelcontextprotocol/core`) — `@modelcontextprotocol/server` and `@modelcontextprotocol/client` keep a Zod-free public surface and never depend on it. The package is runtime-neutral; `zod` is its only dependency.
60+
`@modelcontextprotocol/server` and `@modelcontextprotocol/client` keep a Zod-free public surface, but they resolve their shared schema graph from this package at runtime, so it already arrives transitively in your tree. Add it to your own `dependencies` (`npm install @modelcontextprotocol/core`) when you import from it directly. The package is runtime-neutral; `zod` is its only dependency.
6061

6162
## Pick the schema for the message you hold
6263

0 commit comments

Comments
 (0)