Skip to content

Commit f3a00be

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 6fafdf2 commit f3a00be

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
@@ -633,7 +633,7 @@ public async Task ConnectAsync(CancellationToken cancellationToken = default)
633633
try
634634
{
635635
// Send initialize request
636-
string requestProtocol = _options.ProtocolVersion ?? _options.ExperimentalProtocolVersion ?? McpSessionHandler.LatestProtocolVersion;
636+
string requestProtocol = _options.ProtocolVersion ?? McpSessionHandler.LatestProtocolVersion;
637637
var initializeResponse = await SendRequestAsync(
638638
RequestMethods.Initialize,
639639
new InitializeRequestParams
@@ -661,8 +661,7 @@ public async Task ConnectAsync(CancellationToken cancellationToken = default)
661661
// Validate protocol version
662662
bool isResponseProtocolValid =
663663
_options.ProtocolVersion is { } optionsProtocol ? optionsProtocol == initializeResponse.ProtocolVersion :
664-
McpSessionHandler.SupportedProtocolVersions.Contains(initializeResponse.ProtocolVersion) ||
665-
(_options.ExperimentalProtocolVersion is not null && _options.ExperimentalProtocolVersion == initializeResponse.ProtocolVersion);
664+
McpSessionHandler.SupportedProtocolVersions.Contains(initializeResponse.ProtocolVersion);
666665
if (!isResponseProtocolValid)
667666
{
668667
LogServerProtocolVersionMismatch(_endpointName, requestProtocol, initializeResponse.ProtocolVersion);
@@ -745,17 +744,17 @@ request.Params is System.Text.Json.Nodes.JsonObject paramsObjForHeaders &&
745744
{
746745
JsonRpcResponse response = await _sessionHandler.SendRequestAsync(request, cancellationToken).ConfigureAwait(false);
747746

748-
// Check if the result is an IncompleteResult by looking at result_type.
747+
// Check if the result is an InputRequiredResult by looking at result_type.
749748
if (response.Result is JsonObject resultObj &&
750-
resultObj.TryGetPropertyValue("result_type", out var resultTypeNode) &&
751-
resultTypeNode?.GetValue<string>() is "incomplete")
749+
resultObj.TryGetPropertyValue("resultType", out var resultTypeNode) &&
750+
resultTypeNode?.GetValue<string>() is "input_required")
752751
{
753-
WarnIfIncompleteResultOnNonMrtrSession(request.Method);
752+
WarnIfInputRequiredResultOnNonMrtrSession(request.Method);
754753

755-
var incompleteResult = JsonSerializer.Deserialize(response.Result, McpJsonUtilities.JsonContext.Default.IncompleteResult)
756-
?? throw new JsonException("Failed to deserialize IncompleteResult.");
754+
var InputRequiredResult = JsonSerializer.Deserialize(response.Result, McpJsonUtilities.JsonContext.Default.InputRequiredResult)
755+
?? throw new JsonException("Failed to deserialize InputRequiredResult.");
757756

758-
if (incompleteResult.InputRequests is { Count: > 0 } inputRequests)
757+
if (InputRequiredResult.InputRequests is { Count: > 0 } inputRequests)
759758
{
760759
IDictionary<string, InputResponse> inputResponses =
761760
await ResolveInputRequestsAsync(inputRequests, cancellationToken).ConfigureAwait(false);
@@ -766,25 +765,25 @@ request.Params is System.Text.Json.Nodes.JsonObject paramsObjForHeaders &&
766765
paramsObj["inputResponses"] = JsonSerializer.SerializeToNode(
767766
inputResponses, McpJsonUtilities.JsonContext.Default.IDictionaryStringInputResponse);
768767

769-
if (incompleteResult.RequestState is { } requestState)
768+
if (InputRequiredResult.RequestState is { } requestState)
770769
{
771770
paramsObj["requestState"] = requestState;
772771
}
773772

774773
request = new JsonRpcRequest { Method = request.Method, Params = paramsObj, Context = request.Context };
775774
}
776-
else if (incompleteResult.RequestState is not null)
775+
else if (InputRequiredResult.RequestState is not null)
777776
{
778777
// No input requests but has requestState (e.g., load shedding) — just retry with state.
779778
var paramsObj = request.Params?.DeepClone() as JsonObject ?? new JsonObject();
780-
paramsObj["requestState"] = incompleteResult.RequestState;
779+
paramsObj["requestState"] = InputRequiredResult.RequestState;
781780
paramsObj.Remove("inputResponses");
782781

783782
request = new JsonRpcRequest { Method = request.Method, Params = paramsObj, Context = request.Context };
784783
}
785784
else
786785
{
787-
throw new McpException("Server returned an IncompleteResult without inputRequests or requestState.");
786+
throw new McpException("Server returned an InputRequiredResult without inputRequests or requestState.");
788787
}
789788

790789
continue; // retry with the updated request
@@ -793,7 +792,7 @@ request.Params is System.Text.Json.Nodes.JsonObject paramsObjForHeaders &&
793792
return response;
794793
}
795794

796-
throw new McpException($"Server returned IncompleteResult more than {maxRetries} times.");
795+
throw new McpException($"Server returned InputRequiredResult more than {maxRetries} times.");
797796
}
798797

799798
/// <inheritdoc/>
@@ -832,28 +831,26 @@ public override async ValueTask DisposeAsync()
832831
/// <summary>Logs a warning if the session negotiated MRTR but the server sent a legacy JSON-RPC request.</summary>
833832
private void WarnIfLegacyRequestOnMrtrSession(string method)
834833
{
835-
if (_options.ExperimentalProtocolVersion is not null &&
836-
_negotiatedProtocolVersion == _options.ExperimentalProtocolVersion)
834+
if (_negotiatedProtocolVersion == McpSessionHandler.DraftProtocolVersion)
837835
{
838836
LogLegacyRequestOnMrtrSession(_endpointName, method);
839837
}
840838
}
841839

842-
/// <summary>Logs a warning if the session did not negotiate MRTR but the server sent an IncompleteResult.</summary>
843-
private void WarnIfIncompleteResultOnNonMrtrSession(string method)
840+
/// <summary>Logs a warning if the session did not negotiate MRTR but the server sent an InputRequiredResult.</summary>
841+
private void WarnIfInputRequiredResultOnNonMrtrSession(string method)
844842
{
845-
if (_options.ExperimentalProtocolVersion is null ||
846-
_negotiatedProtocolVersion != _options.ExperimentalProtocolVersion)
843+
if (_negotiatedProtocolVersion != McpSessionHandler.DraftProtocolVersion)
847844
{
848-
LogIncompleteResultOnNonMrtrSession(_endpointName, method, _negotiatedProtocolVersion);
845+
LogInputRequiredResultOnNonMrtrSession(_endpointName, method, _negotiatedProtocolVersion);
849846
}
850847
}
851848

852-
[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.")]
849+
[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.")]
853850
private partial void LogLegacyRequestOnMrtrSession(string endpointName, string method);
854851

855-
[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.")]
856-
private partial void LogIncompleteResultOnNonMrtrSession(string endpointName, string method, string? protocolVersion);
852+
[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.")]
853+
private partial void LogInputRequiredResultOnNonMrtrSession(string endpointName, string method, string? protocolVersion);
857854

858855
[LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} client received server '{ServerInfo}' capabilities: '{Capabilities}'.")]
859856
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)