Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/test-unit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
17 changes: 17 additions & 0 deletions litellm/experimental_mcp_client/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment thread
greptile-apps[bot] marked this conversation as resolved.

## 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
Expand All @@ -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
Expand Down
7 changes: 6 additions & 1 deletion litellm/experimental_mcp_client/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
from .tools import call_openai_tool, load_mcp_tools
try:
from .tools import call_openai_tool, load_mcp_tools
Comment thread
greptile-apps[bot] marked this conversation as resolved.
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"]
12 changes: 12 additions & 0 deletions tests/base_sdk_tests/check_base_sdk_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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),
Expand Down
37 changes: 37 additions & 0 deletions tests/test_litellm/experimental_mcp_client/test_mcp_client.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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