mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-03 02:22:24 +00:00
test(mcp): strongly type new catalog test parameters
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
d03f1d14c9
commit
2c9c042cf0
2 changed files with 21 additions and 15 deletions
|
|
@ -3940,7 +3940,9 @@ class TestMCPServerManager:
|
|||
("get_resource_templates_from_server", "list_resource_templates"),
|
||||
],
|
||||
)
|
||||
async def test_catalog_fetch_failure_is_swallowed_unless_raise_on_error(self, manager_method, client_method):
|
||||
async def test_catalog_fetch_failure_is_swallowed_unless_raise_on_error(
|
||||
self, manager_method: str, client_method: str
|
||||
) -> None:
|
||||
"""Catalog fetches stay best-effort for the MCP protocol aggregate (empty list) but a
|
||||
single-server caller that opts in gets the classified fault instead of empty-success."""
|
||||
manager = MCPServerManager()
|
||||
|
|
@ -3970,7 +3972,9 @@ class TestMCPServerManager:
|
|||
["get_prompts_from_server", "get_resources_from_server", "get_resource_templates_from_server"],
|
||||
)
|
||||
@pytest.mark.parametrize("challenge_carrier", ["resolver_http_exception", "upstream_auth_error"])
|
||||
async def test_catalog_fetch_relays_auth_challenge_like_tools(self, manager_method, challenge_carrier):
|
||||
async def test_catalog_fetch_relays_auth_challenge_like_tools(
|
||||
self, manager_method: str, challenge_carrier: str
|
||||
) -> None:
|
||||
"""An auth challenge raised while building the client (a v2 resolver HTTPException 401) or by
|
||||
the upstream itself must reach a single-server caller as MCPUpstreamAuthError with the
|
||||
WWW-Authenticate intact, exactly as the tools listing relays it, not as a bare fault."""
|
||||
|
|
@ -4006,7 +4010,9 @@ class TestMCPServerManager:
|
|||
("get_resource_templates_from_server", "list_resource_templates"),
|
||||
],
|
||||
)
|
||||
async def test_catalog_fetch_prepares_upstream_headers_like_tools(self, manager_method, client_method, monkeypatch):
|
||||
async def test_catalog_fetch_prepares_upstream_headers_like_tools(
|
||||
self, manager_method: str, client_method: str, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
"""Prompt and resource listings must reach the upstream with the same credentials the tools
|
||||
listing sends: ``${NAME}`` static headers interpolated from the server's env vars and the
|
||||
MCPJWTSigner token injected when nothing else carries an Authorization."""
|
||||
|
|
@ -4040,8 +4046,8 @@ class TestMCPServerManager:
|
|||
mock_client.discovery_auth_fingerprint = AsyncMock(return_value="test-credential-hash")
|
||||
upstream_headers: list[dict[str, str] | None] = []
|
||||
|
||||
async def create_client(**kwargs):
|
||||
upstream_headers.append(kwargs["extra_headers"])
|
||||
async def create_client(*, extra_headers: dict[str, str] | None, **_: object) -> AsyncMock:
|
||||
upstream_headers.append(extra_headers)
|
||||
return mock_client
|
||||
|
||||
with patch.object(manager, "_create_mcp_client", create_client):
|
||||
|
|
|
|||
|
|
@ -2380,11 +2380,11 @@ class TestListPromptsAndResourcesRestAPI:
|
|||
server.available_on_public_internet = True
|
||||
return server
|
||||
|
||||
def _grant(self, monkeypatch, server: MCPServer, allowed: list[str]) -> None:
|
||||
async def fake_contexts(user_api_key_auth):
|
||||
def _grant(self, monkeypatch: pytest.MonkeyPatch, server: MCPServer, allowed: list[str]) -> None:
|
||||
async def fake_contexts(user_api_key_auth: UserAPIKeyAuth) -> list[UserAPIKeyAuth]:
|
||||
return [user_api_key_auth]
|
||||
|
||||
async def fake_get_allowed_mcp_servers(*args, **kwargs):
|
||||
async def fake_get_allowed_mcp_servers(*args: object, **kwargs: object) -> list[str]:
|
||||
return allowed
|
||||
|
||||
monkeypatch.setattr(rest_endpoints, "build_effective_auth_contexts", fake_contexts, raising=False)
|
||||
|
|
@ -2408,7 +2408,7 @@ class TestListPromptsAndResourcesRestAPI:
|
|||
)
|
||||
|
||||
@pytest.mark.parametrize("route_name", ["list_prompts_rest_api", "list_resources_rest_api"])
|
||||
async def test_rejects_server_outside_caller_grant(self, monkeypatch, route_name):
|
||||
async def test_rejects_server_outside_caller_grant(self, monkeypatch: pytest.MonkeyPatch, route_name: str) -> None:
|
||||
server = self._stub_server()
|
||||
self._grant(monkeypatch, server, allowed=["some-other-server"])
|
||||
upstream = AsyncMock()
|
||||
|
|
@ -2426,7 +2426,7 @@ class TestListPromptsAndResourcesRestAPI:
|
|||
assert exc_info.value.detail["error"] == "access_denied"
|
||||
upstream.assert_not_awaited()
|
||||
|
||||
async def test_lists_prompts_with_upstream_names_and_server_credential(self, monkeypatch):
|
||||
async def test_lists_prompts_with_upstream_names_and_server_credential(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
from mcp.types import Prompt, PromptArgument
|
||||
|
||||
server = self._stub_server()
|
||||
|
|
@ -2460,7 +2460,7 @@ class TestListPromptsAndResourcesRestAPI:
|
|||
assert call.kwargs["raw_headers"]["x-mcp-catalog-authorization"] == "Bearer per-server-token"
|
||||
assert call.kwargs["user_api_key_auth"].user_id == "user-123"
|
||||
|
||||
async def test_lists_resources_and_templates_for_allowed_server(self, monkeypatch):
|
||||
async def test_lists_resources_and_templates_for_allowed_server(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
from mcp.types import Resource, ResourceTemplate
|
||||
|
||||
server = self._stub_server()
|
||||
|
|
@ -2496,8 +2496,8 @@ class TestListPromptsAndResourcesRestAPI:
|
|||
],
|
||||
)
|
||||
async def test_upstream_fault_relays_truthful_status_instead_of_empty_success(
|
||||
self, monkeypatch, route_name, manager_method, catalog
|
||||
):
|
||||
self, monkeypatch: pytest.MonkeyPatch, route_name: str, manager_method: str, catalog: str
|
||||
) -> None:
|
||||
"""A broken upstream must answer like /mcp-rest/tools/list does (a gateway status), not as
|
||||
an empty catalog the dashboard would render as "this server has no prompts"."""
|
||||
from litellm.proxy._experimental.mcp_server.exceptions import MCPServerListError
|
||||
|
|
@ -2526,7 +2526,7 @@ class TestListPromptsAndResourcesRestAPI:
|
|||
}
|
||||
assert failing.await_args.kwargs["raise_on_error"] is True
|
||||
|
||||
async def test_upstream_auth_challenge_is_relayed_for_catalog_routes(self, monkeypatch):
|
||||
async def test_upstream_auth_challenge_is_relayed_for_catalog_routes(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
from litellm.proxy._experimental.mcp_server.exceptions import MCPUpstreamAuthError
|
||||
|
||||
server = self._stub_server()
|
||||
|
|
@ -2550,7 +2550,7 @@ class TestListPromptsAndResourcesRestAPI:
|
|||
assert exc_info.value.headers is not None
|
||||
assert "www-authenticate" in {key.lower() for key in exc_info.value.headers}
|
||||
|
||||
def test_openapi_keeps_prompt_management_and_mcp_prompt_contracts_distinct(self):
|
||||
def test_openapi_keeps_prompt_management_and_mcp_prompt_contracts_distinct(self) -> None:
|
||||
"""The catalog response reuses the MCP SDK prompt type, which shares its class name with the
|
||||
prompt-management request model, so the two must land as separate OpenAPI components:
|
||||
POST /prompts still requires prompt_id + litellm_params while the catalog item requires name."""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue