diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs index a03249496..bee17be7c 100644 --- a/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs +++ b/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs @@ -245,34 +245,49 @@ public async Task LongRunningToolCall_DoesNotTimeout_WhenNoEventStreamStore() app.MapMcp(); await app.StartAsync(TestContext.Current.CancellationToken); - // Create a custom HttpClient with a very short timeout (1 second) - // The tool will take 2 seconds to complete - using var shortTimeoutClient = new HttpClient(SocketsHttpHandler) + // Retry a couple of times to reduce occasional flakiness on low-resource machines. + // If the server regresses to flushing only after tool completion, each attempt should still fail + // because HttpClient timeout (1 second) is below the tool duration (2 seconds). + for (var attempt = 0; attempt < 3; attempt++) { - BaseAddress = new Uri("http://localhost:5000/"), - Timeout = TimeSpan.FromSeconds(1) - }; - - var path = UseStreamableHttp ? "/" : "/sse"; - var transportMode = UseStreamableHttp ? HttpTransportMode.StreamableHttp : HttpTransportMode.Sse; - - await using var transport = new HttpClientTransport(new() - { - Endpoint = new($"http://localhost:5000{path}"), - TransportMode = transportMode, - }, shortTimeoutClient, LoggerFactory); + try + { + // Create a custom HttpClient with a very short timeout (1 second) + // The tool will take 2 seconds to complete + using var shortTimeoutClient = new HttpClient(SocketsHttpHandler, disposeHandler: false) + { + BaseAddress = new Uri("http://localhost:5000/"), + Timeout = TimeSpan.FromSeconds(1) + }; - await using var mcpClient = await McpClient.CreateAsync(transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); + var path = UseStreamableHttp ? "/" : "/sse"; + var transportMode = UseStreamableHttp ? HttpTransportMode.StreamableHttp : HttpTransportMode.Sse; - // 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 - var response = await mcpClient.CallToolAsync( - "long_running_operation", - new Dictionary() { ["durationMs"] = 2000 }, - cancellationToken: TestContext.Current.CancellationToken); + await using var transport = new HttpClientTransport(new() + { + Endpoint = new($"http://localhost:5000{path}"), + TransportMode = transportMode, + }, shortTimeoutClient, LoggerFactory); + + await using var mcpClient = await McpClient.CreateAsync(transport, loggerFactory: LoggerFactory, cancellationToken: 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 + var response = await mcpClient.CallToolAsync( + "long_running_operation", + new Dictionary() { ["durationMs"] = 2000 }, + cancellationToken: TestContext.Current.CancellationToken); + + var content = Assert.Single(response.Content.OfType()); + Assert.Equal("Operation completed after 2000ms", content.Text); + return; + } + catch (OperationCanceledException) when (attempt < 2) + { + // Retry intermittent timeout-related failures on slow CI machines. + } + } - var content = Assert.Single(response.Content.OfType()); - Assert.Equal("Operation completed after 2000ms", content.Text); } private ClaimsPrincipal CreateUser(string name)