mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
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:
parent
1fcdb3d92a
commit
ee76c9a6f4
2 changed files with 35 additions and 4 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 == {}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue