You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Gate high-level server-to-client requests on stateless mode, not draft protocol
ElicitAsync/SampleAsync/RequestRootsAsync now throw only when the server is stateless (the existing ThrowIf*Unsupported guards already handled this). Stdio + DRAFT-2026-v1 keeps working via the legacy server-to-client JSON-RPC path; stateless Streamable HTTP throws regardless of protocol revision. A follow-up will force DRAFT-2026-v1 Streamable HTTP to stateless mode.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy file name to clipboardExpand all lines: docs/concepts/elicitation/elicitation.md
+3-3Lines changed: 3 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -172,10 +172,10 @@ Here's an example implementation of how a console application might handle elici
172
172
173
173
### Multi Round-Trip Requests (MRTR)
174
174
175
-
[MRTR](xref:mrtr) is the SEP-2322 mechanism for server-driven input requests, finalized in protocol revision `DRAFT-2026-v1`. Under the draft protocol, the server-to-client `elicitation/create` request method is removed; the only supported way to ask the user for input from a server handler is to throw <xref:ModelContextProtocol.Protocol.InputRequiredException> and let the SDK emit an <xref:ModelContextProtocol.Protocol.InputRequiredResult> on the wire.
175
+
[MRTR](xref:mrtr) is the SEP-2322 mechanism for server-driven input requests, finalized in protocol revision `DRAFT-2026-v1`. Under the draft protocol, the server-to-client `elicitation/create` request method is removed; the recommended way to ask the user for input from a server handler is to throw <xref:ModelContextProtocol.Protocol.InputRequiredException> and let the SDK emit an <xref:ModelContextProtocol.Protocol.InputRequiredResult> on the wire.
176
176
177
177
> [!IMPORTANT]
178
-
> Calling `ElicitAsync`after negotiating `DRAFT-2026-v1`throws `InvalidOperationException`. Use `InputRequiredException` from your tool/prompt handler instead — it works under both the currentprotocol (resolved via legacy JSON-RPC + retry on stateful sessions) and the draft protocol (wire-format `InputRequiredResult`, no server-side handler state required).
178
+
> `ElicitAsync`throws `InvalidOperationException("Elicitation is not supported in stateless mode.")` whenever the server is running stateless — which includes every Streamable HTTP server under `DRAFT-2026-v1`once that revision is forced to stateless-only in a future PR. Stdio servers and current-protocol stateful Streamable HTTP servers continue to work via the legacy server-to-client `elicitation/create` request flow. For code that needs to run on stateless servers — including all `DRAFT-2026-v1` Streamable HTTP servers going forward — throw `InputRequiredException` from your handler instead. It works under both protocols and both session modes.
179
179
180
180
For example:
181
181
@@ -188,7 +188,7 @@ public static string ElicitWithMrtr(
188
188
// On retry, process the client's elicitation response
189
189
if (context.Params!.InputResponses?.TryGetValue("user_input", outvarresponse) istrue)
Copy file name to clipboardExpand all lines: docs/concepts/mrtr/mrtr.md
+12-12Lines changed: 12 additions & 12 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -48,7 +48,7 @@ var clientOptions = new McpClientOptions
48
48
};
49
49
```
50
50
51
-
Under `DRAFT-2026-v1`, MRTR is the **only** way to obtain client input from a server handler. The legacy server-to-client `elicitation/create`, `sampling/createMessage`, and `roots/list` request methods are removed; calling <xref:ModelContextProtocol.Server.McpServer.ElicitAsync*>, <xref:ModelContextProtocol.Server.McpServer.SampleAsync*>, or <xref:ModelContextProtocol.Server.McpServer.RequestRootsAsync*>on a server that negotiated `DRAFT-2026-v1` throws `InvalidOperationException`. Tools that need client input must throw <xref:ModelContextProtocol.Protocol.InputRequiredException> instead.
51
+
Under `DRAFT-2026-v1`, MRTR is the recommended way to obtain client input from a server handler. The spec removes the legacy server-to-client `elicitation/create`, `sampling/createMessage`, and `roots/list` request methods, so any code that needs to work on a `DRAFT-2026-v1` Streamable HTTP server (which will be stateless-only in a future revision) must use `InputRequiredException` rather than <xref:ModelContextProtocol.Server.McpServer.ElicitAsync*>, <xref:ModelContextProtocol.Server.McpServer.SampleAsync*>, or <xref:ModelContextProtocol.Server.McpServer.RequestRootsAsync*>. The legacy methods still work on stateful sessions — that's how stdio servers keep working under draft today — but they throw `InvalidOperationException("X is not supported in stateless mode.")` on any stateless session, current or draft.
52
52
53
53
Under the current protocol revision (`2025-06-18` and earlier), `InputRequiredException` is still supported in stateful sessions via a backward-compatibility resolver — see [Compatibility](#compatibility) below.
54
54
@@ -96,7 +96,7 @@ public static string AnswerTool(
96
96
// On retry, process the client's responses
97
97
if (requestStateisnotnull&&inputResponsesisnotnull)
@@ -137,9 +137,9 @@ When the client retries a tool call, the retry data is available on the request
137
137
138
138
Use <xref:ModelContextProtocol.Protocol.InputResponse.Deserialize*> with the `JsonTypeInfo<T>` matching the response type. The expected type follows from the matching <xref:ModelContextProtocol.Protocol.InputRequest.Method> in the original `inputRequests` map — there is no on-the-wire discriminator.
@@ -278,14 +278,14 @@ The SDK supports `InputRequiredException` across two protocol revisions and two
278
278
> [!NOTE]
279
279
> The backcompat resolver is intentionally limited to 10 retry rounds. Tools that need more rounds should require `DRAFT-2026-v1` (check `IsMrtrSupported`).
280
280
281
-
### Why `ElicitAsync` / `SampleAsync` / `RequestRootsAsync` throw under draft
The `DRAFT-2026-v1` revision removes the server-to-client `elicitation/create`, `sampling/createMessage`, and `roots/list`request methods entirely. Servers cannot use those request methods because clients no longer advertise the corresponding capabilities or implement handlers for them. The SDK fails fast with a clear `InvalidOperationException` so you can fix the call site before it manifests as a wire-level error.
283
+
`ElicitAsync` / `SampleAsync` / `RequestRootsAsync` issue a JSON-RPC request to the client and wait for the response on the same session. Stateless servers don't have a persistent session to wait on, so the SDK fails fast with `InvalidOperationException("X is not supported in stateless mode.")` (the check is `McpServer.ClientCapabilities is null`, which is the SDK's proxy for stateless).
284
284
285
-
Under the current protocol revision (`2025-06-18` and earlier), these methods continue to work normally and are the recommended way to do simple, one-shot client interactions. `InputRequiredException` is the way to write tools that work the same on both revisions.
285
+
Under the current protocol revision (`2025-06-18` and earlier), stdio and stateful Streamable HTTP keep `ClientCapabilities` populated, so the legacy methods work normally and remain the recommended way to do one-shot client interactions. Under `DRAFT-2026-v1`, the spec removes those request methods from Streamable HTTP entirely; the SDK still allows the legacy methods on draft stdio sessions because stdio is implicitly single-process / stateful and the client handler is wired up regardless of negotiated revision. `InputRequiredException` is the way to write tools that work on every supported configuration.
286
286
287
287
### Future direction
288
288
289
-
The `DRAFT-2026-v1` revision is moving toward a stateless-only model: `Mcp-Session-Id` is being removed, and Streamable HTTP servers will run statelessly by default under the draft revision. When that happens, the `Stateful` row of the compatibility matrix above collapses into the `Stateless` row, and `InputRequiredException` becomes uniformly native across both. The current-protocol resolver path will remain for backward compatibility with older clients and stateful servers.
289
+
The `DRAFT-2026-v1` revision is moving toward a stateless-only model: `Mcp-Session-Id` is being removed, and Streamable HTTP servers will run statelessly by default under the draft revision. When that lands, the `Stateful` row for `DRAFT-2026-v1` in the compatibility matrix above collapses into the `Stateless` row (Streamable HTTP under draft becomes stateless-only), and `InputRequiredException` becomes uniformly required for non-stdio servers. The current-protocol resolver path will remain for backward compatibility with older clients and stateful servers.
[MRTR](xref:mrtr) is the SEP-2322 mechanism for server-driven input requests, finalized in protocol revision `DRAFT-2026-v1`. Under the draft protocol, the server-to-client `roots/list` request method is removed; the only supported way to ask the client for its roots from a server handler is to throw <xref:ModelContextProtocol.Protocol.InputRequiredException> and let the SDK emit an <xref:ModelContextProtocol.Protocol.InputRequiredResult> on the wire.
109
+
[MRTR](xref:mrtr) is the SEP-2322 mechanism for server-driven input requests, finalized in protocol revision `DRAFT-2026-v1`. Under the draft protocol, the server-to-client `roots/list` request method is removed; the recommended way to ask the client for its roots from a server handler is to throw <xref:ModelContextProtocol.Protocol.InputRequiredException> and let the SDK emit an <xref:ModelContextProtocol.Protocol.InputRequiredResult> on the wire.
110
110
111
111
> [!IMPORTANT]
112
-
> Calling `RequestRootsAsync`after negotiating `DRAFT-2026-v1`throws `InvalidOperationException`. Use `InputRequiredException` from your tool/prompt handler instead — it works under both the currentprotocol (resolved via legacy JSON-RPC + retry on stateful sessions) and the draft protocol (wire-format `InputRequiredResult`, no server-side handler state required).
112
+
> `RequestRootsAsync`throws `InvalidOperationException("Roots are not supported in stateless mode.")` whenever the server is running stateless — which includes every Streamable HTTP server under `DRAFT-2026-v1`once that revision is forced to stateless-only in a future PR. Stdio servers and current-protocol stateful Streamable HTTP servers continue to work via the legacy server-to-client `roots/list` request flow. For code that needs to run on stateless servers — including all `DRAFT-2026-v1` Streamable HTTP servers going forward — throw `InputRequiredException` from your handler instead. It works under both protocols and both session modes.
113
113
114
114
For example:
115
115
@@ -122,7 +122,7 @@ public static string ListRootsWithMrtr(
122
122
// On retry, process the client's roots response
123
123
if (context.Params!.InputResponses?.TryGetValue("get_roots", outvarresponse) istrue)
Copy file name to clipboardExpand all lines: docs/concepts/sampling/sampling.md
+3-3Lines changed: 3 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -123,10 +123,10 @@ Sampling requires the client to advertise the `sampling` capability. This is han
123
123
124
124
### Multi Round-Trip Requests (MRTR)
125
125
126
-
[MRTR](xref:mrtr) is the SEP-2322 mechanism for server-driven input requests, finalized in protocol revision `DRAFT-2026-v1`. Under the draft protocol, the server-to-client `sampling/createMessage` request method is removed; the only supported way to ask the client to sample from a server handler is to throw <xref:ModelContextProtocol.Protocol.InputRequiredException> and let the SDK emit an <xref:ModelContextProtocol.Protocol.InputRequiredResult> on the wire.
126
+
[MRTR](xref:mrtr) is the SEP-2322 mechanism for server-driven input requests, finalized in protocol revision `DRAFT-2026-v1`. Under the draft protocol, the server-to-client `sampling/createMessage` request method is removed; the recommended way to ask the client to sample from a server handler is to throw <xref:ModelContextProtocol.Protocol.InputRequiredException> and let the SDK emit an <xref:ModelContextProtocol.Protocol.InputRequiredResult> on the wire.
127
127
128
128
> [!IMPORTANT]
129
-
> Calling `SampleAsync`or`AsSamplingChatClient`after negotiating `DRAFT-2026-v1`throws `InvalidOperationException`. Use `InputRequiredException` from your tool/prompt handler instead — it works under both the currentprotocol (resolved via legacy JSON-RPC + retry on stateful sessions) and the draft protocol (wire-format `InputRequiredResult`, no server-side handler state required).
129
+
> `SampleAsync`and`AsSamplingChatClient`throw `InvalidOperationException("Sampling is not supported in stateless mode.")` whenever the server is running stateless — which includes every Streamable HTTP server under `DRAFT-2026-v1`once that revision is forced to stateless-only in a future PR. Stdio servers and current-protocol stateful Streamable HTTP servers continue to work via the legacy server-to-client `sampling/createMessage` request flow. For code that needs to run on stateless servers — including all `DRAFT-2026-v1` Streamable HTTP servers going forward — throw `InputRequiredException` from your handler instead. It works under both protocols and both session modes.
130
130
131
131
For example:
132
132
@@ -139,7 +139,7 @@ public static string SampleWithMrtr(
139
139
// On retry, process the client's sampling response
140
140
if (context.Params!.InputResponses?.TryGetValue("llm_call", outvarresponse) istrue)
0 commit comments