Skip to content

Commit bb8f2f5

Browse files
committed
fix mcp connection error handling
1 parent 2666029 commit bb8f2f5

File tree

3 files changed

+43
-0
lines changed

3 files changed

+43
-0
lines changed

llama_stack/core/server/server.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ def translate_exception(exc: Exception) -> HTTPException | RequestValidationErro
141141
return HTTPException(status_code=httpx.codes.BAD_REQUEST, detail=str(exc))
142142
elif isinstance(exc, PermissionError | AccessDeniedError):
143143
return HTTPException(status_code=httpx.codes.FORBIDDEN, detail=f"Permission denied: {str(exc)}")
144+
elif isinstance(exc, ConnectionError | httpx.ConnectError):
145+
return HTTPException(status_code=httpx.codes.BAD_GATEWAY, detail=str(exc))
144146
elif isinstance(exc, asyncio.TimeoutError | TimeoutError):
145147
return HTTPException(status_code=httpx.codes.GATEWAY_TIMEOUT, detail=f"Operation timed out: {str(exc)}")
146148
elif isinstance(exc, NotImplementedError):

llama_stack/providers/utils/tools/mcp.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,38 @@ async def client_wrapper(endpoint: str, headers: dict[str, str]) -> AsyncGenerat
6767
raise AuthenticationRequiredError(exc) from exc
6868
if i == len(connection_strategies) - 1:
6969
raise
70+
except* httpx.ConnectError as eg:
71+
# Connection refused, server down, network unreachable
72+
if i == len(connection_strategies) - 1:
73+
error_msg = f"Failed to connect to MCP server at {endpoint}: Connection refused"
74+
logger.error(f"MCP connection error: {error_msg}")
75+
raise ConnectionError(error_msg) from eg
76+
else:
77+
logger.warning(
78+
f"failed to connect to MCP server at {endpoint} via {strategy.name}, falling back to {connection_strategies[i + 1].name}"
79+
)
80+
except* httpx.TimeoutException as eg:
81+
# Request timeout, server too slow
82+
if i == len(connection_strategies) - 1:
83+
error_msg = f"MCP server at {endpoint} timed out"
84+
logger.error(f"MCP timeout error: {error_msg}")
85+
raise TimeoutError(error_msg) from eg
86+
else:
87+
logger.warning(
88+
f"MCP server at {endpoint} timed out via {strategy.name}, falling back to {connection_strategies[i + 1].name}"
89+
)
90+
except* httpx.RequestError as eg:
91+
# DNS resolution failures, network errors, invalid URLs
92+
if i == len(connection_strategies) - 1:
93+
# Get the first exception's message for the error string
94+
exc_msg = str(eg.exceptions[0]) if eg.exceptions else "Unknown error"
95+
error_msg = f"Network error connecting to MCP server at {endpoint}: {exc_msg}"
96+
logger.error(f"MCP network error: {error_msg}")
97+
raise ConnectionError(error_msg) from eg
98+
else:
99+
logger.warning(
100+
f"network error connecting to MCP server at {endpoint} via {strategy.name}, falling back to {connection_strategies[i + 1].name}"
101+
)
70102
except* McpError:
71103
if i < len(connection_strategies) - 1:
72104
logger.warning(

tests/unit/server/test_server.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,15 @@ def test_translate_asyncio_timeout_error(self):
113113
assert result.status_code == 504
114114
assert result.detail == "Operation timed out: "
115115

116+
def test_translate_connection_error(self):
117+
"""Test that ConnectionError is translated to 502 HTTP status."""
118+
exc = ConnectionError("Failed to connect to MCP server at http://localhost:9999/sse: Connection refused")
119+
result = translate_exception(exc)
120+
121+
assert isinstance(result, HTTPException)
122+
assert result.status_code == 502
123+
assert result.detail == "Failed to connect to MCP server at http://localhost:9999/sse: Connection refused"
124+
116125
def test_translate_not_implemented_error(self):
117126
"""Test that NotImplementedError is translated to 501 HTTP status."""
118127
exc = NotImplementedError("Not implemented")

0 commit comments

Comments
 (0)