diff --git a/litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py b/litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py index 0e40d48efa..3a3bd4744b 100644 --- a/litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py +++ b/litellm/proxy/_experimental/mcp_server/auth/user_api_key_auth_mcp.py @@ -142,12 +142,18 @@ class MCPRequestHandler: api_key=litellm_api_key, request=request ) except (HTTPException, ProxyException) as e: - # HTTPException.status_code is int; ProxyException.code is normalized - # to str in its __init__ (proxy/_types.py). - status = e.status_code if isinstance(e, HTTPException) else int(e.code) + # HTTPException.status_code is int; ProxyException.code is + # normalized to str in its __init__ but can be ``"None"`` or any + # non-numeric string when the caller didn't supply a numeric + # code, so we compare against both int and str forms rather + # than coercing (``int("None")`` would raise ValueError and + # rewrite the auth error as a 500). + status = e.status_code if isinstance(e, HTTPException) else e.code if status in ( 401, 403, + "401", + "403", ) and MCPRequestHandler._target_servers_use_oauth2( path=request.url.path, mcp_servers=mcp_servers ): diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/auth/test_user_api_key_auth_mcp.py b/tests/test_litellm/proxy/_experimental/mcp_server/auth/test_user_api_key_auth_mcp.py index 2256935f35..c6dd1d83bc 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/auth/test_user_api_key_auth_mcp.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/auth/test_user_api_key_auth_mcp.py @@ -1028,6 +1028,38 @@ class TestMCPOAuth2FallbackTargetGating: await MCPRequestHandler.process_mcp_request(scope) assert exc_info.value.status_code == 401 + async def test_proxy_exception_with_non_numeric_code_propagates(self): + """ + ``ProxyException`` normalises ``code`` via ``str()`` in its __init__, + so callers may produce ``"None"`` or any non-numeric string when no + explicit code was supplied. The exception handler must not coerce + with ``int(...)`` (which would raise ``ValueError`` and rewrite the + auth error as an unhandled 500); it must simply re-raise. + """ + from litellm.proxy._types import ProxyException + + scope = { + "type": "http", + "method": "POST", + "path": "/mcp/atlassian_mcp", + "headers": [(b"authorization", b"Bearer anything")], + } + + async def mock_user_api_key_auth_no_code(api_key, request): + raise ProxyException( + message="Authentication Error", + type="auth_error", + param="api_key", + code=None, + ) + + with patch( + "litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp.user_api_key_auth", + side_effect=mock_user_api_key_auth_no_code, + ): + with pytest.raises(ProxyException): + await MCPRequestHandler.process_mcp_request(scope) + class TestMCPCustomHeaderName: """Test suite for custom MCP authentication header name functionality"""