Skip to content

Commit 1d3ebb8

Browse files
Sync AddIncomingMessageFilter_Intercepts_Request_Messages on initialized notification
The test asserts strict counts for both requests and notifications observed by the incoming message filter, but ListToolsAsync only synchronizes on the request/response exchanges (initialize, tools/list). The notifications/initialized message in between is fire-and-forget on the client, and the server dispatches each incoming message via McpSessionHandler.ProcessMessageAsync as a fire-and-forget Task with a forced thread-pool yield. So the assertion can run before the notification has even reached the filter pipeline, surfacing as ''Expected: 1, Actual: 0'' for the notification count. Observed on Ubuntu/Release/net9.0 CI; reproduces under load on any target. Apply the same TaskCompletionSource pattern already used by the immediately following AddIncomingMessageFilter_Multiple_Filters_Execute_In_Order test: set a TCS inside the filter when the InitializedNotification is observed, and await it before snapshotting the counts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 8ba52bf commit 1d3ebb8

1 file changed

Lines changed: 15 additions & 0 deletions

File tree

tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsMessageFilterTests.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,23 @@ public async Task AddIncomingMessageFilter_Intercepts_Request_Messages()
7272
{
7373
List<string> messageTypes = [];
7474

75+
// The client sends notifications/initialized fire-and-forget, so unlike the initialize and
76+
// tools/list request/response exchanges it has no synchronization point the test can await.
77+
// Signal once the filter finishes processing it so the strict counts below observe a
78+
// complete, stable log instead of racing the still-in-flight notification.
79+
var initializedNotificationProcessed = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
80+
7581
McpServerBuilder
7682
.WithMessageFilters(filters => filters.AddIncomingFilter((next) => async (context, cancellationToken) =>
7783
{
7884
var messageTypeName = context.JsonRpcMessage.GetType().Name;
7985
messageTypes.Add(messageTypeName);
8086
await next(context, cancellationToken);
87+
88+
if (context.JsonRpcMessage is JsonRpcNotification { Method: NotificationMethods.InitializedNotification })
89+
{
90+
initializedNotificationProcessed.TrySetResult(true);
91+
}
8192
}))
8293
.WithTools<TestTool>();
8394

@@ -87,6 +98,10 @@ public async Task AddIncomingMessageFilter_Intercepts_Request_Messages()
8798

8899
await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
89100

101+
// Wait for the fire-and-forget initialized notification to flow through the filter pipeline
102+
// before snapshotting the counts; otherwise the strict counts below can race the notification.
103+
await initializedNotificationProcessed.Task.WaitAsync(TestConstants.DefaultTimeout, TestContext.Current.CancellationToken);
104+
90105
// The message filter should intercept JsonRpcRequest messages.
91106
// Use strict counts so a regression that invokes the filter pipeline more than once per
92107
// incoming message (analogous to the SendRequestAsync double-wrap regression on the outgoing

0 commit comments

Comments
 (0)