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 7d85f3c4908..281a555dc5c 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 @@ -1,6 +1,8 @@ import re from collections.abc import Sequence +from dataclasses import dataclass from datetime import datetime, timezone +from types import MappingProxyType from typing import TYPE_CHECKING, Final, cast from fastapi import HTTPException @@ -13,6 +15,7 @@ import litellm from litellm._logging import verbose_logger from litellm.proxy._experimental.mcp_server.oauth_utils import ( get_passthrough_resource_metadata_url, + get_passthrough_www_authenticate, get_request_base_url, well_known_root_suffix, ) @@ -298,6 +301,16 @@ def _admission_failure_fallback( raise exc +@dataclass(frozen=True, slots=True) +class DcrBridgeTarget: + """The single DCR-bridge server a request targets, paired with the exact name the caller + used to reach it (alias or server_name, whichever they typed), which is the spelling an + ``invalid_token`` challenge must echo back.""" + + requested_name: str + server: MCPServer + + class MCPRequestHandler: """ Class to handle MCP request processing, including: @@ -437,27 +450,33 @@ class MCPRequestHandler: path=request_route, mcp_servers=mcp_servers, client_ip=IPAddressUtils.get_mcp_client_ip(request), + ) or ( + MCPRequestHandler._single_dcr_bridge_delegate_target( + path=request_route, + mcp_servers=mcp_servers, + client_ip=IPAddressUtils.get_mcp_client_ip(request), + ) + is not None + and not oauth2_headers + and not mcp_server_auth_headers + and not mcp_auth_header ): validated_user_api_key_auth = UserAPIKeyAuth() elif ( - ( - bridge_delegate_target := MCPRequestHandler._single_dcr_bridge_delegate_target( - path=request_route, - mcp_servers=mcp_servers, - client_ip=IPAddressUtils.get_mcp_client_ip(request), - ) + bridge_delegate_target := MCPRequestHandler._single_dcr_bridge_delegate_target( + path=request_route, + mcp_servers=mcp_servers, + client_ip=IPAddressUtils.get_mcp_client_ip(request), ) - is not None - and oauth2_headers - and is_bridge_envelope_shaped(oauth2_headers["Authorization"]) - ): - # A single DCR-bridge oauth_delegate target carrying an envelope-shaped - # Authorization: open the envelope, admit under its recovered identity, and - # inject the inner upstream token for egress. A non-envelope bearer on the same - # server is NOT admitted here — it falls through to the oauth2 arm, which 401s. - validated_user_api_key_auth, mcp_server_auth_headers = await MCPRequestHandler._admit_dcr_bridge_delegate( - server=bridge_delegate_target, + ) is not None and oauth2_headers: + ( + validated_user_api_key_auth, + mcp_server_auth_headers, + ) = await MCPRequestHandler._admit_dcr_bridge_authorization( + server=bridge_delegate_target.server, + requested_name=bridge_delegate_target.requested_name, authorization_value=oauth2_headers["Authorization"], + litellm_api_key=litellm_api_key, mcp_server_auth_headers=mcp_server_auth_headers, request=request, route=request_route, @@ -723,10 +742,10 @@ class MCPRequestHandler: @staticmethod def _single_dcr_bridge_delegate_target( path: str, mcp_servers: list[str] | None, client_ip: str | None - ) -> MCPServer | None: + ) -> DcrBridgeTarget | None: """The one DCR-bridge ``oauth_delegate`` server this request targets, or ``None``. - Returns the server only when EXACTLY ONE target resolves and it is both + Returns the target only when EXACTLY ONE name resolves and its server is both ``is_oauth_delegate`` and ``is_dcr_bridge``. Fails closed (``None``) on a multi-target request, an unresolved target, or a non-matching server, so the envelope admission arm never fires for an aggregate scope or a server that did not @@ -740,17 +759,21 @@ class MCPRequestHandler: if len(target_names) != 1: return None server: Final = global_mcp_server_manager.get_mcp_server_by_name(target_names[0], client_ip=client_ip) - if server is None or not server.is_oauth_delegate or not server.is_dcr_bridge: + # Both flags are security-sensitive opt-ins. Require literal booleans so + # partially populated objects and truthy proxy values cannot enable bridge + # admission accidentally. + if server is None or server.is_oauth_delegate is not True or server.is_dcr_bridge is not True: return None # Egress resolves the injected per-server token only by alias / server_name; a server with # neither cannot receive the forwarded token, so fail closed rather than admit-and-drop. if not (server.server_name or server.alias): return None - return server + return DcrBridgeTarget(requested_name=target_names[0], server=server) @staticmethod async def _admit_dcr_bridge_delegate( server: MCPServer, + requested_name: str, authorization_value: str, mcp_server_auth_headers: dict[str, dict[str, str]] | None, request: Request, @@ -798,10 +821,62 @@ class MCPRequestHandler: new_headers: Final = {**(mcp_server_auth_headers or {}), **injected} return admitted, new_headers case BridgeEnvelopeInvalid() | NotBridgeEnvelope(): - raise HTTPException(status_code=401, detail="Invalid or expired credential") + raise MCPRequestHandler._dcr_bridge_invalid_token_challenge( + requested_name=requested_name, request=request + ) case _: assert_never(result) + @staticmethod + async def _admit_dcr_bridge_authorization( + server: MCPServer, + requested_name: str, + authorization_value: str, + litellm_api_key: str, + mcp_server_auth_headers: dict[str, dict[str, str]] | None, # mutable-ok: existing MCP sink shape + request: Request, + route: str, + ) -> tuple[UserAPIKeyAuth, dict[str, dict[str, str]] | None]: # mutable-ok: existing MCP sink shape + if is_bridge_envelope_shaped(authorization_value): + return await MCPRequestHandler._admit_dcr_bridge_delegate( + server=server, + requested_name=requested_name, + authorization_value=authorization_value, + mcp_server_auth_headers=mcp_server_auth_headers, + request=request, + route=route, + ) + try: + admitted: Final = await user_api_key_auth(api_key=litellm_api_key, request=request) + except (HTTPException, ProxyException) as exc: + if not _is_litellm_auth_admission_error(exc): + raise + raise MCPRequestHandler._dcr_bridge_invalid_token_challenge( + requested_name=requested_name, request=request + ) from exc + return admitted, mcp_server_auth_headers + + @staticmethod + def _dcr_bridge_invalid_token_challenge(requested_name: str, request: Request) -> HTTPException: + """The RFC 6750 ``invalid_token`` challenge for a failed bridge admission. + + Named by the exact spelling the caller requested, matching the per-server well-known + document and the other challenge emitters, so ``resource_metadata`` always points at the + resource the client actually asked for even when alias and server_name differ.""" + return HTTPException( + status_code=401, + detail="Invalid or expired credential", + headers=MappingProxyType( + { + "www-authenticate": get_passthrough_www_authenticate( + scope=request.scope, + server_name=requested_name, + invalid_token=True, + ) + } + ), + ) + @staticmethod async def _admit_gateway_session( authorization_value: str, 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 697c9b018ec..99e3e8d7413 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 @@ -7,8 +7,6 @@ from unittest.mock import AsyncMock, MagicMock, patch import pytest from fastapi import HTTPException from fastapi.testclient import TestClient - - from starlette.datastructures import Headers from litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp import ( @@ -5097,13 +5095,10 @@ class TestMCPDcrBridgeDelegateAdmission: """Admission-side arm for a DCR-bridge ``oauth_delegate`` client that authenticates with a single envelope bearer (LIT-4338). - The arm fires only for a single ``is_dcr_bridge`` ``is_oauth_delegate`` target carrying an - envelope-shaped Authorization. It opens the litellm-signed envelope, reloads the live key - record the sealed ``key_hash`` references so the caller is admitted under the key's current - authorization context (team/org/object-permission) and revocation state, and injects the inner - upstream token under the server's per-server auth-header key so egress forwards it. A key that - is missing, blocked, or expired fails closed with a 401. Everything else must stay on its - existing admission path. + A credential-free request reaches the named MCP handler so it can issue the initial OAuth + challenge. Every bearer on that same route enters envelope resolution. A valid envelope opens + under its live authorization context, while invalid envelopes and non-envelope bearers receive + a named ``invalid_token`` challenge. Everything else stays on its existing admission path. """ _MASTER_KEY = "sk-bridge-master-key-for-envelope-derivation" @@ -5138,6 +5133,8 @@ class TestMCPDcrBridgeDelegateAdmission: minted_at=None, master_key=None, ): + from pydantic import SecretStr + from litellm.proxy._experimental.mcp_server.outbound_credentials.bridge_credentials import ( envelope_keys_from_master_key, ) @@ -5148,7 +5145,6 @@ class TestMCPDcrBridgeDelegateAdmission: mint_envelope, user_identity, ) - from pydantic import SecretStr identity = ( user_identity(server_id=server_id, user_id=user_id) @@ -5272,6 +5268,92 @@ class TestMCPDcrBridgeDelegateAdmission: request.body = mock_body return request + async def test_bridge_target_requires_literal_boolean_opt_ins(self): + """Truthy proxy values must not opt an unresolved server into bridge admission.""" + for delegate_value, bridge_value in ((MagicMock(), True), (True, MagicMock())): + server = MagicMock() + server.is_oauth_delegate = delegate_value + server.is_dcr_bridge = bridge_value + server.server_name = "bridge_delegate_server" + server.alias = None + + with patch( # test-quality-ok: isolate the MCP registry when testing target selection + "litellm.proxy._experimental.mcp_server.mcp_server_manager.global_mcp_server_manager" + ) as mock_mgr: + mock_mgr.get_mcp_server_by_name.return_value = server + assert ( + MCPRequestHandler._single_dcr_bridge_delegate_target( + path="/mcp/bridge_delegate_server", + mcp_servers=None, + client_ip=None, + ) + is None + ) + + async def test_credential_free_named_bridge_request_reaches_mcp_handler(self): + scope = { + "type": "http", + "method": "POST", + "path": "/mcp/bridge_delegate_server", + "headers": [], + } + + with ( + patch( # test-quality-ok: observe the auth boundary while testing admission orchestration + "litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp.user_api_key_auth", + new_callable=AsyncMock, + ) as mock_auth, + patch( # test-quality-ok: isolate the MCP registry used by request admission + "litellm.proxy._experimental.mcp_server.mcp_server_manager.global_mcp_server_manager" + ) as mock_mgr, + ): + mock_mgr.get_mcp_server_by_name.return_value = self._bridge_delegate_server() + ( + auth_result, + _mcp_auth_header, + _mcp_servers, + mcp_server_auth_headers, + _oauth2_headers, + _raw_headers, + ) = await MCPRequestHandler.process_mcp_request(scope) + + mock_auth.assert_not_called() + assert auth_result == UserAPIKeyAuth() + assert mcp_server_auth_headers == {} + + @pytest.mark.parametrize( + "headers", + ( + [(b"x-mcp-auth", b"Bearer upstream-token")], + [(b"x-mcp-bridge_delegate_server-authorization", b"Bearer upstream-token")], + ), + ids=("deprecated-mcp-auth", "per-server-auth"), + ) + async def test_client_mcp_credentials_do_not_receive_keyless_bridge_admission(self, headers): + scope = { + "type": "http", + "method": "POST", + "path": "/mcp/bridge_delegate_server", + "headers": headers, + } + + with ( + patch( # test-quality-ok: force credential rejection through request admission + "litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp.user_api_key_auth", + new_callable=AsyncMock, + side_effect=HTTPException(status_code=401, detail="Invalid key"), + ) as mock_auth, + patch( # test-quality-ok: isolate the MCP registry used by request admission + "litellm.proxy._experimental.mcp_server.mcp_server_manager.global_mcp_server_manager" + ) as mock_mgr, + ): + mock_mgr.get_mcp_server_by_name.return_value = self._bridge_delegate_server() + with pytest.raises(HTTPException) as exc_info: + await MCPRequestHandler.process_mcp_request(scope) + + assert exc_info.value.status_code == 401 + mock_auth.assert_awaited_once() + async def test_valid_envelope_reloads_live_key_and_admits_its_authorization_context(self): """A valid envelope admits under the LIVE key record the sealed key_hash references, not a blank identity: the reload is keyed by that exact hash, and the admitted auth carries the @@ -5809,6 +5891,7 @@ class TestMCPDcrBridgeDelegateAdmission: ): _auth, new_headers = await MCPRequestHandler._admit_dcr_bridge_delegate( server=self._bridge_delegate_server(server_name="bridge_name", alias="bridge_alias"), + requested_name="bridge_name", authorization_value=f"Bearer {envelope}", mcp_server_auth_headers=attacker_forwarded, request=self._mcp_request(), @@ -5845,6 +5928,7 @@ class TestMCPDcrBridgeDelegateAdmission: ): _auth, new_headers = await MCPRequestHandler._admit_dcr_bridge_delegate( server=server, + requested_name="bridge_delegate_server", authorization_value=f"Bearer {envelope}", mcp_server_auth_headers=None, request=self._mcp_request(), @@ -5887,8 +5971,7 @@ class TestMCPDcrBridgeDelegateAdmission: mock_auth.assert_called_once() async def test_expired_envelope_fails_closed_401(self): - """An envelope whose exp is in the past must fail closed with a 401, never fall through to - anonymous admission.""" + """An expired envelope fails closed and tells the client where to reauthorize.""" expired = self._mint_bridge_envelope( expires_in=60, minted_at=datetime.now(timezone.utc) - timedelta(hours=2), @@ -5897,7 +5980,10 @@ class TestMCPDcrBridgeDelegateAdmission: "type": "http", "method": "POST", "path": "/mcp/bridge_delegate_server", - "headers": [(b"authorization", f"Bearer {expired}".encode("latin-1"))], + "headers": [ + (b"host", b"testserver"), + (b"authorization", f"Bearer {expired}".encode("latin-1")), + ], } with ( @@ -5914,6 +6000,12 @@ class TestMCPDcrBridgeDelegateAdmission: assert exc_info.value.status_code == 401 mock_auth.assert_not_called() + assert exc_info.value.headers == { + "www-authenticate": ( + 'Bearer error="invalid_token", ' + 'resource_metadata="http://testserver/.well-known/oauth-protected-resource/mcp/bridge_delegate_server"' + ) + } async def test_envelope_minted_for_a_different_server_fails_closed_401(self): """An envelope sealed for another server_id must be rejected when presented to this server, @@ -5924,7 +6016,10 @@ class TestMCPDcrBridgeDelegateAdmission: "type": "http", "method": "POST", "path": "/mcp/bridge_delegate_server", - "headers": [(b"authorization", f"Bearer {wrong_server}".encode("latin-1"))], + "headers": [ + (b"host", b"testserver"), + (b"authorization", f"Bearer {wrong_server}".encode("latin-1")), + ], } with ( @@ -5941,6 +6036,12 @@ class TestMCPDcrBridgeDelegateAdmission: assert exc_info.value.status_code == 401 mock_auth.assert_not_called() + assert exc_info.value.headers == { + "www-authenticate": ( + 'Bearer error="invalid_token", ' + 'resource_metadata="http://testserver/.well-known/oauth-protected-resource/mcp/bridge_delegate_server"' + ) + } async def test_envelope_under_wrong_master_key_fails_closed_401(self): """An envelope-shaped bearer whose signature does not verify under the proxy's derived keys @@ -5950,7 +6051,10 @@ class TestMCPDcrBridgeDelegateAdmission: "type": "http", "method": "POST", "path": "/mcp/bridge_delegate_server", - "headers": [(b"authorization", f"Bearer {foreign}".encode("latin-1"))], + "headers": [ + (b"host", b"testserver"), + (b"authorization", f"Bearer {foreign}".encode("latin-1")), + ], } with ( @@ -5967,26 +6071,71 @@ class TestMCPDcrBridgeDelegateAdmission: assert exc_info.value.status_code == 401 mock_auth.assert_not_called() + assert exc_info.value.headers == { + "www-authenticate": ( + 'Bearer error="invalid_token", ' + 'resource_metadata="http://testserver/.well-known/oauth-protected-resource/mcp/bridge_delegate_server"' + ) + } - async def test_non_envelope_bearer_on_bridge_server_falls_through_to_oauth2_arm(self): - """A plain (non-envelope) bearer on the same bridge server must NOT be admitted by the - envelope arm: it falls through to the oauth2 arm, which validates it as a LiteLLM key and - 401s here. Proves the arm is gated on envelope shape, not merely on the target being a - bridge server.""" + @pytest.mark.parametrize("requested_name", ["bridge_name", "bridge_alias"]) + async def test_invalid_envelope_challenge_names_the_requested_spelling(self, requested_name): + """A server reachable under both its server_name and a distinct alias must challenge with + metadata for the exact spelling the caller used, matching the per-server well-known + document, so the client rediscovers against the resource it actually asked for.""" + foreign = self._mint_bridge_envelope(master_key="a-different-master-key-entirely") + scope = { + "type": "http", + "method": "POST", + "path": f"/mcp/{requested_name}", + "headers": [ + (b"host", b"testserver"), + (b"authorization", f"Bearer {foreign}".encode("latin-1")), + ], + } + + with ( + patch( # test-quality-ok: prove standard admission is never consulted for an envelope bearer + "litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp.user_api_key_auth", + new_callable=AsyncMock, + ) as mock_auth, + patch( # test-quality-ok: isolate the MCP registry, same seam as the sibling challenge tests + "litellm.proxy._experimental.mcp_server.mcp_server_manager.global_mcp_server_manager" + ) as mock_mgr, + patch("litellm.proxy.proxy_server.master_key", self._MASTER_KEY), # test-quality-ok: envelope keys derive from the proxy master_key module global + ): + mock_mgr.get_mcp_server_by_name.return_value = self._bridge_delegate_server( + server_name="bridge_name", alias="bridge_alias" + ) + with pytest.raises(HTTPException) as exc_info: + await MCPRequestHandler.process_mcp_request(scope) + + assert exc_info.value.status_code == 401 + mock_auth.assert_not_called() + assert exc_info.value.headers == { + "www-authenticate": ( + 'Bearer error="invalid_token", ' + f'resource_metadata="http://testserver/.well-known/oauth-protected-resource/mcp/{requested_name}"' + ) + } + + async def test_non_envelope_bearer_on_bridge_server_returns_named_challenge(self): + """A raw provider bearer cannot authorize a bridge route and triggers reauthorization.""" scope = { "type": "http", "method": "POST", "path": "/mcp/bridge_delegate_server", - "headers": [(b"authorization", b"Bearer plain-upstream-bearer-not-an-envelope")], + "headers": [ + (b"host", b"testserver"), + (b"authorization", b"Bearer plain-upstream-bearer-not-an-envelope"), + ], } - async def mock_user_api_key_auth_fails(api_key, request): - raise HTTPException(status_code=401, detail="Invalid API key") - with ( patch( "litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp.user_api_key_auth", - side_effect=mock_user_api_key_auth_fails, + new_callable=AsyncMock, + side_effect=HTTPException(status_code=401, detail="Invalid key"), ) as mock_auth, patch("litellm.proxy._experimental.mcp_server.mcp_server_manager.global_mcp_server_manager") as mock_mgr, patch("litellm.proxy.proxy_server.master_key", self._MASTER_KEY), @@ -5996,8 +6145,77 @@ class TestMCPDcrBridgeDelegateAdmission: await MCPRequestHandler.process_mcp_request(scope) assert exc_info.value.status_code == 401 - # The envelope arm was skipped, so the oauth2 arm ran and validated the bearer. - mock_auth.assert_called_once() + mock_auth.assert_awaited_once() + assert exc_info.value.headers == { + "www-authenticate": ( + 'Bearer error="invalid_token", ' + 'resource_metadata="http://testserver/.well-known/oauth-protected-resource/mcp/bridge_delegate_server"' + ) + } + + async def test_valid_litellm_authorization_key_uses_standard_admission(self): + scope = { + "type": "http", + "method": "POST", + "path": "/mcp/bridge_delegate_server", + "headers": [(b"authorization", b"Bearer sk-valid-litellm-key")], + } + admitted = UserAPIKeyAuth(api_key="hashed-key", user_id="litellm-key-user") + + with ( + patch( # test-quality-ok: supply standard key admission through the auth boundary + "litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp.user_api_key_auth", + new_callable=AsyncMock, + return_value=admitted, + ) as mock_auth, + patch( # test-quality-ok: isolate the MCP registry used by request admission + "litellm.proxy._experimental.mcp_server.mcp_server_manager.global_mcp_server_manager" + ) as mock_mgr, + patch( # test-quality-ok: configure key classification for the orchestration test + "litellm.proxy.proxy_server.master_key", self._MASTER_KEY + ), + ): + mock_mgr.get_mcp_server_by_name.return_value = self._bridge_delegate_server() + ( + auth_result, + _mcp_auth, + _servers, + mcp_server_auth_headers, + _oauth, + _raw, + ) = await MCPRequestHandler.process_mcp_request(scope) + + assert auth_result is admitted + assert mcp_server_auth_headers == {} + assert mock_auth.await_args.kwargs["api_key"] == "Bearer sk-valid-litellm-key" + + async def test_non_401_litellm_key_failure_is_not_converted_to_oauth_challenge(self): + scope = { + "type": "http", + "method": "POST", + "path": "/mcp/bridge_delegate_server", + "headers": [(b"authorization", b"Bearer sk-blocked-litellm-key")], + } + + with ( + patch( # test-quality-ok: force a non-401 auth result through request admission + "litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp.user_api_key_auth", + new_callable=AsyncMock, + side_effect=HTTPException(status_code=403, detail="Key blocked"), + ), + patch( # test-quality-ok: isolate the MCP registry used by request admission + "litellm.proxy._experimental.mcp_server.mcp_server_manager.global_mcp_server_manager" + ) as mock_mgr, + patch( # test-quality-ok: configure key classification for the orchestration test + "litellm.proxy.proxy_server.master_key", self._MASTER_KEY + ), + ): + mock_mgr.get_mcp_server_by_name.return_value = self._bridge_delegate_server() + with pytest.raises(HTTPException) as exc_info: + await MCPRequestHandler.process_mcp_request(scope) + + assert exc_info.value.status_code == 403 + assert not exc_info.value.headers async def test_explicit_litellm_key_wins_over_envelope_arm(self): """An explicit x-litellm-api-key is always a LiteLLM credential and its arm precedes the @@ -6126,6 +6344,7 @@ class TestMCPDcrBridgeDelegateAdmission: ): auth_result, new_headers = await MCPRequestHandler._admit_dcr_bridge_delegate( server=self._bridge_delegate_server(), + requested_name="bridge_delegate_server", authorization_value=f"Bearer {envelope}", mcp_server_auth_headers=existing, request=self._mcp_request(), @@ -6150,6 +6369,7 @@ class TestMCPDcrBridgeDelegateAdmission: with pytest.raises(HTTPException) as exc_info: await MCPRequestHandler._admit_dcr_bridge_delegate( server=self._bridge_delegate_server(), + requested_name="bridge_delegate_server", authorization_value=f"Bearer {envelope}", mcp_server_auth_headers=None, request=self._mcp_request(), @@ -6168,6 +6388,7 @@ class TestMCPDcrBridgeDelegateAdmission: with pytest.raises(HTTPException) as exc_info: await MCPRequestHandler._admit_dcr_bridge_delegate( server=self._bridge_delegate_server(), + requested_name="bridge_delegate_server", authorization_value=f"Bearer {envelope}", mcp_server_auth_headers=None, request=self._mcp_request(), @@ -6480,8 +6701,8 @@ class TestGatewaySessionAdmission: ) from litellm.proxy._experimental.mcp_server.outbound_credentials.session_token import ( SessionPrincipal, - mint_session_token, mint_session_refresh_token, + mint_session_token, ) keys = session_keys_from_master_key(self._MASTER_KEY) @@ -6947,8 +7168,8 @@ class TestUserSubjectTeamUnion: def _manager_with(self, server_ids, allow_all=()): from litellm.proxy._experimental.mcp_server.mcp_server_manager import MCPServerManager - from litellm.types.mcp_server.mcp_server_manager import MCPServer from litellm.types.mcp import MCPTransport + from litellm.types.mcp_server.mcp_server_manager import MCPServer manager = MCPServerManager() for sid in server_ids: