diff --git a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py index adf90317aa8..3fa14e6ec25 100644 --- a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py +++ b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py @@ -2251,15 +2251,19 @@ class MCPServerManager: # For OpenAPI servers, call the tool handler directly instead of via MCP client if mcp_server.spec_path: verbose_logger.debug( - f"Calling OpenAPI tool {name} directly via HTTP handler" + "Calling OpenAPI tool %s directly via HTTP handler", name ) if hook_result.get("extra_headers"): - verbose_logger.warning( - "pre_mcp_call hook returned extra_headers, but OpenAPI-backed " - "MCP servers do not support hook header injection. " - f"Headers will be dropped for tool '{name}' on server " - f"'{server_name}'. Use a regular MCP server (SSE/HTTP transport) " - "for hook header support." + raise HTTPException( + status_code=500, + detail={ + "error": ( + "pre_mcp_call hook returned extra_headers for an " + "OpenAPI-backed MCP server, which does not support " + "hook header injection. Use a regular MCP server " + "(SSE/HTTP transport) for hook header support." + ) + }, ) tasks.append( asyncio.create_task( diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_hook_extra_headers.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_hook_extra_headers.py index fe2d2ab820b..6c97ff244e8 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_hook_extra_headers.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_hook_extra_headers.py @@ -6,15 +6,12 @@ Validates that: 2. pre_call_tool_check returns hook-provided extra_headers AND modified arguments 3. call_tool flows hook headers and modified arguments downstream 4. Hook-provided headers take highest priority (merge after static_headers) -5. OpenAPI-backed servers emit a warning when hook headers are present +5. OpenAPI-backed servers raise HTTPException when hook headers are present 6. JWT claims are propagated in both standard and virtual-key fast paths 7. Backward compatibility: hooks without extra_headers continue to work """ import asyncio -import logging -import sys -from datetime import datetime from typing import Any, Dict, Optional from unittest.mock import AsyncMock, MagicMock, patch @@ -425,8 +422,8 @@ class TestCallToolFlowsHookHeaders: assert call_kwargs.kwargs.get("arguments") == modified_args @pytest.mark.asyncio - async def test_openapi_server_warns_on_hook_headers(self, caplog): - """OpenAPI-backed servers should log a warning when hook injects headers.""" + async def test_openapi_server_raises_on_hook_headers(self): + """OpenAPI-backed servers should raise HTTPException when hook injects headers.""" manager = MCPServerManager() server = MCPServer( server_id="test-id", @@ -452,30 +449,24 @@ class TestCallToolFlowsHookHeaders: "_create_during_hook_task", return_value=asyncio.create_task(asyncio.sleep(0)), ): - with patch.object( - manager, - "_call_openapi_tool_handler", - new_callable=AsyncMock, - return_value=MagicMock(), - ): - proxy_logging = MagicMock(spec=ProxyLogging) + proxy_logging = MagicMock(spec=ProxyLogging) - with caplog.at_level(logging.WARNING): - await manager.call_tool( - server_name="openapi_server", - name="test_tool", - arguments={}, - proxy_logging_obj=proxy_logging, - ) - - assert any( - "do not support hook header injection" in record.message - for record in caplog.records + with pytest.raises(HTTPException) as exc_info: + await manager.call_tool( + server_name="openapi_server", + name="test_tool", + arguments={}, + proxy_logging_obj=proxy_logging, ) + assert exc_info.value.status_code == 500 + assert "does not support hook header injection" in str( + exc_info.value.detail + ) + @pytest.mark.asyncio - async def test_openapi_server_no_warning_without_hook_headers(self, caplog): - """No warning when OpenAPI server has no hook-injected headers.""" + async def test_openapi_server_no_error_without_hook_headers(self): + """No exception when OpenAPI server has no hook-injected headers.""" manager = MCPServerManager() server = MCPServer( server_id="test-id", @@ -509,17 +500,11 @@ class TestCallToolFlowsHookHeaders: ): proxy_logging = MagicMock(spec=ProxyLogging) - with caplog.at_level(logging.WARNING): - await manager.call_tool( - server_name="openapi_server", - name="test_tool", - arguments={}, - proxy_logging_obj=proxy_logging, - ) - - assert not any( - "do not support hook header injection" in record.message - for record in caplog.records + await manager.call_tool( + server_name="openapi_server", + name="test_tool", + arguments={}, + proxy_logging_obj=proxy_logging, )