[BUG] Large MCP `ToolSearch` batches invalidate the whole conversation cache, contradicting the documented tool-search guarantee

Status Open
Reported on v2.1.220
Maintainer reply None cached
Activity 0 comments · opened Aug 4, 2026

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

  1. Configure an MCP server with a large tool surface (ours: 3 Grafana servers, ~65 tools each).
  2. 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

  1. Issue a single ToolSearch with a select: list of 6+ mcp__* tool names (≥249 bytes), claude-in-chrome is a good candidate
  2. 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.

  1. 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=466api_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.

View original on GitHub ↗