fix(mcp): add litellm[mcp] extra and actionable error when streamable_http_client is missing

The MCP client's HTTP transport needs mcp>=1.24.0 for streamable_http_client,
but a base litellm install declares no mcp constraint and no extra existed to
pin one, so environments carrying an older mcp fail at connect time with
'streamable_http_client is not available. Please install mcp with HTTP
support.', which names no version floor and no installable remedy.

Add a litellm[mcp] extra matching the proxy extra's mcp>=1.28.1,<2.0 and
replace the vague ImportError with one naming the required floor, the
installed mcp version, and the pip commands that fix it.
This commit is contained in:
mateo-berri 2026-08-26 13:23:51 -07:00
parent 724c5c2d96
commit 1eb538de18
4 changed files with 64 additions and 3 deletions

View file

@ -7,6 +7,7 @@ import base64
import os
from collections.abc import Awaitable, Callable, Generator
from datetime import timedelta
from importlib import metadata
from typing import Any, Final, TypeVar
import httpx
@ -21,6 +22,18 @@ try:
streamable_http_client = getattr(streamable_http_module, "streamable_http_client", None)
except ImportError:
pass
MCP_STREAMABLE_HTTP_REQUIREMENT: Final = "mcp>=1.28.1"
def missing_streamable_http_client_error() -> ImportError:
return ImportError(
f"MCP streamable HTTP transport requires {MCP_STREAMABLE_HTTP_REQUIREMENT}, but the installed "
f"mcp {metadata.version('mcp')} does not provide streamable_http_client. "
"Fix with: pip install 'litellm[mcp]' (or upgrade mcp directly: pip install -U mcp)"
)
from mcp.types import CallToolRequestParams as MCPCallToolRequestParams
from mcp.types import CallToolResult as MCPCallToolResult
from mcp.types import (
@ -323,7 +336,7 @@ class MCPClient:
)
# HTTP transport (default)
if streamable_http_client is None:
raise ImportError("streamable_http_client is not available. Please install mcp with HTTP support.")
raise missing_streamable_http_client_error()
headers = self._get_auth_headers()
httpx_client_factory = self._create_httpx_client_factory()
verbose_logger.debug("litellm headers for streamable_http_client: %s", headers)

View file

@ -106,6 +106,7 @@ utils = [
"numpydoc>=1.8.0,<2.0",
]
caching = ["diskcache>=5.6.3,<6.0"]
mcp = ["mcp>=1.28.1,<2.0"]
# SAML SSO for the admin UI. python3-saml pulls in xmlsec/lxml, whose wheels
# bundle the native libxmlsec1/libxml2 libraries, so no system packages are
# required. Kept out of the base `proxy` extra so it stays optional.

View file

@ -2,6 +2,8 @@ import asyncio
import base64
import os
import sys
from importlib import metadata
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock, patch
import anyio
@ -24,9 +26,11 @@ from mcp.types import (
import litellm.experimental_mcp_client.client as mcp_client_module
from litellm.experimental_mcp_client.client import (
MCP_STREAMABLE_HTTP_REQUIREMENT,
MCPClient,
_as_read_timeout,
_first_non_cancelled_cause,
missing_streamable_http_client_error,
strip_auth_scheme,
)
from litellm.proxy._experimental.mcp_server.faults.list_outcomes import (
@ -1047,3 +1051,42 @@ def test_openapi_byok_auth_header_emits_exactly_one_scheme(auth_type, auth_value
assert server.is_byok is False
assert _format_byok_openapi_auth_header(server, auth_value) == expected
def test_missing_streamable_http_client_error_names_requirement_and_remedy():
message = str(missing_streamable_http_client_error())
assert MCP_STREAMABLE_HTTP_REQUIREMENT in message
assert "pip install 'litellm[mcp]'" in message
assert metadata.version("mcp") in message
@pytest.mark.asyncio
async def test_http_transport_without_streamable_http_client_raises_actionable_import_error():
client = MCPClient(
server_url="https://mcp-server.example.com",
transport_type=MCPTransport.http,
)
with patch.object(mcp_client_module, "streamable_http_client", None): # test-quality-ok: simulates mcp<1.24.0 whose module lacks this import-time symbol; no HTTP boundary exists before the raise
with pytest.raises(ImportError, match=r"pip install 'litellm\[mcp\]'"):
await client.list_tools(raise_on_error=True)
def test_mcp_extra_matches_proxy_extra_and_supports_streamable_http():
tomllib = pytest.importorskip("tomllib")
from packaging.requirements import Requirement
pyproject_path = Path(__file__).parents[3] / "pyproject.toml"
with pyproject_path.open("rb") as f:
extras = tomllib.load(f)["project"]["optional-dependencies"]
mcp_extra = extras["mcp"]
assert len(mcp_extra) == 1
proxy_mcp_requirements = [req for req in extras["proxy"] if Requirement(req).name == "mcp"]
assert mcp_extra == proxy_mcp_requirements
specifier = Requirement(mcp_extra[0]).specifier
assert not specifier.contains("1.23.0")
assert specifier.contains("1.28.1")

8
uv.lock generated
View file

@ -10,7 +10,7 @@ resolution-markers = [
]
[options]
exclude-newer = "2026-08-23T02:27:57.028643Z"
exclude-newer = "2026-08-23T20:15:58.934396Z"
exclude-newer-span = "P3D"
[manifest]
@ -4315,6 +4315,9 @@ google = [
grpc = [
{ name = "grpcio" },
]
mcp = [
{ name = "mcp" },
]
mlflow = [
{ name = "mlflow" },
]
@ -4526,6 +4529,7 @@ requires-dist = [
{ name = "litellm-proxy-extras", marker = "extra == 'proxy'", editable = "litellm-proxy-extras" },
{ name = "llm-sandbox", marker = "extra == 'proxy-runtime'", specifier = ">=0.3.39,<1.0" },
{ name = "mangum", marker = "extra == 'proxy-runtime'", specifier = ">=0.17.0,<1.0" },
{ name = "mcp", marker = "extra == 'mcp'", specifier = ">=1.28.1,<2.0" },
{ name = "mcp", marker = "extra == 'proxy'", specifier = ">=1.28.1,<2.0" },
{ name = "mlflow", marker = "extra == 'mlflow'", specifier = ">=3.11.1,<4.0" },
{ name = "numpy", marker = "extra == 'stt-nvidia-riva'", specifier = ">=1.26.0" },
@ -4569,7 +4573,7 @@ requires-dist = [
{ name = "uvloop", marker = "sys_platform != 'win32' and extra == 'proxy'", specifier = ">=0.21.0,<1.0" },
{ name = "websockets", marker = "extra == 'proxy'", specifier = ">=15.0.1,<16.0" },
]
provides-extras = ["proxy", "cli", "extra-proxy", "utils", "caching", "saml", "semantic-router", "mlflow", "grpc", "stt-nvidia-riva", "google", "bedrock-realtime", "proxy-runtime"]
provides-extras = ["proxy", "cli", "extra-proxy", "utils", "caching", "mcp", "saml", "semantic-router", "mlflow", "grpc", "stt-nvidia-riva", "google", "bedrock-realtime", "proxy-runtime"]
[package.metadata.requires-dev]
ci = [