Skip to content

Commit 4e002f6

Browse files
docs: examples + 2026-06 migration guide + changeset
Examples migrated to ctx.mcpReq.* (works under both protocols): - toolWithSampleServer.ts: server.createMessage -> ctx.mcpReq.requestSampling - elicitationFormExample.ts: server.elicitInput -> ctx.mcpReq.elicitInput (3x) - serverGuide.examples.ts: server.listRoots -> ctx.mcpReq.listRoots - elicitationUrlExample.ts: out-of-band webhook keeps server.elicitInput; comment documents pre-2026-only limitation (no request context). Headline 2026-06 example: - honoWebStandardStreamableHttp.ts: handleHttp(server.server, opts) Fetch handler. One shared instance, no Transport, no connect(). - simpleStatelessStreamableHttp.ts: shared instance + single connected transport (was per-request); transport per-message router serves both. docs/migration.md + docs/migration-SKILL.md: 2026-06 stateless support section. Server/Client are the same classes (additive); ctx.mcpReq.* mapping table; auto-probe note; subscribe() note; LegacyTestClient pattern for tests. No .legacy/LegacyServer references (sectioned approach). docs/server.md, examples/server/README.md: updated to match. .changeset/stateless-2026-06.md: major bump core/server/client. Satisfies: 2575-R1, 2567-R1, 2322-R1
1 parent 92f410a commit 4e002f6

11 files changed

Lines changed: 153 additions & 63 deletions

CLAUDE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,9 +201,9 @@ The `ctx` parameter in handlers provides a structured context:
201201
- `http?`: HTTP transport info (undefined for stdio)
202202
- `authInfo?`: Validated auth token info
203203

204-
**`ServerContext`** extends `BaseContext.mcpReq` and `BaseContext.http?` via type intersection:
204+
**`ServerContext`** extends `BaseContext.mcpReq` and `BaseContext.http?` via type intersection, and adds a top-level `clientCapabilities?` field:
205205

206-
- `mcpReq` adds: `log(level, data, logger?)`, `elicitInput(params, options?)`, `requestSampling(params, options?)`
206+
- `mcpReq` adds: `log(level, data, logger?)`, `elicitInput(params, options?)`, `requestSampling(params, options?)`, `listRoots(options?)`
207207
- `http?` adds: `req?` (HTTP request info), `closeSSE?`, `closeStandaloneSSE?`
208208

209209
**`ClientContext`** is currently identical to `BaseContext`.

docs/migration-SKILL.md

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,9 @@ Request/notification params remain fully typed. Remove unused schema imports aft
429429
| `ctx.mcpReq.log(level, data, logger?)` | Send log notification (respects client's level filter) | `server.sendLoggingMessage(...)` from within handler |
430430
| `ctx.mcpReq.elicitInput(params, options?)` | Elicit user input (form or URL) | `server.elicitInput(...)` from within handler |
431431
| `ctx.mcpReq.requestSampling(params, options?)` | Request LLM sampling from client | `server.createMessage(...)` from within handler |
432+
| `ctx.mcpReq.listRoots(options?)` | List client roots | `server.listRoots(...)` from within handler |
433+
434+
`ServerContext` also adds a top-level `clientCapabilities?` field. See section 15 for the both-protocols mapping.
432435

433436
## 11. Schema parameter removed from `request()`, `send()`, and `callTool()` (spec methods)
434437

@@ -522,7 +525,33 @@ Access validators explicitly:
522525
- AJV (Node.js): `import { AjvJsonSchemaValidator } from '@modelcontextprotocol/server';`
523526
- CF Worker: `import { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/server/validators/cf-worker';`
524527

525-
## 15. Migration Steps (apply in this order)
528+
## 15. 2026-06 Stateless Support (SEP-2575/2567/2322)
529+
530+
`Server`/`Client` are the same classes (still extend `Protocol`); 2026-06 support is additive. `Client.connect()` auto-probes `server/discover` and falls back to legacy `initialize`. `setRequestHandler` is unchanged.
531+
532+
**Prefer `ctx.mcpReq.*` inside handlers** (works under both protocols). Inside tool/prompt/resource handler bodies where `ctx` (the handler's second argument) is in scope, replace `<expr>.server.X()` (or `server.X()` on a low-level `Server`) with `ctx.mcpReq.X()`. The `<expr>` prefix varies (`mcpServer.server`, `this.server`, just `server`), so search for `.X(` and rewrite by hand:
533+
534+
| Find calls to | Replace with |
535+
| --- | --- |
536+
| `.createMessage(` | `ctx.mcpReq.requestSampling(` |
537+
| `.elicitInput(` | `ctx.mcpReq.elicitInput(` |
538+
| `.listRoots(` | `ctx.mcpReq.listRoots(` |
539+
| `.sendLoggingMessage({ level, data, logger })` | `ctx.mcpReq.log(level, data, logger)` |
540+
| `.getClientCapabilities()` | `ctx.clientCapabilities` |
541+
542+
All five rows are the **both-protocols** path, not 2026-only: `ctx.mcpReq.listRoots()` and `ctx.clientCapabilities` work identically against pre-2026 and 2026-06 clients.
543+
544+
Add `ctx` to the handler signature if not already present. For tools with an `inputSchema`: `async (args) =>``async (args, ctx) =>`. For tools WITHOUT an `inputSchema`: `async () =>``async ctx =>` (single parameter).
545+
546+
The top-level `server.createMessage()` etc. still work with a connected pre-2026 client; this migration is recommended but not required.
547+
548+
`ctx.mcpReq.requestSampling` keeps the same overload narrowing as `server.createMessage`: when `params.tools` is statically present the result type is `CreateMessageResultWithTools`; when statically absent it is `CreateMessageResult`. If `tools` is conditional at the call site, the result is the union; add a runtime `Array.isArray(result.content)` check before indexing.
549+
550+
MRTR via `InputRequiredError` works for handlers registered via `setRequestHandler`; `fallbackRequestHandler` is not wrapped by middleware (matches pre-existing behavior).
551+
552+
For tests that exercise pre-2026 connection-model behavior, construct the test client with `supportedProtocolVersions` filtered to pre-2026 versions only.
553+
554+
## 16. Migration Steps (apply in this order)
526555

527556
1. Update `package.json`: `npm uninstall @modelcontextprotocol/sdk`, install the appropriate v2 packages
528557
2. Replace all imports from `@modelcontextprotocol/sdk/...` using the import mapping tables (sections 3-4), including `StreamableHTTPServerTransport``NodeStreamableHTTPServerTransport`
@@ -534,4 +563,5 @@ Access validators explicitly:
534563
8. If using server SSE transport, migrate to Streamable HTTP
535564
9. If using server auth from the SDK: RS helpers (`requireBearerAuth`, `mcpAuthMetadataRouter`) → `@modelcontextprotocol/express`; AS helpers → external IdP/OAuth library
536565
10. If relying on `listTools()`/`listPrompts()`/etc. throwing on missing capabilities, set `enforceStrictCapabilities: true`
537-
11. Verify: build with `tsc` / run tests
566+
11. Inside tool/prompt/resource handlers, replace `server.createMessage`/`elicitInput`/`listRoots`/`sendLoggingMessage` with `ctx.mcpReq.*` per section 15
567+
12. Verify: build with `tsc` / run tests

docs/migration.md

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -645,7 +645,7 @@ server.setRequestHandler('tools/call', async (request, ctx) => {
645645
});
646646
```
647647

648-
These replace the pattern of calling `server.sendLoggingMessage()`, `server.createMessage()`, and `server.elicitInput()` from within handlers.
648+
These replace the pattern of calling `server.sendLoggingMessage()`, `server.createMessage()`, `server.elicitInput()`, and `server.listRoots()` from within handlers. `ctx.clientCapabilities` likewise replaces `server.getClientCapabilities()`.
649649

650650
### Error hierarchy refactoring
651651

@@ -869,6 +869,48 @@ The 2025-11 experimental tasks side-channel woven through `Protocol` has been re
869869

870870
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`.
871871

872+
## 2026-06 Stateless Protocol Support (SEP-2575, SEP-2567, SEP-2322)
873+
874+
`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.
875+
876+
### What changed
877+
878+
- **`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.
879+
- **`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.
880+
- **`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`.
881+
- **`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`).
882+
- **`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.
883+
884+
### Prefer `ctx.mcpReq.*` for server-to-client interactions
885+
886+
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).
887+
888+
| Top-level (pre-2026 only) | Handler-context (both protocols) |
889+
| --- | --- |
890+
| `server.createMessage(params)` | `ctx.mcpReq.requestSampling(params)` |
891+
| `server.elicitInput(params)` | `ctx.mcpReq.elicitInput(params)` |
892+
| `server.listRoots()` | `ctx.mcpReq.listRoots()` |
893+
| `server.sendLoggingMessage({level, data, logger})` | `ctx.mcpReq.log(level, data, logger)` |
894+
| `server.getClientCapabilities()` | `ctx.clientCapabilities` |
895+
896+
`ctx.mcpReq.listRoots()` and `ctx.clientCapabilities` work under **both** protocols, not just 2026-06.
897+
898+
The top-level methods still exist and work when a pre-2026 client is connected. They are not removed.
899+
900+
MRTR via `InputRequiredError` works for handlers registered via `setRequestHandler`; `fallbackRequestHandler` is not wrapped by middleware (matches pre-existing behavior).
901+
902+
### Tests pinning pre-2026
903+
904+
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.
905+
906+
### Shared instances
907+
908+
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.
909+
910+
### Dual-mode endpoint
911+
912+
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.
913+
872914
## Enhancements
873915

874916
### Automatic JSON Schema validator selection by runtime

docs/server.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@ For runnable examples, see [`elicitationFormExample.ts`](https://github.com/mode
478478

479479
### Roots
480480

481-
Roots let a tool handler discover the client's workspace directories — for example, to scope a file search or identify project boundaries (see [Roots](https://modelcontextprotocol.io/docs/learn/client-concepts#roots) in the MCP overview). Call {@linkcode @modelcontextprotocol/server!server/server.Server#listRoots | server.server.listRoots()} (requires the client to declare the `roots` capability):
481+
Roots let a tool handler discover the client's workspace directories — for example, to scope a file search or identify project boundaries (see [Roots](https://modelcontextprotocol.io/docs/learn/client-concepts#roots) in the MCP overview). Call `ctx.mcpReq.listRoots()` inside the handler (requires the client to declare the `roots` capability). This works under both the pre-2026 connection model and the 2026 stateless model:
482482

483483
```ts source="../examples/server/src/serverGuide.examples.ts#registerTool_roots"
484484
server.registerTool(
@@ -487,8 +487,8 @@ server.registerTool(
487487
description: 'List files across all workspace roots',
488488
inputSchema: z.object({})
489489
},
490-
async (_args, _ctx): Promise<CallToolResult> => {
491-
const { roots } = await server.server.listRoots();
490+
async (_args, ctx): Promise<CallToolResult> => {
491+
const { roots } = await ctx.mcpReq.listRoots();
492492
const summary = roots.map(r => `${r.name ?? r.uri}: ${r.uri}`).join('\n');
493493
return { content: [{ type: 'text', text: summary }] };
494494
}
@@ -572,7 +572,7 @@ If you use `NodeStreamableHTTPServerTransport` directly with your own HTTP frame
572572

573573
| Feature | Description | Example |
574574
|---------|-------------|---------|
575-
| Web Standard transport | Deploy on Cloudflare Workers, Deno, or Bun | [`honoWebStandardStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/honoWebStandardStreamableHttp.ts) |
575+
| 2026-06 stateless `handleHttp()` (Hono) | One shared server, no Transport, no `connect()` — runs on Cloudflare Workers, Deno, or Bun | [`honoWebStandardStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/honoWebStandardStreamableHttp.ts) |
576576
| Session management | Per-session transport routing, initialization, and cleanup | [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleStreamableHttp.ts) |
577577
| Resumability | Replay missed SSE events via an event store | [`inMemoryEventStore.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/inMemoryEventStore.ts) |
578578
| CORS | Expose MCP headers for browser clients | [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleStreamableHttp.ts) |

0 commit comments

Comments
 (0)