From d931446a790ebba013588c8e95b507ea1d2d2e34 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 11 Jul 2025 20:21:02 -0700 Subject: [PATCH] [MCP Gateway] Allow using stdio MCPs with LiteLLM (#12530) * update types * add STDIO to client * allow using STDIO with MCP manager * add Stdio-specific fields to schema.prisma * fixes for MCP mgmt * fix for adding stdio MCP to DB * ui - allow adding stdio MCPs * fix MCP server manager * docs stdio MCP * add_stdio_mcp.png * new stdio tests * allow adding MCPs through config.yaml * fix tool test panel * use TestMCPClient * ui fixes for testing circle ci mcp --- docs/my-website/docs/mcp.md | 29 ++++- docs/my-website/img/add_stdio_mcp.png | Bin 0 -> 115718 bytes litellm/experimental_mcp_client/client.py | 41 +++++-- litellm/proxy/_experimental/mcp_server/db.py | 101 ++++++++------- .../mcp_server/mcp_server_manager.py | 78 ++++++++++-- litellm/proxy/_types.py | 48 +++++++- .../mcp_management_endpoints.py | 12 +- litellm/proxy/schema.prisma | 7 +- litellm/types/mcp.py | 20 ++- .../types/mcp_server/mcp_server_manager.py | 8 +- .../test_mcp_client.py | 79 ++++++++++++ .../mcp_server/test_mcp_server_manager.py | 94 ++++++++++++++ .../mcp_tools/StdioConfiguration.tsx | 58 +++++++++ .../components/mcp_tools/ToolTestPanel.tsx | 115 ++++++++++++------ .../mcp_tools/create_mcp_server.tsx | 113 +++++++++++++---- .../src/components/mcp_tools/types.tsx | 4 + 16 files changed, 666 insertions(+), 141 deletions(-) create mode 100644 docs/my-website/img/add_stdio_mcp.png create mode 100644 tests/test_litellm/experimental_mcp_client/test_mcp_client.py create mode 100644 tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py create mode 100644 ui/litellm-dashboard/src/components/mcp_tools/StdioConfiguration.tsx diff --git a/docs/my-website/docs/mcp.md b/docs/my-website/docs/mcp.md index 28d2535dbd..0574bee1ee 100644 --- a/docs/my-website/docs/mcp.md +++ b/docs/my-website/docs/mcp.md @@ -18,7 +18,7 @@ LiteLLM Proxy provides an MCP Gateway that allows you to use a fixed endpoint fo | Feature | Description | |---------|-------------| | MCP Operations | • List Tools
• Call Tools | -| Supported MCP Transports | • Streamable HTTP
• SSE | +| Supported MCP Transports | • Streamable HTTP
• SSE
• Standard Input/Output (stdio) | | LiteLLM Permission Management | ✨ Enterprise Only
• By Key
• By Team
• By Organization | ## Adding your MCP @@ -33,12 +33,22 @@ On this form, you should enter your MCP Server URL and the transport you want to LiteLLM supports the following MCP transports: - Streamable HTTP - SSE (Server-Sent Events) +- Standard Input/Output (stdio) +### Adding a stdio MCP Server + +For stdio MCP servers, select "Standard Input/Output (stdio)" as the transport type and provide the stdio configuration in JSON format: + + + @@ -60,6 +70,15 @@ mcp_servers: zapier_mcp: url: "https://actions.zapier.com/mcp/sk-akxxxxx/sse" + # Standard Input/Output (stdio) Server - CircleCI Example + circleci_mcp: + transport: "stdio" + command: "npx" + args: ["-y", "@circleci/mcp-server-circleci"] + env: + CIRCLECI_TOKEN: "your-circleci-token" + CIRCLECI_BASE_URL: "https://circleci.com" + # Full configuration with all optional fields my_http_server: url: "https://my-mcp-server.com/mcp" @@ -70,11 +89,15 @@ mcp_servers: ``` **Configuration Options:** -- **Server Name**: Use any descriptive name for your MCP server (e.g., `zapier_mcp`, `deepwiki_mcp`) -- **URL**: The endpoint URL for your MCP server (required) +- **Server Name**: Use any descriptive name for your MCP server (e.g., `zapier_mcp`, `deepwiki_mcp`, `circleci_mcp`) +- **URL**: The endpoint URL for your MCP server (required for HTTP/SSE transports) - **Transport**: Optional transport type (defaults to `sse`) - `sse` - SSE (Server-Sent Events) transport - `http` - Streamable HTTP transport + - `stdio` - Standard Input/Output transport +- **Command**: The command to execute for stdio transport (required for stdio) +- **Args**: Array of arguments to pass to the command (optional for stdio) +- **Env**: Environment variables to set for the stdio process (optional for stdio) - **Description**: Optional description for the server - **Auth Type**: Optional authentication type - **Spec Version**: Optional MCP specification version (defaults to `2025-03-26`) diff --git a/docs/my-website/img/add_stdio_mcp.png b/docs/my-website/img/add_stdio_mcp.png new file mode 100644 index 0000000000000000000000000000000000000000..d82ec72102db517423870002a8c36a781f085fb2 GIT binary patch literal 115718 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 str: @@ -31,11 +38,12 @@ class MCPClient: def __init__( self, - server_url: str, + server_url: str = "", transport_type: MCPTransportType = MCPTransport.http, auth_type: MCPAuthType = None, auth_value: Optional[str] = None, timeout: float = 60.0, + stdio_config: Optional[MCPStdioConfig] = None, ): self.server_url: str = server_url self.transport_type: MCPTransport = transport_type @@ -48,6 +56,7 @@ class MCPClient: self._transport = None self._session_ctx = None self._task: Optional[asyncio.Task] = None + self.stdio_config: Optional[MCPStdioConfig] = stdio_config # handle the basic auth value if provided if auth_value: @@ -70,10 +79,25 @@ class MCPClient: if self._session: return # Already connected - headers = self._get_auth_headers() - try: - if self.transport_type == MCPTransport.sse: + if self.transport_type == MCPTransport.stdio: + # For stdio transport, use stdio_client with command-line parameters + if not self.stdio_config: + raise ValueError("stdio_config is required for stdio transport") + + server_params = StdioServerParameters( + command=self.stdio_config.get("command", ""), + args=self.stdio_config.get("args", []), + env=self.stdio_config.get("env", {}) + ) + + self._transport_ctx = stdio_client(server_params) + self._transport = await self._transport_ctx.__aenter__() + self._session_ctx = ClientSession(self._transport[0], self._transport[1]) + self._session = await self._session_ctx.__aenter__() + await self._session.initialize() + elif self.transport_type == MCPTransport.sse: + headers = self._get_auth_headers() self._transport_ctx = sse_client( url=self.server_url, timeout=self.timeout, @@ -83,7 +107,8 @@ class MCPClient: self._session_ctx = ClientSession(self._transport[0], self._transport[1]) self._session = await self._session_ctx.__aenter__() await self._session.initialize() - else: + else: # http + headers = self._get_auth_headers() self._transport_ctx = streamablehttp_client( url=self.server_url, timeout=timedelta(seconds=self.timeout), diff --git a/litellm/proxy/_experimental/mcp_server/db.py b/litellm/proxy/_experimental/mcp_server/db.py index e9a033c3ee..b7c2d9756c 100644 --- a/litellm/proxy/_experimental/mcp_server/db.py +++ b/litellm/proxy/_experimental/mcp_server/db.py @@ -1,5 +1,5 @@ import uuid -from typing import Iterable, List, Optional, Set +from typing import Any, Dict, Iterable, List, Optional, Set, Union from litellm.proxy._types import ( LiteLLM_MCPServerTable, @@ -13,6 +13,38 @@ from litellm.proxy._types import ( from litellm.proxy.utils import PrismaClient +def _prepare_mcp_server_data( + data: Union[NewMCPServerRequest, UpdateMCPServerRequest] +) -> Dict[str, Any]: + """ + Helper function to prepare MCP server data for database operations. + Handles JSON field serialization for mcp_info and env fields. + + Args: + data: NewMCPServerRequest or UpdateMCPServerRequest object + + Returns: + Dict with properly serialized JSON fields + """ + from litellm.litellm_core_utils.safe_json_dumps import safe_dumps + + # Convert model to dict + data_dict = data.model_dump() + + # Handle mcp_info serialization + if data.mcp_info is not None: + data_dict["mcp_info"] = safe_dumps(data.mcp_info) + + # Handle env serialization + if data.env is not None: + data_dict["env"] = safe_dumps(data.env) + + # mcp_access_groups is already List[str], no serialization needed + + + return data_dict + + async def get_all_mcp_servers( prisma_client: PrismaClient, ) -> List[LiteLLM_MCPServerTable]: @@ -215,35 +247,21 @@ async def create_mcp_server( """ Create a new mcp server record in the db """ - from litellm.litellm_core_utils.safe_json_dumps import safe_dumps if data.server_id is None: data.server_id = str(uuid.uuid4()) - # Convert model to dict and handle JSON fields - data_dict = data.model_dump() + # Use helper to prepare data with proper JSON serialization + data_dict = _prepare_mcp_server_data(data) - # Handle mcp_info serialization - mcp_info: Optional[str] = None - if data.mcp_info is not None: - mcp_info = safe_dumps(data.mcp_info) - del data_dict["mcp_info"] - - # Handle mcp_access_groups - it's already a List[str], no need to serialize - mcp_access_groups: Optional[list] = None - if data.mcp_access_groups is not None: - mcp_access_groups = data.mcp_access_groups - del data_dict["mcp_access_groups"] + # Add audit fields + data_dict["created_by"] = touched_by + data_dict["updated_by"] = touched_by - mcp_server_record = await prisma_client.db.litellm_mcpservertable.create( - data={ - **data_dict, - "created_by": touched_by, - "updated_by": touched_by, - "mcp_info": mcp_info, - "mcp_access_groups": mcp_access_groups, - } + new_mcp_server = await prisma_client.db.litellm_mcpservertable.create( + data=data_dict # type: ignore ) - return mcp_server_record + + return new_mcp_server async def update_mcp_server( @@ -252,33 +270,14 @@ async def update_mcp_server( """ Update a new mcp server record in the db """ - from litellm.litellm_core_utils.safe_json_dumps import safe_dumps + # Use helper to prepare data with proper JSON serialization + data_dict = _prepare_mcp_server_data(data) - # Convert model to dict and handle JSON fields - data_dict = data.model_dump() - - # Handle mcp_info serialization - mcp_info: Optional[str] = None - if data.mcp_info is not None: - mcp_info = safe_dumps(data.mcp_info) - del data_dict["mcp_info"] - - # Handle mcp_access_groups - it's already a List[str], no need to serialize - mcp_access_groups: Optional[list] = None - if data.mcp_access_groups is not None: - mcp_access_groups = data.mcp_access_groups - del data_dict["mcp_access_groups"] + # Add audit fields + data_dict["updated_by"] = touched_by - mcp_server_record = await prisma_client.db.litellm_mcpservertable.update( - where={ - "server_id": data.server_id, - }, - data={ - **data_dict, - "created_by": touched_by, - "updated_by": touched_by, - "mcp_info": mcp_info, - "mcp_access_groups": mcp_access_groups, - }, + updated_mcp_server = await prisma_client.db.litellm_mcpservertable.update( + where={"server_id": data.server_id}, data=data_dict # type: ignore ) - return mcp_server_record + + return updated_mcp_server diff --git a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py index 1f9b09c0e8..3b4d6abf57 100644 --- a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py +++ b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py @@ -36,9 +36,35 @@ from litellm.proxy._types import ( MCPTransportType, UserAPIKeyAuth, ) +from litellm.types.mcp import MCPStdioConfig from litellm.types.mcp_server.mcp_server_manager import MCPInfo, MCPServer +def _deserialize_env_dict(env_data: Any) -> Optional[Dict[str, str]]: + """ + Helper function to deserialize environment dictionary from database storage. + Handles both JSON string and dictionary formats. + + Args: + env_data: The environment data from database (could be JSON string or dict) + + Returns: + Dict[str, str] or None: Deserialized environment dictionary + """ + if not env_data: + return None + + if isinstance(env_data, str): + try: + return json.loads(env_data) + except (json.JSONDecodeError, TypeError): + # If it's not valid JSON, return as-is (shouldn't happen but safety) + return None + else: + # Already a dictionary + return env_data + + class MCPServerManager: def __init__(self): self.registry: Dict[str, MCPServer] = {} @@ -88,7 +114,7 @@ class MCPServerManager: # Generate stable server ID based on parameters server_id = self._generate_stable_server_id( server_name=server_name, - url=server_config["url"], + url=server_config.get("url", None) or "", transport=server_config.get("transport", MCPTransport.http), spec_version=server_config.get("spec_version", MCPSpecVersion.mar_2025), auth_type=server_config.get("auth_type", None), @@ -97,7 +123,10 @@ class MCPServerManager: new_server = MCPServer( server_id=server_id, name=server_name, - url=server_config["url"], + url=server_config.get("url", None) or "", + command=server_config.get("command", None) or "", + args=server_config.get("args", None) or [], + env=server_config.get("env", None) or {}, # TODO: utility fn the default values transport=server_config.get("transport", MCPTransport.http), spec_version=server_config.get("spec_version", MCPSpecVersion.mar_2025), @@ -129,6 +158,10 @@ class MCPServerManager: def add_update_server(self, mcp_server: LiteLLM_MCPServerTable): if mcp_server.server_id not in self.get_registry(): _mcp_info: MCPInfo = mcp_server.mcp_info or {} + + # Use helper to deserialize environment dictionary + env_dict = _deserialize_env_dict(mcp_server.env) + new_server = MCPServer( server_id=mcp_server.server_id, name=mcp_server.alias or mcp_server.server_id, @@ -141,6 +174,10 @@ class MCPServerManager: description=mcp_server.description, mcp_server_cost_info=_mcp_info.get("mcp_server_cost_info", None), ), + # Stdio-specific fields + command=mcp_server.command, + args=mcp_server.args, + env=env_dict, ) self.registry[mcp_server.server_id] = new_server verbose_logger.debug( @@ -227,13 +264,36 @@ class MCPServerManager: MCPClient: Configured MCP client instance """ transport = server.transport or MCPTransport.sse - return MCPClient( - server_url=server.url, - transport_type=transport, - auth_type=server.auth_type, - auth_value=mcp_auth_header or server.authentication_token, - timeout=60.0, - ) + + # Handle stdio transport + if transport == MCPTransport.stdio: + # For stdio, we need to get the stdio config from the server + stdio_config: Optional[MCPStdioConfig] = None + if server.command and server.args is not None: + stdio_config = MCPStdioConfig( + command=server.command, + args=server.args, + env=server.env or {} + ) + + return MCPClient( + server_url="", # Not used for stdio + transport_type=transport, + auth_type=server.auth_type, + auth_value=mcp_auth_header or server.authentication_token, + timeout=60.0, + stdio_config=stdio_config, + ) + else: + # For HTTP/SSE transports + server_url = server.url or "" + return MCPClient( + server_url=server_url, + transport_type=transport, + auth_type=server.auth_type, + auth_value=mcp_auth_header or server.authentication_token, + timeout=60.0, + ) async def _get_tools_from_server(self, server: MCPServer, mcp_auth_header: Optional[str] = None) -> List[MCPTool]: """ diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 811a7b35f0..68de8d8b63 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -847,9 +847,28 @@ class NewMCPServerRequest(LiteLLMPydanticObjectBase): transport: MCPTransportType = MCPTransport.sse spec_version: MCPSpecVersionType = MCPSpecVersion.mar_2025 auth_type: Optional[MCPAuthType] = None - url: str + url: Optional[str] = None mcp_info: Optional[MCPInfo] = None mcp_access_groups: List[str] = Field(default_factory=list) + # Stdio-specific fields + command: Optional[str] = None + args: Optional[List[str]] = None + env: Optional[Dict[str, str]] = None + + @model_validator(mode="before") + @classmethod + def validate_transport_fields(cls, values): + if isinstance(values, dict): + transport = values.get("transport") + if transport == MCPTransport.stdio: + if not values.get("command"): + raise ValueError("command is required for stdio transport") + if not values.get("args"): + raise ValueError("args is required for stdio transport") + elif transport in [MCPTransport.http, MCPTransport.sse]: + if not values.get("url"): + raise ValueError("url is required for HTTP/SSE transport") + return values @@ -860,9 +879,28 @@ class UpdateMCPServerRequest(LiteLLMPydanticObjectBase): transport: MCPTransportType = MCPTransport.sse spec_version: MCPSpecVersionType = MCPSpecVersion.mar_2025 auth_type: Optional[MCPAuthType] = None - url: str + url: Optional[str] = None mcp_info: Optional[MCPInfo] = None mcp_access_groups: List[str] = Field(default_factory=list) + # Stdio-specific fields + command: Optional[str] = None + args: Optional[List[str]] = None + env: Optional[Dict[str, str]] = None + + @model_validator(mode="before") + @classmethod + def validate_transport_fields(cls, values): + if isinstance(values, dict): + transport = values.get("transport") + if transport == MCPTransport.stdio: + if not values.get("command"): + raise ValueError("command is required for stdio transport") + if not values.get("args"): + raise ValueError("args is required for stdio transport") + elif transport in [MCPTransport.http, MCPTransport.sse]: + if not values.get("url"): + raise ValueError("url is required for HTTP/SSE transport") + return values @@ -872,7 +910,7 @@ class LiteLLM_MCPServerTable(LiteLLMPydanticObjectBase): server_id: str alias: Optional[str] = None description: Optional[str] = None - url: str + url: Optional[str] = None transport: MCPTransportType spec_version: MCPSpecVersionType auth_type: Optional[MCPAuthType] = None @@ -883,6 +921,10 @@ class LiteLLM_MCPServerTable(LiteLLMPydanticObjectBase): teams: List[Dict[str, Optional[str]]] = Field(default_factory=list) mcp_access_groups: List[str] = Field(default_factory=list) mcp_info: Optional[MCPInfo] = None + # Stdio-specific fields + command: Optional[str] = None + args: Optional[List[str]] = None + env: Optional[Dict[str, str]] = None class NewUserRequestTeam(LiteLLMPydanticObjectBase): diff --git a/litellm/proxy/management_endpoints/mcp_management_endpoints.py b/litellm/proxy/management_endpoints/mcp_management_endpoints.py index 351fe75291..b182ce5de1 100644 --- a/litellm/proxy/management_endpoints/mcp_management_endpoints.py +++ b/litellm/proxy/management_endpoints/mcp_management_endpoints.py @@ -23,8 +23,8 @@ from fastapi.responses import JSONResponse import litellm from litellm._logging import verbose_logger, verbose_proxy_logger from litellm.constants import LITELLM_PROXY_ADMIN_NAME -from litellm.proxy.auth.model_checks import get_mcp_server_ids from litellm.proxy._experimental.mcp_server.utils import validate_mcp_server_name +from litellm.proxy.auth.model_checks import get_mcp_server_ids router = APIRouter(prefix="/v1/mcp", tags=["mcp"]) MCP_AVAILABLE: bool = True @@ -244,6 +244,10 @@ if MCP_AVAILABLE: created_at=datetime.now(), updated_at=datetime.now(), mcp_info=_server_config.mcp_info, + # Stdio-specific fields + command=_server_config.command, + args=_server_config.args, + env=_server_config.env, ) ) @@ -263,7 +267,11 @@ if MCP_AVAILABLE: updated_by=server.updated_by, mcp_access_groups=server.mcp_access_groups if server.mcp_access_groups is not None else [], mcp_info=server.mcp_info, - teams=cast(List[Dict[str, str | None]], server_to_teams_map.get(server.server_id, [])) + teams=cast(List[Dict[str, str | None]], server_to_teams_map.get(server.server_id, [])), + # Stdio-specific fields + command=server.command, + args=server.args, + env=server.env, ) for server in LIST_MCP_SERVERS ] diff --git a/litellm/proxy/schema.prisma b/litellm/proxy/schema.prisma index 3b2cf0af16..b92f4af384 100644 --- a/litellm/proxy/schema.prisma +++ b/litellm/proxy/schema.prisma @@ -168,7 +168,7 @@ model LiteLLM_MCPServerTable { server_id String @id @default(uuid()) alias String? description String? - url String + url String? transport String @default("sse") spec_version String @default("2025-03-26") auth_type String? @@ -178,7 +178,10 @@ model LiteLLM_MCPServerTable { updated_by String? mcp_info Json? @default("{}") mcp_access_groups String[] - + // Stdio-specific fields + command String? + args String[] @default([]) + env Json? @default("{}") } // Generate Tokens for Proxy diff --git a/litellm/types/mcp.py b/litellm/types/mcp.py index 081bce10b6..dc54694410 100644 --- a/litellm/types/mcp.py +++ b/litellm/types/mcp.py @@ -18,6 +18,7 @@ else: class MCPTransport(str, enum.Enum): sse = "sse" http = "http" + stdio = "stdio" class MCPSpecVersion(str, enum.Enum): @@ -32,7 +33,7 @@ class MCPAuth(str, enum.Enum): # MCP Literals -MCPTransportType = Literal[MCPTransport.sse, MCPTransport.http] +MCPTransportType = Literal[MCPTransport.sse, MCPTransport.http, MCPTransport.stdio] MCPSpecVersionType = Literal[MCPSpecVersion.nov_2024, MCPSpecVersion.mar_2025] MCPAuthType = Optional[ Literal[MCPAuth.none, MCPAuth.api_key, MCPAuth.bearer_token, MCPAuth.basic] @@ -52,6 +53,23 @@ class MCPServerCostInfo(TypedDict, total=False): """ +class MCPStdioConfig(TypedDict, total=False): + command: str + """ + Command to run the MCP server (e.g., 'npx', 'python', 'node') + """ + + args: List[str] + """ + Arguments to pass to the command + """ + + env: Optional[Dict[str, str]] + """ + Environment variables to set when running the command + """ + + class MCPPostCallResponseObject(BaseModel): """ Pydantic object used for MCP post_call_hook response diff --git a/litellm/types/mcp_server/mcp_server_manager.py b/litellm/types/mcp_server/mcp_server_manager.py index 1eeab925e2..8ed95b8394 100644 --- a/litellm/types/mcp_server/mcp_server_manager.py +++ b/litellm/types/mcp_server/mcp_server_manager.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Dict, List, Optional from pydantic import BaseModel, ConfigDict from typing_extensions import TypedDict @@ -17,10 +17,14 @@ class MCPInfo(TypedDict, total=False): class MCPServer(BaseModel): server_id: str name: str - url: str + url: Optional[str] = None transport: MCPTransportType spec_version: MCPSpecVersionType auth_type: Optional[MCPAuthType] = None authentication_token: Optional[str] = None mcp_info: Optional[MCPInfo] = None + # Stdio-specific fields + command: Optional[str] = None + args: Optional[List[str]] = None + env: Optional[Dict[str, str]] = None model_config = ConfigDict(arbitrary_types_allowed=True) diff --git a/tests/test_litellm/experimental_mcp_client/test_mcp_client.py b/tests/test_litellm/experimental_mcp_client/test_mcp_client.py new file mode 100644 index 0000000000..cf1af288d5 --- /dev/null +++ b/tests/test_litellm/experimental_mcp_client/test_mcp_client.py @@ -0,0 +1,79 @@ +import sys +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + +# Add the parent directory to the path so we can import litellm +sys.path.insert(0, '../../../') + +from litellm.experimental_mcp_client.client import MCPClient +from litellm.types.mcp import MCPStdioConfig, MCPTransport + + +class TestMCPClient: + """Test MCP Client stdio functionality""" + + def test_mcp_client_stdio_init(self): + """Test MCPClient initialization with stdio config""" + stdio_config = MCPStdioConfig( + command="python", + args=["-m", "my_mcp_server"], + env={"DEBUG": "1"} + ) + + client = MCPClient( + transport_type=MCPTransport.stdio, + stdio_config=stdio_config + ) + + assert client.transport_type == MCPTransport.stdio + assert client.stdio_config == stdio_config + assert client.stdio_config["command"] == "python" + assert client.stdio_config["args"] == ["-m", "my_mcp_server"] + + @pytest.mark.asyncio + async def test_mcp_client_stdio_connect_error(self): + """Test MCP client stdio connection error handling""" + # Test missing stdio_config + client = MCPClient(transport_type=MCPTransport.stdio) + + with pytest.raises(ValueError, match="stdio_config is required for stdio transport"): + await client.connect() + + @pytest.mark.asyncio + @patch('litellm.experimental_mcp_client.client.stdio_client') + @patch('litellm.experimental_mcp_client.client.ClientSession') + async def test_mcp_client_stdio_connect_success(self, mock_session, mock_stdio_client): + """Test successful stdio connection""" + # Setup mocks + mock_transport = (MagicMock(), MagicMock()) + mock_stdio_client.return_value.__aenter__ = AsyncMock(return_value=mock_transport) + + mock_session_instance = MagicMock() + mock_session_instance.__aenter__ = AsyncMock(return_value=mock_session_instance) + mock_session_instance.initialize = AsyncMock() + mock_session.return_value = mock_session_instance + + stdio_config = MCPStdioConfig( + command="python", + args=["-m", "my_mcp_server"], + env={"DEBUG": "1"} + ) + + client = MCPClient( + transport_type=MCPTransport.stdio, + stdio_config=stdio_config + ) + + await client.connect() + + # Verify stdio_client was called with correct parameters + mock_stdio_client.assert_called_once() + call_args = mock_stdio_client.call_args[0][0] + assert call_args.command == "python" + assert call_args.args == ["-m", "my_mcp_server"] + assert call_args.env == {"DEBUG": "1"} + + +if __name__ == "__main__": + pytest.main([__file__]) \ No newline at end of file diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py new file mode 100644 index 0000000000..486ad8fe33 --- /dev/null +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py @@ -0,0 +1,94 @@ +import sys +from datetime import datetime +from unittest.mock import MagicMock + +import pytest + +# Add the parent directory to the path so we can import litellm +sys.path.insert(0, '../../../../../') + +from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + MCPServerManager, + _deserialize_env_dict, +) +from litellm.proxy._types import LiteLLM_MCPServerTable, MCPSpecVersion, MCPTransport +from litellm.types.mcp_server.mcp_server_manager import MCPServer + + +class TestMCPServerManager: + """Test MCP Server Manager stdio functionality""" + + def test_deserialize_env_dict(self): + """Test environment dictionary deserialization""" + # Test JSON string + env_json = '{"PATH": "/usr/bin", "DEBUG": "1"}' + result = _deserialize_env_dict(env_json) + assert result == {"PATH": "/usr/bin", "DEBUG": "1"} + + # Test already dict + env_dict = {"PATH": "/usr/bin", "DEBUG": "1"} + result = _deserialize_env_dict(env_dict) + assert result == {"PATH": "/usr/bin", "DEBUG": "1"} + + # Test invalid JSON + invalid_json = '{"PATH": "/usr/bin", "DEBUG": 1' + result = _deserialize_env_dict(invalid_json) + assert result is None + + def test_add_update_server_stdio(self): + """Test adding stdio MCP server""" + manager = MCPServerManager() + + stdio_server = LiteLLM_MCPServerTable( + server_id="stdio-server-1", + alias="test_stdio_server", + description="Test stdio server", + url=None, + transport=MCPTransport.stdio, + spec_version=MCPSpecVersion.mar_2025, + command="python", + args=["-m", "server"], + env={"DEBUG": "1", "TEST": "1"}, + created_at=datetime.now(), + updated_at=datetime.now() + ) + + manager.add_update_server(stdio_server) + + # Verify server was added + assert "stdio-server-1" in manager.registry + added_server = manager.registry["stdio-server-1"] + + assert added_server.server_id == "stdio-server-1" + assert added_server.name == "test_stdio_server" + assert added_server.transport == MCPTransport.stdio + assert added_server.command == "python" + assert added_server.args == ["-m", "server"] + assert added_server.env == {"DEBUG": "1", "TEST": "1"} + + def test_create_mcp_client_stdio(self): + """Test creating MCP client for stdio transport""" + manager = MCPServerManager() + + stdio_server = MCPServer( + server_id="stdio-server-2", + name="test_stdio_server", + url=None, + transport=MCPTransport.stdio, + spec_version=MCPSpecVersion.mar_2025, + command="node", + args=["server.js"], + env={"NODE_ENV": "test"} + ) + + client = manager._create_mcp_client(stdio_server) + + assert client.transport_type == MCPTransport.stdio + assert client.stdio_config is not None + assert client.stdio_config["command"] == "node" + assert client.stdio_config["args"] == ["server.js"] + assert client.stdio_config["env"] == {"NODE_ENV": "test"} + + +if __name__ == "__main__": + pytest.main([__file__]) \ No newline at end of file diff --git a/ui/litellm-dashboard/src/components/mcp_tools/StdioConfiguration.tsx b/ui/litellm-dashboard/src/components/mcp_tools/StdioConfiguration.tsx new file mode 100644 index 0000000000..2c3c4e515f --- /dev/null +++ b/ui/litellm-dashboard/src/components/mcp_tools/StdioConfiguration.tsx @@ -0,0 +1,58 @@ +import React from "react"; +import { Form, Input, Tooltip } from "antd"; +import { InfoCircleOutlined } from "@ant-design/icons"; + +interface StdioConfigurationProps { + isVisible: boolean; +} + +const StdioConfiguration: React.FC = ({ isVisible }) => { + if (!isVisible) return null; + + return ( + + Stdio Configuration (JSON) + + + + + } + name="stdio_config" + rules={[ + { required: true, message: "Please enter stdio configuration" }, + { + validator: (_, value) => { + if (!value) return Promise.resolve(); + try { + JSON.parse(value); + return Promise.resolve(); + } catch { + return Promise.reject("Please enter valid JSON"); + } + }, + }, + ]} + > + + + ); +}; + +export default StdioConfiguration; \ No newline at end of file diff --git a/ui/litellm-dashboard/src/components/mcp_tools/ToolTestPanel.tsx b/ui/litellm-dashboard/src/components/mcp_tools/ToolTestPanel.tsx index 4718dcd5d2..6f2845a63d 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/ToolTestPanel.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/ToolTestPanel.tsx @@ -4,24 +4,6 @@ import { MCPTool, InputSchema } from "./types"; import { Form, Tooltip, message } from "antd"; import { InfoCircleOutlined, ClockCircleOutlined } from "@ant-design/icons"; -const AuthBanner = ({ needsAuth, authValue }: { needsAuth: boolean; authValue?: string | null }) => { - if (!needsAuth || (needsAuth && authValue)) { - return ( - - This tool does not require authentication or has authentication added. - - ); - } - - if (needsAuth && !authValue) { - return ( - - Please provide authentication details if this tool call requires auth. - - ); - } - return null; -}; export function ToolTestPanel({ tool, @@ -65,11 +47,59 @@ export function ToolTestPanel({ return tool.inputSchema as InputSchema; }, [tool.inputSchema]); + // Check if this is a nested params structure and extract the actual parameters + const actualSchema: InputSchema = React.useMemo(() => { + if (schema.properties && schema.properties.params && + schema.properties.params.type === "object" && + schema.properties.params.properties) { + // This is a nested params structure, extract the actual parameters + return { + type: "object", + properties: schema.properties.params.properties, + required: schema.properties.params.required || [], + }; + } + return schema; + }, [schema]); + const handleSubmit = (values: Record) => { const start = Date.now(); setStartTime(start); setDuration(null); - onSubmit(values); + + // Convert form values to proper types based on schema + const convertedValues: Record = {}; + const schemaToUse = actualSchema; + + Object.entries(values).forEach(([key, value]) => { + const prop = schemaToUse.properties?.[key]; + if (prop && value !== null && value !== undefined && value !== "") { + switch (prop.type) { + case "boolean": + convertedValues[key] = value === "true" || value === true; + break; + case "number": + convertedValues[key] = Number(value); + break; + case "string": + convertedValues[key] = String(value); + break; + default: + convertedValues[key] = value; + } + } else if (value !== null && value !== undefined && value !== "") { + convertedValues[key] = value; + } + }); + + // If this was a nested params structure, wrap the values back in params + const submitValues = (schema.properties && schema.properties.params && + schema.properties.params.type === "object" && + schema.properties.params.properties) + ? { params: convertedValues } + : convertedValues; + + onSubmit(submitValues); }; // Track when result changes to calculate duration @@ -183,9 +213,7 @@ export function ToolTestPanel({ - {/* Auth Banner */} - - + {/* Two Column Layout - Always Side by Side */}
{/* Left Column - Input Parameters */} @@ -219,7 +247,7 @@ export function ToolTestPanel({ />
- ) : schema.properties === undefined ? ( + ) : actualSchema.properties === undefined ? (

No Parameters Required

@@ -228,13 +256,13 @@ export function ToolTestPanel({
) : (
- {Object.entries(schema.properties).map(([key, prop]) => ( + {Object.entries(actualSchema.properties).map(([key, prop]) => ( {key}{" "} - {schema.required?.includes(key) && *} + {actualSchema.required?.includes(key) && *} {prop.description && ( @@ -245,13 +273,29 @@ export function ToolTestPanel({ name={key} rules={[ { - required: schema.required?.includes(key), + required: actualSchema.required?.includes(key), message: `Please enter ${key}`, }, ]} className="mb-3" > - {prop.type === "string" && ( + {prop.type === "string" && prop.enum && ( + + )} + + {prop.type === "string" && !prop.enum && ( - - Enable this option -
+ )} ))} diff --git a/ui/litellm-dashboard/src/components/mcp_tools/create_mcp_server.tsx b/ui/litellm-dashboard/src/components/mcp_tools/create_mcp_server.tsx index 22654fa67d..65d493a495 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/create_mcp_server.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/create_mcp_server.tsx @@ -6,15 +6,15 @@ import { Select, message, Button as AntdButton, - Space, Input, } from "antd"; -import { InfoCircleOutlined, MinusCircleOutlined, PlusOutlined } from "@ant-design/icons"; +import { InfoCircleOutlined } from "@ant-design/icons"; import { Button, TextInput } from "@tremor/react"; import { createMCPServer } from "../networking"; import { MCPServer, MCPServerCostInfo } from "./types"; import MCPServerCostConfig from "./mcp_server_cost_config"; import MCPConnectionStatus from "./mcp_connection_status"; +import StdioConfiguration from "./StdioConfiguration"; import { isAdminRole } from "@/utils/roles"; @@ -38,6 +38,7 @@ const CreateMCPServer: React.FC = ({ const [mcpAccessGroups, setMcpAccessGroups] = useState([]); const [formValues, setFormValues] = useState>({}); const [tools, setTools] = useState([]); + const [transportType, setTransportType] = useState('sse'); const handleCreate = async (formValues: Record) => { setIsLoading(true); @@ -46,9 +47,51 @@ const CreateMCPServer: React.FC = ({ const accessGroups = formValues.mcp_access_groups + // Process stdio configuration if present + let stdioFields = {}; + if (formValues.stdio_config && transportType === 'stdio') { + try { + const stdioConfig = JSON.parse(formValues.stdio_config); + + // Handle both formats: + // 1. Full mcpServers structure: {"mcpServers": {"server-name": {...}}} + // 2. Direct config: {"command": "...", "args": [...], "env": {...}} + + let actualConfig = stdioConfig; + + // If it's the full mcpServers structure, extract the first server config + if (stdioConfig.mcpServers && typeof stdioConfig.mcpServers === 'object') { + const serverNames = Object.keys(stdioConfig.mcpServers); + if (serverNames.length > 0) { + const firstServerName = serverNames[0]; + actualConfig = stdioConfig.mcpServers[firstServerName]; + + // If no alias is provided, use the server name from the JSON + if (!formValues.alias) { + formValues.alias = firstServerName.replace(/-/g, '_'); // Replace hyphens with underscores + } + } + } + + stdioFields = { + command: actualConfig.command, + args: actualConfig.args, + env: actualConfig.env + }; + + console.log('Parsed stdio config:', stdioFields); + } catch (error) { + message.error("Invalid JSON in stdio configuration"); + return; + } + } + // Prepare the payload with cost configuration const payload = { ...formValues, + ...stdioFields, + // Remove the raw stdio_config field as we've extracted its components + stdio_config: undefined, mcp_info: { server_name: formValues.alias || formValues.url, description: formValues.description, @@ -89,7 +132,15 @@ const CreateMCPServer: React.FC = ({ setModalVisible(false); }; - + const handleTransportChange = (value: string) => { + setTransportType(value); + // Clear fields that are not relevant for the selected transport + if (value === 'stdio') { + form.setFieldsValue({ url: undefined, auth_type: undefined }); + } else { + form.setFieldsValue({ command: undefined, args: undefined, env: undefined }); + } + }; // rendering if (!isAdminRole(userRole)) { @@ -186,41 +237,48 @@ const CreateMCPServer: React.FC = ({ - MCP Server URL + Transport Type } - name="url" - rules={[ - { required: true, message: "Please enter a server URL" }, - { type: 'url', message: "Please enter a valid URL" } - ]} + name="transport" + rules={[{ required: true, message: "Please select a transport type" }]} > - + -
+ {/* URL field - only show for HTTP and SSE */} + {transportType !== 'stdio' && ( - Transport Type + MCP Server URL } - name="transport" - rules={[{ required: true, message: "Please select a transport type" }]} + name="url" + rules={[ + { required: true, message: "Please enter a server URL" }, + { type: 'url', message: "Please enter a valid URL" } + ]} > - + + )} + {/* Authentication - only show for HTTP and SSE */} + {transportType !== 'stdio' && ( @@ -241,7 +299,10 @@ const CreateMCPServer: React.FC = ({ Basic Auth -
+ )} + + {/* Stdio Configuration - only show for stdio transport */} + { export interface InputSchemaProperty { type: string; description?: string; + properties?: Record; // For nested object properties + required?: string[]; // For required fields in nested objects + enum?: string[]; // For enum values + default?: any; // For default values } // Define the structure for the input schema of a tool