@@ -467,4 +467,180 @@ await StartAsync(
467467 var text = Assert . IsType < TextContentBlock > ( Assert . Single ( result . Content ) ) . Text ;
468468 Assert . Equal ( "True" , text ) ;
469469 }
470+
471+ [ Fact ]
472+ public async Task Stateless_IsMrtrSupported_ReturnsFalse_WhenClientDoesNotOptIn ( )
473+ {
474+ // When the client doesn't set ExperimentalProtocolVersion, IsMrtrSupported should
475+ // be false even if the server has it configured.
476+ var isMrtrSupportedTool = McpServerTool . Create (
477+ static string ( McpServer server ) => server . IsMrtrSupported . ToString ( ) ,
478+ new McpServerToolCreateOptions
479+ {
480+ Name = "check-mrtr" ,
481+ Description = "Returns IsMrtrSupported"
482+ } ) ;
483+
484+ await StartAsync (
485+ options => options . ExperimentalProtocolVersion = "2026-06-XX" ,
486+ isMrtrSupportedTool ) ;
487+
488+ // Client does NOT set ExperimentalProtocolVersion
489+ await using var client = await ConnectAsync ( ) ;
490+
491+ var result = await client . CallToolAsync ( "check-mrtr" ,
492+ cancellationToken : TestContext . Current . CancellationToken ) ;
493+
494+ var text = Assert . IsType < TextContentBlock > ( Assert . Single ( result . Content ) ) . Text ;
495+ Assert . Equal ( "False" , text ) ;
496+ }
497+
498+ [ Fact ]
499+ public async Task Stateless_IsMrtrSupportedCheck_ThenThrow_WorksEndToEnd ( )
500+ {
501+ // This mirrors the doc pattern: check IsMrtrSupported, return fallback if false,
502+ // throw IncompleteResultException if true. This is the exact code from mrtr.md
503+ // and elicitation.md that was previously untested in stateless mode.
504+ var docPatternTool = McpServerTool . Create (
505+ static string ( McpServer server , RequestContext < CallToolRequestParams > context ) =>
506+ {
507+ if ( context . Params ! . InputResponses ? . TryGetValue ( "user_input" , out var response ) is true )
508+ {
509+ var elicitResult = response . ElicitationResult ;
510+ return elicitResult ? . Action == "accept"
511+ ? $ "User accepted: { elicitResult . Content ? . FirstOrDefault ( ) . Value } "
512+ : "User declined." ;
513+ }
514+
515+ if ( ! server . IsMrtrSupported )
516+ {
517+ return "This tool requires MRTR support." ;
518+ }
519+
520+ throw new IncompleteResultException (
521+ inputRequests : new Dictionary < string , InputRequest >
522+ {
523+ [ "user_input" ] = InputRequest . ForElicitation ( new ElicitRequestParams
524+ {
525+ Message = "Please confirm" ,
526+ RequestedSchema = new ( )
527+ } )
528+ } ,
529+ requestState : "awaiting-confirmation" ) ;
530+ } ,
531+ new McpServerToolCreateOptions
532+ {
533+ Name = "doc-pattern-elicit" ,
534+ Description = "Mirrors the low-level elicitation doc sample"
535+ } ) ;
536+
537+ await StartAsync (
538+ options => options . ExperimentalProtocolVersion = "2026-06-XX" ,
539+ docPatternTool ) ;
540+
541+ // With MRTR client: should complete the full flow
542+ var mrtrClientOptions = CreateClientOptionsWithAllHandlers ( ) ;
543+ mrtrClientOptions . ExperimentalProtocolVersion = "2026-06-XX" ;
544+
545+ await using var mrtrClient = await ConnectAsync ( mrtrClientOptions ) ;
546+
547+ var result = await mrtrClient . CallToolAsync ( "doc-pattern-elicit" ,
548+ cancellationToken : TestContext . Current . CancellationToken ) ;
549+
550+ var text = Assert . IsType < TextContentBlock > ( Assert . Single ( result . Content ) ) . Text ;
551+ Assert . Equal ( "User accepted: yes" , text ) ;
552+ }
553+
554+ [ Fact ]
555+ public async Task Stateless_IsMrtrSupportedCheck_ReturnsFallback_WhenClientDoesNotOptIn ( )
556+ {
557+ // Same doc pattern tool, but the client doesn't opt in. Should return fallback message.
558+ var docPatternTool = McpServerTool . Create (
559+ static string ( McpServer server , RequestContext < CallToolRequestParams > context ) =>
560+ {
561+ if ( context . Params ! . InputResponses ? . TryGetValue ( "user_input" , out var response ) is true )
562+ {
563+ var elicitResult = response . ElicitationResult ;
564+ return elicitResult ? . Action == "accept"
565+ ? $ "User accepted: { elicitResult . Content ? . FirstOrDefault ( ) . Value } "
566+ : "User declined." ;
567+ }
568+
569+ if ( ! server . IsMrtrSupported )
570+ {
571+ return "This tool requires MRTR support." ;
572+ }
573+
574+ throw new IncompleteResultException (
575+ inputRequests : new Dictionary < string , InputRequest >
576+ {
577+ [ "user_input" ] = InputRequest . ForElicitation ( new ElicitRequestParams
578+ {
579+ Message = "Please confirm" ,
580+ RequestedSchema = new ( )
581+ } )
582+ } ,
583+ requestState : "awaiting-confirmation" ) ;
584+ } ,
585+ new McpServerToolCreateOptions
586+ {
587+ Name = "doc-pattern-elicit" ,
588+ Description = "Mirrors the low-level elicitation doc sample"
589+ } ) ;
590+
591+ await StartAsync (
592+ options => options . ExperimentalProtocolVersion = "2026-06-XX" ,
593+ docPatternTool ) ;
594+
595+ // Client does NOT set ExperimentalProtocolVersion — should get fallback
596+ await using var client = await ConnectAsync ( ) ;
597+
598+ var result = await client . CallToolAsync ( "doc-pattern-elicit" ,
599+ cancellationToken : TestContext . Current . CancellationToken ) ;
600+
601+ var text = Assert . IsType < TextContentBlock > ( Assert . Single ( result . Content ) ) . Text ;
602+ Assert . Equal ( "This tool requires MRTR support." , text ) ;
603+ }
604+
605+ [ Fact ]
606+ public async Task Stateless_LoadShedding_RequestStateOnly_CompletesViaMrtr ( )
607+ {
608+ // Tests the load shedding pattern from mrtr.md — requestState-only IncompleteResult
609+ // without inputRequests. The client should auto-retry with just the requestState.
610+ var loadSheddingTool = McpServerTool . Create (
611+ static string ( McpServer server , RequestContext < CallToolRequestParams > context ) =>
612+ {
613+ var requestState = context . Params ! . RequestState ;
614+ if ( requestState is not null )
615+ {
616+ return $ "resumed:{ requestState } ";
617+ }
618+
619+ if ( ! server . IsMrtrSupported )
620+ {
621+ return "MRTR not supported." ;
622+ }
623+
624+ throw new IncompleteResultException ( requestState : "deferred-work" ) ;
625+ } ,
626+ new McpServerToolCreateOptions
627+ {
628+ Name = "stateless-loadshed" ,
629+ Description = "Load shedding with IsMrtrSupported check"
630+ } ) ;
631+
632+ await StartAsync (
633+ options => options . ExperimentalProtocolVersion = "2026-06-XX" ,
634+ loadSheddingTool ) ;
635+
636+ var clientOptions = new McpClientOptions { ExperimentalProtocolVersion = "2026-06-XX" } ;
637+
638+ await using var client = await ConnectAsync ( clientOptions ) ;
639+
640+ var result = await client . CallToolAsync ( "stateless-loadshed" ,
641+ cancellationToken : TestContext . Current . CancellationToken ) ;
642+
643+ var text = Assert . IsType < TextContentBlock > ( Assert . Single ( result . Content ) ) . Text ;
644+ Assert . Equal ( "resumed:deferred-work" , text ) ;
645+ }
470646}
0 commit comments