fix(mcp): explain missing public client dependencies

This commit is contained in:
Joshua Valluru 2026-09-19 19:52:13 -07:00
parent b4447096e4
commit 4fda0092d3
4 changed files with 72 additions and 1 deletions

View file

@ -2,6 +2,17 @@
LiteLLM MCP Client allows you to use MCP tools with LiteLLM
Install the optional dependencies with `pip install 'litellm[mcp]'`, then use the existing public imports:
```python
from litellm.experimental_mcp_client import call_openai_tool, load_mcp_tools
from litellm.experimental_mcp_client.client import MCPClient
client = MCPClient(server_url="https://mcp.example.com/mcp")
```
Core `import litellm` works without the MCP extra. Importing the experimental MCP client without its MCP or HTTPX2 dependency raises an error with this installation command
## MCP Python SDK compatibility
The `mcp` and `proxy` extras require MCP Python SDK 2.2 or newer within the 2.x release line. Installing core LiteLLM without these extras does not require MCP
@ -16,6 +27,12 @@ The shared unit-test workflow runs the MCP integration suite once, with SDK2 in
See the official [SDK migration guide](https://py.sdk.modelcontextprotocol.io/migration/) for Python API changes
## Custom HTTP clients and authentication
MCP HTTP and SSE transports now use `httpx2`. Custom authentication passed through `aws_auth` or `resolved_auth` must implement `httpx2.Auth`. Integrations that override the client's HTTP client factory or customize its event hooks must use `httpx2.AsyncClient`, request, response, timeout and transport types
HTTPX1 clients, auth objects and hooks are not adapted by a compatibility shim. Migrate those integrations to HTTPX2 before upgrading. Ordinary `MCPClient` construction and LiteLLM's existing helper imports remain supported; this does not restore SDK1 Python imports or camelCase SDK model attributes in the shared Python environment
## HTTP redirects
For streamable HTTP POST requests, the MCP SDK follows method-preserving redirects such as HTTP 307/308 within the configured endpoint's origin. Redirects to another path on the same scheme, host and port work. The SDK also permits an HTTP-to-HTTPS upgrade on the same host using the default ports

View file

@ -1,3 +1,8 @@
from .tools import call_openai_tool, load_mcp_tools
try:
from .tools import call_openai_tool, load_mcp_tools
except ModuleNotFoundError as exc:
if exc.name not in ("mcp", "httpx2"):
raise
raise ImportError("MCP client dependencies are missing. Install them with: pip install 'litellm[mcp]'") from exc
__all__ = ["call_openai_tool", "load_mcp_tools"]

View file

@ -50,6 +50,17 @@ def check_completion() -> str:
return "mock completion round-trips"
def check_mcp_install_guidance() -> str:
try:
import litellm.experimental_mcp_client
except ImportError as error:
_require("pip install 'litellm[mcp]'" in str(error), f"missing MCP installation guidance: {error}")
_require(isinstance(error.__cause__, ModuleNotFoundError), "original missing-dependency cause was lost")
_require(error.__cause__.name == "mcp", f"unexpected missing dependency: {error.__cause__}")
return "optional MCP client explains how to install litellm[mcp]"
raise AssertionError("MCP client imported without the MCP extra")
def check_embedding() -> str:
import litellm
@ -109,6 +120,7 @@ def check_bedrock_credential_resolution() -> str:
CHECKS: tuple[tuple[str, Callable[[], str]], ...] = (
("environment is base-only", check_environment_is_base_only),
("import litellm", check_import),
("optional MCP installation guidance", check_mcp_install_guidance),
("chat completion", check_completion),
("embedding", check_embedding),
("bundled model metadata", check_bundled_model_metadata),

View file

@ -1,10 +1,12 @@
import asyncio
import base64
import importlib
import json
import os
import sys
from collections.abc import AsyncIterator
from pathlib import Path
from types import ModuleType
from typing import Final
from unittest.mock import AsyncMock, MagicMock, Mock, patch
@ -2160,3 +2162,38 @@ async def test_404_before_session_initialization_preserves_method_not_found() ->
)
assert caught.value.error.code == METHOD_NOT_FOUND
assert caught.value.error.message == "Not Found"
@pytest.mark.parametrize("missing_module", ("mcp", "httpx2", "mcp.types", "openai.types.chat"))
def test_public_mcp_import_missing_dependency(missing_module: str) -> None:
with patch.dict(sys.modules):
for name in tuple(sys.modules):
if name.startswith(("litellm.experimental_mcp_client", "mcp.", "mcp_types.")) or name == "mcp":
del sys.modules[name]
with patch.dict(sys.modules, {missing_module: None}):
with pytest.raises(ImportError) as caught:
importlib.import_module("litellm.experimental_mcp_client.client")
if missing_module in ("mcp", "httpx2"):
assert "pip install 'litellm[mcp]'" in str(caught.value)
assert isinstance(caught.value.__cause__, ModuleNotFoundError)
assert caught.value.__cause__.name == missing_module
else:
assert isinstance(caught.value, ModuleNotFoundError)
assert caught.value.name == missing_module
assert caught.value.__cause__ is None
assert "litellm[mcp]" not in str(caught.value)
def test_public_mcp_import_preserves_incompatible_sdk_error() -> None:
with patch.dict(sys.modules):
for name in tuple(sys.modules):
if name.startswith("litellm.experimental_mcp_client"):
del sys.modules[name]
with patch.dict(sys.modules, {"mcp": ModuleType("mcp")}):
with pytest.raises(ImportError, match="cannot import name 'ClientSession'") as caught:
importlib.import_module("litellm.experimental_mcp_client.client")
assert not isinstance(caught.value, ModuleNotFoundError)
assert caught.value.__cause__ is None
assert "litellm[mcp]" not in str(caught.value)