-
Notifications
You must be signed in to change notification settings - Fork 1.9k
docs: 2026-06 migration guide + examples + changeset #2133
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: fweinberger/v2-pipe-transports
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -645,7 +645,7 @@ | |
| }); | ||
| ``` | ||
|
|
||
| These replace the pattern of calling `server.sendLoggingMessage()`, `server.createMessage()`, and `server.elicitInput()` from within handlers. | ||
| These replace the pattern of calling `server.sendLoggingMessage()`, `server.createMessage()`, `server.elicitInput()`, and `server.listRoots()` from within handlers. `ctx.clientCapabilities` likewise replaces `server.getClientCapabilities()`. | ||
|
|
||
| ### Error hierarchy refactoring | ||
|
|
||
|
|
@@ -869,6 +869,48 @@ | |
|
|
||
| There is no migration path for the removed surface; it was always `@experimental`. Under SEP-2663, tasks reattach via a `DispatchMiddleware` (`mcp.use(tasksPlugin({ store }))`) and handlers read task context from `ctx.ext.task` instead of `ctx.task`. | ||
|
|
||
| ## 2026-06 Stateless Protocol Support (SEP-2575, SEP-2567, SEP-2322) | ||
|
|
||
| `Server` and `Client` now support the 2026-06 stateless connection model alongside the existing pre-2026 model. They remain the same classes (still extending `Protocol`); the new behavior is additive. | ||
|
|
||
| ### What changed | ||
|
|
||
| - **`Client.connect()` auto-probes.** On connect, the client sends `server/discover` via the transport's `sendAndReceive` path. If the server responds, the client operates in stateless mode; typed methods (`callTool`, `listTools`, etc.) route via `sendAndReceive` and the MRTR loop. If discover fails (server doesn't support it, transport doesn't have `sendAndReceive`), the client falls back to the legacy `initialize` handshake. Existing code works unchanged. | ||
| - **`Server` gained a stateless dispatch path.** `server.statelessHandlers()` returns `{dispatch, listen}` for transports to call. `connect(transport)` wires this automatically via `transport.setStatelessHandlers?.()`. Handlers registered with `setRequestHandler` serve both paths. | ||
| - **`handleHttp(server, opts)`** is a new Fetch-API entry point: one shared `Server` instance, no `Transport`, no `connect()`. Returns `(Request) => Promise<Response>`. See `examples/server/src/honoWebStandardStreamableHttp.ts`. | ||
| - **`client.subscribe(filter)`** opens a `subscriptions/listen` stream for list-changed and resource-updated notifications (the 2026-06 replacement for unsolicited notifications and `resources/subscribe`). | ||
| - **`Transport` interface gained two optional methods:** `setStatelessHandlers?(handlers)` (server side) and `sendAndReceive?(req, opts?)` (client side). Implement these in custom transports to support 2026-06. | ||
|
|
||
| ### Prefer `ctx.mcpReq.*` for server-to-client interactions | ||
|
|
||
| Inside a tool/prompt/resource handler, use `ctx.mcpReq.{elicitInput, requestSampling, listRoots, log}` instead of the top-level `server.elicitInput()` / `server.createMessage()` / `server.listRoots()` / `server.sendLoggingMessage()`. The `ctx.mcpReq.*` form works under **both** protocols: with a pre-2026 client it sends a real request; with a 2026-06 client it returns an `input_required` result and the client retries with the response embedded (SEP-2322 MRTR). | ||
|
Check warning on line 886 in docs/migration.md
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 The prose in the new "Prefer Extended reasoning...What the bug is. The new "Prefer
That sentence enumerates four members and then describes them all with request/MRTR semantics. The description is correct for Why
So the prose's blanket claim — "sends a real request" / "returns an How a reader would be misled. A migrator reading this paragraph and looking at the comparison table immediately below it would reasonably expect every row to behave the same way. Concretely: someone porting a tool that currently calls Step-by-step proof.
(The verifiers also checked How to fix. Scope the request/MRTR sentence to the three methods that actually participate, and add a short clause for
This is a documentation-only precision issue with no runtime impact, so it's a nit. |
||
|
|
||
| | Top-level (pre-2026 only) | Handler-context (both protocols) | | ||
| | --- | --- | | ||
| | `server.createMessage(params)` | `ctx.mcpReq.requestSampling(params)` | | ||
| | `server.elicitInput(params)` | `ctx.mcpReq.elicitInput(params)` | | ||
| | `server.listRoots()` | `ctx.mcpReq.listRoots()` | | ||
| | `server.sendLoggingMessage({level, data, logger})` | `ctx.mcpReq.log(level, data, logger)` | | ||
| | `server.getClientCapabilities()` | `ctx.clientCapabilities` | | ||
|
|
||
| `ctx.mcpReq.listRoots()` and `ctx.clientCapabilities` work under **both** protocols, not just 2026-06. | ||
|
|
||
| The top-level methods still exist and work when a pre-2026 client is connected. They are not removed. | ||
|
Check warning on line 898 in docs/migration.md
|
||
|
Comment on lines
+896
to
+898
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 The migration prose says the top-level Extended reasoning...What the bug is. Both new passages added by this PR — What the implementation actually says. In
Each annotation reads roughly: Step-by-step proof of the surprise. A maintainer of an existing pre-2026 server reads the new migration section, sees "this migration is recommended but not required" / "They are not removed," and reasonably concludes the existing Why this matters. "Not removed" and "not deprecated" are different claims. The current prose is technically accurate but communicates a lower urgency than the codebase signals. This is exactly the class of "prose that doesn't match the implementation in the same diff stack" that the repo's own How to fix. A one-clause addition to each passage closes the gap:
Documentation-completeness nit: no runtime impact, but the omission directly contradicts the reader's first-hand IDE experience after upgrading. |
||
|
|
||
| MRTR via `InputRequiredError` works for handlers registered via `setRequestHandler`; `fallbackRequestHandler` is not wrapped by middleware (matches pre-existing behavior). | ||
|
|
||
| ### Tests pinning pre-2026 | ||
|
|
||
| If a test exercises pre-2026 connection-model behavior (e.g., `oninitialized`, server-initiated requests, in-band logging) and breaks because the auto-probe now succeeds, construct the client with `supportedProtocolVersions` filtered to pre-2026 versions only, or use a fixture like `LegacyTestClient` that does so. The probe falls back to legacy when no mutual stateless version exists. | ||
|
Check warning on line 904 in docs/migration.md
|
||
|
Comment on lines
+902
to
+904
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 The "Tests pinning pre-2026" subsection references Extended reasoning...What the issue is The new "Tests pinning pre-2026" subsection in
Why it matters
Why the parallel doc gets it right The same guidance appears in
That copy deliberately omits the Step-by-step proof
How to fix Either drop the reference entirely (matching the SKILL.md wording), or rephrase so it's unambiguous that it's a pattern to copy, not a symbol to import — e.g.:
This is a documentation-clarity nit only: the primary actionable instruction ( |
||
|
|
||
| ### Shared instances | ||
|
|
||
| A single `Server` instance can safely serve many concurrent 2026-06 clients via `handleHttp` or a connected transport's per-message router. For pre-2026 clients, the existing per-instance isolation guidance still applies (the legacy path's `_clientCapabilities` is per-connection state): either per-session (a transport map keyed by session ID) or a fresh server per request. | ||
|
|
||
| ### Dual-mode endpoint | ||
|
|
||
| To serve both protocol eras from one HTTP endpoint, use `WebStandardStreamableHTTPServerTransport`'s per-message router for both eras, or compose `handleHttp` with a legacy transport behind your own router (e.g. branch on `MCP-Protocol-Version` / `isInitializeRequest` to send pre-2026 traffic to a per-session transport and everything else to the shared `handleHttp` handler). The shared `Server` instance handles 2026-06 traffic; pre-2026 traffic gets per-instance isolation as above. | ||
|
Check warning on line 912 in docs/migration.md
|
||
|
Comment on lines
+910
to
+912
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 The first option in the new "Dual-mode endpoint" subsection ("use Extended reasoning...The contradiction The "Dual-mode endpoint" subsection added in commit 4e002f6 says:
The closing sentence asserts pre-2026 traffic gets per-instance isolation. That holds for option 2 (your own router branches pre-2026 traffic to a per-session transport map). It does not hold for option 1 as written, and the immediately-preceding "Shared instances" subsection — added in the same diff — already explains why. What the per-message router actually does
Why this is internally inconsistent within the PR The "Shared instances" subsection two paragraphs earlier says exactly that:
And Step-by-step reader walkthrough
Why this is a nit, not a blocking bug This is a docs-only consistency issue in newly-added prose, with no runtime impact. Option 2 is a complete recipe and the closing sentence partially mitigates the confusion by signaling that pre-2026 isolation is required. But option 1 as worded reads as if the per-message router alone is sufficient, contradicting the example comment and the preceding subsection. Suggested fix Either drop option 1, qualify it (e.g., "use |
||
|
|
||
| ## Enhancements | ||
|
|
||
| ### Automatic JSON Schema validator selection by runtime | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 The §10 table row added in this PR documents the convenience method as
ctx.mcpReq.listRoots(options?), and the matching CLAUDE.md line (also updated here) sayslistRoots(options?)— but the actual signature islistRoots(params?, options?). A reader passing aRequestOptionsobject as the first argument would silently bind it toparamsand lose the timeout/signal; small fix to(params?, options?)in both files.Extended reasoning...
What the bug is
The
ServerContextconvenience methods table in §10 ofdocs/migration-SKILL.md(thelistRootsrow was added in this PR via commit 4e002f6) documents the method as:CLAUDE.md's "Request Handler Context" section (also touched in this PR) says the same:
mcpReq adds: ... listRoots(options?).But the actual declared and wired signature takes
params?first, thenoptions?:packages/core/src/shared/protocol.ts:246—listRoots: (params?: ListRootsRequest['params'], options?: RequestOptions) => Promise<ListRootsResult>packages/server/src/server/server.ts:566— wired aslistRoots: (params, options) => this.listRoots(params, options)packages/server/src/server/server.ts:932— the deprecated top-levelServer.listRootsis alsoasync listRoots(params?, options?)Why it's also internally inconsistent
The sibling rows in the same §10 table correctly show
elicitInput(params, options?)andrequestSampling(params, options?). SolistRoots(options?)contradicts both the implementation and its neighbors. It also contradicts the §15 mapping table in the same file, which shows.listRoots( → ctx.mcpReq.listRoots(as a parameter-preserving rewrite (i.e., it inherits the(params?, options?)signature).What the impact would be
migration-SKILL.mdis explicitly the LLM-optimized table for mechanical/programmatic rewrites, so signature accuracy matters more here than in prose docs. A migrator (human or LLM) following the documented signature and writingctx.mcpReq.listRoots({ timeout: 5000, signal })would have that object bind toparams(typed asListRootsRequest['params'], which is{ _meta?: ... } | undefined) instead ofoptions. The timeout and abort signal would silently not apply. TypeScript may or may not catch this depending on whether theRequestOptionsshape is excess-property compatible with{ _meta?: ... }— but even when caught, the doc is sending the reader to the wrong place.Step-by-step proof
ctx.mcpReq.listRoots(options?).await ctx.mcpReq.listRoots({ timeout: 30_000 })expecting a 30s timeout.(params?, options?). The{ timeout: 30_000 }object is bound to theparamspositional slot.optionsisundefined. The default request timeout applies instead of the requested 30s.paramsobject is forwarded as theroots/listrequest params — at best a no-op, at worst a malformed request body if validation is strict.How to fix
Change to
ctx.mcpReq.listRoots(params?, options?)in:docs/migration-SKILL.md§10 table (line ~432)CLAUDE.mdline ~206 (mcpReq adds:list)This brings both in line with the implementation and with the sibling rows
elicitInput(params, options?)andrequestSampling(params, options?).