diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs index 5517789a3..678b27022 100644 --- a/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs +++ b/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs @@ -5,6 +5,7 @@ using ModelContextProtocol.Client; using ModelContextProtocol.Protocol; using ModelContextProtocol.Server; +using ModelContextProtocol.Tests.Utils; using System.ComponentModel; using System.Diagnostics; using System.Net; @@ -394,6 +395,7 @@ await client.CallToolAsync("sampling-tool", public async Task OutgoingFilter_MultipleFilters_ExecuteInOrder() { var executionOrder = new List(); + var allFiltersComplete = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); Builder.Services.AddMcpServer() .WithHttpTransport(ConfigureStateless) @@ -411,6 +413,7 @@ public async Task OutgoingFilter_MultipleFilters_ExecuteInOrder() if (context.JsonRpcMessage is JsonRpcResponse r2 && r2.Result is JsonObject obj2 && obj2.ContainsKey("tools")) { executionOrder.Add("filter1-after"); + allFiltersComplete.TrySetResult(); } }); @@ -439,6 +442,14 @@ public async Task OutgoingFilter_MultipleFilters_ExecuteInOrder() await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken); + // The outermost filter's "after" callback runs after the response has been + // sent to the client, so ListToolsAsync may return before it executes. + // Wait for it to complete before asserting, but use a timeout to avoid hanging + // the test indefinitely if the filter pipeline regresses. + using var allFiltersCts = CancellationTokenSource.CreateLinkedTokenSource(TestContext.Current.CancellationToken); + allFiltersCts.CancelAfter(TestConstants.DefaultTimeout); + await allFiltersComplete.Task.WaitAsync(allFiltersCts.Token); + Assert.Equal(["filter1-before", "filter2-before", "filter2-after", "filter1-after"], executionOrder); }