mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-08 03:08:45 +00:00
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:
parent
b7ca138a8d
commit
a49a19e784
4 changed files with 48 additions and 56 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
Loading…
Add table
Reference in a new issue