diff --git a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py index 344fb39f792..aa99a11318b 100644 --- a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py +++ b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py @@ -50,6 +50,7 @@ class MCPServerManager: MCPSSEServer( name=server_name, url=server_config["url"], + mcp_info=server_config.get("mcp_info", None), ) ) verbose_logger.debug( diff --git a/litellm/proxy/_experimental/mcp_server/server.py b/litellm/proxy/_experimental/mcp_server/server.py index a1de700d50a..4fc91342c25 100644 --- a/litellm/proxy/_experimental/mcp_server/server.py +++ b/litellm/proxy/_experimental/mcp_server/server.py @@ -6,11 +6,15 @@ import asyncio from typing import Any, Dict, List, Union from anyio import BrokenResourceError -from fastapi import APIRouter, HTTPException, Request +from fastapi import APIRouter, Depends, HTTPException, Request from fastapi.responses import StreamingResponse from pydantic import ValidationError from litellm._logging import verbose_logger +from litellm.proxy.auth.user_api_key_auth import user_api_key_auth +from litellm.types.mcp_server.mcp_server_manager import ( + ListMCPToolsRestAPIResponseObject, +) # Check if MCP is available # "mcp" requires python 3.10 or higher, but several litellm users use python 3.8 @@ -53,9 +57,14 @@ if MCP_AVAILABLE: ######################################################## ############### MCP Server Routes ####################### ######################################################## - @server.list_tools() async def list_tools() -> list[MCPTool]: + """ + List all available tools + """ + return await _list_mcp_tools() + + async def _list_mcp_tools() -> List[MCPTool]: """ List all available tools """ @@ -95,6 +104,18 @@ if MCP_AVAILABLE: HTTPException: If tool not found or arguments missing """ # Validate arguments + response = await call_mcp_tool( + name=name, + arguments=arguments, + ) + return response + + async def call_mcp_tool( + name: str, arguments: Dict[str, Any] | None + ) -> List[Union[MCPTextContent, MCPImageContent, MCPEmbeddedResource]]: + """ + Call a specific tool with the provided arguments + """ if arguments is None: raise HTTPException( status_code=400, detail="Request arguments are required" @@ -154,6 +175,63 @@ if MCP_AVAILABLE: await sse.handle_post_message(request.scope, request.receive, request._send) await request.close() + ######################################################## + ############ MCP Server REST API Routes ################# + ######################################################## + @router.get("/tools/list", dependencies=[Depends(user_api_key_auth)]) + async def list_tool_rest_api() -> ( + List[Dict[str, ListMCPToolsRestAPIResponseObject]] + ): + """ + List all available tools with information about the server they belong to. + + Example response: + Tools: + [ + "zapier": { + "tools": [ + { + "name": "create_zap", + "description": "Create a new zap", + "inputSchema": "tool_input_schema", + } + ], + "mcp_info": { + "logo_url": "https://www.zapier.com/logo.png", + } + }, + "fetch": { + "tools": [ + { + "name": "fetch_data", + "description": "Fetch data from a URL", + } + ], + "mcp_info": { + "logo_url": "https://www.fetch.com/logo.png", + } + } + """ + list_tools_result: List[Dict[str, ListMCPToolsRestAPIResponseObject]] = [] + for server in global_mcp_server_manager.mcp_servers: + tools = await global_mcp_server_manager._get_tools_from_server(server) + list_tools_result.append( + { + server.name: ListMCPToolsRestAPIResponseObject( + tools=tools, + mcp_info=server.mcp_info, + ) + } + ) + return list_tools_result + + @router.post("/tools/call", dependencies=[Depends(user_api_key_auth)]) + async def call_tool_rest_api(name: str, arguments: Dict[str, Any]): + return await call_mcp_tool( + name=name, + arguments=arguments, + ) + options = InitializationOptions( server_name="litellm-mcp-server", server_version="0.1.0", diff --git a/litellm/proxy/proxy_config.yaml b/litellm/proxy/proxy_config.yaml index 9da28ff51f7..bffb39a1b8c 100644 --- a/litellm/proxy/proxy_config.yaml +++ b/litellm/proxy/proxy_config.yaml @@ -5,9 +5,10 @@ model_list: mcp_servers: { - "Zapier MCP": { - "url": "os.environ/ZAPIER_MCP_SERVER_URL", - }, + "Zapier MCP": { + "url": "os.environ/ZAPIER_MCP_SERVER_URL", + "mcp_info": { + "logo_url": "https://www.zapier.com/logo.png", + } + } } - -} \ No newline at end of file diff --git a/litellm/types/mcp_server/mcp_server_manager.py b/litellm/types/mcp_server/mcp_server_manager.py index a106557fb6f..0996e62b3aa 100644 --- a/litellm/types/mcp_server/mcp_server_manager.py +++ b/litellm/types/mcp_server/mcp_server_manager.py @@ -1,6 +1,7 @@ -from typing import Optional +from typing import Any, Dict, List, Optional from mcp import ClientSession +from mcp.types import Tool as MCPTool from pydantic import BaseModel, ConfigDict @@ -8,4 +9,15 @@ class MCPSSEServer(BaseModel): name: str url: str client_session: Optional[ClientSession] = None + mcp_info: Optional[Dict[str, Any]] = None + model_config = ConfigDict(arbitrary_types_allowed=True) + + +class ListMCPToolsRestAPIResponseObject(BaseModel): + """ + Object returned by the /tools/list REST API route. + """ + + tools: List[MCPTool] + mcp_info: Optional[Dict[str, Any]] = None model_config = ConfigDict(arbitrary_types_allowed=True)