Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 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
96bf12f
feat(server): serveStdio — connection-pinned era serving for stdio; r…
felixweinberger Jun 18, 2026
3f2ca34
feat(server): default createMcpHandler to stateless legacy serving; e…
felixweinberger Jun 18, 2026
6de84cf
fix(server): settle a cancelled serveStdio probe so a pipelined initi…
felixweinberger Jun 18, 2026
f8d8fc0
chore(core): repin the 2026-07-28 spec references at spec commit 2fb2…
felixweinberger Jun 18, 2026
dab933d
feat: multi round-trip requests — neutral contract and client auto-fu…
felixweinberger Jun 18, 2026
0dfc5ce
feat: multi round-trip requests — server seam, e2e coverage, docs and…
felixweinberger Jun 18, 2026
f7c29e8
test(conformance): arm the fixtures for the input-required scenario f…
felixweinberger Jun 18, 2026
7f425ee
feat(client): per-request envelope auto-emission and probe completion…
felixweinberger Jun 18, 2026
0ff1bd0
feat(server): subscriptions/listen — entry-handled router and ServerE…
felixweinberger Jun 18, 2026
699d6a6
feat(client): Client.listen() and listChanged auto-open on modern con…
felixweinberger Jun 19, 2026
8766f68
test(conformance): arm the fixture for the sep-2575 list_changed-on-l…
felixweinberger Jun 19, 2026
d28dcde
docs: @deprecated JSDoc on push-style server→client APIs that throw o…
felixweinberger Jun 19, 2026
4a1ef36
refactor(examples): per-story directory layout, self-verifying CI har…
felixweinberger Jun 19, 2026
d8ed081
ci: declare read-only GITHUB_TOKEN permissions on the examples workfl…
felixweinberger Jun 19, 2026
0e6a624
feat(client): minimal response-cache substrate (ResponseCacheStore + …
felixweinberger Jun 22, 2026
ef99992
feat: SEP-2243 custom half — Mcp-Param header codec, client mirroring…
felixweinberger Jun 22, 2026
faf94f8
feat: SEP-2243 standard half — std-header server validation; http-hea…
felixweinberger Jun 22, 2026
5f18fcd
fix(server,client): per-request ctx.log delivery; 2026-era HTTP cance…
felixweinberger Jun 22, 2026
8043e9a
feat(server)\!: drop McpHttpHandler.node; ship toNodeHandler(handler)…
felixweinberger Jun 23, 2026
61b9c1f
feat(client): SEP-2549 — honor cacheHints (ttlMs/scope) on the respon…
felixweinberger Jun 23, 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.
7 changes: 7 additions & 0 deletions .changeset/client-honor-cache-hints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@modelcontextprotocol/client': minor
---

`Client` now **honours** the server-stamped SEP-2549 `ttlMs`/`cacheScope` cache hints on the cacheable verbs (`listTools()`, `listPrompts()`, `listResources()`, `listResourceTemplates()`, `readResource()`): a still-fresh held entry is served without a round trip. New `CacheableRequestOptions.cacheMode` (`'use'` — the default; `'refresh'` — always fetch and re-store; `'bypass'` — fetch without consulting or writing the cache) gives per-call control. The behaviour is opt-in by hint: a server that sends `ttlMs: 0` (the conservative default this SDK's server stamps) sees byte-identical behaviour — every call fetches.

Entries are automatically scoped by connected-server identity (derived from `serverInfo` after connect, encoded collision-free via `JSON.stringify`); `ClientOptions.cachePartition` is the opaque per-principal slot for `'private'`-scoped entries — set it to your principal identifier (e.g. the auth subject) when one `responseCacheStore` backs several principals. With the default `''` every entry lives at the connected server's shared partition (the safe single-tenant posture). `ClientOptions.defaultCacheTtlMs` (default `0`) supplies the TTL when a result lacks one (e.g. a legacy-era response); the server-supplied `ttlMs` is clamped at 24 h (`MAX_CACHE_TTL_MS`). The list verbs always store the aggregate (so `callTool`'s mirroring/output-validation index keeps working at any TTL); `readResource` stores only when the resolved TTL is positive. `notifications/resources/updated` evicts the cached `resources/read` body for that URI. `ResponseCacheStore` gained `delete(key)`; `InMemoryResponseCacheStore` is now bounded (`{ maxEntries }`, default 512, oldest-first eviction). New exports: `CacheMode`, `CacheableRequestOptions`, `InMemoryResponseCacheStoreOptions`, `MAX_CACHE_TTL_MS`.
6 changes: 6 additions & 0 deletions .changeset/client-http-stream-close-cancel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@modelcontextprotocol/core': minor
'@modelcontextprotocol/client': minor
---

Client request cancellation on a 2026-07-28 Streamable HTTP connection now closes that request's SSE response stream — the spec cancellation signal — instead of POSTing `notifications/cancelled`. Cancellation on a 2025-era connection, and on stdio at any era, still sends `notifications/cancelled` as before. Adds the optional `Transport.hasPerRequestStream` capability flag (set on `StreamableHTTPClientTransport`) for the protocol layer to route the per-transport cancel path.
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/client-response-cache-substrate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@modelcontextprotocol/client': major
---

`Client.listTools()` / `listPrompts()` / `listResources()` / `listResourceTemplates()` now **auto-aggregate every page** when called without a `cursor` and return the complete result with `nextCursor: undefined` (matching the C#, Java, and mcp.d SDKs). Pass an explicit `{ cursor }` string to fetch a single page; the per-page path is unchanged. Existing manual pagination loops keep working — the first iteration returns everything and the loop exits — but can be deleted. The aggregated result is written to the new pluggable `ResponseCacheStore` (default: a fresh per-instance `InMemoryResponseCacheStore`); a `ClientResponseCache` collaborator owns the eviction-generation guard and the derived `tools/list` index that `callTool`'s output validation and SEP-2243 `Mcp-Param-*` mirroring read. New exports: `ResponseCacheStore`, `CacheKey`, `CacheEntry`, `CacheScope`, `MaybePromise`, `InMemoryResponseCacheStore`; new `ClientOptions.responseCacheStore` / `ClientOptions.listMaxPages` (caps the auto-aggregate walk at 64 pages by default; throws `SdkError` with `SdkErrorCode.ListPaginationExceeded` on overrun so a partial aggregate is never cached). The store interface is async-ready (`MaybePromise<…>`); the in-memory default stays synchronous. Entries are automatically scoped by the connected server's identity and (when set) the consumer-supplied `cachePartition`, so a shared store does not collide across servers or principals; evictions are likewise scoped to the connected server's partitions.

**Behavior change (every era):** output-schema validator compilation is now lazy — validators are compiled on the first `callTool()` against the cached `tools/list` entry, not eagerly inside `listTools()` — and non-throwing: an uncompilable `outputSchema` is `console.warn`-ed and validation is skipped for that tool only (previously `listTools()` threw). A pluggable `jsonSchemaValidator` provider therefore observes compilation at `callTool` time, not `listTools` time. The legacy-era `listTools()` path is unchanged at the wire level but is observably different at the validator-lifecycle level.
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.
5 changes: 2 additions & 3 deletions .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": [
"@modelcontextprotocol/examples-client",
"@modelcontextprotocol/examples",
"@modelcontextprotocol/examples-client-quickstart",
"@modelcontextprotocol/examples-server",
"@modelcontextprotocol/examples-server-quickstart",
"@modelcontextprotocol/examples-shared"
"@mcp-examples/*"
]
}
9 changes: 9 additions & 0 deletions .changeset/create-mcp-handler-legacy-revision.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@modelcontextprotocol/server': minor
---

Revise `createMcpHandler`'s legacy handling (a behavior change to the unreleased entry). The entry now serves 2025-era (non-envelope) traffic **by default** through per-request stateless serving from the same factory — `legacy: 'stateless'` is the default rather than an
opt-in — and the strict, modern-only posture is selected with the new `legacy: 'reject'` value (the earlier alpha's default). The handler-valued `legacy` option (bring-your-own legacy serving) is removed: existing legacy deployments (for example a sessionful streamable
HTTP wiring) keep serving 2025 traffic by routing in user land with the new `isLegacyRequest(request, parsedBody?)` export, which runs the entry's own classification step — it returns `true` only for requests with no per-request `_meta` envelope claim, while malformed or
incomplete modern claims are NOT legacy and must be routed to the modern handler, which answers them with the documented validation errors. The predicate classifies a clone, so the routed request body stays readable. `legacyStatelessFallback` remains exported as a
standalone fetch-shaped handler with the same stateless serving as the default.
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 selected with the `legacy` option (`'stateless'` — the default — for per-request stateless serving via the existing streamable HTTP transport, `'reject'` for a modern-only strict endpoint
that answers 2025-era requests with the unsupported-protocol-version error naming its supported revisions). The handler is a web-standard `{ fetch, close, notify }` object: `fetch(request, { authInfo?, parsedBody? })` is the only request face (Node frameworks wrap it with
`toNodeHandler(handler)` from `@modelcontextprotocol/node`), and `close()` tears down in-flight modern exchanges. Also exported: `legacyStatelessFallback` (the same stateless legacy serving as a standalone fetch-shaped handler), 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.
11 changes: 11 additions & 0 deletions .changeset/envelope-auto-emission.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@modelcontextprotocol/client': minor
'@modelcontextprotocol/core': minor
---

Per-request `_meta` envelope auto-emission on modern-era connections: once a client negotiates a 2026-07-28+ protocol revision (via `versionNegotiation: { mode: 'auto' }` or `{ pin }`), it automatically attaches the reserved protocol-version / client-info / client-capabilities
`_meta` keys to every outgoing request and notification — you no longer set the envelope by hand. User-supplied `_meta` keys take precedence over the auto-attached ones; the auto-attached client-capabilities reflect what the client actually registered. Legacy-era connections
(the default, and the `'auto'`-mode fallback) never gain these keys, so 2025-era outbound traffic is byte-identical to before.

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
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.
10 changes: 0 additions & 10 deletions .changeset/extract-task-manager.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/fix-session-status-codes.md

This file was deleted.

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

This file was deleted.

9 changes: 9 additions & 0 deletions .changeset/handler-drop-node-face.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@modelcontextprotocol/server': major
'@modelcontextprotocol/node': minor
---

`createMcpHandler` now returns a web-standards-only `{ fetch, close, notify, bus }` handler — the shape Workers/Bun/Deno expect from `export default`. The duck-typed `.node(req, res, parsedBody?)` face is removed; Node frameworks (Express, Fastify, plain `node:http`) wrap the
handler once with the new `toNodeHandler(handler, { onerror? })` exported from `@modelcontextprotocol/node`, which converts the Node request to a web-standard `Request`, calls `handler.fetch`, and writes the `Response` back honoring write backpressure. The optional `onerror`
receives the adapter-level error fallback (request conversion / `handler.fetch` throw) before the `500` response is written, restoring observability parity with the removed `.node` face. `NodeIncomingMessageLike` and `NodeServerResponseLike` move from
`@modelcontextprotocol/server` to `@modelcontextprotocol/node`.
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".
Loading
Loading