@@ -530,6 +530,202 @@ void testElicitationCreateRequestHandlingWithNullHandler() {
530530 .hasMessage ("Elicitation handler must not be null when client capabilities include elicitation" );
531531 }
532532
533+ @ Test
534+ void testElicitationResponseIncludesRelatedTaskMetadata () {
535+ MockMcpClientTransport transport = initializationEnabledTransport ();
536+
537+ // Create a test elicitation handler that returns a simple response
538+ Function <McpSchema .ElicitRequest , Mono <McpSchema .ElicitResult >> elicitationHandler = request -> Mono
539+ .just (new McpSchema .ElicitResult (McpSchema .ElicitResult .Action .ACCEPT , Map .of ("value" , "42" ), null ));
540+
541+ // Create client with elicitation capability and handler
542+ McpAsyncClient asyncMcpClient = McpClient .async (transport )
543+ .capabilities (ClientCapabilities .builder ().elicitation ().build ())
544+ .elicitation (elicitationHandler )
545+ .build ();
546+
547+ assertThat (asyncMcpClient .initialize ().block ()).isNotNull ();
548+
549+ // Create elicitation request WITH related-task metadata (simulating
550+ // side-channeling)
551+ String taskId = "test-task-123" ;
552+ Map <String , Object > relatedTaskMeta = Map .of ("taskId" , taskId );
553+ Map <String , Object > requestMeta = Map .of (McpSchema .RELATED_TASK_META_KEY , relatedTaskMeta );
554+
555+ var elicitRequest = new McpSchema .ElicitRequest ("What is your favorite number?" ,
556+ Map .of ("type" , "object" , "properties" , Map .of ("value" , Map .of ("type" , "string" ))), null , requestMeta );
557+
558+ // Simulate incoming request
559+ McpSchema .JSONRPCRequest request = new McpSchema .JSONRPCRequest (McpSchema .JSONRPC_VERSION ,
560+ McpSchema .METHOD_ELICITATION_CREATE , "test-id" , elicitRequest );
561+ transport .simulateIncomingMessage (request );
562+
563+ // Verify response
564+ McpSchema .JSONRPCMessage sentMessage = transport .getLastSentMessage ();
565+ assertThat (sentMessage ).isInstanceOf (McpSchema .JSONRPCResponse .class );
566+
567+ McpSchema .JSONRPCResponse response = (McpSchema .JSONRPCResponse ) sentMessage ;
568+ assertThat (response .id ()).isEqualTo ("test-id" );
569+ assertThat (response .error ()).isNull ();
570+
571+ McpSchema .ElicitResult result = transport .unmarshalFrom (response .result (), new TypeRef <>() {
572+ });
573+ assertThat (result ).isNotNull ();
574+ assertThat (result .action ()).isEqualTo (McpSchema .ElicitResult .Action .ACCEPT );
575+ assertThat (result .content ()).isEqualTo (Map .of ("value" , "42" ));
576+
577+ // Verify related-task metadata was echoed back
578+ assertThat (result .meta ()).isNotNull ();
579+ assertThat (result .meta ()).containsKey (McpSchema .RELATED_TASK_META_KEY );
580+ @ SuppressWarnings ("unchecked" )
581+ Map <String , Object > echoedRelatedTask = (Map <String , Object >) result .meta ()
582+ .get (McpSchema .RELATED_TASK_META_KEY );
583+ assertThat (echoedRelatedTask .get ("taskId" )).isEqualTo (taskId );
584+
585+ asyncMcpClient .closeGracefully ();
586+ }
587+
588+ @ Test
589+ void testElicitationResponseWithoutRelatedTaskMetadata () {
590+ MockMcpClientTransport transport = initializationEnabledTransport ();
591+
592+ // Create a test elicitation handler that returns a response with custom meta
593+ Map <String , Object > handlerMeta = Map .of ("custom-key" , "custom-value" );
594+ Function <McpSchema .ElicitRequest , Mono <McpSchema .ElicitResult >> elicitationHandler = request -> Mono
595+ .just (new McpSchema .ElicitResult (McpSchema .ElicitResult .Action .ACCEPT , Map .of ("value" , "42" ), handlerMeta ));
596+
597+ // Create client with elicitation capability and handler
598+ McpAsyncClient asyncMcpClient = McpClient .async (transport )
599+ .capabilities (ClientCapabilities .builder ().elicitation ().build ())
600+ .elicitation (elicitationHandler )
601+ .build ();
602+
603+ assertThat (asyncMcpClient .initialize ().block ()).isNotNull ();
604+
605+ // Create elicitation request WITHOUT related-task metadata
606+ var elicitRequest = new McpSchema .ElicitRequest ("What is your favorite number?" ,
607+ Map .of ("type" , "object" , "properties" , Map .of ("value" , Map .of ("type" , "string" ))));
608+
609+ // Simulate incoming request
610+ McpSchema .JSONRPCRequest request = new McpSchema .JSONRPCRequest (McpSchema .JSONRPC_VERSION ,
611+ McpSchema .METHOD_ELICITATION_CREATE , "test-id" , elicitRequest );
612+ transport .simulateIncomingMessage (request );
613+
614+ // Verify response
615+ McpSchema .JSONRPCMessage sentMessage = transport .getLastSentMessage ();
616+ McpSchema .JSONRPCResponse response = (McpSchema .JSONRPCResponse ) sentMessage ;
617+
618+ McpSchema .ElicitResult result = transport .unmarshalFrom (response .result (), new TypeRef <>() {
619+ });
620+ assertThat (result ).isNotNull ();
621+
622+ // Verify handler's meta is preserved and no related-task was added
623+ assertThat (result .meta ()).isEqualTo (handlerMeta );
624+ assertThat (result .meta ()).doesNotContainKey (McpSchema .RELATED_TASK_META_KEY );
625+
626+ asyncMcpClient .closeGracefully ();
627+ }
628+
629+ @ Test
630+ void testSamplingResponseIncludesRelatedTaskMetadata () {
631+ MockMcpClientTransport transport = initializationEnabledTransport ();
632+
633+ // Create a test sampling handler that returns a simple response
634+ Function <McpSchema .CreateMessageRequest , Mono <McpSchema .CreateMessageResult >> samplingHandler = request -> Mono
635+ .just (new McpSchema .CreateMessageResult (McpSchema .Role .ASSISTANT , new McpSchema .TextContent ("Response" ),
636+ "test-model" , McpSchema .CreateMessageResult .StopReason .END_TURN , null ));
637+
638+ // Create client with sampling capability and handler
639+ McpAsyncClient asyncMcpClient = McpClient .async (transport )
640+ .capabilities (ClientCapabilities .builder ().sampling ().build ())
641+ .sampling (samplingHandler )
642+ .build ();
643+
644+ assertThat (asyncMcpClient .initialize ().block ()).isNotNull ();
645+
646+ // Create sampling request WITH related-task metadata (simulating side-channeling)
647+ String taskId = "test-task-456" ;
648+ Map <String , Object > relatedTaskMeta = Map .of ("taskId" , taskId );
649+ Map <String , Object > requestMeta = Map .of (McpSchema .RELATED_TASK_META_KEY , relatedTaskMeta );
650+
651+ var messageRequest = new McpSchema .CreateMessageRequest (
652+ List .of (new McpSchema .SamplingMessage (McpSchema .Role .USER , new McpSchema .TextContent ("Test message" ))),
653+ null , "Test system prompt" , McpSchema .CreateMessageRequest .ContextInclusionStrategy .NONE , 0.7 , 100 ,
654+ null , null , null , requestMeta );
655+
656+ // Simulate incoming request
657+ McpSchema .JSONRPCRequest request = new McpSchema .JSONRPCRequest (McpSchema .JSONRPC_VERSION ,
658+ McpSchema .METHOD_SAMPLING_CREATE_MESSAGE , "test-id" , messageRequest );
659+ transport .simulateIncomingMessage (request );
660+
661+ // Verify response
662+ McpSchema .JSONRPCMessage sentMessage = transport .getLastSentMessage ();
663+ assertThat (sentMessage ).isInstanceOf (McpSchema .JSONRPCResponse .class );
664+
665+ McpSchema .JSONRPCResponse response = (McpSchema .JSONRPCResponse ) sentMessage ;
666+ assertThat (response .id ()).isEqualTo ("test-id" );
667+ assertThat (response .error ()).isNull ();
668+
669+ McpSchema .CreateMessageResult result = transport .unmarshalFrom (response .result (), new TypeRef <>() {
670+ });
671+ assertThat (result ).isNotNull ();
672+ assertThat (result .role ()).isEqualTo (McpSchema .Role .ASSISTANT );
673+
674+ // Verify related-task metadata was echoed back
675+ assertThat (result .meta ()).isNotNull ();
676+ assertThat (result .meta ()).containsKey (McpSchema .RELATED_TASK_META_KEY );
677+ @ SuppressWarnings ("unchecked" )
678+ Map <String , Object > echoedRelatedTask = (Map <String , Object >) result .meta ()
679+ .get (McpSchema .RELATED_TASK_META_KEY );
680+ assertThat (echoedRelatedTask .get ("taskId" )).isEqualTo (taskId );
681+
682+ asyncMcpClient .closeGracefully ();
683+ }
684+
685+ @ Test
686+ void testElicitationResponseMergesHandlerMetaWithRelatedTask () {
687+ MockMcpClientTransport transport = initializationEnabledTransport ();
688+
689+ // Create a test elicitation handler that returns a response with custom meta
690+ Map <String , Object > handlerMeta = Map .of ("custom-key" , "custom-value" );
691+ Function <McpSchema .ElicitRequest , Mono <McpSchema .ElicitResult >> elicitationHandler = request -> Mono
692+ .just (new McpSchema .ElicitResult (McpSchema .ElicitResult .Action .ACCEPT , Map .of ("value" , "42" ), handlerMeta ));
693+
694+ // Create client with elicitation capability and handler
695+ McpAsyncClient asyncMcpClient = McpClient .async (transport )
696+ .capabilities (ClientCapabilities .builder ().elicitation ().build ())
697+ .elicitation (elicitationHandler )
698+ .build ();
699+
700+ assertThat (asyncMcpClient .initialize ().block ()).isNotNull ();
701+
702+ // Create elicitation request WITH related-task metadata
703+ String taskId = "test-task-789" ;
704+ Map <String , Object > relatedTaskMeta = Map .of ("taskId" , taskId );
705+ Map <String , Object > requestMeta = Map .of (McpSchema .RELATED_TASK_META_KEY , relatedTaskMeta );
706+
707+ var elicitRequest = new McpSchema .ElicitRequest ("Test?" ,
708+ Map .of ("type" , "object" , "properties" , Map .of ("value" , Map .of ("type" , "string" ))), null , requestMeta );
709+
710+ // Simulate incoming request
711+ McpSchema .JSONRPCRequest request = new McpSchema .JSONRPCRequest (McpSchema .JSONRPC_VERSION ,
712+ McpSchema .METHOD_ELICITATION_CREATE , "test-id" , elicitRequest );
713+ transport .simulateIncomingMessage (request );
714+
715+ // Verify response
716+ McpSchema .JSONRPCResponse response = (McpSchema .JSONRPCResponse ) transport .getLastSentMessage ();
717+ McpSchema .ElicitResult result = transport .unmarshalFrom (response .result (), new TypeRef <>() {
718+ });
719+
720+ // Verify both handler's meta AND related-task are present (merged)
721+ assertThat (result .meta ()).isNotNull ();
722+ assertThat (result .meta ()).containsKey ("custom-key" );
723+ assertThat (result .meta ().get ("custom-key" )).isEqualTo ("custom-value" );
724+ assertThat (result .meta ()).containsKey (McpSchema .RELATED_TASK_META_KEY );
725+
726+ asyncMcpClient .closeGracefully ();
727+ }
728+
533729 @ Test
534730 void testPingMessageRequestHandling () {
535731 MockMcpClientTransport transport = initializationEnabledTransport ();
0 commit comments