From 364e466210a92cae9f60acf605a7257698b5f9d3 Mon Sep 17 00:00:00 2001 From: Joshua Valluru <326636767+joshua-berri@users.noreply.github.com> Date: Sat, 12 Sep 2026 17:18:54 -0700 Subject: [PATCH] fix(mcp): strip duplicate delegated credentials --- .../mcp_server/mcp_server_manager.py | 14 ++++----- .../mcp_server/test_mcp_server.py | 30 +++++++++++++++++++ .../mcp_server/test_mcp_server_manager.py | 23 ++++++++++++++ 3 files changed, 59 insertions(+), 8 deletions(-) diff --git a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py index d186b4178b4..fb0c623473a 100644 --- a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py +++ b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py @@ -1083,7 +1083,9 @@ def _should_strip_caller_authorization( has_explicit_litellm_admission_header: Final = _has_explicit_litellm_admission_header(raw_headers) if is_delegated_oauth: - return not has_explicit_litellm_admission_header + return not has_explicit_litellm_admission_header or _authorization_is_litellm_admission_credential( + raw_headers, user_api_key_auth + ) return _authorization_is_litellm_admission_credential(raw_headers, user_api_key_auth) or ( user_api_key_auth is None and not has_explicit_litellm_admission_header ) @@ -1110,15 +1112,11 @@ def _authorization_is_litellm_admission_credential( That is the case when no usable ``x-litellm-api-key`` was sent, or when the client repeated the same key in both headers. """ - if user_api_key_auth is None or not user_api_key_auth.api_key: - return False admission_header: Final = _raw_header_value(raw_headers, "x-litellm-api-key") - if not admission_header: - return True authorization: Final = _raw_header_value(raw_headers, "authorization") - return authorization is not None and strip_auth_scheme(authorization, "Bearer") == strip_auth_scheme( - admission_header, "Bearer" - ) + if admission_header and authorization: + return strip_auth_scheme(authorization, "Bearer") == strip_auth_scheme(admission_header, "Bearer") + return bool(user_api_key_auth and user_api_key_auth.api_key and not admission_header) def _format_byok_openapi_auth_header(mcp_server: MCPServer, mcp_auth_header: str) -> str: diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server.py index 7dbf49788a0..f5e4a420496 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server.py @@ -578,6 +578,36 @@ def test_prepare_mcp_server_headers_legacy_delegate_preserves_separate_upstream_ assert extra_headers == {"Authorization": "Bearer upstream-token"} +def test_prepare_mcp_server_headers_legacy_delegate_strips_repeated_admission_key(): + from litellm.proxy._experimental.mcp_server.server import ( + _prepare_mcp_server_headers, + ) + from litellm.proxy._types import UserAPIKeyAuth + + server = MCPServer( + server_id="legacy-delegate-repeated-key", + name="legacy-delegate", + transport=MCPTransport.http, + auth_type=MCPAuth.oauth2, + delegate_auth_to_upstream=True, + ) + + server_auth_header, extra_headers = _prepare_mcp_server_headers( + server=server, + mcp_server_auth_headers=None, + mcp_auth_header=None, + oauth2_headers={"Authorization": "Bearer sk-litellm-key"}, + raw_headers={ + "x-litellm-api-key": "Bearer sk-litellm-key", + "authorization": "Bearer sk-litellm-key", + }, + user_api_key_auth=UserAPIKeyAuth(api_key="sk-litellm-key"), + ) + + assert server_auth_header is None + assert extra_headers is None + + def test_prepare_mcp_server_headers_m2m_skips_authorization_from_raw_extra_headers(): """M2M must not merge caller Authorization from raw_headers when extra_headers lists it.""" try: diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py index dc4d7721190..3ccb388116b 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py @@ -3433,6 +3433,29 @@ class TestMCPServerManager: ) assert extra_headers == {"Authorization": "Bearer upstream-token"} + @pytest.mark.asyncio + async def test_call_regular_mcp_tool_legacy_delegate_strips_repeated_admission_key(self): + from litellm.proxy._types import UserAPIKeyAuth + + server = MCPServer( + server_id="server-legacy-delegate-repeated-key", + name="legacy-delegate", + url="https://example.com", + transport=MCPTransport.http, + auth_type=MCPAuth.oauth2, + delegate_auth_to_upstream=True, + ) + extra_headers = await self._capture_call_extra_headers( + server, + oauth2_headers={"Authorization": "Bearer sk-litellm-key"}, + raw_headers={ + "x-litellm-api-key": "Bearer sk-litellm-key", + "authorization": "Bearer sk-litellm-key", + }, + user_api_key_auth=UserAPIKeyAuth(api_key="sk-litellm-key"), + ) + assert not extra_headers or "authorization" not in {k.lower() for k in extra_headers} + def test_should_strip_caller_authorization_new_modes(self): from litellm.proxy._types import UserAPIKeyAuth