mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-24 00:52:24 +00:00
Merge pull request #42148 from BerriAI/litellm_mcp_public_client_7740
fix(mcp): explain missing public client dependencies
This commit is contained in:
commit
b4d9231b31
5 changed files with 73 additions and 3 deletions
3
.github/workflows/test-unit.yml
vendored
3
.github/workflows/test-unit.yml
vendored
|
|
@ -51,7 +51,7 @@ jobs:
|
|||
include:
|
||||
- shard: mcp-integration
|
||||
artifact-name: mcp-integration
|
||||
test-path: "tests/mcp_tests"
|
||||
test-path: "tests/mcp_tests tests/test_litellm/experimental_mcp_client"
|
||||
workers: 2
|
||||
reruns: 0
|
||||
timeout-minutes: 20
|
||||
|
|
@ -113,7 +113,6 @@ jobs:
|
|||
tests/test_litellm/compression
|
||||
tests/test_litellm/containers
|
||||
tests/test_litellm/endpoints
|
||||
tests/test_litellm/experimental_mcp_client
|
||||
tests/test_litellm/models
|
||||
tests/test_litellm/repositories
|
||||
tests/test_litellm/images
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue