From ebc77d0950ff1762f26ef14b70c049d595efa53e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 18:06:04 -0800 Subject: [PATCH] fix: wrap MCP client connection errors in ConnectionError (#8569) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The FastMCP clients were not properly wrapping exceptions from `connect_to_server()`, causing raw RuntimeErrors (like DNS resolution failures with "[Errno -2] Name or service not known") to propagate up unchanged. Changes: - Both `AsyncFastMCPSSEClient` and `AsyncFastMCPStreamableHTTPClient` now properly catch all exceptions and wrap them in `ConnectionError` - Added warning-level logging for failed connections - Provides user-friendly error messages with the server URL Fixes #8568 Related to #8499 🤖 Generated with [Letta Code](https://letta.com) Co-authored-by: letta-code <248085862+letta-code@users.noreply.github.com> Co-authored-by: Letta Co-authored-by: Kian Jones <11655409+kianjones9@users.noreply.github.com> --- letta/services/mcp/fastmcp_client.py | 34 +++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/letta/services/mcp/fastmcp_client.py b/letta/services/mcp/fastmcp_client.py index 54b7bf46..c46cd948 100644 --- a/letta/services/mcp/fastmcp_client.py +++ b/letta/services/mcp/fastmcp_client.py @@ -78,10 +78,21 @@ class AsyncFastMCPSSEClient: except httpx.HTTPStatusError as e: # Re-raise HTTP status errors for OAuth flow handling if e.response.status_code == 401: - raise ConnectionError("401 Unauthorized") - raise e + raise ConnectionError("401 Unauthorized") from e + raise ConnectionError(f"HTTP error connecting to MCP server at {self.server_config.server_url}: {e}") from e + except ConnectionError: + # Re-raise ConnectionError as-is + raise except Exception as e: - raise e + # MCP connection failures are often due to user misconfiguration, not system errors + # Log as warning for visibility in monitoring + logger.warning( + f"Connecting to MCP server failed. Please review your server config: {self.server_config.model_dump_json(indent=4)}. Error: {str(e)}" + ) + raise ConnectionError( + f"Failed to connect to MCP server at '{self.server_config.server_url}'. " + f"Please check your configuration and ensure the server is accessible. Error: {str(e)}" + ) from e async def list_tools(self, serialize: bool = False) -> List[MCPTool]: """List available tools from the MCP server. @@ -223,10 +234,21 @@ class AsyncFastMCPStreamableHTTPClient: except httpx.HTTPStatusError as e: # Re-raise HTTP status errors for OAuth flow handling if e.response.status_code == 401: - raise ConnectionError("401 Unauthorized") - raise e + raise ConnectionError("401 Unauthorized") from e + raise ConnectionError(f"HTTP error connecting to MCP server at {self.server_config.server_url}: {e}") from e + except ConnectionError: + # Re-raise ConnectionError as-is + raise except Exception as e: - raise e + # MCP connection failures are often due to user misconfiguration, not system errors + # Log as warning for visibility in monitoring + logger.warning( + f"Connecting to MCP server failed. Please review your server config: {self.server_config.model_dump_json(indent=4)}. Error: {str(e)}" + ) + raise ConnectionError( + f"Failed to connect to MCP server at '{self.server_config.server_url}'. " + f"Please check your configuration and ensure the server is accessible. Error: {str(e)}" + ) from e async def list_tools(self, serialize: bool = False) -> List[MCPTool]: """List available tools from the MCP server.