fix(mcp): accept raw x-litellm-api-key on streamable HTTP admission (#38364)

* fix(mcp): accept raw x-litellm-api-key on streamable HTTP admission

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* chore(mcp): drop comments restating parser behavior

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
devin-ai-integration[bot] 2026-08-26 20:45:51 -07:00 committed by GitHub
parent 1fcdb3d92a
commit ee76c9a6f4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 35 additions and 4 deletions

View file

@ -46,6 +46,7 @@ from litellm.proxy._types import (
)
from litellm.proxy.auth.ip_address_utils import IPAddressUtils
from litellm.proxy.auth.user_api_key_auth import (
_get_bearer_token_or_received_api_key, # pyright: ignore[reportPrivateUsage] # shared x-litellm-api-key parser lives with user_api_key_auth
_run_centralized_common_checks,
user_api_key_auth,
)
@ -429,7 +430,10 @@ class MCPRequestHandler:
# An explicit x-litellm-api-key is always a LiteLLM credential, even
# for a delegated server, so validate it: identity / spend / rate
# limits resolve and any stored upstream token can be forwarded.
validated_user_api_key_auth = await user_api_key_auth(api_key=litellm_api_key, request=request)
validated_user_api_key_auth = await user_api_key_auth(
api_key=f"Bearer {_get_bearer_token_or_received_api_key(litellm_api_key)}",
request=request,
)
elif MCPRequestHandler._target_servers_delegate_auth_to_upstream(
path=request_route,
mcp_servers=mcp_servers,

View file

@ -1082,11 +1082,38 @@ class TestMCPOAuth2AuthFlow:
# LiteLLM key should be used for auth
mock_auth.assert_called_once()
call_args = mock_auth.call_args
assert call_args.kwargs["api_key"] == "sk-litellm-valid-key"
assert call_args.kwargs["api_key"] == "Bearer sk-litellm-valid-key"
# OAuth2 headers should still contain the Authorization token
assert oauth2_headers.get("Authorization") == "Bearer atlassian-oauth2-token"
@pytest.mark.parametrize(
"header_value",
[b"sk-litellm-valid-key", b"Bearer sk-litellm-valid-key", b"bearer sk-litellm-valid-key"],
)
async def test_x_litellm_api_key_survives_bearer_only_strip(self, header_value):
from litellm.proxy.auth.user_api_key_auth import _get_bearer_token
scope = {
"type": "http",
"method": "POST",
"path": "/mcp/some_server",
"headers": [(b"x-litellm-api-key", header_value)],
}
async def mock_user_api_key_auth(api_key, request):
return UserAPIKeyAuth(api_key=api_key, user_id="test-user")
with patch( # test-quality-ok: capturing the exact api_key handed to key validation is the regression under test
"litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp.user_api_key_auth",
side_effect=mock_user_api_key_auth,
) as mock_auth:
auth_result, *_rest = await MCPRequestHandler.process_mcp_request(scope)
mock_auth.assert_called_once()
assert _get_bearer_token(api_key=mock_auth.call_args.kwargs["api_key"]) == "sk-litellm-valid-key"
assert auth_result.user_id == "test-user"
async def test_litellm_key_in_authorization_backward_compat(self):
"""
Backward compatibility: when only Authorization header is present
@ -3007,7 +3034,7 @@ class TestMCPCustomHeaderName:
# Verify the mock was called
mock_auth.assert_called_once()
call_args = mock_auth.call_args
assert call_args.kwargs["api_key"] == "test-api-key"
assert call_args.kwargs["api_key"] == "Bearer test-api-key"
def test_get_mcp_server_auth_headers_from_headers(self):
"""Test _get_mcp_server_auth_headers_from_headers method"""
@ -6254,7 +6281,7 @@ class TestMCPDcrBridgeDelegateAdmission:
) = await MCPRequestHandler.process_mcp_request(scope)
mock_auth.assert_called_once()
assert mock_auth.call_args.kwargs["api_key"] == "sk-explicit-litellm-key"
assert mock_auth.call_args.kwargs["api_key"] == "Bearer sk-explicit-litellm-key"
# The explicit-key arm admitted; the envelope arm never ran, so no inner token is injected.
assert auth_result.user_id == "litellm-key-user"
assert mcp_server_auth_headers == {}