diff --git a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py index 8a6759e1185..e5c62f7a72b 100644 --- a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py +++ b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py @@ -4513,4 +4513,19 @@ class MCPServerManager: return [server for server in results if server is not None] -global_mcp_server_manager: MCPServerManager = MCPServerManager() +def _make_global_mcp_server_manager() -> MCPServerManager: + """Pick the egress manager: v2 (UpstreamConnection-backed) when LITELLM_USE_V2_MCP_EGRESS is + set, else v1. The v2 import is lazy so it can subclass MCPServerManager without an import + cycle, and flag-off never touches the v2 module.""" + from litellm.proxy._experimental.mcp_server.v2_egress import v2_egress_enabled + + if v2_egress_enabled(): + from litellm.proxy._experimental.mcp_server.mcp_server_manager_v2 import ( + MCPServerManagerV2, + ) + + return MCPServerManagerV2() + return MCPServerManager() + + +global_mcp_server_manager: MCPServerManager = _make_global_mcp_server_manager() diff --git a/litellm/proxy/_experimental/mcp_server/mcp_server_manager_v2.py b/litellm/proxy/_experimental/mcp_server/mcp_server_manager_v2.py new file mode 100644 index 00000000000..4f01a603633 --- /dev/null +++ b/litellm/proxy/_experimental/mcp_server/mcp_server_manager_v2.py @@ -0,0 +1,20 @@ +"""v2-owned MCP egress manager (the cutover seam). + +``MCPServerManagerV2`` subclasses v1's ``MCPServerManager`` and, in later steps, overrides the +per-server egress methods (``_get_tools_from_server``, ``call_tool``, the prompt/resource ops) to +route through the v2 ``UpstreamConnection`` + ``resolve()`` instead of ``_create_mcp_client``. +Registry, RBAC, cross-server aggregation, namespacing, and static-header resolution are inherited +from v1 unchanged. It is injected at the composition root when ``LITELLM_USE_V2_MCP_EGRESS`` is set +(see ``mcp_server_manager._make_global_mcp_server_manager``); flag-off keeps v1 exactly. + +Step 6a lands the skeleton only: no overrides, so behavior is identical to v1. The egress overrides +land in 6b/6c. +""" + +from __future__ import annotations + +from litellm.proxy._experimental.mcp_server.mcp_server_manager import MCPServerManager + + +class MCPServerManagerV2(MCPServerManager): + """v2 egress manager; see the module docstring. No overrides yet (step 6a).""" diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager_v2.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager_v2.py new file mode 100644 index 00000000000..c40756ef0a4 --- /dev/null +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager_v2.py @@ -0,0 +1,27 @@ +"""Tests for the v2 egress manager factory + skeleton (step 6a).""" + +from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + MCPServerManager, + _make_global_mcp_server_manager, +) +from litellm.proxy._experimental.mcp_server.mcp_server_manager_v2 import ( + MCPServerManagerV2, +) + +FLAG = "LITELLM_USE_V2_MCP_EGRESS" + + +def test_v2_is_a_manager_subclass(): + assert issubclass(MCPServerManagerV2, MCPServerManager) + + +def test_factory_returns_v2_when_egress_enabled(monkeypatch): + monkeypatch.setenv(FLAG, "true") + assert isinstance(_make_global_mcp_server_manager(), MCPServerManagerV2) + + +def test_factory_returns_v1_when_egress_disabled(monkeypatch): + monkeypatch.delenv(FLAG, raising=False) + manager = _make_global_mcp_server_manager() + assert isinstance(manager, MCPServerManager) + assert not isinstance(manager, MCPServerManagerV2)