mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-10 22:41:41 +00:00
fix(e2e): keep MCP admin routes on the data plane
/v1/mcp/* is a lazily mounted feature, so a gateway registers it on the first matching request, which happens after the startup route trim that drops management endpoints. Routing it to the control plane therefore sent every MCP call to the one backend process: the new lifecycle read-backs proved a single process rather than every replica, and mcp_client's await_registered barrier waited on a registry that does not serve the tools/list call it guards, so the existing MCP suites polled a gateway that had not synced yet until poll_timeout Verified against a two-gateway split stack (backend on 4001, gateways on 4010 and 4011, one postgres): both gateways answer /v1/mcp/server and /v1/mcp/toolset, and each served 6 server reads and 7 toolset reads over the run
This commit is contained in:
parent
d85023e38c
commit
a90989d43e
2 changed files with 15 additions and 3 deletions
|
|
@ -148,7 +148,7 @@ class TestReplicasFor:
|
|||
control_plane_base_url="http://backend",
|
||||
replica_urls=("http://gateway-1", "http://gateway-2"),
|
||||
)
|
||||
assert set(client.replicas_for("/v1/mcp/server/abc")) == {"http://backend"}
|
||||
assert set(client.replicas_for("/key/info")) == {"http://backend"}
|
||||
assert set(client.replicas_for("/v1/models")) == {"http://gateway-1", "http://gateway-2"}
|
||||
|
||||
def test_monolith_reads_management_routes_back_from_every_replica(self) -> None:
|
||||
|
|
@ -157,7 +157,20 @@ class TestReplicasFor:
|
|||
control_plane_base_url="http://lb",
|
||||
replica_urls=("http://pod-1", "http://pod-2"),
|
||||
)
|
||||
assert set(client.replicas_for("/v1/mcp/server/abc")) == {"http://pod-1", "http://pod-2"}
|
||||
assert set(client.replicas_for("/key/info")) == {"http://pod-1", "http://pod-2"}
|
||||
|
||||
def test_mcp_admin_routes_read_back_from_every_data_plane_replica(self) -> None:
|
||||
"""/v1/mcp/* is a lazily mounted feature, so a data-plane replica serves it
|
||||
too and answers from its own in-memory registry. Routing it to the control
|
||||
plane would leave every replica but that one unproven, and would move the
|
||||
tools/list barrier in mcp_client off the plane that serves tools/list."""
|
||||
client: Final = build_proxy_client(
|
||||
base_url="http://lb",
|
||||
control_plane_base_url="http://backend",
|
||||
replica_urls=("http://gateway-1", "http://gateway-2"),
|
||||
)
|
||||
assert set(client.replicas_for("/v1/mcp/server/abc")) == {"http://gateway-1", "http://gateway-2"}
|
||||
assert set(client.replicas_for("/v1/mcp/toolset/abc")) == {"http://gateway-1", "http://gateway-2"}
|
||||
|
||||
def test_a_route_no_replica_serves_is_refused_rather_than_read_back_vacuously(self) -> None:
|
||||
"""A read-back over zero replicas would satisfy every predicate and assert
|
||||
|
|
|
|||
|
|
@ -291,7 +291,6 @@ class HttpTransport:
|
|||
# (/chat, /embeddings, and native passthrough like /gemini, /anthropic) are NOT
|
||||
# here and fall through to the data plane. Matched as path prefixes.
|
||||
CONTROL_PLANE_PREFIXES: tuple[str, ...] = (
|
||||
"/v1/mcp/",
|
||||
"/key",
|
||||
"/user",
|
||||
"/team",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue