Skip to content
37 changes: 23 additions & 14 deletions docs/migration/support-2026-07-28.md
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ and the multi-round-trip retry fields (`inputResponses`, `requestState`).
- **High-level methods return the named public types** (`client.callTool()` →
`Promise<CallToolResult>`, etc.). Handler return positions are unaffected.
- **Reserved envelope keys and retry fields appear in no public params/result type.**
The `RequestMetaEnvelope` type and the four `*_META_KEY` constants stay exported.
The `RequestMetaEnvelope` type and the four envelope `*_META_KEY` constants stay exported.

The protocol layer enforces the same boundary at runtime:

Expand Down Expand Up @@ -405,18 +405,22 @@ obtain client input (elicitation, sampling, roots) **in-band** by returning
`inputRequired(...)` from a `tools/call` / `prompts/get` / `resources/read` handler; the
client retries the original call with the responses.

| Handler serving 2026-07-28 requests | Mechanical fix |
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `await ctx.mcpReq.elicitInput({…})` / `requestSampling({…})` | `return inputRequired({ inputRequests: { id: inputRequired.elicit({…}) } })`; read `acceptedContent(ctx.mcpReq.inputResponses, 'id')` on re-entry |
| `throw new UrlElicitationRequiredError([…])` | `return inputRequired({ inputRequests: { id: inputRequired.elicitUrl({…}) } })` |
| Handler serving 2026-07-28 requests | Mechanical fix |
| ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `await ctx.mcpReq.elicitInput({…})` / `requestSampling({…})` | `return inputRequired({ inputRequests: { id: inputRequired.elicit({…}) } })`; read `acceptedContent(ctx.mcpReq.inputResponses, 'id')` on re-entry |
| `throw new UrlElicitationRequiredError([…])` | `return inputRequired({ inputRequests: { id: inputRequired.elicitUrl({…}) } })` |
| handler shared across both eras | **no branch needed** — write the `inputRequired(...)` form once; the [legacy shim](#legacy-shim-for-input_required) serves it to 2025-era connections by issuing real server→client requests |

`inputRequired` / `acceptedContent` / `InputRequiredSpec` are exported from
`@modelcontextprotocol/server`. On 2026-era requests the push-style APIs
(`ctx.mcpReq.send` of server→client requests, `ctx.mcpReq.elicitInput`,
`ctx.mcpReq.requestSampling`, instance-level `createMessage()`/`elicitInput()`/`listRoots()`/`ping()`)
fail with a typed local error before anything reaches the wire; their behavior toward
2025-era requests is unchanged.
2025-era requests is unchanged. The same split applies to
`throw new UrlElicitationRequiredError(...)`: on 2025-era connections it is unchanged —
the throw still produces the `-32042` protocol error, not an `isError` result; on
2026-07-28 requests it fails with a clear error steering to
`inputRequired.elicitUrl(...)` rather than being converted silently.

`requestState` round-trips as an opaque, **untrusted** string — see
[Replacing per-session state: `requestState`](#replacing-per-session-state-requeststate)
Expand Down Expand Up @@ -484,11 +488,11 @@ driver's semantics exactly:

Knobs live at `ServerOptions.inputRequired`:

| Member | Default | Meaning |
| --- | --- | --- |
| `maxRounds` | `8` | Handler re-entries per originating request before failing — deliberately tighter than the client driver's 10: the shim holds a live wire request open for the whole flow |
| `roundTimeoutMs` | `600_000` | Per-leg timeout (with `resetTimeoutOnProgress`) — embedded requests are human-paced, so the 60s protocol default does not apply |
| `legacyShim` | `true` | `false` restores the pre-shim loud failure (`-32603`) and the branch-on-era pattern |
| Member | Default | Meaning |
| ---------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `maxRounds` | `8` | Handler re-entries per originating request before failing — deliberately tighter than the client driver's 10: the shim holds a live wire request open for the whole flow |
| `roundTimeoutMs` | `600_000` | Per-leg timeout (with `resetTimeoutOnProgress`) — embedded requests are human-paced, so the 60s protocol default does not apply |
| `legacyShim` | `true` | `false` restores the pre-shim loud failure (`-32603`) and the branch-on-era pattern |

Failures surface **per family**: `tools/call` failures (capability refusal, a failed
leg, round-cap exhaustion) become `isError` tool results — the 2025-era idiom hosts
Expand Down Expand Up @@ -519,8 +523,10 @@ phase state so they increase across re-entries — the token spans the whole flo
today's `elicitInput` in that configuration, which waits out its own 60s
default. Interactive tools need a streaming-capable session.
- The 2025-era `notifications/elicitation/complete` channel for URL-mode elicitation
is not bridged (upstream gap F8): URL-mode legs complete like any other elicitation
response.
is not bridged: URL-mode legs complete like any other elicitation
response. The sender API for that channel,
`Server.createElicitationCompletionNotifier()`, is itself unchanged from v1 for
2025-era URL-mode elicitation — only the shim does not bridge it.

---

Expand Down Expand Up @@ -607,7 +613,10 @@ Task methods are excluded from the typed method maps: `RequestMethod` / `Request
`notifications/tasks/status` entries, so the method-keyed overloads of `request()`,
`ctx.mcpReq.send()`, `setRequestHandler()`, `setNotificationHandler()` reject task
methods at compile time. `ResultTypeMap['tools/call']` is plain `CallToolResult` (no
`| CreateTaskResult`); same for `sampling/createMessage` and `elicitation/create`. Where
`| CreateTaskResult`); same for `sampling/createMessage` and `elicitation/create`.
(The published `2.0.0-alpha.3` typings predate this exclusion — there the typed maps
still carry the `tasks/*` entries and the `CreateTaskResult` unions; narrow with the
`isCallToolResult` guard until the next published alpha.) Where
task interop is genuinely required, use the explicit-schema custom-method form
(`request({ method: 'tasks/get', params }, GetTaskResultSchema)`). Inbound `tasks/*`
requests → `-32601`.
Expand Down
Loading
Loading