Skip to content

[Feat] MCP - Add support for streamablehttp_client MCP Servers #11628

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jun 12, 2025
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
4 changes: 2 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -785,7 +785,7 @@ jobs:
pip install "pytest-asyncio==0.21.1"
pip install "respx==0.22.0"
pip install "pydantic==2.10.2"
pip install "mcp==1.5.0"
pip install "mcp==1.9.3"
# Run pytest and generate JUnit XML report
- run:
name: Run tests
Expand Down Expand Up @@ -920,7 +920,7 @@ jobs:
pip install "respx==0.22.0"
pip install "hypercorn==0.17.3"
pip install "pydantic==2.10.2"
pip install "mcp==1.5.0"
pip install "mcp==1.9.3"
pip install "requests-mock>=1.12.1"
pip install "responses==0.25.7"
pip install "pytest-xdist==3.6.1"
Expand Down
2 changes: 1 addition & 1 deletion .circleci/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ pydantic==2.10.2
google-cloud-aiplatform==1.43.0
fastapi-sso==0.16.0
uvloop==0.21.0
mcp==1.5.0 # for MCP server
mcp==1.9.3 # for MCP server
73 changes: 64 additions & 9 deletions litellm/proxy/_experimental/mcp_server/mcp_server_manager.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""
MCP Client Manager

This class is responsible for managing MCP SSE clients.
This class is responsible for managing MCP clients with support for both SSE and HTTP streamable transports.

This is a Proxy
"""
Expand All @@ -25,6 +25,12 @@
MCPTransport,
MCPTransportType,
)

try:
from mcp.client.streamable_http import streamablehttp_client
except ImportError:
streamablehttp_client = None # type: ignore

from litellm.types.mcp_server.mcp_server_manager import MCPInfo, MCPServer


Expand Down Expand Up @@ -169,16 +175,43 @@ async def _get_tools_from_server(self, server: MCPServer) -> List[MCPTool]:

# Update tool to server mapping
for tool in tools_result.tools:
self.tool_name_to_mcp_server_name_mapping[
tool.name
] = server.name
self.tool_name_to_mcp_server_name_mapping[tool.name] = (
server.name
)

return tools_result.tools
elif server.transport == MCPTransport.http:
# TODO: implement http transport
return []
if streamablehttp_client is None:
verbose_logger.error(
"streamablehttp_client not available - install mcp with HTTP support"
)
raise ValueError(
"streamablehttp_client not available - please run `pip install mcp -U`"
)
verbose_logger.debug(f"Using HTTP streamable transport for {server.url}")
Copy link
Preview

Copilot AI Jun 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The HTTP transport blocks in both _get_tools_from_server and call_tool are nearly identical. Consider extracting session initialization and mapping logic into a shared helper to reduce duplication.

Copilot uses AI. Check for mistakes.

async with streamablehttp_client(
url=server.url,
) as (read_stream, write_stream, get_session_id):
async with ClientSession(read_stream, write_stream) as session:
await session.initialize()

if get_session_id is not None:
session_id = get_session_id()
if session_id:
verbose_logger.debug(f"HTTP session ID: {session_id}")

tools_result = await session.list_tools()
verbose_logger.debug(f"Tools from {server.name}: {tools_result}")

# Update tool to server mapping
for tool in tools_result.tools:
self.tool_name_to_mcp_server_name_mapping[tool.name] = (
server.name
)

return tools_result.tools
else:
# TODO: throw error on transport found or skip
verbose_logger.warning(f"Unsupported transport type: {server.transport}")
return []

def initialize_tool_name_to_mcp_server_name_mapping(self):
Expand Down Expand Up @@ -217,8 +250,30 @@ async def call_tool(self, name: str, arguments: Dict[str, Any]):
await session.initialize()
return await session.call_tool(name, arguments)
elif mcp_server.transport == MCPTransport.http:
# TODO: implement http transport
raise NotImplementedError("HTTP transport is not implemented yet")
if streamablehttp_client is None:
verbose_logger.error(
"streamablehttp_client not available - install mcp with HTTP support"
)
raise ValueError(
"streamablehttp_client not available - please run `pip install mcp -U`"
)
verbose_logger.debug(
f"Using HTTP streamable transport for tool call: {name}"
)
async with streamablehttp_client(
url=mcp_server.url,
) as (read_stream, write_stream, get_session_id):
async with ClientSession(read_stream, write_stream) as session:
await session.initialize()

if get_session_id is not None:
session_id = get_session_id()
if session_id:
verbose_logger.debug(
f"HTTP session ID for tool call: {session_id}"
)

return await session.call_tool(name, arguments)
else:
return CallToolResult(content=[], isError=True)

Expand Down
11 changes: 6 additions & 5 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ pynacl = {version = "^1.5.0", optional = true}
websockets = {version = "^13.1.0", optional = true}
boto3 = {version = "1.34.34", optional = true}
redisvl = {version = "^0.4.1", optional = true, markers = "python_version >= '3.9' and python_version < '3.14'"}
mcp = {version = "1.5.0", optional = true, python = ">=3.10"}
mcp = {version = "1.9.3", optional = true, python = ">=3.10"}
litellm-proxy-extras = {version = "0.2.3", optional = true}
rich = {version = "13.7.1", optional = true}
litellm-enterprise = {version = "0.1.7", optional = true}
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ mangum==0.17.0 # for aws lambda functions
pynacl==1.5.0 # for encrypting keys
google-cloud-aiplatform==1.47.0 # for vertex ai calls
anthropic[vertex]==0.21.3
mcp==1.5.0 # for MCP server
mcp==1.9.3 # for MCP server
google-generativeai==0.5.0 # for vertex ai calls
async_generator==1.10.0 # for async ollama calls
langfuse==2.45.0 # for langfuse self-hosted logging
Expand Down
Loading
Loading