55using ModelContextProtocol . Client ;
66using ModelContextProtocol . Protocol ;
77using ModelContextProtocol . Server ;
8+ using ModelContextProtocol . Tests . Utils ;
89using System . ComponentModel ;
910using System . Diagnostics ;
1011using System . Net ;
@@ -394,6 +395,7 @@ await client.CallToolAsync("sampling-tool",
394395 public async Task OutgoingFilter_MultipleFilters_ExecuteInOrder ( )
395396 {
396397 var executionOrder = new List < string > ( ) ;
398+ var allFiltersComplete = new TaskCompletionSource ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
397399
398400 Builder . Services . AddMcpServer ( )
399401 . WithHttpTransport ( ConfigureStateless )
@@ -411,6 +413,7 @@ public async Task OutgoingFilter_MultipleFilters_ExecuteInOrder()
411413 if ( context . JsonRpcMessage is JsonRpcResponse r2 && r2 . Result is JsonObject obj2 && obj2 . ContainsKey ( "tools" ) )
412414 {
413415 executionOrder . Add ( "filter1-after" ) ;
416+ allFiltersComplete . TrySetResult ( ) ;
414417 }
415418 } ) ;
416419
@@ -439,6 +442,14 @@ public async Task OutgoingFilter_MultipleFilters_ExecuteInOrder()
439442
440443 await client . ListToolsAsync ( cancellationToken : TestContext . Current . CancellationToken ) ;
441444
445+ // The outermost filter's "after" callback runs after the response has been
446+ // sent to the client, so ListToolsAsync may return before it executes.
447+ // Wait for it to complete before asserting, but use a timeout to avoid hanging
448+ // the test indefinitely if the filter pipeline regresses.
449+ using var allFiltersCts = CancellationTokenSource . CreateLinkedTokenSource ( TestContext . Current . CancellationToken ) ;
450+ allFiltersCts . CancelAfter ( TestConstants . DefaultTimeout ) ;
451+ await allFiltersComplete . Task . WaitAsync ( allFiltersCts . Token ) ;
452+
442453 Assert . Equal ( [ "filter1-before" , "filter2-before" , "filter2-after" , "filter1-after" ] , executionOrder ) ;
443454 }
444455
0 commit comments