Skip to content

Commit 2d67bda

Browse files
feat(core): adopt spec#2907 / conformance#353 error-code renumber for 2026-07-28
HeaderMismatch -32001 -> -32020, MissingRequiredClientCapability -32003 -> -32021, UnsupportedProtocolVersion -32004 -> -32022. These codes are part of the draft 2026-07-28 protocol revision only and have never appeared on a 2025-era wire — the 2025 serving paths and the SDK-conventional -32001 ('Session not found') on the stateful Streamable HTTP transport are unchanged. - ProtocolErrorCode enum members and HEADER_MISMATCH_ERROR_CODE constant renumbered; spec.types.2026-07-28 anchor constants follow. - Emission sites (createMcpHandler/serveStdio/inboundClassification/mcpParamHeaders), the LADDER_ERROR_HTTP_STATUS table and the multi-round-trip capability gate emit the renumbered codes via the renumbered enum members. - Client recognition: the probe classifier recognizes -32022 for the corrective continuation, NOT_PROBE_RECOGNIZED keeps -32001 (deployed Session-not-found overload) and adds -32020/-32021; ProtocolError.fromError reconstructs the typed classes from the renumbered codes; the SEP-2243 one-refresh-on-miss retry keys on -32020. - encodeErrorCode pass-through pin: -32000 literal untouched; the pass-through list updated to -32020/-32021/-32022. - Conformance: the 0.2.0-alpha.4 referee still asserts the pre-renumber codes, so request-metadata / server-stateless / http-custom-header-server-validation / http-header-validation are added to expected-failures with the "SDK ahead of referee" note; they burn when the conformance pin bumps to a published release carrying conformance#353. - Migration docs note the alpha-to-alpha renumber (no v1.x->v2 impact). The vendored schema-twins/2026-07-28.schema.json and corpus/fixtures/2026-07-28/* are deliberately untouched — they are sha256- locked upstream artifacts at spec commit 2fb207da (pre-#2907) and refresh atomically with the next spec-anchor repin.
1 parent f8aaae3 commit 2d67bda

65 files changed

Lines changed: 321 additions & 239 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.

.changeset/envelope-auto-emission.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ Per-request `_meta` envelope auto-emission on modern-era connections: once a cli
88
(the default, and the `'auto'`-mode fallback) never gain these keys, so 2025-era outbound traffic is byte-identical to before.
99

1010
Adds `Client.getProtocolEra()` (`'legacy' | 'modern' | undefined`), the `ProtocolEra` type, `Client.setVersionNegotiation()` for configuring negotiation pre-connect on an already-constructed instance, and the `probe.maxRetries` knob (default `0`) which governs probe-timeout
11-
re-sends only — the spec-mandated `-32004` corrective continuation is never counted against it. The `versionNegotiation` default remains `'legacy'`: absent (or `mode: 'legacy'`), `connect()` runs the plain 2025 sequence, byte-identical to a v1.x client.
11+
re-sends only — the spec-mandated `-32022` corrective continuation is never counted against it. The `versionNegotiation` default remains `'legacy'`: absent (or `mode: 'legacy'`), `connect()` runs the plain 2025 sequence, byte-identical to a v1.x client.

.changeset/missing-client-capability-error.md

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

7-
Add `MissingRequiredClientCapabilityError`, the typed error class for the 2026-07-28 `-32003` protocol error (processing a request requires a capability the client did not declare). Its `data.requiredCapabilities` lists the missing capabilities and `ProtocolError.fromError` recognizes the code/data shape. The 2026-07-28 HTTP entry gains a pre-dispatch gate that refuses a request requiring an undeclared client capability with this error and HTTP status `400`; no method served on the 2026-07-28 registry currently carries such a requirement, so observable behavior is unchanged until methods with capability requirements exist.
7+
Add `MissingRequiredClientCapabilityError`, the typed error class for the 2026-07-28 `-32021` protocol error (processing a request requires a capability the client did not declare). Its `data.requiredCapabilities` lists the missing capabilities and `ProtocolError.fromError` recognizes the code/data shape. The 2026-07-28 HTTP entry gains a pre-dispatch gate that refuses a request requiring an undeclared client capability with this error and HTTP status `400`; no method served on the 2026-07-28 registry currently carries such a requirement, so observable behavior is unchanged until methods with capability requirements exist.

.changeset/mrtr-server-seam.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
Add the server side of multi round-trip requests (protocol revision 2026-07-28, SEP-2322). Handlers for `tools/call`, `prompts/get`, and `resources/read` can return the value built by `inputRequired()` (exported from the server package together with `acceptedContent()`)
77
to request additional client input in-band; the structured-content requirement and the tools/call result-schema validation are skipped for that return, the encode seam emits it as `resultType: 'input_required'`, and the handler reads the responses on re-entry from
88
`ctx.mcpReq.inputResponses` (with non-bare entries reported via `ctx.mcpReq.droppedInputResponseKeys`). The seam re-checks the at-least-one rule for hand-built results, checks every embedded request against the capabilities the client declared on that request's envelope
9-
(answering the typed `-32003` error on violation), and fails loudly — never emitting a mis-typed result — when an input-required value is returned from any other method or toward a 2025-era request. A `UrlElicitationRequiredError` escaping a handler on a 2026-era request
9+
(answering the typed `-32021` error on violation), and fails loudly — never emitting a mis-typed result — when an input-required value is returned from any other method or toward a 2025-era request. A `UrlElicitationRequiredError` escaping a handler on a 2026-era request
1010
fails as an internal error with a clear steer to `inputRequired.elicitUrl(...)`, so the `-32042` error never reaches the 2026-07-28 wire; 2025-era serving keeps today's `-32042` behavior
1111
exactly. The typed local error raised when push-style server-to-client request APIs are used while serving a 2026-era request now steers to `inputRequired(...)`. Tool, prompt, and resource callback types accept the new return alongside their existing result types; 2025-era
1212
wire behavior is unchanged. An optional `ServerOptions.requestState.verify` hook lets a server integrity-check the echoed `requestState` before the handler runs — a throw answers the wire-level `-32602` Invalid Params error with `data.reason: 'invalid_request_state'`; the SDK provides no default verification.

.changeset/pin-modern-rejection-codes.md

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

6-
Pin the modern (2026-07-28) HTTP serving path's rejection codes to the assignments the published conformance suite asserts: a header/body cross-check mismatch (`MCP-Protocol-Version` or `Mcp-Method` disagreeing with the request body) is now rejected with `-32001` (HeaderMismatch), and a request whose protocol-version header names a modern revision but whose body is missing the `_meta` envelope (or its required protocol-version key) is rejected with `-32602` invalid params naming the missing key(s). Both keep HTTP 400. These cells previously emitted a provisional `-32004` while the upstream error-code discussion was open. The envelope-less rejection on a modern-only endpoint (`-32004` with the supported-versions list), the 2025-era serving paths, and the client-side probe handling are unchanged.
6+
Pin the modern (2026-07-28) HTTP serving path's rejection codes to the assignments the published conformance suite asserts: a header/body cross-check mismatch (`MCP-Protocol-Version` or `Mcp-Method` disagreeing with the request body) is now rejected with `-32020` (HeaderMismatch), and a request whose protocol-version header names a modern revision but whose body is missing the `_meta` envelope (or its required protocol-version key) is rejected with `-32602` invalid params naming the missing key(s). Both keep HTTP 400. These cells previously emitted a provisional `-32004` while the upstream error-code discussion was open. The envelope-less rejection on a modern-only endpoint (`-32022` with the supported-versions list), the 2025-era serving paths, and the client-side probe handling are unchanged.

.changeset/sep-2243-mcp-param-server.md

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

5-
SEP-2243 `Mcp-Param-*` server-side validation (protocol revision 2026-07-28). On the modern (2026-07-28) serving path, `createMcpHandler` now validates `Mcp-Param-{Name}` headers against the named tool's `x-mcp-header` declarations and the body `arguments` before dispatch: a missing header for a present body value, a header that decodes to a different value than the body, or an invalid `=?base64?…?=` sentinel is rejected with `400 Bad Request` and JSON-RPC `-32001` (`HeaderMismatch`) — the same shape the existing standard-header cross-checks emit. A `null`/absent body value passes regardless of any header (the spec's "server MUST NOT expect the header" rows). `McpServer.registerTool` now warns at registration time when an `x-mcp-header` declaration violates the spec's constraints. The 2025-era serving paths and the low-level `Server` factory shape are unchanged.
5+
SEP-2243 `Mcp-Param-*` server-side validation (protocol revision 2026-07-28). On the modern (2026-07-28) serving path, `createMcpHandler` now validates `Mcp-Param-{Name}` headers against the named tool's `x-mcp-header` declarations and the body `arguments` before dispatch: a missing header for a present body value, a header that decodes to a different value than the body, or an invalid `=?base64?…?=` sentinel is rejected with `400 Bad Request` and JSON-RPC `-32020` (`HeaderMismatch`) — the same shape the existing standard-header cross-checks emit. A `null`/absent body value passes regardless of any header (the spec's "server MUST NOT expect the header" rows). `McpServer.registerTool` now warns at registration time when an `x-mcp-header` declaration violates the spec's constraints. The 2025-era serving paths and the low-level `Server` factory shape are unchanged.

.changeset/sep-2243-std-header-server.md

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

6-
SEP-2243 standard-header server-side validation (protocol revision 2026-07-28). On the modern (2026-07-28) serving path, `createMcpHandler` now enforces the required `Mcp-Method` and `Mcp-Name` standard request headers in addition to the existing `MCP-Protocol-Version` and `Mcp-Method` cross-checks: a modern request without an `Mcp-Method` header, a `tools/call` / `prompts/get` / `resources/read` request without an `Mcp-Name` header, an `Mcp-Name` header carrying an invalid `=?base64?…?=` sentinel, and an `Mcp-Name` header whose (decoded) value disagrees with the body's `params.name` / `params.uri` are all rejected with `400 Bad Request` and JSON-RPC `-32001` (`HeaderMismatch`). The 2025-era serving paths are unchanged.
6+
SEP-2243 standard-header server-side validation (protocol revision 2026-07-28). On the modern (2026-07-28) serving path, `createMcpHandler` now enforces the required `Mcp-Method` and `Mcp-Name` standard request headers in addition to the existing `MCP-Protocol-Version` and `Mcp-Method` cross-checks: a modern request without an `Mcp-Method` header, a `tools/call` / `prompts/get` / `resources/read` request without an `Mcp-Name` header, an `Mcp-Name` header carrying an invalid `=?base64?…?=` sentinel, and an `Mcp-Name` header whose (decoded) value disagrees with the body's `params.name` / `params.uri` are all rejected with `400 Bad Request` and JSON-RPC `-32020` (`HeaderMismatch`). The 2025-era serving paths are unchanged.
77

88
New public surface:
99

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@modelcontextprotocol/core': minor
3+
'@modelcontextprotocol/client': minor
4+
'@modelcontextprotocol/server': minor
5+
---
6+
7+
Align the 2026-07-28 protocol error codes to the spec renumber: `HeaderMismatch` is now `-32020` (was `-32001`), `MissingRequiredClientCapability` is now `-32021` (was `-32003`), and `UnsupportedProtocolVersion` is now `-32022` (was `-32004`). These codes are part of the draft 2026-07-28 protocol revision only and have never appeared on a 2025-era wire — the 2025 serving paths and the SDK-conventional `-32001` (`Session not found`) on the stateful Streamable HTTP transport are unchanged. `ProtocolErrorCode.MissingRequiredClientCapability`, `ProtocolErrorCode.UnsupportedProtocolVersion`, the `HEADER_MISMATCH_ERROR_CODE` constant, and the `HEADER_MISMATCH` / `MISSING_REQUIRED_CLIENT_CAPABILITY` / `UNSUPPORTED_PROTOCOL_VERSION` spec-type constants now carry the renumbered values; the `UnsupportedProtocolVersionError` and `MissingRequiredClientCapabilityError` classes (and `ProtocolError.fromError` recognition) follow. The client probe classifier recognizes `-32022` for the corrective continuation and the SEP-2243 one-refresh-on-miss retry triggers on `-32020`.

.changeset/spec-reference-types-2026-07-28.md

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

6-
Add per-revision spec reference types (2025-11-25 and 2026-07-28) with split comparison tests, and the 2026-07-28 wire contract surface: request-meta key constants, `RequestMetaEnvelopeSchema`, `server/discover` shapes, the typed `-32004` error, the `-32003` code constant, and a `resultType` passthrough on the base result. Types and constants only — no behavior changes.
6+
Add per-revision spec reference types (2025-11-25 and 2026-07-28) with split comparison tests, and the 2026-07-28 wire contract surface: request-meta key constants, `RequestMetaEnvelopeSchema`, `server/discover` shapes, the typed `-32022` error, the `-32021` code constant, and a `resultType` passthrough on the base result. Types and constants only — no behavior changes.

docs/client.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ console.log(result.content);
305305

306306
On a 2026-07-28 connection over Streamable HTTP, `callTool()` mirrors any argument whose `inputSchema` property carries an `x-mcp-header` annotation into an `Mcp-Param-{Name}` HTTP request header so intermediaries can route on it without parsing the body. The mirrored headers
307307
are built from the client's internal `tools/list` cache; if you already hold the tool definition (e.g. from configuration), pass it via `CallToolRequestOptions.toolDefinition` so mirroring runs without a prior list. On a cache miss the call is sent without `Mcp-Param-*` headers
308-
and, when a conforming server rejects it with `-32001` (`HeaderMismatch`), `callTool()` refreshes the definition cache once and retries.
308+
and, when a conforming server rejects it with `-32020` (`HeaderMismatch`), `callTool()` refreshes the definition cache once and retries.
309309

310310
On a non-stdio modern connection `listTools()` (and the internal `tools/list` cache) exclude tool definitions whose `x-mcp-header` declarations violate the spec's constraints, logging a warning that names the tool and the reason. Browser clients skip mirroring (dynamically named
311311
headers cannot be statically allow-listed for credentialed CORS), so calling an `x-mcp-header` tool with a non-null designated argument from a browser against a server that enforces SEP-2243 validation will be rejected — a known limitation. The legacy-era `callTool`/`listTools`

docs/migration-SKILL.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ Three error classes now exist:
124124
| Session termination failed | `StreamableHTTPError` | `SdkHttpError` with `SdkErrorCode.ClientHttpFailedToTerminateSession` |
125125
| Response result fails schema | `ZodError` (raw) | `SdkError` with `SdkErrorCode.InvalidResult` |
126126

127-
**Modern-era exception** to the `SdkHttpError` rows above: on a modern-enveloped (2026-07-28) Streamable HTTP request, an HTTP `400` whose body is a well-formed JSON-RPC error response addressed to the pending request id is delivered in-band as a `ProtocolError` (e.g. `-32001`
127+
**Modern-era exception** to the `SdkHttpError` rows above: on a modern-enveloped (2026-07-28) Streamable HTTP request, an HTTP `400` whose body is a well-formed JSON-RPC error response addressed to the pending request id is delivered in-band as a `ProtocolError` (e.g. `-32020`
128128
HeaderMismatch from a SEP-2243 `Mcp-Param-*` rejection), not as `SdkHttpError`. Legacy-era exchanges and generic HTTP failures are unchanged.
129129

130130
New `SdkErrorCode` enum values:
@@ -179,7 +179,7 @@ if (error instanceof SdkHttpError) {
179179
}
180180
// Modern-era (2026-07-28) only: a 400 carrying a JSON-RPC error body addressed
181181
// to the pending request id surfaces as ProtocolError, NOT SdkHttpError — e.g.
182-
// a SEP-2243 -32001 HeaderMismatch from createMcpHandler. Legacy-era 400s and
182+
// a SEP-2243 -32020 HeaderMismatch from createMcpHandler. Legacy-era 400s and
183183
// generic HTTP failures still map to SdkHttpError above.
184184
if (error instanceof ProtocolError) {
185185
console.log('In-band JSON-RPC error:', error.code);
@@ -590,6 +590,8 @@ No code changes required; these are wire-behavior notes:
590590
longer enable it. Behavior for all currently supported protocol versions is unchanged.
591591
- Session-ID mismatch still responds `404 Not Found` with JSON-RPC error code `-32001` (`Session not found`), unchanged from v1. This `-32001` usage is an SDK convention, not a spec-assigned code, and may be re-derived as 2026 protocol revision error handling is adopted —
592592
migrated client code should key off the HTTP `404` status, not the `-32001` code.
593+
- The 2026-07-28 draft error codes were renumbered between v2 alphas: `HeaderMismatch` `-32001``-32020`, `MissingRequiredClientCapability` `-32003``-32021`, `UnsupportedProtocolVersion` `-32004``-32022`. No v1.x→v2 impact (these codes never existed in v1); v2-alpha code that
594+
hard-coded the old literals must update — prefer `ProtocolErrorCode.*` / `HEADER_MISMATCH_ERROR_CODE`.
593595

594596
### Server (deprecated accessors and app-factory Origin validation)
595597

@@ -611,7 +613,7 @@ New in 2.0 — v1 has no equivalent API. How v1 Streamable HTTP hosting maps ont
611613
- An existing sessionful v1 Streamable HTTP setup (a `StreamableHTTPServerTransport` wiring with session IDs) keeps serving 2025 clients by routing in user land in front of a strict entry:
612614
`if (await isLegacyRequest(request)) return myExistingLegacyHandler(request); return strictHandler.fetch(request)` where `strictHandler = createMcpHandler(factory, { legacy: 'reject' })`.
613615
- `isLegacyRequest(request: Request, parsedBody?: unknown): Promise<boolean>` from `@modelcontextprotocol/server` is the entry's own classification step. Returns `true` only for requests with no per-request `_meta` envelope claim (claim-less POSTs including `initialize`,
614-
GET/DELETE session operations, all-legacy batches, posted responses, non-JSON bodies). Returns `false` for envelope-claiming requests AND for malformed/incomplete modern claims (the modern path answers those with `-32602`/`-32001`) — route `false` traffic to the modern handler,
616+
GET/DELETE session operations, all-legacy batches, posted responses, non-JSON bodies). Returns `false` for envelope-claiming requests AND for malformed/incomplete modern claims (the modern path answers those with `-32602`/`-32020`) — route `false` traffic to the modern handler,
615617
never to a legacy handler. The predicate classifies a clone (the body stays readable); pass the parsed body as the second argument when the stream was already consumed.
616618
- `legacyStatelessFallback(factory)` is exported as a standalone fetch-shaped handler producing the same stateless legacy serving as the default.
617619
- The handler is web-standards-only (`{ fetch, close, notify, bus }`). On Workers / Bun / Deno, `export default handler` works directly. On Node frameworks (Express, Fastify, plain `node:http`), wrap once with `toNodeHandler(handler, { onerror? })` from

0 commit comments

Comments
 (0)