@@ -726,6 +726,149 @@ registerScenario(
726726 runEnterpriseManagedAuthorization
727727) ;
728728
729+ // ============================================================================
730+ // MRTR client conformance (SEP-2322)
731+ // ============================================================================
732+
733+ async function runMRTRClient ( serverUrl : string ) : Promise < void > {
734+ let nextId = 1 ;
735+
736+ async function sendRpc (
737+ method : string ,
738+ params ?: Record < string , unknown >
739+ ) : Promise < {
740+ id : number ;
741+ result ? : Record < string , unknown > ;
742+ error ?: { code : number ; message : string } ;
743+ } > {
744+ const id = nextId ++ ;
745+ const body : Record < string , unknown > = {
746+ jsonrpc : '2.0' ,
747+ id,
748+ method
749+ } ;
750+ if ( params ) body . params = params ;
751+
752+ const resp = await fetch ( serverUrl , {
753+ method : 'POST' ,
754+ headers : { 'Content-Type' : 'application/json' } ,
755+ body : JSON . stringify ( body )
756+ } ) ;
757+
758+ if ( resp . status === 204 ) return { id , result : { } } ;
759+ return ( await resp . json ( ) ) as {
760+ id : number ;
761+ result ?: Record < string , unknown > ;
762+ error ?: { code : number ; message : string } ;
763+ } ;
764+ }
765+
766+ // List tools
767+ const toolsResp = await sendRpc ( 'tools/list' ) ;
768+ const tools =
769+ ( toolsResp . result as { tools : Array < { name : string } > } ) ?. tools ?? [ ] ;
770+ logger . debug (
771+ 'Available tools:' ,
772+ tools . map ( ( t ) => t . name )
773+ ) ;
774+
775+ // Tool 1: test_mrtr_echo_state — call, get InputRequiredResult with requestState, retry
776+ const r1 = await sendRpc ( 'tools/call' , {
777+ name : 'test_mrtr_echo_state' ,
778+ arguments : { }
779+ } ) ;
780+
781+ const r1Result = r1 . result as Record < string , unknown > | undefined ;
782+ if ( r1Result ?. resultType === 'input_required' ) {
783+ const inputRequests = r1Result . inputRequests as Record < string , unknown > ;
784+ const requestState = r1Result . requestState as string | undefined ;
785+
786+ // Build inputResponses by fulfilling each inputRequest
787+ const inputResponses : Record < string , unknown > = { } ;
788+ for ( const [ key , req ] of Object . entries ( inputRequests ) ) {
789+ const request = req as { method : string ; params : unknown } ;
790+ if ( request . method === 'elicitation/create' ) {
791+ inputResponses [ key ] = {
792+ action : 'accept' ,
793+ content : { confirmed : true }
794+ } ;
795+ }
796+ }
797+
798+ // Call an unrelated tool BEFORE retrying — must NOT carry over inputResponses/requestState
799+ await sendRpc ( 'tools/call' , {
800+ name : 'test_mrtr_unrelated' ,
801+ arguments : { }
802+ } ) ;
803+ logger . debug (
804+ 'test_mrtr_unrelated: called without MRTR state (isolation check)'
805+ ) ;
806+
807+ // Retry with inputResponses + requestState echoed back unchanged
808+ const retryParams : Record < string , unknown > = {
809+ name : 'test_mrtr_echo_state' ,
810+ arguments : { } ,
811+ inputResponses
812+ } ;
813+ if ( requestState !== undefined ) {
814+ retryParams . requestState = requestState ;
815+ }
816+
817+ await sendRpc ( 'tools/call' , retryParams ) ;
818+ logger . debug ( 'test_mrtr_echo_state: MRTR flow completed' ) ;
819+ }
820+
821+ // Tool 2: test_mrtr_no_state — call, get InputRequiredResult WITHOUT requestState, retry without it
822+ const r2 = await sendRpc ( 'tools/call' , {
823+ name : 'test_mrtr_no_state' ,
824+ arguments : { }
825+ } ) ;
826+
827+ const r2Result = r2 . result as Record < string , unknown > | undefined ;
828+ if ( r2Result ?. resultType === 'input_required' ) {
829+ const inputRequests = r2Result . inputRequests as Record < string , unknown > ;
830+
831+ // Build inputResponses
832+ const inputResponses : Record < string , unknown > = { } ;
833+ for ( const [ key , req ] of Object . entries ( inputRequests ) ) {
834+ const request = req as { method : string ; params : unknown } ;
835+ if ( request . method === 'elicitation/create' ) {
836+ inputResponses [ key ] = {
837+ action : 'accept' ,
838+ content : { confirmed : true }
839+ } ;
840+ }
841+ }
842+
843+ // Retry WITHOUT requestState (server didn't send one)
844+ await sendRpc ( 'tools/call' , {
845+ name : 'test_mrtr_no_state' ,
846+ arguments : { } ,
847+ inputResponses
848+ } ) ;
849+ logger . debug ( 'test_mrtr_no_state: MRTR flow completed' ) ;
850+ }
851+
852+ // Tool 3: test_mrtr_no_result_type — returns result without resultType field
853+ // Client must treat it as complete (default) and NOT retry
854+ const r3 = await sendRpc ( 'tools/call' , {
855+ name : 'test_mrtr_no_result_type' ,
856+ arguments : { }
857+ } ) ;
858+
859+ const r3Result = r3 . result as Record < string , unknown > | undefined ;
860+ if ( r3Result && ! r3Result . resultType ) {
861+ // No resultType means default to "complete" — do nothing, don't retry
862+ logger . debug (
863+ 'test_mrtr_no_result_type: result has no resultType, treating as complete'
864+ ) ;
865+ }
866+
867+ logger . debug ( 'MRTR client scenario completed' ) ;
868+ }
869+
870+ registerScenario ( 'sep-2322-client-request-state' , runMRTRClient ) ;
871+
729872// ============================================================================
730873// Main entry point
731874// ============================================================================
0 commit comments