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