From b6efae14b65e5a9e22e7a102787c389c473986e2 Mon Sep 17 00:00:00 2001 From: "Eric St. John" Date: Tue, 31 Mar 2026 19:47:49 -0700 Subject: [PATCH 1/3] Fix flaky OutgoingFilter_MultipleFilters_ExecuteInOrder test The outermost filter's 'after' callback runs after the response has been sent to the client via next(). In stateless/SSE mode, ListToolsAsync can return before the server-side filter pipeline finishes executing the 'after' callbacks. Assert.Equal then enumerates the executionOrder list concurrently with the filter adding to it, causing 'Collection was modified' InvalidOperationException. Fix by waiting for the outermost filter's 'after' callback to signal completion before asserting on the execution order. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs index 5517789a3..cf56ced86 100644 --- a/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs +++ b/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs @@ -394,6 +394,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 +412,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 +441,11 @@ 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. + await allFiltersComplete.Task.WaitAsync(TestContext.Current.CancellationToken); + Assert.Equal(["filter1-before", "filter2-before", "filter2-after", "filter1-after"], executionOrder); } From ce471c25c61e621b20a38e25723a2f2b36edea15 Mon Sep 17 00:00:00 2001 From: Eric StJohn Date: Wed, 1 Apr 2026 12:56:21 -0700 Subject: [PATCH 2/3] Apply suggestion from @ericstj --- tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs index cf56ced86..89d24574c 100644 --- a/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs +++ b/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs @@ -443,8 +443,11 @@ public async Task OutgoingFilter_MultipleFilters_ExecuteInOrder() // 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. - await allFiltersComplete.Task.WaitAsync(TestContext.Current.CancellationToken); + // 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); } From aebf35d1ed46b125a27cfec61ac043ea5cd2bdc1 Mon Sep 17 00:00:00 2001 From: Eric StJohn Date: Wed, 1 Apr 2026 13:12:16 -0700 Subject: [PATCH 3/3] Add using --- tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs index 89d24574c..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;