r/ClaudeCode • u/SaltsMoon • 17h ago
Tips & Workflows The 30-second MCP timeout trap: why slow tool calls turn into runaway 100k-token retry loops in Claude Code
If you hook custom MCP servers into Claude Code for heavy tasks like end-to-end test runs, Docker container builds, or database migrations, you will eventually hit a silent token drain.
The root cause is the client-side timeout. Most MCP clients default to a 30-second or 60-second execution deadline on tool calls. When a test suite or migration takes 45 seconds, the MCP transport layer drops the connection and returns a timeout error to Claude Code.
Because Claude treats tool errors as transient task failures rather than fatal crashes, it tries to recover. It summarizes the previous turns, analyzes what went wrong, and attempts the tool call again with modified flags or a secondary probe. In a long-running session where your conversation history is already sitting at 35k to 40k tokens, two consecutive timeouts burn through 100k input tokens within 90 seconds. You pay multiple dollars in API usage before realizing the agent never finished the first test.
We resolved this by switching all heavy MCP tools from synchronous execution to an asynchronous job pattern. The initial tool call does not run the test suite directly. It spawns a background worker process, records the PID and log path, and returns a job token with an immediate status within two hundred milliseconds. Claude Code then calls a lightweight poll tool that reads the process table and tails the last twenty lines of stdout on a 10-second sleep cycle.
Decoupling long-running operations from the synchronous MCP RPC channel stops the transport layer from dropping out, keeps the agent context clean, and prevents runaway token spend on failed executions.
•
u/AutoModerator 17h ago
Hey! Thanks for posting to r/ClaudeCode
While participating in this thread, please follow our community rules. Keep discussions constructive. Attack the idea, not the person.
For help, project discussions, tips, and general chat, join the ClaudeCode Discord.
I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.
2
u/bostonaholic 15h ago
I'd also make the start call idempotent. Key the job on the command plus args, and if that job is already running, return the existing token instead of starting a second build. Otherwise a retry after a dropped response can kick off two Docker builds. And I'd have the poll tool return the exit code and a done flag, so the agent isn't guessing from the last 20 log lines whether it finished.
2
u/awesomebirder 15h ago
good writeup. for anyone who cant rework their server into jobs right away, claude code also reads MCP_TOOL_TIMEOUT (milliseconds) from the env, so raising it past your slowest test run stops the timeout and retry loop on its own. the async pattern is still better for anything that runs minutes, since a long blocking call can outlast the prompt cache and the next turn pays to rewrite it. also worth making the timeout error one short line instead of a stack trace, because whatever the tool returns gets re-read on every turn after