@@ -317,7 +317,7 @@ await Assert.ThrowsAnyAsync<OperationCanceledException>(async () =>
317317 }
318318
319319 [ Fact ]
320- public async Task AddOutgoingMessageFilter_Sees_Initialize_Progress_And_Response ( )
320+ public async Task AddOutgoingMessageFilter_Sees_Responses_Notifications_And_Requests ( )
321321 {
322322 var observedMessages = new List < string > ( ) ;
323323
@@ -326,6 +326,9 @@ public async Task AddOutgoingMessageFilter_Sees_Initialize_Progress_And_Response
326326 {
327327 switch ( context . JsonRpcMessage )
328328 {
329+ case JsonRpcRequest request :
330+ observedMessages . Add ( $ "request:{ request . Method } ") ;
331+ break ;
329332 case JsonRpcResponse response when response . Result is JsonObject result :
330333 if ( result . ContainsKey ( "protocolVersion" ) )
331334 {
@@ -343,22 +346,41 @@ public async Task AddOutgoingMessageFilter_Sees_Initialize_Progress_And_Response
343346
344347 await next ( context , cancellationToken ) ;
345348 } ) )
346- . WithTools < ProgressTool > ( ) ;
349+ . WithTools < ProgressTool > ( )
350+ . WithTools < SamplingTool > ( ) ;
347351
348352 StartServer ( ) ;
349353
350- await using McpClient client = await CreateMcpClientForServer ( ) ;
354+ var clientOptions = new McpClientOptions
355+ {
356+ Capabilities = new ( ) { Sampling = new ( ) } ,
357+ Handlers = new ( )
358+ {
359+ SamplingHandler = ( _ , _ , _ ) => new ( new CreateMessageResult
360+ {
361+ Content = [ new TextContentBlock { Text = "sampled" } ] ,
362+ Model = "test-model" ,
363+ } ) ,
364+ } ,
365+ } ;
366+
367+ await using McpClient client = await CreateMcpClientForServer ( clientOptions ) ;
351368
352369 IProgress < ProgressNotificationValue > progress = new Progress < ProgressNotificationValue > ( _ => { } ) ;
353370 await client . CallToolAsync ( "progress-tool" , progress : progress , cancellationToken : TestContext . Current . CancellationToken ) ;
354371
372+ await client . CallToolAsync ( "sampling-tool" , new Dictionary < string , object ? > { [ "prompt" ] = "Hello" } ,
373+ cancellationToken : TestContext . Current . CancellationToken ) ;
374+
355375 int initializeIndex = observedMessages . IndexOf ( "initialize" ) ;
356376 int progressIndex = observedMessages . IndexOf ( "progress" ) ;
357377 int responseIndex = observedMessages . LastIndexOf ( "response" ) ;
378+ int requestIndex = observedMessages . IndexOf ( $ "request:{ RequestMethods . SamplingCreateMessage } ") ;
358379
359380 Assert . True ( initializeIndex >= 0 ) ;
360381 Assert . True ( progressIndex > initializeIndex ) ;
361382 Assert . True ( responseIndex > progressIndex ) ;
383+ Assert . True ( requestIndex >= 0 ) ;
362384 }
363385
364386 [ Fact ]
@@ -429,43 +451,124 @@ public async Task AddOutgoingMessageFilter_Can_Send_Additional_Messages()
429451 }
430452
431453 [ Fact ]
432- public async Task AddOutgoingMessageFilter_Sees_ServerOriginatedRequests ( )
454+ public async Task AddOutgoingMessageFilter_SkipNext_DoesNotLogSending ( )
433455 {
434- var observedMethods = new List < string > ( ) ;
456+ ServiceCollection . AddLogging ( builder => builder . SetMinimumLevel ( LogLevel . Debug ) ) ;
435457
436458 McpServerBuilder
437- . WithMessageFilters ( filters => filters . AddOutgoingFilter ( ( next ) => async ( context , cancellationToken ) =>
459+ . WithMessageFilters ( filters => filters . AddOutgoingFilter ( ( next ) => ( context , cancellationToken ) =>
438460 {
439- if ( context . JsonRpcMessage is JsonRpcRequest request )
461+ // Skip sending tool list responses
462+ if ( context . JsonRpcMessage is JsonRpcResponse response && response . Result is JsonObject result && result . ContainsKey ( "tools" ) )
440463 {
441- observedMethods . Add ( request . Method ) ;
464+ return Task . CompletedTask ;
442465 }
443466
444- await next ( context , cancellationToken ) ;
467+ return next ( context , cancellationToken ) ;
445468 } ) )
446- . WithTools < SamplingTool > ( ) ;
469+ . WithTools < TestTool > ( ) ;
447470
448471 StartServer ( ) ;
449472
450- var clientOptions = new McpClientOptions
473+ await using McpClient client = await CreateMcpClientForServer ( ) ;
474+
475+ // Clear any logs from initialization
476+ while ( MockLoggerProvider . LogMessages . TryDequeue ( out _ ) ) { }
477+
478+ using var requestCts = new CancellationTokenSource ( TimeSpan . FromSeconds ( 2 ) ) ;
479+ await Assert . ThrowsAnyAsync < OperationCanceledException > ( async ( ) =>
451480 {
452- Capabilities = new ( ) { Sampling = new ( ) } ,
453- Handlers = new ( )
481+ await client . ListToolsAsync ( cancellationToken : requestCts . Token ) ;
482+ } ) ;
483+
484+ // Since the filter skipped next, no "sending message" log should appear for the skipped response
485+ Assert . DoesNotContain ( MockLoggerProvider . LogMessages , m =>
486+ m . Category . Contains ( "McpServer" ) && m . Message . Contains ( "sending message" , StringComparison . OrdinalIgnoreCase ) ) ;
487+ }
488+
489+ [ Fact ]
490+ public async Task AddOutgoingMessageFilter_CallsNext_LogsSending ( )
491+ {
492+ ServiceCollection . AddLogging ( builder => builder . SetMinimumLevel ( LogLevel . Debug ) ) ;
493+
494+ McpServerBuilder
495+ . WithTools < TestTool > ( ) ;
496+
497+ StartServer ( ) ;
498+
499+ await using McpClient client = await CreateMcpClientForServer ( ) ;
500+
501+ // Clear any logs from initialization
502+ while ( MockLoggerProvider . LogMessages . TryDequeue ( out _ ) ) { }
503+
504+ await client . ListToolsAsync ( cancellationToken : TestContext . Current . CancellationToken ) ;
505+
506+ // The response should have been sent, producing a "sending message" log from the server
507+ Assert . Contains ( MockLoggerProvider . LogMessages , m =>
508+ m . Category . Contains ( "McpServer" ) && m . Message . Contains ( "sending message" , StringComparison . OrdinalIgnoreCase ) ) ;
509+ }
510+
511+ [ Fact ]
512+ public async Task AddIncomingMessageFilter_SkipNext_DoesNotLogSendingResponse ( )
513+ {
514+ ServiceCollection . AddLogging ( builder => builder . SetMinimumLevel ( LogLevel . Debug ) ) ;
515+
516+ McpServerBuilder
517+ . WithMessageFilters ( filters => filters . AddIncomingFilter ( ( next ) => ( context , cancellationToken ) =>
454518 {
455- SamplingHandler = ( _ , _ , _ ) => new ( new CreateMessageResult
519+ // Skip processing tools/list requests — handler never runs, no response sent
520+ if ( context . JsonRpcMessage is JsonRpcRequest request && request . Method == RequestMethods . ToolsList )
456521 {
457- Content = [ new TextContentBlock { Text = "sampled" } ] ,
458- Model = "test-model" ,
459- } ) ,
460- } ,
461- } ;
522+ return Task . CompletedTask ;
523+ }
462524
463- await using McpClient client = await CreateMcpClientForServer ( clientOptions ) ;
525+ return next ( context , cancellationToken ) ;
526+ } ) )
527+ . WithTools < TestTool > ( ) ;
464528
465- await client . CallToolAsync ( "sampling-tool" , new Dictionary < string , object ? > { [ "prompt" ] = "Hello" } ,
466- cancellationToken : TestContext . Current . CancellationToken ) ;
529+ StartServer ( ) ;
530+
531+ await using McpClient client = await CreateMcpClientForServer ( ) ;
532+
533+ // Clear any logs from initialization
534+ while ( MockLoggerProvider . LogMessages . TryDequeue ( out _ ) ) { }
535+
536+ using var requestCts = new CancellationTokenSource ( TimeSpan . FromSeconds ( 2 ) ) ;
537+ await Assert . ThrowsAnyAsync < OperationCanceledException > ( async ( ) =>
538+ {
539+ await client . ListToolsAsync ( cancellationToken : requestCts . Token ) ;
540+ } ) ;
541+
542+ // Since the incoming filter skipped next, no handler ran, so no response was sent
543+ Assert . DoesNotContain ( MockLoggerProvider . LogMessages , m =>
544+ m . Category . Contains ( "McpServer" ) && m . Message . Contains ( "sending message" , StringComparison . OrdinalIgnoreCase ) ) ;
545+ }
546+
547+ [ Fact ]
548+ public async Task AddIncomingMessageFilter_CallsNext_LogsSendingResponse ( )
549+ {
550+ ServiceCollection . AddLogging ( builder => builder . SetMinimumLevel ( LogLevel . Debug ) ) ;
551+
552+ McpServerBuilder
553+ . WithMessageFilters ( filters => filters . AddIncomingFilter ( ( next ) => ( context , cancellationToken ) =>
554+ {
555+ // Pass through — handler runs, response is sent
556+ return next ( context , cancellationToken ) ;
557+ } ) )
558+ . WithTools < TestTool > ( ) ;
559+
560+ StartServer ( ) ;
561+
562+ await using McpClient client = await CreateMcpClientForServer ( ) ;
563+
564+ // Clear any logs from initialization
565+ while ( MockLoggerProvider . LogMessages . TryDequeue ( out _ ) ) { }
566+
567+ await client . ListToolsAsync ( cancellationToken : TestContext . Current . CancellationToken ) ;
467568
468- Assert . Contains ( RequestMethods . SamplingCreateMessage , observedMethods ) ;
569+ // The handler ran and sent a response, producing a "sending message" log from the server
570+ Assert . Contains ( MockLoggerProvider . LogMessages , m =>
571+ m . Category . Contains ( "McpServer" ) && m . Message . Contains ( "sending message" , StringComparison . OrdinalIgnoreCase ) ) ;
469572 }
470573
471574 [ Fact ]
0 commit comments