mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-10 03:28:53 +00:00
fix(mcp): keep the plain OBO challenge naming the configured alias on every route
The connect preflight passes connected_as only for caller sign-in servers, so an oauth2_token_exchange server challenges with the merge-base resource_metadata path (its alias) on /mcp/<server_name>, the aggregate route and the legacy route as well as on /mcp/<alias>. The case-variant integration assertion that expected 403 is corrected to the 200 both base and head return Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
0a1134d06a
commit
0b262d6594
3 changed files with 15 additions and 10 deletions
|
|
@ -1741,6 +1741,9 @@ if MCP_AVAILABLE:
|
|||
# key without access gets the grant's 403 instead of a sign-in it could not use. The one
|
||||
# admission lookup above serves the challenge, the sign-in preflight and the exchange.
|
||||
sign_in = caller_sign_in_for(server, user_api_key_auth) if server is not None else None
|
||||
challenge_route: str | None = (
|
||||
None if server is not None and server.auth_type == MCPAuth.oauth2_token_exchange else server_name
|
||||
)
|
||||
subject_token = (
|
||||
operations.global_mcp_server_manager._extract_subject_token( # pyright: ignore[reportPrivateUsage] # the manager owns the subject/admission filter shared with the preflight
|
||||
oauth2_headers, raw_headers, user_api_key_auth
|
||||
|
|
@ -1756,7 +1759,7 @@ if MCP_AVAILABLE:
|
|||
get_request_root_path,
|
||||
)
|
||||
|
||||
raise_token_exchange_challenge(server, root_path=get_request_root_path(), connected_as=server_name)
|
||||
raise_token_exchange_challenge(server, root_path=get_request_root_path(), connected_as=challenge_route)
|
||||
if server and sign_in is not None and subject_token is not None and granted_single:
|
||||
from litellm.proxy._experimental.mcp_server.caller_sign_in import ( # noqa: PLC0415 # lazy: provider discovery pulls the guardrail registry
|
||||
preflight_caller_sign_in,
|
||||
|
|
@ -1770,7 +1773,7 @@ if MCP_AVAILABLE:
|
|||
user_api_key_auth,
|
||||
subject_token,
|
||||
root_path=get_request_root_path(),
|
||||
connected_as=server_name,
|
||||
connected_as=challenge_route,
|
||||
)
|
||||
|
||||
# Exchange-backed modes (token_exchange's OBO mint, id_jag's stored-assertion mint): run
|
||||
|
|
@ -1786,7 +1789,7 @@ if MCP_AVAILABLE:
|
|||
oauth2_headers=oauth2_headers,
|
||||
user_api_key_auth=user_api_key_auth,
|
||||
raw_headers=raw_headers,
|
||||
connected_as=server_name,
|
||||
connected_as=challenge_route,
|
||||
)
|
||||
|
||||
# Pass-through OAuth: when the admin has opted a server into
|
||||
|
|
|
|||
|
|
@ -211,7 +211,7 @@ def test_name_of_a_server_hidden_from_an_external_ip_does_not_reroute_to_a_case_
|
|||
candidate.client.get(f"/.well-known/oauth-protected-resource/mcp/{stem}", headers=external).status_code
|
||||
== 404
|
||||
)
|
||||
assert _rpc(candidate, f"/mcp/{stem}", key, {}).status_code == 403
|
||||
assert _rpc(candidate, f"/mcp/{stem}", key, {}).status_code == 200
|
||||
|
||||
own_name: Final = _rpc(candidate, f"/mcp/{cased}", key, external)
|
||||
assert own_name.status_code == 200, own_name.text
|
||||
|
|
|
|||
|
|
@ -10664,7 +10664,7 @@ class TestOboPreflightScopedToAllowedServers:
|
|||
"x-litellm-api-key": key.api_key,
|
||||
"authorization": self.SUBJECT_HEADERS["Authorization"],
|
||||
},
|
||||
connected_as=requested.alias,
|
||||
connected_as=None,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -11574,13 +11574,15 @@ class TestConnectChallengeResolver:
|
|||
assert exc.value.status_code == 401
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_obo_challenge_www_authenticate_matches_main_byte_for_byte(self, monkeypatch):
|
||||
@pytest.mark.parametrize("route_name", ["obo", "obo_server"], ids=["alias_route", "server_name_route"])
|
||||
async def test_obo_challenge_www_authenticate_matches_main_byte_for_byte(self, monkeypatch, route_name):
|
||||
"""The provider redesign must not change what an OBO server challenges with: the relative
|
||||
RFC 9728 resource_metadata path plus the RFC 6750 invalid_token triple, exactly as main."""
|
||||
RFC 9728 resource_metadata path naming the configured alias whichever route the client used,
|
||||
plus the RFC 6750 invalid_token triple, exactly as main."""
|
||||
from litellm.proxy._experimental.mcp_server import server as server_module
|
||||
|
||||
monkeypatch.delenv("SERVER_ROOT_PATH", raising=False)
|
||||
obo = _make_obo_server("obo")
|
||||
obo = _make_obo_server("obo").model_copy(update={"name": "obo_server", "server_name": "obo_server"})
|
||||
with (
|
||||
patch.object(
|
||||
mcp_operations.global_mcp_server_manager,
|
||||
|
|
@ -11590,8 +11592,8 @@ class TestConnectChallengeResolver:
|
|||
pytest.raises(HTTPException) as exc,
|
||||
):
|
||||
await server_module._raise_preemptive_401_for_unauthenticated_servers(
|
||||
scope={"type": "http", "method": "POST", "path": "/mcp/obo", "headers": []},
|
||||
mcp_servers=["obo"],
|
||||
scope={"type": "http", "method": "POST", "path": f"/mcp/{route_name}", "headers": []},
|
||||
mcp_servers=[route_name],
|
||||
oauth2_headers=None,
|
||||
mcp_server_auth_headers=None,
|
||||
user_api_key_auth=UserAPIKeyAuth(api_key="sk-litellm-virtual-key", user_id="u-1"),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue