fix(mcp/): re-raise exception from server to client

allows user to debug why mcp server is not working as expected

Related to https://github.com/BerriAI/litellm/pull/15180
This commit is contained in:
Krrish Dholakia 2025-10-04 18:14:32 -07:00
parent b7ca138a8d
commit a49a19e784
4 changed files with 48 additions and 56 deletions

View file

@ -235,10 +235,9 @@ class MCPClient:
await self.disconnect()
raise
except Exception as e:
verbose_logger.warning(f"MCP client list_tools failed: {str(e)}")
verbose_logger.debug(f"MCP client list_tools failed: {str(e)}")
await self.disconnect()
# Return empty list instead of raising to allow graceful degradation
return []
raise e
async def call_tool(
self, call_tool_request_params: MCPCallToolRequestParams

View file

@ -490,10 +490,10 @@ class MCPServerManager:
return prefixed_or_original_tools
except Exception as e:
verbose_logger.warning(
verbose_logger.debug(
f"Failed to get tools from server {server.name}: {str(e)}"
)
return []
raise e
finally:
if client:
try:
@ -522,14 +522,14 @@ class MCPServerManager:
tools = await client.list_tools()
verbose_logger.debug(f"Tools from {server_name}: {tools}")
return tools
except asyncio.CancelledError:
verbose_logger.warning(f"Client operation cancelled for {server_name}")
return []
except asyncio.CancelledError as e:
verbose_logger.debug(f"Client operation cancelled for {server_name}")
raise e
except Exception as e:
verbose_logger.warning(
verbose_logger.debug(
f"Client operation failed for {server_name}: {str(e)}"
)
return []
raise e
finally:
try:
await client.disconnect()
@ -538,22 +538,22 @@ class MCPServerManager:
try:
return await asyncio.wait_for(_list_tools_task(), timeout=30.0)
except asyncio.TimeoutError:
verbose_logger.warning(f"Timeout while listing tools from {server_name}")
return []
except asyncio.CancelledError:
verbose_logger.warning(
except asyncio.TimeoutError as e:
verbose_logger.debug(f"Timeout while listing tools from {server_name}")
raise e
except asyncio.CancelledError as e:
verbose_logger.debug(
f"Task cancelled while listing tools from {server_name}"
)
return []
raise e
except ConnectionError as e:
verbose_logger.warning(
verbose_logger.debug(
f"Connection error while listing tools from {server_name}: {str(e)}"
)
return []
raise e
except Exception as e:
verbose_logger.warning(f"Error listing tools from {server_name}: {str(e)}")
return []
verbose_logger.debug(f"Error listing tools from {server_name}: {str(e)}")
raise e
def _create_prefixed_tools(
self, tools: List[MCPTool], server: MCPServer, add_prefix: bool = True

View file

@ -206,10 +206,8 @@ if MCP_AVAILABLE:
)
return tools
except Exception as e:
verbose_logger.exception(f"Error in list_tools endpoint: {str(e)}")
# Return empty list instead of failing completely
# This prevents the HTTP stream from failing and allows the client to get a response
return []
verbose_logger.debug(f"Error in list_tools endpoint: {str(e)}")
raise e
@server.call_tool()
async def mcp_server_tool_call(
@ -364,25 +362,25 @@ if MCP_AVAILABLE:
def _tool_name_matches(tool_name: str, filter_list: List[str]) -> bool:
"""
Check if a tool name matches any name in the filter list.
Checks both the full tool name and unprefixed version (without server prefix).
This allows users to configure simple tool names regardless of prefixing.
Args:
tool_name: The tool name to check (may be prefixed like "server-tool_name")
filter_list: List of tool names to match against
Returns:
True if the tool name (prefixed or unprefixed) is in the filter list
"""
from litellm.proxy._experimental.mcp_server.utils import (
get_server_name_prefix_tool_mcp,
)
# Check if the full name is in the list
if tool_name in filter_list:
return True
# Check if the unprefixed name is in the list
unprefixed_name, _ = get_server_name_prefix_tool_mcp(tool_name)
return unprefixed_name in filter_list
@ -393,34 +391,36 @@ if MCP_AVAILABLE:
) -> List[MCPTool]:
"""
Filter tools by allowed/disallowed tools configuration.
If allowed_tools is set, only tools in that list are returned.
If disallowed_tools is set, tools in that list are excluded.
Tool names are matched with and without server prefixes for flexibility.
Args:
tools: List of tools to filter
mcp_server: Server configuration with allowed_tools/disallowed_tools
Returns:
Filtered list of tools
"""
tools_to_return = tools
# Filter by allowed_tools (whitelist)
if mcp_server.allowed_tools:
tools_to_return = [
tool for tool in tools
tool
for tool in tools
if _tool_name_matches(tool.name, mcp_server.allowed_tools)
]
# Filter by disallowed_tools (blacklist)
if mcp_server.disallowed_tools:
tools_to_return = [
tool for tool in tools_to_return
tool
for tool in tools_to_return
if not _tool_name_matches(tool.name, mcp_server.disallowed_tools)
]
return tools_to_return
async def _get_tools_from_mcp_servers(
@ -497,18 +497,19 @@ if MCP_AVAILABLE:
extra_headers=extra_headers,
add_prefix=add_prefix,
)
filtered_tools = filter_tools_by_allowed_tools(tools, server)
all_tools.extend(filtered_tools)
verbose_logger.debug(
f"Successfully fetched {len(tools)} tools from server {server.name}, {len(filtered_tools)} after filtering"
)
except Exception as e:
verbose_logger.exception(
verbose_logger.debug(
f"Error getting tools from server {server.name}: {str(e)}"
)
# Continue with other servers instead of failing completely
raise e
verbose_logger.info(
f"Successfully fetched {len(all_tools)} tools total from all MCP servers"
@ -556,7 +557,7 @@ if MCP_AVAILABLE:
f"Error getting tools from managed MCP servers: {str(e)}"
)
# Continue with empty managed tools list instead of failing completely
raise e
# Get tools from local registry
local_tools = []
try:

View file

@ -16,18 +16,10 @@ model_list:
api_base: "https://webhook.site/2f385e05-00aa-402b-86d1-efc9261471a5"
api_key: dummy
# mcp_servers:
# github_mcp:
# url: "https://api.githubcopilot.com/mcp"
# auth_type: oauth2
# authorization_url: https://github.com/login/oauth/authorize
# token_url: https://github.com/login/oauth/access_token
# client_id: os.environ/GITHUB_OAUTH_CLIENT_ID
# client_secret: os.environ/GITHUB_OAUTH_CLIENT_SECRET
# scopes: ["public_repo", "user:email"]
# allowed_tools: ["list_tools"]
# # disallowed_tools: ["repo_delete"]
litellm_settings:
callbacks: ["prometheus"]
custom_prometheus_metadata_labels: ["metadata.initiative", "metadata.business-unit"]
mcp_servers:
local_fake_mcp:
url: "http://127.0.0.1:8001/mcp"
transport: "http"
description: "My custom MCP server"
auth_type: "api_key"
auth_value: "abc123"