[BUG] Large MCP `ToolSearch` batches invalidate the whole conversation cache, contradicting the documented tool-search guarantee
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?
A single ToolSearch call that loads roughly 6 or more MCP tool schemas at once causes the
next API request to rebuild the entire prompt prefix, discarding a cache that was hit
successfully just earlier.
I have an OTEL setup that watch every Claude code session on my machine. A suspicious large cache write (300k tokens) in the middle of a conversation sparked me to dive in a little deeper.
According to my data: there was 5 instances of ToolSearch call loading roughly 6 MCP tool triggered a cache burst, despite a normal cache hit just 0.8 seconds earlier in some cases. Below that batch size the same operation appends normally, as documented. The docs state in three separate places (see below) that ToolSearch should never disturbs the cache, with no stated exception.
What Should Happen?
"Internally, the API excludes deferred tools from the system-prompt prefix… The prefix is untouched, so prompt caching is preserved." — tool-search-tool
"adding tools dynamically through tool search does not break your cache." — tool-use-with-prompt-caching
"Deferred tools, the default on supported models: a server connecting, disconnecting, or changing its tool list only appends new content and doesn't disturb anything already cached." — code.claude.com/docs/en/prompt-caching
According to these, cache should not be bursting
Error Messages/Logs
Steps to Reproduce
- Configure an MCP server with a large tool surface (ours: 3 Grafana servers, ~65 tools each).
- Run a session until context is large (≥150k tokens) — the bug is visible at any size but the
cost signal is clearest when large. Loading claude-api bring the context to 300k instantly
- Issue a single
ToolSearchwith aselect:list of 6+mcp__*tool names (≥249 bytes),claude-in-chromeis a good candidate - Inspect the next request's
cache_read_input_tokens/cache_creation_input_tokens.
Expected: a small append. Observed: cache_read collapses to the ~26k remnant and
cache_creation approximately equals the prior context.
- Control: repeat with 4–5 tools (≤210 bytes), and again with builtin tools only. Both append.
Claude Model
Opus
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.220
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
IntelliJ IDEA terminal
Additional Information
Evidence
The documented mechanism for what we observe is also in the docs — *"Modifying tool
definitions → Entire cache (tools, system, messages)"* — but that is precisely the path tool
search is documented to avoid.
Worked example, session 88972fad, 2026-08-03 03:15 +07, $2.26. Request start times
reconstructed as _time − duration_ms:
03:15:02.694 REQUEST START seq=549
03:15:06.994 tool_result ToolSearch (277-byte query, 524-byte result)
03:15:07.016 request end seq=549 cache_write=1,476 cache_read=244,551 ← normal hit
03:15:07.800 REQUEST START seq=550 ← 0.8s after the schemas landed
03:15:15.472 request end seq=550 cache_write=224,145 cache_read=26,265 ← full rebuild
03:15:17.186 tool_decision mcp_tool tool_source=mcp ← calls the tool it just loaded
03:15:29.185 request end seq=553 cache_write=244 cache_read=250,410 ← back to normal
event.sequence is unbroken (549 → 550 → 553), so this is neither an idle expiry nor a process
restart.
More concrete data from my observability stack
All MCP-loading ToolSearch calls measured over 2026-07-20 → 08-03 (n=38), plus a fifth failure
found on re-verification today (08-04):
| select: query size | calls | next request's cache-write |
| --- | --- | --- |
| ≤ 210 bytes | 34 | 827 – 6,522 (pure append) |
| ≥ 249 bytes | 5 | 153,493 / 188,012 / 224,145 / 231,941 / 656,881 (full rebuild) |
No overlap in either column and nothing in between — a step function, not a gradient. The five
failures were at 249 / 277 / 284 / 310 / 321 bytes. Query byte length is a proxy for tool count
(mcp__<server>__<tool> names run ~35 bytes each), so the boundary sits at roughly 6 tools in
one call.
(The safe-side count of 34 is from the original measured window and was not re-derived for the
fifth event, to avoid mixing two counting methodologies. The failing side is verified at 5.)
Only MCP loads do this. 0 of 37 builtin-tool-loading ToolSearch calls ever triggered a
rebuild; 5 of 39 MCP-loading ones did.
More data on a specific session
The fifth failure is the cleanest single
piece of evidence, because it holds session, project, MCP config, and conversation constant and
varies only batch size:
| select: bytes | tools loaded | outcome |
| --- | --- | --- |
| 28 | 2 builtin | safe |
| 35 | 1 MCP | safe |
| 82 | 2 MCP | safe (occurred twice) |
| 147 | 4 MCP + 1 builtin | safe |
| 321 | 9 MCP (claude-in-chrome) | full rebuild — 231,941 written, $2.33 |
Event chain from telemetry: tool_decision ToolSearch at event.sequence=466 → api_request at
seq=470 rebuilds (cache_write 231,941 / cache_read 26,252) → tool_decision mcp_tool at seq=471
and 474 calls the tools it just loaded. MCP tools were already in use earlier in the same session
(seq 450, 456, 464) and cached fine; it is the batch that adds more that breaks it.
How we distinguish mutation from ordinary expiry
cache_read_tokens is the surviving prefix, and it is the discriminator:
= 21,098→ cache was gone (idle TTL or process restart)> 21,098→ prefix was mutated mid-session (core block + already-loaded tool defs survived)
Across all five failures the remnant is remarkably stable — 24,803 / 26,235 / 26,252 / 26,265 /
26,274 — even though the contexts they sat in ranged from 179k to 683k tokens. The remnant is a
property of where the prefix was cut, not of conversation size. (This is version-pinned; 21,098
would need re-deriving after a CC upgrade.)
**A superficially similar event that is not this bug, included to show the detector
discriminates: 2026-08-03 14:25, loading the claude-api skill mid-session wrote 324,627
tokens ($3.36)** — larger than 18 of our 19 cold re-caches — but cache_read held at 144,520 and
144,520 + 324,627 = 469,147 exactly. Pure append; the documented cache-safety guarantee for
skills held precisely. Only the prefix-mutation events show cache_read collapsing.
Ruled out
| Hypothesis | Why not |
| --- | --- |
| Idle TTL | 0.8 s gap between the cache hit and the rebuild |
| Process restart / resume | event.sequence unbroken 549 → 550 → 553 |
| Context size | The two largest contexts in the dataset (605k, 564k) both loaded MCP tools for free with small queries |
| Skill activation | The request built after skill_activated and before the ToolSearch cache-hit normally at 1,476 tokens; claude-in-chrome also appears on safe rows |
| MCP server reconnection (the docs' most-cited invalidator) | 2 of the failures had zero mcp_server_connection events within 120 s, while 3 of the 34 safe calls had one (nearest −30 s). Neither explains nor predicts the failures |
| 5-hour usage-window reset | Boundary arithmetic 34 min off; first request after the boundary cache-hit normally (22-token write) |
| alwaysLoad, threshold mode, tool search disabled, unsupported model | All four documented conditions that push MCP tools into the prefix were checked against this machine; none apply |
Impact
Over 2026-07-20 → 08-04 (4,625 api_request events, $835.80 of usage): 19 cold re-caches
costing $43.06, 5.2% of spend, splitting 11 idle-TTL ($22.53) / 3 restart ($5.91) /
5 prefix mutation ($14.62). The three buckets sum exactly to the 19 / $43.06 total, so every
cold re-cache is attributed to one of them.
The cause also clusters: 3 of the 5 mutations landed on 2026-08-03 alone, $11.17 of the $14.62
(76%) — that was a day of long sessions with heavy MCP use, which is exactly the profile that
makes this expensive.
Prefix mutation is the most expensive of the three causes despite being the rarest, because
the cost is the context it rebuilds rather than the schemas it adds (context = cache_write +
cache_read at the rebuild):
| context at fire time | cost |
| --- | --- |
| 179k | $1.55 |
| 212k | $1.89 |
| 250k | $2.26 |
| 258k | $2.33 |
| 683k | $6.58 |
The same event at 50k context is ~$0.45 — a 15× spread driven entirely by when it fires. It
fires in exactly the long sessions where context is largest, so unlike idle-TTL the tail is
effectively unbounded.
A fourth case validated the prediction on unseen data. After we shipped a three-way cause
split, a mutation landed hours later in an unrelated session (17336543) and was classified
correctly with no manual work, following the predicted chain exactly: skill_activated
(claude-in-chrome) → ToolSearch with a 310-byte query → next request rebuilds.
Aggravating factor: a bundled skill steers users into it
The claude-in-chrome skill instructs: *"batch every tool you expect to need into ONE ToolSearch
call"*, and warns against splitting. Its 5-tool starter batch computes to ~213 bytes — just under
the line — and the task-specific tools it then suggests adding push it over. That skill is
implicated in 4 of the 5 observed cases, including the 321-byte / 9-tool batch that is the
largest and cleanest failure in the set. The advice is right in a small context and expensive in a
large one.