[MCP] Bug fix - adding SSE MCP tools - fix connection test when adding MCPs (#14048)

* fix: _execute_with_mcp_client

* add warning for SSE MCP

* docs - MCP videos / guides
This commit is contained in:
Ishaan Jaff 2025-08-28 15:16:34 -07:00 committed by GitHub
parent 37a599932d
commit f3338bef9f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 112 additions and 41 deletions

View file

@ -40,7 +40,28 @@ LiteLLM supports the following MCP transports:
style={{width: '80%', display: 'block', margin: '0'}}
/>
### Adding a stdio MCP Server
<br/>
<br/>
### Add HTTP MCP Server
This video walks through adding and using an HTTP MCP server on LiteLLM UI and using it in Cursor IDE.
<iframe width="840" height="500" src="https://www.loom.com/embed/e2aebce78e8d46beafeb4bacdde31f14" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
<br/>
<br/>
### Add SSE MCP Server
This video walks through adding and using an SSE MCP server on LiteLLM UI and using it in Cursor IDE.
<iframe width="840" height="500" src="https://www.loom.com/embed/07e04e27f5e74475b9cf8ef8247d2c3e" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
<br/>
<br/>
### 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:

View file

@ -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)

View file

@ -39,6 +39,23 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
const [tools, setTools] = useState<any[]>([])
const [transportType, setTransportType] = useState<string>("sse")
const [searchValue, setSearchValue] = useState<string>("")
const [urlWarning, setUrlWarning] = useState<string>("")
// 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<string, any>) => {
setIsLoading(true)
@ -110,6 +127,7 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
form.resetFields()
setCostConfig({})
setTools([])
setUrlWarning("")
setModalVisible(false)
onCreateSuccess(response)
}
@ -125,6 +143,7 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
form.resetFields()
setCostConfig({})
setTools([])
setUrlWarning("")
setModalVisible(false)
}
@ -133,8 +152,14 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
// 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<CreateMCPServerProps> = ({
{ validator: (_, value) => validateMCPServerUrl(value) },
]}
>
<TextInput
placeholder="https://your-mcp-server.com"
className="rounded-lg border-gray-300 focus:border-blue-500 focus:ring-blue-500"
/>
<div>
<TextInput
placeholder="https://your-mcp-server.com"
className="rounded-lg border-gray-300 focus:border-blue-500 focus:ring-blue-500"
onChange={(e) => checkUrlFormat(e.target.value, transportType)}
/>
{urlWarning && (
<div className="mt-1 text-red-500 text-sm font-medium">
{urlWarning}
</div>
)}
</div>
</Form.Item>
)}