diff --git a/litellm/proxy/_experimental/mcp_server/oauth2_token_cache.py b/litellm/proxy/_experimental/mcp_server/oauth2_token_cache.py index 0592f083c40..d7f098908a4 100644 --- a/litellm/proxy/_experimental/mcp_server/oauth2_token_cache.py +++ b/litellm/proxy/_experimental/mcp_server/oauth2_token_cache.py @@ -27,11 +27,21 @@ from litellm.proxy.common_utils.encrypt_decrypt_utils import ( encrypt_value_helper, ) from litellm.proxy._experimental.mcp_server.auth import token_exchange -from litellm.proxy._experimental.mcp_server.v2_resolver_bridge import ( - resolve_v2_auth_value, -) from litellm.types.llms.custom_http import httpxSpecialProvider +# The v2 resolver bridge pulls in the v2 gateway package (and its `expression` dependency). +# Guard the import so a missing optional dep degrades this graft to v1 rather than taking down +# the entire MCP feature on startup. +try: + from litellm.proxy._experimental.mcp_server.v2_resolver_bridge import ( + resolve_v2_auth_value, + ) +except ImportError as _e: + verbose_logger.warning( + "v2 MCP resolver bridge unavailable (%s); using v1 resolution", _e + ) + resolve_v2_auth_value = None # type: ignore[assignment] + if TYPE_CHECKING: from litellm.types.mcp_server.mcp_server_manager import MCPServer @@ -290,9 +300,10 @@ async def resolve_mcp_auth( """ if mcp_auth_header: return mcp_auth_header - v2_auth_value = await resolve_v2_auth_value(server) - if v2_auth_value is not None: - return v2_auth_value + if resolve_v2_auth_value is not None: + v2_auth_value = await resolve_v2_auth_value(server) + if v2_auth_value is not None: + return v2_auth_value if server.has_token_exchange_config: if subject_token: return await token_exchange.mcp_token_exchange_handler.exchange_token( diff --git a/uv.lock b/uv.lock index 5339b56df7f..6e9b44e9727 100644 --- a/uv.lock +++ b/uv.lock @@ -9,7 +9,7 @@ resolution-markers = [ ] [options] -exclude-newer = "2026-06-14T15:53:04.946308996Z" +exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. exclude-newer-span = "P3D" [manifest] @@ -1477,6 +1477,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, ] +[[package]] +name = "expression" +version = "5.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/c7/bb061623b5815566bda69f5e9d156e38a97ebb383b8db3d2dedb26415466/expression-5.6.0.tar.gz", hash = "sha256:454f6fe138347194a43c7f878d958efe9b84b9cc770e462010c7a52e18058065", size = 59147, upload-time = "2025-02-19T09:37:37.432Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/a2/656b8bebe495117342a8676ccabf52b3885ce11a856c8dfe1fbbdc250d2d/expression-5.6.0-py3-none-any.whl", hash = "sha256:f5c62e38186c9287e088dee9cf3939b0bbde21cb4c59571872154a53d33dd7c0", size = 69673, upload-time = "2025-02-19T09:37:35.476Z" }, +] + [[package]] name = "fakeredis" version = "2.34.1" @@ -3285,6 +3297,7 @@ proxy = [ { name = "backoff" }, { name = "boto3" }, { name = "cryptography" }, + { name = "expression" }, { name = "fastapi" }, { name = "fastapi-sso" }, { name = "granian" }, @@ -3446,6 +3459,7 @@ requires-dist = [ { name = "ddtrace", marker = "extra == 'proxy-runtime'", specifier = ">=2.19.0,<3.0" }, { name = "detect-secrets", marker = "extra == 'proxy-runtime'", specifier = ">=1.5.0,<2.0" }, { name = "diskcache", marker = "extra == 'caching'", specifier = ">=5.6.3,<6.0" }, + { name = "expression", marker = "extra == 'proxy'", specifier = ">=5.6.0,<6.0" }, { name = "fastapi", marker = "extra == 'proxy'", specifier = ">=0.136.3,<1.0" }, { name = "fastapi-sso", marker = "extra == 'proxy'", specifier = ">=0.19.0,<1.0" }, { name = "fastuuid", specifier = ">=0.14.0,<1.0" },