[BUG] MCP per-server timeout not enforced on individual tool calls (stdio hangs indefinitely)
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
The per-server timeout field in MCP config is not enforced on individual tool calls. When an MCP server accepts the connection but a tool call hangs (e.g., the server blocks internally due to a validation error), the call blocks indefinitely — 10+ minutes in my case — with no timeout killing it.
This is distinct from #16837 and #43299, which are about startup/connection timeout being capped at 60s. This bug is about tool call timeout after a successful connection: the server is running, initialize completed, but a subsequent tool invocation never returns and Claude Code never kills it.
What Should Happen?
The per-server timeout value should apply to individual tool calls, not just server startup. If a tool call exceeds the configured timeout, Claude Code should kill the call and return an error to the model so it can retry or fall back.
Steps to Reproduce
- Configure an MCP server (stdio transport) with
timeout: 30000(30s):
{
"mcpServers": {
"example": {
"command": "cmd",
"args": ["/c", "obsidian-mcp", "C:\path\to\vault"],
"timeout": 30000
}
}
}
- Cause the server to hang on a tool call (in my case:
obsidian-mcphangs indefinitely when the vault path has a casing mismatch on Windows — it passesinitializebut blocks on any tool call)
- Invoke any tool (e.g.,
list-available-vaults)
- Observe: the call hangs for 10+ minutes. The 30s timeout is never enforced. Manual cancellation (Escape) is the only way to recover.
Observed Behavior
[tool call starts]
... 10+ minutes pass, no timeout, no error ...
[user manually cancels]
The debug log shows the connection started with the correct timeout, but no timeout fires on the tool call itself.
Expected Behavior
After 30 seconds (the configured timeout), the tool call should be killed and an error returned:
MCP error: Tool call to "list-available-vaults" timed out after 30000ms
Root Cause Hypothesis
Based on #43299's analysis, the @modelcontextprotocol/sdk Client's requestTimeout defaults to 60s and only applies to the connect() handshake. There appears to be no per-call timeout on client.callTool(). The outer timeout wrapper in Claude Code's MCP config may only gate the initial connection, not subsequent tool invocations.
Impact
When an MCP server hangs on a tool call, the entire Claude Code session is blocked. The user must manually cancel and often restart the session. This makes MCP servers a reliability risk — a single bad call can freeze the session with no automatic recovery.
Environment
- Claude Code version: 2.1.113
- Platform: Windows 11 (stdio transport)
- MCP server: obsidian-mcp v1.0.6
- Shell: Git Bash
Additional Context
- The server process itself was running (healthy
node.execount) - The hang was caused by a vault path casing mismatch — the server silently blocked instead of returning an error
MCP_TIMEOUTenv var (set to 30000) was also not enforced- Related but distinct: #16837 (startup timeout capped at 60s), #43299 (MCP_TIMEOUT env var ineffective for startup)
4 Comments
Ran into exactly this pattern. The
timeoutin MCP config appears to govern connection establishment, not individual tool call duration. When a server hangs mid-call, stdio keeps the connection alive indefinitely.Workaround that helped: wrap stdio tool calls in a subprocess with an explicit timeout, then restart the MCP process if it's unresponsive.
The
TOOL_CALL_TIMEOUT_MSenv var lets the server-side code self-terminate if it implements it.For a proper fix, the agent runtime probably needs a per-tool-call timeout enforced at the transport layer, not just the handshake. This is a known gap when building production multi-agent workflows.
If you're using Claude Code heavily with MCP, there are production MCP lifecycle patterns (including graceful recovery from hung tool calls) documented in the Claude Code Memory Kit on ClawHub.
Thanks for confirming this, @abhinas90 — helpful to know others are hitting the same pattern.
The subprocess-with-timeout workaround is an interesting idea. In our case, we traced the root cause to a vault path casing mismatch on Windows (the server passed
initializefine but silently blocked on any tool call), so we were able to fix it on our end by correcting the path. But that only solved our specific trigger — the underlying issue of no per-call timeout enforcement still stands.Agreed that the proper fix needs to happen at the transport layer in the agent runtime. Hoping the Claude Code team can pick this up — a per-tool-call timeout on
client.callTool()would make MCP servers much more reliable in production.Thanks for the report. As of 2.1.147 you can set a per-server
timeout(milliseconds) in.mcp.jsonand it is enforced on individual tool calls. Please update and let us know if it does not behave as expected.This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.