@@ -946,6 +946,111 @@ async def test_prepare_responses_params_api_status_error_on_models(
946946 await prepare_responses_params (mock_client , query_request , None , "token" )
947947 assert exc_info .value .status_code == 500
948948
949+ @pytest .mark .asyncio
950+ async def test_prepare_responses_params_includes_mcp_provider_data_headers (
951+ self , mocker : MockerFixture
952+ ) -> None :
953+ """Test that extra_headers with x-llamastack-provider-data is set when MCP tools have headers."""
954+ mock_client = mocker .AsyncMock ()
955+ mock_model = mocker .Mock ()
956+ mock_model .id = "provider1/model1"
957+ mock_model .custom_metadata = {"model_type" : "llm" , "provider_id" : "provider1" }
958+ mock_client .models .list = mocker .AsyncMock (return_value = [mock_model ])
959+
960+ mock_conversation = mocker .Mock ()
961+ mock_conversation .id = "new_conv_id"
962+ mock_client .conversations .create = mocker .AsyncMock (
963+ return_value = mock_conversation
964+ )
965+
966+ query_request = QueryRequest (query = "test" ) # pyright: ignore[reportCallIssue]
967+
968+ # Simulate MCP tools with headers (as returned by prepare_tools/get_mcp_tools)
969+ mcp_tools_with_headers = [
970+ {
971+ "type" : "mcp" ,
972+ "server_label" : "mcp::aap-controller" ,
973+ "server_url" : "http://aap.foo.redhat.com:8004/sse" ,
974+ "require_approval" : "never" ,
975+ "headers" : {"X-Authorization" : "client-token" },
976+ },
977+ {
978+ "type" : "mcp" ,
979+ "server_label" : "mcp::aap-lightspeed" ,
980+ "server_url" : "http://aap.foo.redhat.com:8005/sse" ,
981+ "require_approval" : "never" ,
982+ "headers" : {"X-Authorization" : "client-token-2" },
983+ },
984+ ]
985+
986+ mocker .patch ("utils.responses.configuration" , mocker .Mock ())
987+ mocker .patch (
988+ "utils.responses.select_model_and_provider_id" ,
989+ return_value = ("provider1/model1" , "model1" , "provider1" ),
990+ )
991+ mocker .patch ("utils.responses.evaluate_model_hints" , return_value = (None , None ))
992+ mocker .patch ("utils.responses.get_system_prompt" , return_value = "System prompt" )
993+ mocker .patch (
994+ "utils.responses.prepare_tools" , return_value = mcp_tools_with_headers
995+ )
996+ mocker .patch ("utils.responses.prepare_input" , return_value = "test" )
997+
998+ result = await prepare_responses_params (
999+ mock_client , query_request , None , "token"
1000+ )
1001+
1002+ # The result should contain extra_headers with x-llamastack-provider-data
1003+ dumped = result .model_dump ()
1004+ assert (
1005+ dumped ["extra_headers" ] is not None
1006+ ), "extra_headers should not be None when MCP tools have headers"
1007+ assert "x-llamastack-provider-data" in dumped ["extra_headers" ]
1008+
1009+ provider_data = json .loads (
1010+ dumped ["extra_headers" ]["x-llamastack-provider-data" ]
1011+ )
1012+ assert "mcp_headers" in provider_data
1013+ assert provider_data ["mcp_headers" ] == {
1014+ "http://aap.foo.redhat.com:8004/sse" : {"X-Authorization" : "client-token" },
1015+ "http://aap.foo.redhat.com:8005/sse" : {"X-Authorization" : "client-token-2" },
1016+ }
1017+
1018+ @pytest .mark .asyncio
1019+ async def test_prepare_responses_params_no_extra_headers_without_mcp_tools (
1020+ self , mocker : MockerFixture
1021+ ) -> None :
1022+ """Test that extra_headers is None when no MCP tools have headers."""
1023+ mock_client = mocker .AsyncMock ()
1024+ mock_model = mocker .Mock ()
1025+ mock_model .id = "provider1/model1"
1026+ mock_model .custom_metadata = {"model_type" : "llm" , "provider_id" : "provider1" }
1027+ mock_client .models .list = mocker .AsyncMock (return_value = [mock_model ])
1028+
1029+ mock_conversation = mocker .Mock ()
1030+ mock_conversation .id = "new_conv_id"
1031+ mock_client .conversations .create = mocker .AsyncMock (
1032+ return_value = mock_conversation
1033+ )
1034+
1035+ query_request = QueryRequest (query = "test" ) # pyright: ignore[reportCallIssue]
1036+
1037+ mocker .patch ("utils.responses.configuration" , mocker .Mock ())
1038+ mocker .patch (
1039+ "utils.responses.select_model_and_provider_id" ,
1040+ return_value = ("provider1/model1" , "model1" , "provider1" ),
1041+ )
1042+ mocker .patch ("utils.responses.evaluate_model_hints" , return_value = (None , None ))
1043+ mocker .patch ("utils.responses.get_system_prompt" , return_value = "System prompt" )
1044+ mocker .patch ("utils.responses.prepare_tools" , return_value = None )
1045+ mocker .patch ("utils.responses.prepare_input" , return_value = "test" )
1046+
1047+ result = await prepare_responses_params (
1048+ mock_client , query_request , None , "token"
1049+ )
1050+
1051+ dumped = result .model_dump ()
1052+ assert dumped .get ("extra_headers" ) is None
1053+
9491054 @pytest .mark .asyncio
9501055 async def test_prepare_responses_params_api_status_error_on_conversation (
9511056 self , mocker : MockerFixture
0 commit comments