Skip to content

Commit 05dc068

Browse files
fix: Retry MCP 408s on isolated session (#2709)
1 parent 1bb5ff6 commit 05dc068

File tree

2 files changed

+32
-1
lines changed

2 files changed

+32
-1
lines changed

src/agents/mcp/server.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from mcp.client.session import MessageHandlerFnT
2121
from mcp.client.sse import sse_client
2222
from mcp.client.streamable_http import GetSessionIdCallback, streamablehttp_client
23+
from mcp.shared.exceptions import McpError
2324
from mcp.shared.message import SessionMessage
2425
from mcp.types import CallToolResult, GetPromptResult, InitializeResult, ListPromptsResult
2526
from typing_extensions import NotRequired, TypedDict
@@ -1195,6 +1196,8 @@ def _should_retry_in_isolated_session(self, exc: BaseException) -> bool:
11951196
return True
11961197
if isinstance(exc, httpx.HTTPStatusError):
11971198
return exc.response.status_code >= 500
1199+
if isinstance(exc, McpError):
1200+
return exc.error.code == httpx.codes.REQUEST_TIMEOUT
11981201
if isinstance(exc, BaseExceptionGroup):
11991202
return bool(exc.exceptions) and all(
12001203
self._should_retry_in_isolated_session(inner) for inner in exc.exceptions

tests/mcp/test_client_session_retries.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
import pytest
88
from anyio import ClosedResourceError
99
from mcp import ClientSession, Tool as MCPTool
10-
from mcp.types import CallToolResult, GetPromptResult, ListPromptsResult, ListToolsResult
10+
from mcp.shared.exceptions import McpError
11+
from mcp.types import CallToolResult, ErrorData, GetPromptResult, ListPromptsResult, ListToolsResult
1112

1213
from agents.exceptions import UserError
1314
from agents.mcp.server import MCPServerStreamableHttp, _MCPServerWithClientSession
@@ -228,6 +229,18 @@ async def call_tool(self, tool_name, arguments, meta=None):
228229
raise ClosedResourceError()
229230

230231

232+
class McpRequestTimeoutSession:
233+
def __init__(self, message: str = "timed out"):
234+
self.call_tool_attempts = 0
235+
self.message = message
236+
237+
async def call_tool(self, tool_name, arguments, meta=None):
238+
self.call_tool_attempts += 1
239+
raise McpError(
240+
ErrorData(code=httpx.codes.REQUEST_TIMEOUT, message=self.message),
241+
)
242+
243+
231244
class IsolatedRetrySession:
232245
def __init__(self):
233246
self.call_tool_attempts = 0
@@ -326,6 +339,21 @@ async def test_streamable_http_retries_closed_resource_on_isolated_session():
326339
assert isolated_session.call_tool_attempts == 1
327340

328341

342+
@pytest.mark.asyncio
343+
async def test_streamable_http_retries_mcp_408_on_isolated_session():
344+
isolated_session = IsolatedRetrySession()
345+
server = DummyStreamableHttpServer(
346+
McpRequestTimeoutSession("Timed out while waiting for response to ClientRequest."),
347+
isolated_session,
348+
)
349+
server.max_retry_attempts = 1
350+
351+
result = await server.call_tool("tool", None)
352+
353+
assert isinstance(result, CallToolResult)
354+
assert isolated_session.call_tool_attempts == 1
355+
356+
329357
@pytest.mark.asyncio
330358
async def test_streamable_http_does_not_retry_4xx_on_isolated_session():
331359
isolated_session = IsolatedRetrySession()

0 commit comments

Comments
 (0)