feat(mcp/v2): MCPServerManagerV2 skeleton + flag-gated injection

Step 6a of the egress cutover. MCPServerManagerV2 subclasses v1's MCPServerManager with no overrides
yet, so behavior is identical. The composition root selects it via _make_global_mcp_server_manager()
when LITELLM_USE_V2_MCP_EGRESS is set (lazy import avoids the subclass import cycle); flag-off stays
v1 exactly. Verified at startup in both flag states. The per-server egress overrides
(UpstreamConnection + resolve()) land in 6b/6c.
This commit is contained in:
Tin Chi Lo 2026-06-19 14:51:51 -07:00
parent b7aa83140f
commit 6edba3f272
3 changed files with 63 additions and 1 deletions

View file

@ -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()

View file

@ -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)."""

View file

@ -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)