From 2c9c042cf0660b1fde1032248b3806560d1e3146 Mon Sep 17 00:00:00 2001 From: joshua Date: Sat, 19 Sep 2026 02:00:26 +0000 Subject: [PATCH] test(mcp): strongly type new catalog test parameters Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../mcp_server/test_mcp_server_manager.py | 16 ++++++++++----- .../mcp_server/test_rest_endpoints.py | 20 +++++++++---------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py index 9e6563acce0..0f4cfd6076e 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py @@ -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): diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_rest_endpoints.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_rest_endpoints.py index f2c1667e6d3..eaaf6a9cc26 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_rest_endpoints.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_rest_endpoints.py @@ -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."""