[BUG] MCP tools running sequentially instead of in parallel in v2.0.71
Status Open
Maintainer reply None cached
Activity 7 comments · opened Dec 17, 2025
Description
Multiple MCP tool calls in the same message are running sequentially instead of in parallel after upgrading to Claude Code v2.0.71 (released Dec 17, 2025).
Steps to Reproduce
- Make three MCP tool calls in a single message block (format, test, inspect)
- Observe execution time
Expected Behavior
All three tools should run in parallel:
- Total time: ~1m20s (longest individual tool)
Actual Behavior
Tools run sequentially:
- format: 1m20s
- test: 11s
- inspect: 1m20s
- Total: ~3 minutes
Environment
- Claude Code version: 2.0.71
- Date: December 17, 2025
- MCP Server: developer-cli (custom project MCP server)
Additional Context
This worked correctly in previous versions where multiple tool calls in the same message block would execute in parallel. The regression appears to have been introduced in v2.0.71 or a recent version.
7 Comments
I also experience this problem when using MCP server over STDIO.
Claude Code version: 2.1.1
Date: December 8.01.2026
MCP Server: briefkit-mcp (https://github.com/orbiqd/orbiqd-briefkit)
I'm also having the issue with claude 2.1.2. A very simple way to reproduce it without any MCP is the prompt
run in parallel 3 python scripts that display the time, wait 1 second, then display the time again.Here's the ensuing claude conversation I had:
same in vscode
This issue has been inactive for 30 days. If the issue is still occurring, please comment to let us know. Otherwise, this issue will be automatically closed in 30 days for housekeeping purposes.
Ran into this issue as well: https://x.com/graphtheory/status/2020963433118843345?s=20
<img width="878" height="292" alt="Image" src="https://github.com/user-attachments/assets/84c1df28-42b1-455c-9fde-bd2c38b04da0" />
It seriously thrashes the performance of MCP-enabled agents on tasks like a benchmark compared to ones using native tools.
I looked into this issue and it appears to be an annotation that may be missing from the MCP server in use.
If the server doesn't provide
readOnlyHintoftrue, then the model doesn't attempt parallel tool calls with the agent. This follows good design principles as such operations wouldn't be idempotent. Triggering multiple operations with mutating side effects in parallel can introduce some tricky failure modes!For MCP server developers, if your tool does _not_ create side-effects in the environment, set
readOnlyHintto true. This will enable tool call parallelism in Claude Code for your MCP server tool.I recomment we close #14353, although perhaps the
readOnlyHintdocumentation should mention this functionality.You can reproduce my full experiment and see raw output data here: https://github.com/greynewell/mcp-serialization-repro?tab=readme-ov-file
Additional findings:
readOnlyHintmay be necessary but not sufficientWe investigated this further and found what appears to be two separate layers to the problem.
Layer 1: Runtime
The Claude Code binary (v2.1.75) appears to have two MCP tool definitions. Extracted from minified JS in the binary:
isMcp:!0,isEnabled(){return!0},isConcurrencySafe(){return!1},isReadOnly(){return!1},...name:"mcp"
isMcp:!0,...isConcurrencySafe(){return K.annotations?.readOnlyHint??!1},isReadOnly(){return K.annotations?.readOnlyHint??!1}
The first is a generic fallback with hardcoded
false. The second reads thereadOnlyHintannotation. We're not certain which path is used for a given tool call, but the per-tool path suggestsreadOnlyHint: trueshould unblock the runtime.Layer 2: Model behavior
Even with
readOnlyHint: trueset and verified intools/listresponses, we observed the model consistently emitting one tool_use block per response across 5 sessions with 100+ tool calls. Each call chains viaparentUuidto theprevious, ~300-700ms apart.
We also tried:
instructionstelling the model to parallelisedescriptiontext saying "call this tool multiple times in parallel"~/.claude/CLAUDE.mdwith explicit parallel search guidanceNone appeared to change the behavior. In one test session, even built-in Grep (which has
isConcurrencySafe(){return!0}) was sequential when explicitly asked to search for 3 patterns "in parallel". From the session JSONL:Each
parentUuidpoints to the previous call'suuid- these are sequential turns, not parallel calls in one response. (This could be session-specific rather than a general rule.)When asked directly "did you do those calls in parallel?", the model said yes, claiming they were "in the same antml:function_calls block". The JSONL logs showed otherwise.
Reproduction
~/.claude/projects/<project>/<session>.jsonl- in our testing, each tool_use was a separate assistant event with sequentialparentUuidchainsOur interpretation
@greynewell's finding about
readOnlyHintappears correct for the runtime layer - it should be set on read-only MCP tools. But in our testing the practical impact was near-zero because the model didn't emit multiple tool calls per response.We may be missing something in our setup, but the pattern was consistent across multiple sessions and prompt variations.
---
Investigation conducted with Claude Code (Opus 4.6) analysing its own session JSONL logs and the Claude Code binary.