@@ -874,6 +874,111 @@ async def test_prepare_responses_params_api_status_error_on_models(
874874 await prepare_responses_params (mock_client , query_request , None , "token" )
875875 assert exc_info .value .status_code == 500
876876
877+ @pytest .mark .asyncio
878+ async def test_prepare_responses_params_includes_mcp_provider_data_headers (
879+ self , mocker : MockerFixture
880+ ) -> None :
881+ """Test that extra_headers with x-llamastack-provider-data is set when MCP tools have headers."""
882+ mock_client = mocker .AsyncMock ()
883+ mock_model = mocker .Mock ()
884+ mock_model .id = "provider1/model1"
885+ mock_model .custom_metadata = {"model_type" : "llm" , "provider_id" : "provider1" }
886+ mock_client .models .list = mocker .AsyncMock (return_value = [mock_model ])
887+
888+ mock_conversation = mocker .Mock ()
889+ mock_conversation .id = "new_conv_id"
890+ mock_client .conversations .create = mocker .AsyncMock (
891+ return_value = mock_conversation
892+ )
893+
894+ query_request = QueryRequest (query = "test" ) # pyright: ignore[reportCallIssue]
895+
896+ # Simulate MCP tools with headers (as returned by prepare_tools/get_mcp_tools)
897+ mcp_tools_with_headers = [
898+ {
899+ "type" : "mcp" ,
900+ "server_label" : "mcp::aap-controller" ,
901+ "server_url" : "http://aap.foo.redhat.com:8004/sse" ,
902+ "require_approval" : "never" ,
903+ "headers" : {"X-Authorization" : "client-token" },
904+ },
905+ {
906+ "type" : "mcp" ,
907+ "server_label" : "mcp::aap-lightspeed" ,
908+ "server_url" : "http://aap.foo.redhat.com:8005/sse" ,
909+ "require_approval" : "never" ,
910+ "headers" : {"X-Authorization" : "client-token-2" },
911+ },
912+ ]
913+
914+ mocker .patch ("utils.responses.configuration" , mocker .Mock ())
915+ mocker .patch (
916+ "utils.responses.select_model_and_provider_id" ,
917+ return_value = ("provider1/model1" , "model1" , "provider1" ),
918+ )
919+ mocker .patch ("utils.responses.evaluate_model_hints" , return_value = (None , None ))
920+ mocker .patch ("utils.responses.get_system_prompt" , return_value = "System prompt" )
921+ mocker .patch (
922+ "utils.responses.prepare_tools" , return_value = mcp_tools_with_headers
923+ )
924+ mocker .patch ("utils.responses.prepare_input" , return_value = "test" )
925+
926+ result = await prepare_responses_params (
927+ mock_client , query_request , None , "token"
928+ )
929+
930+ # The result should contain extra_headers with x-llamastack-provider-data
931+ dumped = result .model_dump ()
932+ assert (
933+ dumped ["extra_headers" ] is not None
934+ ), "extra_headers should not be None when MCP tools have headers"
935+ assert "x-llamastack-provider-data" in dumped ["extra_headers" ]
936+
937+ provider_data = json .loads (
938+ dumped ["extra_headers" ]["x-llamastack-provider-data" ]
939+ )
940+ assert "mcp_headers" in provider_data
941+ assert provider_data ["mcp_headers" ] == {
942+ "http://aap.foo.redhat.com:8004/sse" : {"X-Authorization" : "client-token" },
943+ "http://aap.foo.redhat.com:8005/sse" : {"X-Authorization" : "client-token-2" },
944+ }
945+
946+ @pytest .mark .asyncio
947+ async def test_prepare_responses_params_no_extra_headers_without_mcp_tools (
948+ self , mocker : MockerFixture
949+ ) -> None :
950+ """Test that extra_headers is None when no MCP tools have headers."""
951+ mock_client = mocker .AsyncMock ()
952+ mock_model = mocker .Mock ()
953+ mock_model .id = "provider1/model1"
954+ mock_model .custom_metadata = {"model_type" : "llm" , "provider_id" : "provider1" }
955+ mock_client .models .list = mocker .AsyncMock (return_value = [mock_model ])
956+
957+ mock_conversation = mocker .Mock ()
958+ mock_conversation .id = "new_conv_id"
959+ mock_client .conversations .create = mocker .AsyncMock (
960+ return_value = mock_conversation
961+ )
962+
963+ query_request = QueryRequest (query = "test" ) # pyright: ignore[reportCallIssue]
964+
965+ mocker .patch ("utils.responses.configuration" , mocker .Mock ())
966+ mocker .patch (
967+ "utils.responses.select_model_and_provider_id" ,
968+ return_value = ("provider1/model1" , "model1" , "provider1" ),
969+ )
970+ mocker .patch ("utils.responses.evaluate_model_hints" , return_value = (None , None ))
971+ mocker .patch ("utils.responses.get_system_prompt" , return_value = "System prompt" )
972+ mocker .patch ("utils.responses.prepare_tools" , return_value = None )
973+ mocker .patch ("utils.responses.prepare_input" , return_value = "test" )
974+
975+ result = await prepare_responses_params (
976+ mock_client , query_request , None , "token"
977+ )
978+
979+ dumped = result .model_dump ()
980+ assert dumped .get ("extra_headers" ) is None
981+
877982 @pytest .mark .asyncio
878983 async def test_prepare_responses_params_api_status_error_on_conversation (
879984 self , mocker : MockerFixture
0 commit comments