Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
1f3c63d
ci: run workflows on pushes to the v2-2026-07-28 integration branch
felixweinberger Jun 12, 2026
d55030a
test: pin schema boundaries, error-code tables, package topology, and…
felixweinberger Jun 12, 2026
7103d48
ci: remove duplicated branch entry in the push filter
felixweinberger Jun 12, 2026
b4d65a8
build: committed API reports per public package with a CI gate (#2285)
felixweinberger Jun 12, 2026
9c5b56b
fix(codemod): flag removed task options instead of migrating them; dr…
felixweinberger Jun 12, 2026
571d973
chore(core): repin 2026-07-28 spec reference types; freeze released-r…
felixweinberger Jun 12, 2026
fd25778
test: wire-safety nets — cross-bundle error pin and spec example corp…
felixweinberger Jun 12, 2026
eeeb3ba
feat(core)!: hide wire-only members from the public types; lift them …
felixweinberger Jun 12, 2026
2b92556
build: remove the committed API reports and their CI gate (#2299)
felixweinberger Jun 15, 2026
bd799be
feat(core)!: per-era wire codec interface (#2294)
felixweinberger Jun 15, 2026
6c4c871
feat(core): the 2026-07-28 era codec; registry and schema CI oracles …
felixweinberger Jun 15, 2026
981488a
feat(client): opt-in 2026-07-28 version negotiation; era-aware server…
felixweinberger Jun 15, 2026
6a19cc8
feat(server): per-request serving core and inbound validation ladder …
felixweinberger Jun 16, 2026
a7e8f59
feat(server): createMcpHandler entry point, legacy slot model, and or…
felixweinberger Jun 16, 2026
ea53b04
feat(server): opt-in stdio dual-era serving via ServerOptions.eraSupp…
felixweinberger Jun 16, 2026
e22980b
feat(server): complete the stateless call — result stamping, cache hi…
felixweinberger Jun 16, 2026
3e97603
fix(core): pin the modern-path rejection codes for header mismatches …
felixweinberger Jun 16, 2026
cfd7dbf
test(e2e): dual-era serving coverage — entry, stdio, sessionful BYO, …
felixweinberger Jun 16, 2026
5a4677f
test(conformance): arm the fixtures for 2026-07-28 serving and refres…
felixweinberger Jun 16, 2026
a716481
test(e2e): audit transport-restricted requirements for the entry arms…
felixweinberger Jun 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
11 changes: 11 additions & 0 deletions .changeset/add-version-negotiation-option.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@modelcontextprotocol/client': minor
'@modelcontextprotocol/core': minor
---

Add opt-in protocol version negotiation on `ClientOptions.versionNegotiation`. The default is unchanged: without the option (or with `mode: 'legacy'`) the client performs today's 2025 connect sequence byte-identically. `mode: 'auto'` probes the server with `server/discover` at
connect time and conservatively falls back to the plain legacy `initialize` handshake on the same connection unless the outcome is definitive modern evidence (with a supported-versions list that has no 2025-era entry there is nothing to fall back to, and connect rejects
with a typed error instead); a network outage rejects with a typed connect error, and a probe timeout is transport-aware — on stdio it indicates
a legacy server and falls back to `initialize` on the same stream, on HTTP it rejects with a typed timeout error.
`mode: { pin: '<version>' }` negotiates exactly the pinned modern revision with no fallback. Probe policy lives under `probe: { timeoutMs? }` — the probe inherits the standard request timeout. The probe's `MCP-Protocol-Version`/`Mcp-Method` headers derive from the probe
message body; the transport version slot is never touched during negotiation, so legacy-era traffic carries zero 2026 headers by construction. Adds the `SdkErrorCode.EraNegotiationFailed` code for negotiation-phase connect failures.
6 changes: 6 additions & 0 deletions .changeset/cacheable-result-cache-fields.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@modelcontextprotocol/core': minor
'@modelcontextprotocol/server': minor
---

Results of the cacheable 2026-07-28 operations (`tools/list`, `prompts/list`, `resources/list`, `resources/templates/list`, `resources/read`, `server/discover`) now always carry the revision's required `ttlMs`/`cacheScope` fields when served on that revision, defaulting to `ttlMs: 0` / `cacheScope: 'private'`. Servers can configure the emitted values with the new `ServerOptions.cacheHints` option (per operation) and the new `cacheHint` member of the `registerResource` config (per resource); resolution is per field, most specific author first: cache fields returned by a handler win over the per-resource hint, which wins over the per-operation hint, and configured hints are validated at construction/registration time (`RangeError` on invalid values). Responses on 2025-era connections are unchanged and never carry these fields. Note for untyped callers: `registerResource` now interprets a `cacheHint` key in its config object — it is validated and kept out of the resource's list metadata, where it was previously passed through as ordinary metadata.
6 changes: 6 additions & 0 deletions .changeset/client-modern-era-inbound-drop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@modelcontextprotocol/client': patch
---

Drop inbound JSON-RPC requests on connections that negotiated the 2026-07-28 draft revision instead of answering them: the modern era has no server→client request channel (server-initiated interactions are carried in `input_required` results), and the stdio transport forbids the
client from writing JSON-RPC responses. Dropped requests are surfaced via `onerror`. Legacy-era connections, responses, and notifications are unchanged.
7 changes: 7 additions & 0 deletions .changeset/codec-era-gates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@modelcontextprotocol/core': minor
'@modelcontextprotocol/client': minor
'@modelcontextprotocol/server': minor
---

Add `SdkErrorCode.MethodNotSupportedByProtocolVersion`: a typed local error raised before anything reaches the transport when a spec method is sent toward a peer whose negotiated protocol version's wire era does not define it (for example `tasks/get` toward a 2026-07-28 peer). The protocol layer now resolves a per-era wire codec from the connection's negotiated protocol version (instance state on `Client`/`Server`, with the legacy era as the pre-negotiation default) and resolves per-method schemas at dispatch time instead of registration time; an edge classification on an inbound message is validated against that instance era, and a mismatch is rejected as an entry/routing error. Behavior on existing (2025-era) connections is unchanged.
15 changes: 15 additions & 0 deletions .changeset/codec-split-wire-break.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'@modelcontextprotocol/core': major
'@modelcontextprotocol/client': major
'@modelcontextprotocol/server': major
---

Split the wire layer into per-era codecs and make protocol-revision deletions physical. Deliberate wire/schema behavior changes (see docs/migration.md "Per-era wire codecs"):

- `resultType` is no longer modeled by any neutral wire schema: `EmptyResultSchema` (strict) now rejects `{resultType}` bodies; on 2025-era connections a foreign `resultType` is stripped before validation instead of rejected; the member exists only inside the 2026-era codec, which requires it.
- `CallToolResult.content` / `ToolResultContent.content` are required at the wire boundary (`content.default([])` removed): handler results without `content` are rejected with `-32602` instead of silently defaulted, and content-less wire results fail the client parse loudly.
- Custom (3-arg) handlers now receive `_meta` minus the reserved envelope keys instead of having it deleted before params validation.
- `specTypeSchemas` re-scoped to the neutral model: result validators no longer accept `resultType`; task message-type validators and `RequestMetaEnvelope` left the public set (`SpecTypeName` narrowed).
- Role aggregate types/schemas (`ClientRequest`, `ServerResult`, …) no longer carry task vocabulary; the deprecated `Task*` types remain importable unchanged.
- Era-mismatched spec methods fail physically: inbound era-deleted methods get `-32601` even with a handler registered; outbound sends throw `SdkErrorCode.MethodNotSupportedByProtocolVersion` locally.
- Value guards (`isCallToolResult`, …) are documented as neutral-shape consumer checks, not wire validators.
5 changes: 5 additions & 0 deletions .changeset/codemod-flag-removed-task-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@modelcontextprotocol/codemod': patch
---

The v1→v2 codemod no longer rewrites `taskStore`/`taskMessageQueue` McpServer constructor options into `capabilities.tasks` — that target does not exist in v2 (the experimental tasks runtime was removed, SEP-2663). The codemod now leaves the code untouched and emits an action-required diagnostic telling migrators to remove the option, matching the removal guidance already given for `experimental/tasks` imports and the migration guide.
10 changes: 10 additions & 0 deletions .changeset/create-mcp-handler.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@modelcontextprotocol/server': minor
---

Add `createMcpHandler(factory, { legacy?, onerror?, responseMode? })`, an HTTP entry point that serves the 2026-07-28 draft revision per request: each envelope-carrying request is classified once, served on a fresh instance from the factory bound to the claimed revision,
and answered with a JSON body or a lazily-upgraded SSE stream. 2025-era serving is opt-in through the `legacy` slot (`'stateless'` for per-request stateless serving via the existing streamable HTTP transport, or any fetch-shaped handler for bring-your-own wiring); without
the slot the endpoint is modern-only and rejects 2025-era requests with the unsupported-protocol-version error naming its supported revisions. The handler exposes a web-standard `fetch(request, { authInfo?, parsedBody? })` face and a duck-typed `node(req, res, parsedBody?)`
face, plus `close()` for tearing down in-flight modern exchanges. Also exported: `legacyStatelessFallback` (the canonical slot value), the `PerRequestHTTPServerTransport` single-exchange transport and the `classifyInboundRequest` classifier for hand-wired compositions, and
the supporting types. `responseMode: 'json'` never streams and drops mid-call notifications (progress, logging and other related messages emitted before the result); listen-class subscription streams are always served over SSE. The entry performs no Origin/Host validation
(use the middleware packages) and no token verification — `authInfo` is pass-through and never derived from request headers.
6 changes: 6 additions & 0 deletions .changeset/deprecate-client-identity-accessors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@modelcontextprotocol/server': patch
---

Deprecate `Server.getClientCapabilities()`, `Server.getClientVersion()` and `Server.getNegotiatedProtocolVersion()` in favor of the per-request handler context: on 2026-07-28 requests the validated `_meta` envelope carries the client's identity (`ctx.mcpReq.envelope`),
and instances serving that revision through `createMcpHandler` are backfilled per request so the accessors keep answering. Behavior on 2025-era connections is unchanged; the accessors remain functional.
10 changes: 0 additions & 10 deletions .changeset/extract-task-manager.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/fix-task-session-isolation.md

This file was deleted.

7 changes: 7 additions & 0 deletions .changeset/hide-wire-only-members.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@modelcontextprotocol/core': major
'@modelcontextprotocol/client': major
'@modelcontextprotocol/server': major
---

Hide wire-only protocol members from the public surface, at the type level and at runtime. `resultType` (the 2026-07-28 result discrimination field) is no longer declared on any public result type — the wire schemas keep parsing it, and the client funnel now consumes it raw-first: `'complete'` results are stripped to the public shape and any other kind (e.g. `input_required`) rejects with the new `SdkErrorCode.UnsupportedResultType` instead of masking into an empty success. The reserved `_meta` envelope keys are lifted out of inbound requests and notifications before handlers run, and the multi-round-trip retry fields (`inputResponses`, `requestState`) out of inbound requests only (the spec reserves those names on client-initiated requests; notification params keep them), so handler params keep the 2025-era shape; for requests the lifted material surfaces at `ctx.mcpReq.envelope`, `ctx.mcpReq.inputResponses`, and `ctx.mcpReq.requestState` (notifications have no ctx — their lifted envelope keys are not surfaced). High-level client/server methods now return the named public result types (`Promise<CallToolResult>` etc.). Task wire vocabulary stays importable but is `@deprecated` and excluded from the typed method maps (`RequestMethod`/`RequestTypeMap`/`ResultTypeMap`/`NotificationTypeMap`), and `callTool` is typed as plain `CallToolResult`. See docs/migration.md "Wire-only protocol members hidden from the public types".
7 changes: 7 additions & 0 deletions .changeset/missing-client-capability-error.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@modelcontextprotocol/core': minor
'@modelcontextprotocol/client': minor
'@modelcontextprotocol/server': minor
---

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.
6 changes: 6 additions & 0 deletions .changeset/node-forward-supported-versions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@modelcontextprotocol/node': patch
---

Forward `setSupportedProtocolVersions` from `NodeStreamableHTTPServerTransport` to the wrapped Web Standard transport. Previously a server's `supportedProtocolVersions` option never reached the Node adapter's `MCP-Protocol-Version` header validation, which silently kept
validating against the default version list.
12 changes: 12 additions & 0 deletions .changeset/origin-validation-middleware.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@modelcontextprotocol/server': minor
'@modelcontextprotocol/express': minor
'@modelcontextprotocol/hono': minor
'@modelcontextprotocol/fastify': minor
'@modelcontextprotocol/node': minor
---

Add Origin header validation alongside the existing Host header validation. The server package gains framework-agnostic helpers (`validateOriginHeader`, `localhostAllowedOrigins`, `originValidationResponse`); the Express, Hono and Fastify adapters gain `originValidation` /
`localhostOriginValidation` middleware and a new `allowedOrigins` option on their app factories, which now arm Origin validation by default for localhost-class binds (mirroring the Host validation ladder; the 0.0.0.0-without-allowlist warning is unchanged). Requests
without an `Origin` header pass — non-browser MCP clients are unaffected — while a present `Origin` that is not allowed or cannot be parsed (including the opaque `null` origin) is rejected with `403`. The Node adapter ships `hostHeaderValidation` / `originValidation`
request guards for plain `node:http` servers, which previously had no validation helpers.
6 changes: 6 additions & 0 deletions .changeset/pin-modern-rejection-codes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@modelcontextprotocol/core': patch
'@modelcontextprotocol/server': patch
---

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.
9 changes: 9 additions & 0 deletions .changeset/server-era-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@modelcontextprotocol/server': minor
---

Add `ServerOptions.eraSupport: 'legacy' | 'dual-era' | 'modern'`, the opt-in for serving the 2026-07-28 draft revision on long-lived connections such as stdio. The default is `'legacy'` and preserves today's behavior exactly: nothing 2026-era is registered or advertised, and 2025
wire behavior is unchanged by the upgrade. `'dual-era'` serves both protocol eras on the same connection, selecting the era per message (`initialize`-negotiated 2025 traffic as before, per-request `_meta` envelope traffic — including `server/discover` — on the modern era), while
methods that exist in only one era stay invisible to the other. `'modern'` is strict 2026-only: requests without the envelope (including `initialize`) are answered with the unsupported-protocol-version error naming the supported revisions. A 2026-era revision in
`supportedProtocolVersions` now requires declaring `eraSupport` (`'dual-era'` or `'modern'`); on a default `'legacy'` instance it throws a `TypeError` at construction instead of silently installing the `server/discover` handler. On dual-era instances the deprecated
client-identity accessors keep their `initialize`-scoped semantics and are never backfilled from 2026-era requests; handlers read per-request identity from `ctx.mcpReq.envelope`.
5 changes: 5 additions & 0 deletions .changeset/spec-corpus-and-leak-net.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@modelcontextprotocol/core': patch
---

Test-only hardening, no runtime changes: a spec example corpus harness (the draft revision's 86 example directories vendored from the specification repository plus a frozen hand-built 2025-11-25 corpus, with rejection-side fixtures routed through real dispatch), a cross-bundle typed-error recognition guard, and extended end-to-end draft-vocabulary leak coverage for hosted transports, SSE streams, and compatibility fallback paths.
7 changes: 7 additions & 0 deletions .changeset/spec-types-2026-repin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@modelcontextprotocol/core': patch
'@modelcontextprotocol/client': patch
'@modelcontextprotocol/server': patch
---

Internal: regenerate the 2026-07-28 spec reference types from the latest draft schema (`DiscoverResult` now extends `CacheableResult`; `ElicitationCompleteNotificationParams` extracted as a named interface) and document the anchor lifecycle policy. Released-revision spec-type generation is now pinned to a fixed spec commit; draft anchors keep floating via the nightly refresh PRs. No public API or runtime behavior changes.
11 changes: 11 additions & 0 deletions .changeset/wire-server-discover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@modelcontextprotocol/core': minor
'@modelcontextprotocol/server': minor
'@modelcontextprotocol/client': minor
---

Wire `server/discover` (protocol revision 2026-07-28) into the typed request funnel and serve it era-aware. The request joins `ClientRequestSchema`/`ServerResultSchema`/`ResultTypeMap` (per-era availability stays with the wire registries: only the 2026-era registry serves
it), and `Client.discover()` issues it as a typed request on 2026-era connections. A `Server` whose `supportedProtocolVersions` list carries a modern (2026-07-28+) revision installs the `server/discover` handler, advertising ONLY its modern revisions and excluding the
listChanged/subscribe-class capabilities until the `subscriptions/listen` flow ships; servers with today's default list are unchanged and keep answering `-32601`. The `initialize` handshake is now era-aware in the other direction: its accept check and counter-offer consult
only the legacy subset of the supported versions — a 2026-era revision is never negotiated via `initialize` — so a 2025-era client can never be offered a 2026 version string; with the default list this is byte-identical to previous behavior. Serving the 2026 revision to
ordinary HTTP/stdio traffic arrives with an upcoming server-side entry point: today the negotiation surface is client-side, and `mode: 'auto'` falls back cleanly against current SDK servers.
4 changes: 3 additions & 1 deletion .github/workflows/conformance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Conformance Tests

on:
push:
branches: [main]
branches: [main, v2-2026-07-28]
pull_request:
workflow_dispatch:

Expand Down Expand Up @@ -30,6 +30,7 @@ jobs:
- run: pnpm install
- run: pnpm run build:all
- run: pnpm run test:conformance:client:all
- run: pnpm --filter @modelcontextprotocol/test-conformance run test:conformance:client:2026

server-conformance:
runs-on: ubuntu-latest
Expand All @@ -48,3 +49,4 @@ jobs:
- run: pnpm run build:all
- run: pnpm run test:conformance:server
- run: pnpm run test:conformance:server:draft
- run: pnpm --filter @modelcontextprotocol/test-conformance run test:conformance:server:2026
1 change: 1 addition & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ on:
push:
branches:
- main
- v2-2026-07-28
pull_request:
workflow_dispatch:

Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/update-spec-types.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
# Nightly refresh of the draft-tracking spec anchor (2026-07-28).
#
# Anchor lifecycle (see packages/core/src/types/README.md for the full policy):
# - Draft anchors float: this job regenerates the draft-tracking anchor from the
# latest upstream draft schema and, on drift, opens a refresh PR for review.
# It only ever proposes — it never merges.
# - Released anchors are frozen: generation for released revisions is pinned in
# scripts/fetch-spec-types.ts (RELEASED_REVISION_PINS) and is not refreshed by
# this job. Repinning a released revision — including the freeze of a newly
# published revision, when its schema moves out of schema/draft/ — must land
# in the same commit that retargets this workflow.
name: Update Spec Types

on:
Expand Down
8 changes: 8 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ pnpm-lock.yaml
**/src/types/spec.types.2025-11-25.ts
**/src/types/spec.types.2026-07-28.ts

# Spec example corpora: vendored verbatim from the spec repository
# (fetch:spec-examples) or hand-built and frozen - byte-faithful artifacts.
packages/core/test/corpus/fixtures/

# Schema twins: raw upstream schema.json bytes (fetch:schema-twins), locked to
# manifest.json by sha256 in schemaTwinConformance - reformatting breaks the lock.
packages/core/test/corpus/schema-twins/

# Batch test cloned repos and results
packages/codemod/batch-test/repos
packages/codemod/batch-test/results
Expand Down
Loading
Loading