How do cacheable MCP tool lists change your token bill?
AI Architect
Key takeaways
- Every list endpoint now returns ttlMs and cacheScope, so a client can cache tool and resource lists instead of polling them.
- Servers should return tools in a deterministic order, and the spec says plainly this is to improve LLM prompt cache hit rates.
- Tool definitions ride in the prompt on every agent step, so an unstable tool list invalidates the cached prefix over and over.
- cacheScope of public or private decides whether a shared intermediary may hold the response, which matters once a gateway sits between client and server.
The 2026-07-28 revision of the Model Context Protocol is mostly discussed for removing sessions and the handshake. Two entries filed under minor changes will show up on your invoice faster than either of those.
What actually changed in the list endpoints?
Two things, both small in the text and large in effect.
The revision requires ttlMs and cacheScope on results from tools/list,
prompts/list, resources/list, resources/read and
resources/templates/list, through a new CacheableResult interface. The spec
describes ttlMs as a freshness hint that lets clients cache responses and
reduce polling, and cacheScope as controlling whether shared intermediaries may
cache the response. Both are described as complementing the existing
listChanged notifications rather than replacing them.
Separately, the
changelog
says servers should return tools from tools/list in a deterministic order to
enable client-side caching and improve LLM prompt cache hit rates. That second
clause is the interesting one. A protocol document does not usually reach into
the economics of the model call it feeds.
Why does tool order touch your bill?
Because tool definitions live in the part of the prompt you were hoping to cache.
Prompt caching matches a prefix byte for byte from the first token. An agent sends its toolset on every step, near the front, ahead of the conversation. That block is usually the largest stable thing in the prompt, which makes it the main reason caching is worth turning on for agent workloads at all.
Where an MCP toolset sits in a cached prompt
Show as text
| # | Layer | Note |
|---|---|---|
| 1 | System instructions | Changes on deploy, if ever |
| 2 | Tool definitions from tools/list | The block this revision is about |
| 3 | Reference documents and examples | |
| · | cache breakpoint (breakpoint) | everything above is the cache key |
| 4 | Conversation and tool results | |
| 5 | Current user input |
If the server returns the same tools in a different order between two calls, the serialised prompt differs. The prefix stops matching at the first changed byte, and everything after it is reprocessed at full input price. Nothing errors. You see a hit rate that sits at some unremarkable number and no obvious cause, which is the failure mode this site documented before the spec addressed it.
Is this a real saving or a rounding error?
It depends entirely on how much of your prompt is toolset, and for agent loops that fraction is often large.
A server exposing thirty tools with full JSON Schema for each can run to thousands of tokens, competing for the same room as everything else in your context window. That block is sent on every step of every loop. Anthropic’s documentation prices a cache read at a fraction of a base input token, so the difference between a matching prefix and a broken one is close to the full cost of those tokens, repeated for every step the agent takes.
Caching a tool list under revision 2026-07-28
Show as text
| # | From | To | Message |
|---|---|---|---|
| 1 | MCP client | MCP server | tools/list |
| 2 | MCP server | MCP client | result + ttlMs + cacheScope. Deterministic order, so the serialised bytes repeat. |
| 3 | MCP client | MCP server | tools/call (list served from cache). No second tools/list until ttlMs expires or listChanged fires. |
| 4 | MCP server | MCP client | result |
The polling saving is smaller and still real. A client that called tools/list
every turn now has a stated window in which it does not need to, and a
notification to tell it when the window is wrong.
What should you be sceptical about?
Two things, because the change is a SHOULD rather than a MUST.
Deterministic ordering is a recommendation, so a server can ignore it and remain
compliant. If you are the client, you cannot assume stability just because the
server advertises the current revision. Measure it: call tools/list twice in
separate processes and compare the serialised bytes rather than the parsed objects.
ttlMs also carries no guarantee that the value is sensible. A server is free to
return a number that reflects nothing, and a client that trusts a long TTL on a
list that changes hourly will act on a stale toolset until a listChanged
notification arrives. Treat the hint as a hint, keep the subscription, and pick
your own ceiling if the server’s number looks optimistic.
Where is MCP heading on cost?
A protocol adding cache-control fields to its list endpoints is a protocol that
expects to be called a great deal, by software paying per token. The
pull request
for CacheableResult reads like ordinary HTTP caching arriving in a JSON-RPC
protocol, roughly a decade after the web settled the same argument.
My read is that this is the more useful signal in the revision. Removing sessions made MCP simpler to implement. Adding cache hints to list results is an admission about where the money actually goes in an agent. It goes on the tokens the protocol puts in front of a model, and the spec is now optimising for that, which suggests the next round of changes will be shaped by inference cost rather than by protocol elegance.
Do this
Make your MCP toolset cache-friendly
Three of these are server side and two are client side. The measurement step is the one people skip, and it is the only way to know any of it worked.
Return tools in a fixed order and keep it fixed
Sort by name, or pin an explicit order, and do not let the order fall out of a map or a set. An order that varies between processes produces a hit rate that looks inexplicably mediocre rather than an obvious failure.
Serialise deterministically as well as ordering deterministically
Sort object keys inside each tool definition. A stable list of unstably serialised objects is still a different byte sequence, and the cache matches bytes rather than meaning.
Set ttlMs to how long the list is genuinely valid
Pick a real number rather than a placeholder. A short TTL costs you round trips, and a long one on a list that actually changes means clients act on a stale toolset until listChanged reaches them.
Set cacheScope private unless the list is identical for every caller
If tools vary by user, tenant or credential, a shared intermediary caching that result serves one caller's toolset to another. Public is only correct when the response carries nothing caller-specific.
On the client, honour ttlMs instead of polling
Cache the list for the stated window and refresh on listChanged. Calling tools/list every turn wastes a round trip and gains nothing the notification does not already give you.
Log cache reads against writes and watch the ratio by deploy
Providers return both counts per request. If the ratio does not move after this work, the prefix is being invalidated somewhere else, and you now know to look above the breakpoint.
Frequently asked questions
- Why would tool ordering affect cost at all?
- Prompt caching matches a prefix byte for byte from the first token. Tool definitions are usually near the front of that prefix. If the server returns them in a different order between calls, the serialised prompt differs, the prefix stops matching, and the whole cached block is reprocessed at full price.
- Is ttlMs a cache-control header?
- No. It is a freshness hint carried in the result body, and the spec describes it as complementing the existing listChanged notifications rather than replacing them. A client may still be told the list changed before the TTL expires.
- What does cacheScope actually control?
- Whether a shared intermediary may cache the response. Private means the result is specific to that caller and a gateway must not serve it to anyone else. Public means it can be shared. If you run a proxy in front of MCP servers, this is the field that decides correctness.
- Does this apply to stdio servers or only HTTP?
- The cache fields are on the result objects, so they travel on either transport. The practical benefit is larger over HTTP, where a shared intermediary can exist and where polling costs a network round trip.
- Do I have to do anything if I only write clients?
- Yes. Honour ttlMs instead of calling tools/list on every turn, and keep whatever order the server gave you rather than re-sorting into your own.