Skip to content

Commit 5ac82bc

Browse files
halter73Copilot
andcommitted
Apply SEP-2322 spec renames and remove ExperimentalProtocolVersion
- IncompleteResult/IncompleteResultException -> InputRequiredResult/InputRequiredException - Wire format: result_type -> resultType, `incomplete` -> `input_required` - Drop ExperimentalProtocolVersion option; opt in via ProtocolVersion = `DRAFT-2026-v1` - Add DraftProtocolVersion constant and include in SupportedProtocolVersions - Restrict implicit MRTR continuation path to legacy stateful sessions; DRAFT-2026-v1 and stateless sessions always use the exception-based path Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent fb78b76 commit 5ac82bc

31 files changed

Lines changed: 270 additions & 305 deletions

docs/concepts/elicitation/elicitation.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ var result = await server.ElicitAsync(new ElicitRequestParams
198198

199199
#### Low-level API
200200

201-
For stateless servers or scenarios requiring manual control, throw <xref:ModelContextProtocol.Protocol.IncompleteResultException> with an elicitation input request. On retry, read the client's response from <xref:ModelContextProtocol.Protocol.RequestParams.InputResponses>:
201+
For stateless servers or scenarios requiring manual control, throw <xref:ModelContextProtocol.Protocol.InputRequiredException> with an elicitation input request. On retry, read the client's response from <xref:ModelContextProtocol.Protocol.RequestParams.InputResponses>:
202202

203203
```csharp
204204
[McpServerTool, Description("Tool that elicits via low-level MRTR")]
@@ -221,7 +221,7 @@ public static string ElicitWithMrtr(
221221
}
222222

223223
// First call — request user input
224-
throw new IncompleteResultException(
224+
throw new InputRequiredException(
225225
inputRequests: new Dictionary<string, InputRequest>
226226
{
227227
["user_input"] = InputRequest.ForElicitation(new ElicitRequestParams

docs/concepts/mrtr/mrtr.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ MRTR is useful when:
2626
## How MRTR works
2727

2828
1. The client calls a tool on the server via `tools/call`.
29-
2. The server tool determines it needs client input and returns an `IncompleteResult` containing `inputRequests` and/or `requestState`.
29+
2. The server tool determines it needs client input and returns an `InputRequiredResult` containing `inputRequests` and/or `requestState`.
3030
3. The client resolves each input request (e.g., prompts the user for elicitation, calls an LLM for sampling).
3131
4. The client retries the original `tools/call` with `inputResponses` (keyed to the input requests) and `requestState` echoed back.
32-
5. The server processes the responses and either returns a final result or another `IncompleteResult` for additional rounds.
32+
5. The server processes the responses and either returns a final result or another `InputRequiredResult` for additional rounds.
3333

3434
## Opting in
3535

@@ -130,7 +130,7 @@ public static string MyTool(
130130

131131
### Returning an incomplete result
132132

133-
Throw <xref:ModelContextProtocol.Protocol.IncompleteResultException> to return an incomplete result to the client. The exception carries an <xref:ModelContextProtocol.Protocol.IncompleteResult> containing `inputRequests` and/or `requestState`:
133+
Throw <xref:ModelContextProtocol.Protocol.InputRequiredException> to return an incomplete result to the client. The exception carries an <xref:ModelContextProtocol.Protocol.InputRequiredResult> containing `inputRequests` and/or `requestState`:
134134

135135
```csharp
136136
[McpServerTool, Description("Stateless tool managing its own MRTR flow")]
@@ -155,7 +155,7 @@ public static string StatelessTool(
155155
}
156156

157157
// First call — request user input
158-
throw new IncompleteResultException(
158+
throw new InputRequiredException(
159159
inputRequests: new Dictionary<string, InputRequest>
160160
{
161161
["user_answer"] = InputRequest.ForElicitation(new ElicitRequestParams
@@ -217,7 +217,7 @@ public static string DeferredTool(
217217

218218
// Defer work to a later retry
219219
var initialState = new MyState { Step = 1 };
220-
throw new IncompleteResultException(
220+
throw new InputRequiredException(
221221
requestState: Convert.ToBase64String(
222222
JsonSerializer.SerializeToUtf8Bytes(initialState)));
223223
}
@@ -227,7 +227,7 @@ The client automatically retries `requestState`-only incomplete results, echoing
227227

228228
### Multiple round trips
229229

230-
A tool can perform multiple rounds of interaction by throwing `IncompleteResultException` multiple times across retries:
230+
A tool can perform multiple rounds of interaction by throwing `InputRequiredException` multiple times across retries:
231231

232232
```csharp
233233
[McpServerTool, Description("Multi-step wizard")]
@@ -250,7 +250,7 @@ public static string WizardTool(
250250
var name = inputResponses["name"].ElicitationResult?.Content?.FirstOrDefault().Value;
251251

252252
// Second round — ask for age
253-
throw new IncompleteResultException(
253+
throw new InputRequiredException(
254254
inputRequests: new Dictionary<string, InputRequest>
255255
{
256256
["age"] = InputRequest.ForElicitation(new ElicitRequestParams
@@ -277,7 +277,7 @@ public static string WizardTool(
277277
}
278278

279279
// First round — ask for name
280-
throw new IncompleteResultException(
280+
throw new InputRequiredException(
281281
inputRequests: new Dictionary<string, InputRequest>
282282
{
283283
["name"] = InputRequest.ForElicitation(new ElicitRequestParams
@@ -332,7 +332,7 @@ When a server has MRTR enabled but the connected client does not:
332332

333333
### Backward compatibility for MRTR-native tools
334334

335-
Tools written with the low-level MRTR pattern (`IncompleteResultException`) work automatically with clients that don't support MRTR. When a tool throws `IncompleteResultException` and the client hasn't negotiated MRTR, the SDK resolves each `InputRequest` by sending the corresponding standard JSON-RPC call (elicitation, sampling, or roots) to the client, then retries the handler with the resolved responses.
335+
Tools written with the low-level MRTR pattern (`InputRequiredException`) work automatically with clients that don't support MRTR. When a tool throws `InputRequiredException` and the client hasn't negotiated MRTR, the SDK resolves each `InputRequest` by sending the corresponding standard JSON-RPC call (elicitation, sampling, or roots) to the client, then retries the handler with the resolved responses.
336336

337337
This means you can write a single tool implementation using the MRTR-native pattern and it will work with any client:
338338

@@ -350,7 +350,7 @@ public static string GetWeather(
350350
}
351351

352352
// First call: request the user's preferred units
353-
throw new IncompleteResultException(
353+
throw new InputRequiredException(
354354
inputRequests: new Dictionary<string, InputRequest>
355355
{
356356
["units"] = InputRequest.ForElicitation(new ElicitRequestParams
@@ -363,8 +363,8 @@ public static string GetWeather(
363363
}
364364
```
365365

366-
- **With an MRTR client**: The `IncompleteResult` is sent over the wire. The client resolves the elicitation and retries with `inputResponses`.
367-
- **Without MRTR**: The SDK sends a standard `elicitation/create` JSON-RPC request to the client, collects the response, and retries the handler internally. The client never sees the `IncompleteResult`.
366+
- **With an MRTR client**: The `InputRequiredResult` is sent over the wire. The client resolves the elicitation and retries with `inputResponses`.
367+
- **Without MRTR**: The SDK sends a standard `elicitation/create` JSON-RPC request to the client, collects the response, and retries the handler internally. The client never sees the `InputRequiredResult`.
368368

369369
> [!NOTE]
370370
> The backcompat retry loop resolves up to 10 rounds. Tools that need more rounds should use the high-level API (`ElicitAsync`) instead.

docs/concepts/roots/roots.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ foreach (var root in result.Roots)
123123

124124
#### Low-level API
125125

126-
For stateless servers or scenarios requiring manual control, throw <xref:ModelContextProtocol.Protocol.IncompleteResultException> with a roots input request. On retry, read the client's response from <xref:ModelContextProtocol.Protocol.RequestParams.InputResponses>:
126+
For stateless servers or scenarios requiring manual control, throw <xref:ModelContextProtocol.Protocol.InputRequiredException> with a roots input request. On retry, read the client's response from <xref:ModelContextProtocol.Protocol.RequestParams.InputResponses>:
127127

128128
```csharp
129129
[McpServerTool, Description("Tool that requests roots via low-level MRTR")]
@@ -144,7 +144,7 @@ public static string ListRootsWithMrtr(
144144
}
145145

146146
// First call — request the client's root list
147-
throw new IncompleteResultException(
147+
throw new InputRequiredException(
148148
inputRequests: new Dictionary<string, InputRequest>
149149
{
150150
["get_roots"] = InputRequest.ForRootsList(new ListRootsRequestParams())

docs/concepts/sampling/sampling.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ var result = await server.SampleAsync(
149149

150150
#### Low-level API
151151

152-
For stateless servers or scenarios requiring manual control, throw <xref:ModelContextProtocol.Protocol.IncompleteResultException> with a sampling input request. On retry, read the client's response from <xref:ModelContextProtocol.Protocol.RequestParams.InputResponses>:
152+
For stateless servers or scenarios requiring manual control, throw <xref:ModelContextProtocol.Protocol.InputRequiredException> with a sampling input request. On retry, read the client's response from <xref:ModelContextProtocol.Protocol.RequestParams.InputResponses>:
153153

154154
```csharp
155155
[McpServerTool, Description("Tool that samples via low-level MRTR")]
@@ -171,7 +171,7 @@ public static string SampleWithMrtr(
171171
}
172172

173173
// First call — request LLM completion from the client
174-
throw new IncompleteResultException(
174+
throw new InputRequiredException(
175175
inputRequests: new Dictionary<string, InputRequest>
176176
{
177177
["llm_call"] = InputRequest.ForSampling(new CreateMessageRequestParams

src/ModelContextProtocol.AspNetCore/StreamableHttpHandler.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -552,8 +552,7 @@ private bool ValidateProtocolVersionHeader(HttpContext context, out string? erro
552552
{
553553
var protocolVersionHeader = context.Request.Headers[McpProtocolVersionHeaderName].ToString();
554554
if (!string.IsNullOrEmpty(protocolVersionHeader) &&
555-
!s_supportedProtocolVersions.Contains(protocolVersionHeader) &&
556-
!(mcpServerOptionsSnapshot.Value.ExperimentalProtocolVersion is { } experimentalVersion && protocolVersionHeader == experimentalVersion))
555+
!s_supportedProtocolVersions.Contains(protocolVersionHeader))
557556
{
558557
errorMessage = $"Bad Request: The MCP-Protocol-Version header value '{protocolVersionHeader}' is not supported.";
559558
return false;

src/ModelContextProtocol.Core/Client/McpClientImpl.cs

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -650,7 +650,7 @@ public async Task ConnectAsync(CancellationToken cancellationToken = default)
650650
try
651651
{
652652
// Send initialize request
653-
string requestProtocol = _options.ProtocolVersion ?? _options.ExperimentalProtocolVersion ?? McpSessionHandler.LatestProtocolVersion;
653+
string requestProtocol = _options.ProtocolVersion ?? McpSessionHandler.LatestProtocolVersion;
654654
var initializeResponse = await SendRequestAsync(
655655
RequestMethods.Initialize,
656656
new InitializeRequestParams
@@ -678,8 +678,7 @@ public async Task ConnectAsync(CancellationToken cancellationToken = default)
678678
// Validate protocol version
679679
bool isResponseProtocolValid =
680680
_options.ProtocolVersion is { } optionsProtocol ? optionsProtocol == initializeResponse.ProtocolVersion :
681-
McpSessionHandler.SupportedProtocolVersions.Contains(initializeResponse.ProtocolVersion) ||
682-
(_options.ExperimentalProtocolVersion is not null && _options.ExperimentalProtocolVersion == initializeResponse.ProtocolVersion);
681+
McpSessionHandler.SupportedProtocolVersions.Contains(initializeResponse.ProtocolVersion);
683682
if (!isResponseProtocolValid)
684683
{
685684
LogServerProtocolVersionMismatch(_endpointName, requestProtocol, initializeResponse.ProtocolVersion);
@@ -832,17 +831,17 @@ request.Params is System.Text.Json.Nodes.JsonObject paramsObjForHeaders &&
832831
{
833832
JsonRpcResponse response = await _sessionHandler.SendRequestAsync(request, cancellationToken).ConfigureAwait(false);
834833

835-
// Check if the result is an IncompleteResult by looking at result_type.
834+
// Check if the result is an InputRequiredResult by looking at result_type.
836835
if (response.Result is JsonObject resultObj &&
837-
resultObj.TryGetPropertyValue("result_type", out var resultTypeNode) &&
838-
resultTypeNode?.GetValue<string>() is "incomplete")
836+
resultObj.TryGetPropertyValue("resultType", out var resultTypeNode) &&
837+
resultTypeNode?.GetValue<string>() is "input_required")
839838
{
840-
WarnIfIncompleteResultOnNonMrtrSession(request.Method);
839+
WarnIfInputRequiredResultOnNonMrtrSession(request.Method);
841840

842-
var incompleteResult = JsonSerializer.Deserialize(response.Result, McpJsonUtilities.JsonContext.Default.IncompleteResult)
843-
?? throw new JsonException("Failed to deserialize IncompleteResult.");
841+
var InputRequiredResult = JsonSerializer.Deserialize(response.Result, McpJsonUtilities.JsonContext.Default.InputRequiredResult)
842+
?? throw new JsonException("Failed to deserialize InputRequiredResult.");
844843

845-
if (incompleteResult.InputRequests is { Count: > 0 } inputRequests)
844+
if (InputRequiredResult.InputRequests is { Count: > 0 } inputRequests)
846845
{
847846
IDictionary<string, InputResponse> inputResponses =
848847
await ResolveInputRequestsAsync(inputRequests, cancellationToken).ConfigureAwait(false);
@@ -853,25 +852,25 @@ request.Params is System.Text.Json.Nodes.JsonObject paramsObjForHeaders &&
853852
paramsObj["inputResponses"] = JsonSerializer.SerializeToNode(
854853
inputResponses, McpJsonUtilities.JsonContext.Default.IDictionaryStringInputResponse);
855854

856-
if (incompleteResult.RequestState is { } requestState)
855+
if (InputRequiredResult.RequestState is { } requestState)
857856
{
858857
paramsObj["requestState"] = requestState;
859858
}
860859

861860
request = new JsonRpcRequest { Method = request.Method, Params = paramsObj, Context = request.Context };
862861
}
863-
else if (incompleteResult.RequestState is not null)
862+
else if (InputRequiredResult.RequestState is not null)
864863
{
865864
// No input requests but has requestState (e.g., load shedding) — just retry with state.
866865
var paramsObj = request.Params?.DeepClone() as JsonObject ?? new JsonObject();
867-
paramsObj["requestState"] = incompleteResult.RequestState;
866+
paramsObj["requestState"] = InputRequiredResult.RequestState;
868867
paramsObj.Remove("inputResponses");
869868

870869
request = new JsonRpcRequest { Method = request.Method, Params = paramsObj, Context = request.Context };
871870
}
872871
else
873872
{
874-
throw new McpException("Server returned an IncompleteResult without inputRequests or requestState.");
873+
throw new McpException("Server returned an InputRequiredResult without inputRequests or requestState.");
875874
}
876875

877876
continue; // retry with the updated request
@@ -880,7 +879,7 @@ request.Params is System.Text.Json.Nodes.JsonObject paramsObjForHeaders &&
880879
return response;
881880
}
882881

883-
throw new McpException($"Server returned IncompleteResult more than {maxRetries} times.");
882+
throw new McpException($"Server returned InputRequiredResult more than {maxRetries} times.");
884883
}
885884

886885
/// <inheritdoc/>
@@ -919,28 +918,26 @@ public override async ValueTask DisposeAsync()
919918
/// <summary>Logs a warning if the session negotiated MRTR but the server sent a legacy JSON-RPC request.</summary>
920919
private void WarnIfLegacyRequestOnMrtrSession(string method)
921920
{
922-
if (_options.ExperimentalProtocolVersion is not null &&
923-
_negotiatedProtocolVersion == _options.ExperimentalProtocolVersion)
921+
if (_negotiatedProtocolVersion == McpSessionHandler.DraftProtocolVersion)
924922
{
925923
LogLegacyRequestOnMrtrSession(_endpointName, method);
926924
}
927925
}
928926

929-
/// <summary>Logs a warning if the session did not negotiate MRTR but the server sent an IncompleteResult.</summary>
930-
private void WarnIfIncompleteResultOnNonMrtrSession(string method)
927+
/// <summary>Logs a warning if the session did not negotiate MRTR but the server sent an InputRequiredResult.</summary>
928+
private void WarnIfInputRequiredResultOnNonMrtrSession(string method)
931929
{
932-
if (_options.ExperimentalProtocolVersion is null ||
933-
_negotiatedProtocolVersion != _options.ExperimentalProtocolVersion)
930+
if (_negotiatedProtocolVersion != McpSessionHandler.DraftProtocolVersion)
934931
{
935-
LogIncompleteResultOnNonMrtrSession(_endpointName, method, _negotiatedProtocolVersion);
932+
LogInputRequiredResultOnNonMrtrSession(_endpointName, method, _negotiatedProtocolVersion);
936933
}
937934
}
938935

939-
[LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} received legacy '{Method}' JSON-RPC request on session that negotiated MRTR. The server should use IncompleteResult instead of sending direct requests.")]
936+
[LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} received legacy '{Method}' JSON-RPC request on session that negotiated MRTR. The server should use InputRequiredResult instead of sending direct requests.")]
940937
private partial void LogLegacyRequestOnMrtrSession(string endpointName, string method);
941938

942-
[LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} received IncompleteResult for '{Method}' on session that did not negotiate MRTR (protocol version '{ProtocolVersion}'). The server may not be spec-compliant.")]
943-
private partial void LogIncompleteResultOnNonMrtrSession(string endpointName, string method, string? protocolVersion);
939+
[LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} received InputRequiredResult for '{Method}' on session that did not negotiate MRTR (protocol version '{ProtocolVersion}'). The server may not be spec-compliant.")]
940+
private partial void LogInputRequiredResultOnNonMrtrSession(string endpointName, string method, string? protocolVersion);
944941

945942
[LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} client received server '{ServerInfo}' capabilities: '{Capabilities}'.")]
946943
private partial void LogServerCapabilitiesReceived(string endpointName, string capabilities, string serverInfo);

src/ModelContextProtocol.Core/Client/McpClientOptions.cs

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -111,24 +111,4 @@ public McpClientHandlers Handlers
111111
/// </remarks>
112112
[Experimental(Experimentals.Tasks_DiagnosticId, UrlFormat = Experimentals.Tasks_Url)]
113113
public bool SendTaskStatusNotifications { get; set; } = true;
114-
115-
/// <summary>
116-
/// Gets or sets an experimental protocol version that enables draft protocol features such as
117-
/// Multi Round-Trip Requests (MRTR).
118-
/// </summary>
119-
/// <remarks>
120-
/// <para>
121-
/// When set, this version is used as the requested protocol version during initialization instead of
122-
/// the latest stable version. The server must also have a matching <c>ExperimentalProtocolVersion</c>
123-
/// configured for the experimental features to activate. If the server does not recognize the
124-
/// experimental version, it will negotiate to the latest stable version and the client will work
125-
/// normally without experimental features.
126-
/// </para>
127-
/// <para>
128-
/// This property is intended for proof-of-concept and testing of draft MCP specification features
129-
/// that have not yet been ratified.
130-
/// </para>
131-
/// </remarks>
132-
[Experimental(Experimentals.Mrtr_DiagnosticId, UrlFormat = Experimentals.Mrtr_Url)]
133-
public string? ExperimentalProtocolVersion { get; set; }
134114
}

src/ModelContextProtocol.Core/McpJsonUtilities.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Microsoft.Extensions.AI;
1+
using Microsoft.Extensions.AI;
22
using ModelContextProtocol.Authentication;
33
using ModelContextProtocol.Protocol;
44
using System.Diagnostics.CodeAnalysis;
@@ -145,7 +145,7 @@ internal static bool IsValidMcpToolSchema(JsonElement element)
145145
[JsonSerializable(typeof(UnsubscribeRequestParams))]
146146

147147
// MCP MRTR (Multi Round-Trip Requests)
148-
[JsonSerializable(typeof(IncompleteResult))]
148+
[JsonSerializable(typeof(InputRequiredResult))]
149149
[JsonSerializable(typeof(InputRequest))]
150150
[JsonSerializable(typeof(InputResponse))]
151151
[JsonSerializable(typeof(IDictionary<string, InputRequest>))]

0 commit comments

Comments
 (0)