Repository navigation
fix(mcp): do not resend a tool call after a timeout or transport error - #8933
Ray (RaycarlLei) wants to merge 1 commit into
Conversation
MCPClient.call_tool_result and MCPToolWrapper retried tools/call up to three times on timeouts and transport errors. The server may already have run the tool by then, so a slow non-idempotent tool (send an email, place an order) could run three times for one agent action. Send tools/call once. MCPToolWrapper still retries when connecting or initializing fails, before the call is sent, and tool discovery keeps its retries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Strix Security Review✅ No open security findings. PR summary: Fixes an MCP reliability/safety bug where a `tools/call` that timed out or hit a transport error after being sent was retried up to three times, so a non-idempotent MCP tool could run three times for one agent action. The client now makes a single tool-call attempt, and the tool wrapper records that the request was sent and reports a non-retryable "interrupted" error instead of retrying. Review summaryThe change stops an MCP Ruled out (7)
Review 1 on this PR · Reviewed by Strix |
Related issue
Fixes #8932
Summary
A
tools/callthat timed out or hit a transport error after it was sent was retried up to 3 times. The server may already have run the tool, so one agent action could run a non-idempotent MCP tool three times.MCPClient.call_tool_resultnow calls_retry_operationwithmax_attempts=1._retry_operationgains that optional parameter (defaultmax_retries), solist_tools,list_promptsandget_promptkeep their retries.MCPToolWrapper._execute_toolsets acall_sentflag right beforesession.call_tool. Any error after that (timeout, cancellation, transport error) raises a private_ToolCallInterruptedError, which_execute_single_attemptreports without retrying. Failures while connecting or initializing, before the call is sent, are retried as before.The agent now gets:
MCP tool 'send_email' timed out after the call was sent. It was not retried because the server may already have run it.Verification
New
lib/crewai/tests/mcp/test_tool_call_not_replayed.py:call_toolawaited oncelist_toolsstill retriedmax_retriestimesEnd to end against a FastMCP streamable-http server whose tool sleeps longer than the client timeout (script in the issue): on main the tool ran 3 times; with this change it runs once.
Additional context
ToolFailure); whichever lands second needs a small rebase, with no semantic conflict.llm-generatedlabel.🤖 Generated with Claude Code
Note
Medium Risk
Changes retry semantics for MCP tool execution (agents no longer auto-retry slow or flaky tools), which is intentional for safety but may affect workflows that depended on transparent retries.
Overview
Fixes duplicate side effects when MCP
tools/callfails after the request reaches the server (timeout, reset, etc.). Retrying could run non-idempotent tools multiple times (e.g. send email three times).MCPClient:call_tool_resultnow passesmax_attempts=1into_retry_operation, so tool execution is a single attempt._retry_operationaccepts optionalmax_attempts(default stillmax_retries);list_tools,list_prompts, andget_promptkeep multi-attempt retries.MCPToolWrapper: Acall_sentflag is set immediately beforesession.call_tool. Failures after that raise_ToolCallInterruptedError, surfaced to the agent with a clear message and no retry. Connection/setup failures before the call still use the existing 3-attempt backoff.New tests in
test_tool_call_not_replayed.pylock in single-send behavior for client and wrapper while preserving retries for discovery and pre-send connect errors.Reviewed by Cursor Bugbot for commit e1d9830. Bugbot is set up for automated code reviews on this repo. Configure here.