diff --git a/docs/my-website/docs/mcp.md b/docs/my-website/docs/mcp.md
index 684c2e6ca74..7eaccf3180f 100644
--- a/docs/my-website/docs/mcp.md
+++ b/docs/my-website/docs/mcp.md
@@ -40,7 +40,28 @@ LiteLLM supports the following MCP transports:
style={{width: '80%', display: 'block', margin: '0'}}
/>
-### Adding a stdio MCP Server
+
+
+
+### Add HTTP MCP Server
+
+This video walks through adding and using an HTTP MCP server on LiteLLM UI and using it in Cursor IDE.
+
+
+
+
+
+
+### Add SSE MCP Server
+
+This video walks through adding and using an SSE MCP server on LiteLLM UI and using it in Cursor IDE.
+
+
+
+
+
+
+### Add STDIO MCP Server
For stdio MCP servers, select "Standard Input/Output (stdio)" as the transport type and provide the stdio configuration in JSON format:
diff --git a/litellm/proxy/_experimental/mcp_server/rest_endpoints.py b/litellm/proxy/_experimental/mcp_server/rest_endpoints.py
index 6624eb7e64e..048b25fa35a 100644
--- a/litellm/proxy/_experimental/mcp_server/rest_endpoints.py
+++ b/litellm/proxy/_experimental/mcp_server/rest_endpoints.py
@@ -1,5 +1,5 @@
import importlib
-from typing import Optional, Dict
+from typing import Dict, List, Optional
from fastapi import APIRouter, Depends, Query, Request
@@ -21,9 +21,10 @@ router = APIRouter(
)
if MCP_AVAILABLE:
+ from litellm.experimental_mcp_client.client import MCPTool
from litellm.proxy._experimental.mcp_server.mcp_server_manager import (
- global_mcp_server_manager,
_convert_protocol_version_to_enum,
+ global_mcp_server_manager,
)
from litellm.proxy._experimental.mcp_server.server import (
ListMCPToolsRestAPIResponseObject,
@@ -100,7 +101,9 @@ if MCP_AVAILABLE:
"message": "Successfully retrieved tools"
}
"""
- from litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp import MCPRequestHandler
+ from litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp import (
+ MCPRequestHandler,
+ )
try:
# Extract auth headers from request
@@ -172,10 +175,11 @@ if MCP_AVAILABLE:
"""
REST API to call a specific MCP tool with the provided arguments
"""
- from litellm.proxy.proxy_server import add_litellm_data_to_request, proxy_config
- from litellm.exceptions import BlockedPiiEntityError, GuardrailRaisedException
from fastapi import HTTPException
+ from litellm.exceptions import BlockedPiiEntityError, GuardrailRaisedException
+ from litellm.proxy.proxy_server import add_litellm_data_to_request, proxy_config
+
try:
data = await request.json()
data = await add_litellm_data_to_request(
@@ -230,13 +234,19 @@ if MCP_AVAILABLE:
from litellm.proxy.management_endpoints.mcp_management_endpoints import (
NewMCPServerRequest,
)
- @router.post("/test/connection")
- async def test_connection(
- request: NewMCPServerRequest,
- ):
+
+ async def _execute_with_mcp_client(request: NewMCPServerRequest, operation):
"""
- Test if we can connect to the provided MCP server before adding it
+ Common helper to create MCP client, execute operation, and ensure proper cleanup.
+
+ Args:
+ request: MCP server configuration
+ operation: Async function that takes a client and returns the operation result
+
+ Returns:
+ Operation result or error response
"""
+ client = None
try:
client = global_mcp_server_manager._create_mcp_client(
server=MCPServer(
@@ -250,12 +260,31 @@ if MCP_AVAILABLE:
),
mcp_auth_header=None,
)
-
- await client.connect()
+
+ return await operation(client)
+
except Exception as e:
- verbose_logger.error(f"Error in test_connection: {e}", exc_info=True)
+ verbose_logger.error(f"Error in MCP operation: {e}", exc_info=True)
return {"status": "error", "message": "An internal error has occurred."}
- return {"status": "ok"}
+ finally:
+ # Ensure client is properly disconnected before response is sent
+ if client is not None:
+ try:
+ await client.disconnect()
+ except Exception as e:
+ verbose_logger.warning(f"Error disconnecting MCP client: {e}")
+ @router.post("/test/connection")
+ async def test_connection(
+ request: NewMCPServerRequest,
+ ):
+ """
+ Test if we can connect to the provided MCP server before adding it
+ """
+ async def _test_connection_operation(client):
+ await client.connect()
+ return {"status": "ok"}
+
+ return await _execute_with_mcp_client(request, _test_connection_operation)
@router.post("/test/tools/list")
@@ -266,25 +295,13 @@ if MCP_AVAILABLE:
"""
Preview tools available from MCP server before adding it
"""
- try:
- client = global_mcp_server_manager._create_mcp_client(
- server=MCPServer(
- server_id=request.server_id or "",
- name=request.alias or request.server_name or "",
- url=request.url,
- transport=request.transport,
- spec_version=_convert_protocol_version_to_enum(request.spec_version),
- auth_type=request.auth_type,
- mcp_info=request.mcp_info,
- ),
- mcp_auth_header=None,
- )
- list_tools_result = await client.list_tools()
- except Exception as e:
- verbose_logger.error(f"Error in test_tools_list: {e}", exc_info=True)
- return {"status": "error", "message": "An internal error has occurred."}
- return {
- "tools": list_tools_result,
- "error": None,
- "message": "Successfully retrieved tools"
- }
+ async def _list_tools_operation(client):
+ list_tools_result: List[MCPTool] = await client.list_tools()
+ model_dumped_tools: List[dict] = [tool.model_dump() for tool in list_tools_result]
+ return {
+ "tools": model_dumped_tools,
+ "error": None,
+ "message": "Successfully retrieved tools"
+ }
+
+ return await _execute_with_mcp_client(request, _list_tools_operation)
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 528da6856e1..1087977b4c5 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
@@ -39,6 +39,23 @@ const CreateMCPServer: React.FC = ({
const [tools, setTools] = useState([])
const [transportType, setTransportType] = useState("sse")
const [searchValue, setSearchValue] = useState("")
+ const [urlWarning, setUrlWarning] = useState("")
+
+ // Function to check URL format based on transport type
+ const checkUrlFormat = (url: string, transport: string) => {
+ if (!url) {
+ setUrlWarning("")
+ return
+ }
+
+ if (transport === "sse" && !url.endsWith("/sse")) {
+ setUrlWarning("Typically MCP SSE URLs end with /sse. You can add this url but this is a warning.")
+ } else if (transport === "http" && !url.endsWith("/mcp")) {
+ setUrlWarning("Typically MCP HTTP URLs end with /mcp. You can add this url but this is a warning.")
+ } else {
+ setUrlWarning("")
+ }
+ }
const handleCreate = async (formValues: Record) => {
setIsLoading(true)
@@ -110,6 +127,7 @@ const CreateMCPServer: React.FC = ({
form.resetFields()
setCostConfig({})
setTools([])
+ setUrlWarning("")
setModalVisible(false)
onCreateSuccess(response)
}
@@ -125,6 +143,7 @@ const CreateMCPServer: React.FC = ({
form.resetFields()
setCostConfig({})
setTools([])
+ setUrlWarning("")
setModalVisible(false)
}
@@ -133,8 +152,14 @@ const CreateMCPServer: React.FC = ({
// Clear fields that are not relevant for the selected transport
if (value === "stdio") {
form.setFieldsValue({ url: undefined, auth_type: undefined })
+ setUrlWarning("")
} else {
form.setFieldsValue({ command: undefined, args: undefined, env: undefined })
+ // Check URL format for the new transport type
+ const currentUrl = form.getFieldValue("url")
+ if (currentUrl) {
+ checkUrlFormat(currentUrl, value)
+ }
}
}
@@ -310,10 +335,18 @@ const CreateMCPServer: React.FC = ({
{ validator: (_, value) => validateMCPServerUrl(value) },
]}
>
-
+
+
checkUrlFormat(e.target.value, transportType)}
+ />
+ {urlWarning && (
+
+ {urlWarning}
+
+ )}
+
)}