litellm/tests/unit/proxy/management_endpoints/test_mcp_servers.py
Mateo Wang 0a1dbc0fb4
refactor(mcp): drop the experimental label from the MCP server and client paths (#48513)
* refactor(mcp): drop the experimental label from the MCP server and client paths

Move litellm/proxy/_experimental/mcp_server to litellm/proxy/mcp_server and litellm/experimental_mcp_client to litellm/mcp_client, with the mirrored tests moved under tests/unit/proxy/mcp_server and tests/unit/mcp_client. Both old packages stay as shims that warn with DeprecationWarning, re-export the new package's public names and alias every submodule in sys.modules so deep imports keep resolving to the same objects. The OpenAPI docs gate now puts the merge base's enterprise package on PYTHONPATH so its base counts do not mix the checked-out enterprise code with the base litellm tree

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* ci(mcp): keep tests/unit/proxy/_experimental as a sharded root for the shim tests

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(mcp): alias namespace subpackages and bind attributes in the deprecated MCP shims

pkgutil.walk_packages skipped mcp_server/auth because it has no __init__.py, so old imports of litellm.proxy._experimental.mcp_server.auth.* failed, and sys.modules entries alone left litellm.experimental_mcp_client.tools unreachable as an attribute. Walk the package on disk and setattr each alias on its old parent.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-27 17:47:50 +00:00

127 lines
4 KiB
Python

from datetime import datetime
from typing import Final
import pytest
from fastapi import HTTPException
from litellm.proxy._types import (
LiteLLM_MCPServerTable,
MCPTransport,
NewMCPServerRequest,
)
from litellm.proxy.management_endpoints.mcp_management_endpoints import (
_redact_mcp_credentials,
does_mcp_server_exist,
raise_mcp_identifier_conflict,
validate_and_normalize_mcp_server_payload,
)
from litellm.proxy.mcp_server.db import McpIdentifierConflict
from litellm.proxy.mcp_server.utils import validate_mcp_server_name
def test_does_mcp_server_exist() -> None:
created_at: Final = datetime(2025, 1, 1)
records: Final = (
LiteLLM_MCPServerTable(
server_id="server-1",
alias="first",
url="https://first.example.com/mcp",
transport=MCPTransport.sse,
created_at=created_at,
updated_at=created_at,
),
LiteLLM_MCPServerTable(
server_id="server-2",
alias="second",
url="https://second.example.com/mcp",
transport=MCPTransport.http,
created_at=created_at,
updated_at=created_at,
),
)
assert does_mcp_server_exist(records, "server-1")
assert does_mcp_server_exist(records, "server-2")
assert not does_mcp_server_exist(records, "missing-server")
def test_create_duplicate_mcp_server_returns_conflict_error() -> None:
conflict: Final = McpIdentifierConflict(
field="alias",
value="duplicate-server",
server_id="existing-server",
)
with pytest.raises(HTTPException) as error:
raise_mcp_identifier_conflict(conflict)
assert error.value.status_code == 400
assert error.value.detail == {
"error": "An MCP server with alias 'duplicate-server' already exists "
"(server_id=existing-server). MCP server names and aliases must be unique, case-insensitive."
}
def test_create_mcp_server_direct_normalizes_alias() -> None:
payload: Final = NewMCPServerRequest(
server_id="server-1",
alias="Test Server",
url="https://server.example.com/mcp",
transport=MCPTransport.sse,
)
validate_and_normalize_mcp_server_payload(payload)
assert payload.alias == "Test_Server"
assert payload.url == "https://server.example.com/mcp"
assert payload.transport == MCPTransport.sse
def test_edit_mcp_server_redacts_credentials() -> None:
created_at: Final = datetime(2025, 1, 1)
server: Final = LiteLLM_MCPServerTable(
server_id="server-1",
alias="server",
url="https://server.example.com/mcp",
transport=MCPTransport.http,
created_at=created_at,
updated_at=created_at,
credentials={"auth_value": "secret-value"},
)
redacted: Final = _redact_mcp_credentials(server)
assert redacted.credentials is None
assert server.credentials == {"auth_value": "secret-value"}
def test_create_mcp_server_invalid_alias() -> None:
payload: Final = NewMCPServerRequest(
alias="invalid-alias",
url="https://server.example.com/mcp",
transport=MCPTransport.sse,
)
with pytest.raises(HTTPException) as error:
validate_and_normalize_mcp_server_payload(payload)
assert error.value.status_code == 400
assert error.value.detail == {
"error": "Server name cannot contain '-'. Use an alternative character instead Found: invalid-alias"
}
def test_validate_mcp_server_name_direct() -> None:
validate_mcp_server_name("valid_name")
validate_mcp_server_name("valid name")
with pytest.raises(Exception, match="Server name cannot contain"):
validate_mcp_server_name("invalid-name")
with pytest.raises(HTTPException) as error:
validate_mcp_server_name("invalid-name", raise_http_exception=True)
assert error.value.status_code == 400
assert error.value.detail == {
"error": "Server name cannot contain '-'. Use an alternative character instead Found: invalid-name"
}