From a49a19e7840ceacc6c113cb81c2e7618426ded03 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 4 Oct 2025 18:14:32 -0700 Subject: [PATCH] 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 --- litellm/experimental_mcp_client/client.py | 5 +-- .../mcp_server/mcp_server_manager.py | 34 +++++++-------- .../proxy/_experimental/mcp_server/server.py | 43 ++++++++++--------- litellm/proxy/_new_secret_config.yaml | 22 +++------- 4 files changed, 48 insertions(+), 56 deletions(-) diff --git a/litellm/experimental_mcp_client/client.py b/litellm/experimental_mcp_client/client.py index 225349b4e8a..b7db917cba6 100644 --- a/litellm/experimental_mcp_client/client.py +++ b/litellm/experimental_mcp_client/client.py @@ -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 diff --git a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py index 2c46f9561db..bfe7a3e4679 100644 --- a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py +++ b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py @@ -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 diff --git a/litellm/proxy/_experimental/mcp_server/server.py b/litellm/proxy/_experimental/mcp_server/server.py index 3e7c291810a..75c04ebe947 100644 --- a/litellm/proxy/_experimental/mcp_server/server.py +++ b/litellm/proxy/_experimental/mcp_server/server.py @@ -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: diff --git a/litellm/proxy/_new_secret_config.yaml b/litellm/proxy/_new_secret_config.yaml index b7b30d36f99..50d36fee048 100644 --- a/litellm/proxy/_new_secret_config.yaml +++ b/litellm/proxy/_new_secret_config.yaml @@ -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"] \ No newline at end of file +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" \ No newline at end of file