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:
joshua 2026-09-19 02:00:26 +00:00
parent d03f1d14c9
commit 2c9c042cf0
2 changed files with 21 additions and 15 deletions

View file

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

View file

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