Skip to content

Commit f1dd4c4

Browse files
halter73Copilot
andcommitted
Remove stateless check from ClientSupportsMrtr
ClientSupportsMrtr now purely reflects whether the client negotiated the MRTR protocol version, independent of server transport mode. The stateless guard is moved to the call site that gates the high-level await path (which requires storing continuations). This makes IsMrtrSupported return true in stateless mode when the protocol matches, which is correct for the low-level IncompleteResultException API. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 80417c0 commit f1dd4c4

File tree

2 files changed

+45
-10
lines changed

2 files changed

+45
-10
lines changed

src/ModelContextProtocol.Core/Server/McpServerImpl.cs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1143,11 +1143,9 @@ internal static LoggingLevel ToLoggingLevel(LogLevel level) =>
11431143
private partial void ReadResourceCompleted(string resourceUri);
11441144

11451145
/// <summary>
1146-
/// Checks whether the negotiated protocol version enables MRTR and the server
1147-
/// operates in a mode where MRTR continuations can be stored (i.e., not stateless).
1146+
/// Checks whether the negotiated protocol version enables MRTR.
11481147
/// </summary>
11491148
internal bool ClientSupportsMrtr() =>
1150-
_sessionTransport is not StreamableHttpServerTransport { Stateless: true } &&
11511149
_negotiatedProtocolVersion is not null &&
11521150
_negotiatedProtocolVersion == ServerOptions.ExperimentalProtocolVersion;
11531151

@@ -1217,8 +1215,9 @@ private void WrapHandlerWithMrtr(string method)
12171215
// high-level handlers that call ElicitAsync/SampleAsync.
12181216
}
12191217

1220-
// Not a retry, or a retry without a continuation - check if the client supports MRTR.
1221-
if (!ClientSupportsMrtr())
1218+
// Not a retry, or a retry without a continuation - check if the client supports MRTR
1219+
// and the server is stateful (the high-level await path requires storing continuations).
1220+
if (!ClientSupportsMrtr() || _sessionTransport is StreamableHttpServerTransport { Stateless: true })
12221221
{
12231222
return await InvokeWithIncompleteResultHandlingAsync(originalHandler, request, cancellationToken).ConfigureAwait(false);
12241223
}
@@ -1262,10 +1261,10 @@ private void WrapHandlerWithMrtr(string method)
12621261
}
12631262
catch (IncompleteResultException ex)
12641263
{
1265-
// In stateless mode, the server has no persistent session or negotiated protocol
1266-
// version, so it cannot determine client MRTR support. The tool handler has
1267-
// explicitly chosen to return an IncompleteResult, so we trust that decision.
1268-
if (_sessionTransport is not StreamableHttpServerTransport { Stateless: true } && !ClientSupportsMrtr())
1264+
// Allow the IncompleteResult if the client supports MRTR or the server is stateless
1265+
// (in stateless mode, the tool handler has explicitly chosen to return an IncompleteResult
1266+
// via the low-level API, so we trust that decision regardless of negotiated version).
1267+
if (!ClientSupportsMrtr() && _sessionTransport is not StreamableHttpServerTransport { Stateless: true })
12691268
{
12701269
throw new McpException(
12711270
"A tool handler returned an incomplete result, but the client does not support Multi Round-Trip Requests (MRTR). " +

tests/ModelContextProtocol.AspNetCore.Tests/StatelessMrtrTests.cs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ public class StatelessMrtrTests(ITestOutputHelper outputHelper) : KestrelInMemor
2525
TransportMode = HttpTransportMode.StreamableHttp,
2626
};
2727

28-
private async Task StartAsync()
28+
private Task StartAsync() => StartAsync(configureOptions: null);
29+
30+
private async Task StartAsync(Action<McpServerOptions>? configureOptions, params McpServerTool[] additionalTools)
2931
{
3032
Builder.Services.AddMcpServer(options =>
3133
{
@@ -34,6 +36,7 @@ private async Task StartAsync()
3436
Name = nameof(StatelessMrtrTests),
3537
Version = "1",
3638
};
39+
configureOptions?.Invoke(options);
3740
})
3841
.WithHttpTransport(httpOptions =>
3942
{
@@ -228,6 +231,7 @@ static string (RequestContext<CallToolRequestParams> context) =>
228231
Name = "stateless-multi-roundtrip",
229232
Description = "Stateless tool with multiple MRTR round-trips"
230233
}),
234+
..additionalTools,
231235
]);
232236

233237
_app = Builder.Build();
@@ -434,4 +438,36 @@ public async Task Stateless_MultiRoundTrip_CompletesAcrossMultipleRetries()
434438
Assert.Equal(1, samplingCalls);
435439
Assert.Equal(1, elicitCalls);
436440
}
441+
442+
[Fact]
443+
public async Task Stateless_IsMrtrSupported_ReturnsFalse_BecauseProtocolIsNotNegotiatedPerRequest()
444+
{
445+
// In stateless mode, each HTTP request creates a new McpServerImpl. The initialize
446+
// request negotiates the protocol version on one instance, but subsequent tool calls
447+
// run on different instances that never saw the initialize. So IsMrtrSupported is
448+
// always false. The low-level IncompleteResultException API still works because it
449+
// bypasses the IsMrtrSupported check — the developer takes responsibility for MRTR
450+
// compatibility when using the exception-based API.
451+
var isMrtrSupportedTool = McpServerTool.Create(
452+
static string (McpServer server) => server.IsMrtrSupported.ToString(),
453+
new McpServerToolCreateOptions
454+
{
455+
Name = "check-mrtr",
456+
Description = "Returns IsMrtrSupported"
457+
});
458+
459+
await StartAsync(
460+
options => options.ExperimentalProtocolVersion = "2026-06-XX",
461+
isMrtrSupportedTool);
462+
463+
var clientOptions = new McpClientOptions { ExperimentalProtocolVersion = "2026-06-XX" };
464+
465+
await using var client = await ConnectAsync(clientOptions);
466+
467+
var result = await client.CallToolAsync("check-mrtr",
468+
cancellationToken: TestContext.Current.CancellationToken);
469+
470+
var text = Assert.IsType<TextContentBlock>(Assert.Single(result.Content)).Text;
471+
Assert.Equal("False", text);
472+
}
437473
}

0 commit comments

Comments
 (0)