fix(aspnetcore): make SEP-2575 status mapping deterministic (#1837)
This commit is contained in:
parent
df11d7da3b
commit
cb3e6e87fe
2 changed files with 60 additions and 85 deletions
|
|
@ -239,10 +239,9 @@ public abstract partial class MapMcpTests(ITestOutputHelper testOutputHelper) :
|
|||
[Fact]
|
||||
public async Task LongRunningToolCall_DoesNotTimeout_WhenNoEventStreamStore()
|
||||
{
|
||||
// Regression test for: Tool calls that last over HttpClient timeout without producing
|
||||
// intermediate notifications will timeout because HttpClient doesn't see the 200 response
|
||||
// until the first message is written. When primingItem is null (no ISseEventStreamStore),
|
||||
// we should flush the response stream so HttpClient sees the 200 immediately.
|
||||
// Legacy Streamable HTTP tool calls that run past the HttpClient timeout without producing
|
||||
// intermediate notifications need an eager response flush. Per-request-metadata protocol
|
||||
// revisions instead wait for the first JSON-RPC message so SEP-2575 can map its status.
|
||||
|
||||
Builder.Services.AddMcpServer().WithHttpTransport(ConfigureStateless).WithTools<LongRunningTools>();
|
||||
|
||||
|
|
@ -274,7 +273,11 @@ public abstract partial class MapMcpTests(ITestOutputHelper testOutputHelper) :
|
|||
TransportMode = transportMode,
|
||||
}, shortTimeoutClient, LoggerFactory);
|
||||
|
||||
await using var mcpClient = await McpClient.CreateAsync(transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken);
|
||||
await using var mcpClient = await McpClient.CreateAsync(
|
||||
transport,
|
||||
new McpClientOptions { ProtocolVersion = "2025-11-25" },
|
||||
LoggerFactory,
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
// Call a tool that takes 2 seconds - this should succeed despite the 1 second HttpClient timeout
|
||||
// because the response stream is flushed immediately after receiving the request
|
||||
|
|
|
|||
|
|
@ -207,6 +207,27 @@ public class RawHttpConformanceTests(ITestOutputHelper outputHelper) : KestrelIn
|
|||
Assert.Equal((int)McpErrorCode.MissingRequiredClientCapability, json["error"]!["code"]!.GetValue<int>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task July2026Post_SlowHandler_MissingRequiredCapability_Returns400()
|
||||
{
|
||||
await StartAsync();
|
||||
|
||||
var body =
|
||||
@"{""jsonrpc"":""2.0"",""id"":20,""method"":""tools/call"",""params"":{""name"":""slow_requires_sampling"",""arguments"":{}," +
|
||||
July2026ProtocolMetaFragment() + "}}";
|
||||
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, "") { Content = JsonContent(body) };
|
||||
request.Headers.Add(ProtocolVersionHeader, McpProtocolVersions.July2026ProtocolVersion);
|
||||
request.Headers.Add("Mcp-Method", "tools/call");
|
||||
request.Headers.Add("Mcp-Name", "slow_requires_sampling");
|
||||
using var response = await HttpClient.SendAsync(request, TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
var json = await ReadJsonResponseAsync(response, TestContext.Current.CancellationToken);
|
||||
Assert.Equal(20, json["id"]!.GetValue<long>());
|
||||
Assert.Equal((int)McpErrorCode.MissingRequiredClientCapability, json["error"]!["code"]!.GetValue<int>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ServerDiscover_WithConfiguredPerRequestMetadataProtocol_ReturnsOnlyConfiguredVersion()
|
||||
{
|
||||
|
|
@ -500,10 +521,21 @@ public class RawHttpConformanceTests(ITestOutputHelper outputHelper) : KestrelIn
|
|||
[McpServerToolType]
|
||||
private sealed class CapabilityTools
|
||||
{
|
||||
private static readonly TimeSpan SlowHandlerDelay = TimeSpan.FromSeconds(1);
|
||||
|
||||
[McpServerTool(Name = "requires_sampling")]
|
||||
public static string RequiresSampling() =>
|
||||
throw new MissingRequiredClientCapabilityException(
|
||||
new ClientCapabilities { Sampling = new() },
|
||||
"sampling capability required but not declared by client");
|
||||
|
||||
[McpServerTool(Name = "slow_requires_sampling")]
|
||||
public static async Task<string> SlowRequiresSampling(CancellationToken cancellationToken)
|
||||
{
|
||||
await Task.Delay(SlowHandlerDelay, cancellationToken);
|
||||
throw new MissingRequiredClientCapabilityException(
|
||||
new ClientCapabilities { Sampling = new() },
|
||||
"sampling capability required but not declared by client");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue