- ttlMs describes freshness, while cacheScope describes whether a result may be shared.
- listChanged and subscriptions/listen can invalidate a cached catalog before its TTL expires.
- A private catalog must stay isolated by authorization context, even when the endpoint is shared.
- Cache hints do not replace authorization or guarantee a consistent multi-page snapshot.
MCP tool-list caching exists to reduce repeated discovery round trips without making freshness or privacy an implicit guess. In the 2026-07-28 protocol revision, cacheable list and resource results carry ttlMs and cacheScope. ttlMs is a freshness hint in milliseconds. cacheScope says whether the response is safe to share broadly or must stay within the requesting authorization context.
This article is verified against the MCP 2026-07-28 documentation and SEP-2549 on September 12, 2026. Older 2025-era servers may omit these fields, and clients must continue to interoperate with that behavior.
Why MCP needs its own cache hints
MCP is transport-agnostic. An HTTP server could use HTTP cache headers, but MCP also works over stdio and other transports where HTTP headers do not exist. Embedding the hint in the result keeps the semantic decision with the protocol message. A client can understand the same freshness policy regardless of how the message traveled.
The problem is practical. A host may list tools at startup, cache the catalog, and continue using it for many turns. If a server adds a tool, removes a tool, changes a schema, or filters tools differently for a caller, the host needs a way to decide when its copy is stale.
ttlMs is a freshness hint
- ttlMs: 0 means the result should be treated as immediately stale.
- A positive ttlMs tells the client how long it may consider the result fresh after receipt.
- An absent value in an older response should be treated conservatively, not as an infinite lifetime.
- A negative value is invalid and should be treated as zero rather than as a long-lived cache.
- TTL is not a promise that the underlying data cannot change before expiry.
const receivedAt = Date.now();
const freshUntil = receivedAt + result.ttlMs;
const isFresh = Date.now() < freshUntil;The freshness clock begins when the client receives the result. TTL is not automatically a polling interval. A client may re-fetch when it needs the data and discovers that the result is stale. If it chooses to poll, it should use backoff and jitter so a large client population does not create a synchronized discovery spike.
cacheScope is a privacy boundary
cacheScope has two important values. public means the response does not contain user-specific data and may be stored or served by a shared intermediary. private means the response belongs to the requesting authorization context and must not be shared across users or tokens.
A private tool catalog is common when authorization controls which tools a caller can see. The same endpoint can therefore return different tools to different identities. A shared cache that ignores cacheScope can turn a performance optimization into a cross-user data disclosure. Cache hints are not access control, so the server must still enforce authorization on every request.
The interaction with listChanged
A server can advertise that its tools list changes and send notifications/tools/list_changed when it changes. In the modern 2026-07-28 model, the client opts into notification delivery through subscriptions/listen. When a relevant notification arrives, the cached result becomes stale immediately even if its TTL has not expired. The client can then re-fetch tools/list.
{
"jsonrpc": "2.0",
"method": "subscriptions/listen",
"params": {
"notifications": { "toolsListChanged": true }
}
}TTL and notification invalidation solve different parts of the problem. TTL gives a client a bounded freshness window when no push signal arrives. A notification gives an early invalidation signal when the server knows the list changed. Either mechanism can exist without the other.
Caching paginated tool lists
Each page of a paginated list is an independently cacheable response. The cursor belongs in the cache key. A client must not reuse page two from one authorization context for another, and it must not assume that a fresh first page makes every later page fresh. If a cursor expires, the safe recovery is to discard the cached pages and restart from the beginning.
There is no cross-page consistency guarantee. If the catalog changes between requests, a client can see duplicates or gaps. If an application needs a consistent complete catalog, the server must provide an appropriate snapshot strategy or the client must re-fetch from the beginning and validate the resulting catalog version when one is available.
A server decision table
- A static, identical public catalog can use a positive TTL and public scope.
- A catalog filtered by user, tenant, role, or token should use private scope.
- A rapidly changing catalog should use a short TTL and a reliable change notification path when the transport supports it.
- A result containing user-specific resources should not be marked public merely because the endpoint itself is public.
- If the server cannot prove that a result is safe to share, private or zero TTL is the safer posture.
Debugging a stale tools list
- Record the protocol revision and whether the response contained ttlMs and cacheScope.
- Compare the cached result timestamp with the advertised TTL.
- Check whether the server sent a list-changed notification and whether the client was subscribed.
- Verify that the client invalidated the correct cache key, including authorization context and cursor.
- Compare the re-fetched catalog with the response actually received, not with an assumption about what the server should return.
- Check whether a client-side SDK automatically aggregates pages and hides the raw page boundaries.
Modern versus legacy behavior
The 2025-era protocol used the initialize handshake and older notification delivery patterns. The 2026-07-28 revision removes protocol-level sessions, carries request metadata per request, and introduces subscriptions/listen for change notifications. A server that supports both eras needs tests for both. A client that only knows the older era should ignore unknown cache fields and continue using conservative freshness behavior.
What TrackMCP can and cannot tell you
TrackMCP can help show when a server exposed a catalog, when tools changed at the server boundary, which client identity and protocol metadata were available, and whether later tool calls reached the server. This can reveal patterns such as a server updating its catalog while a client continues calling an older tool set.
TrackMCP cannot invalidate a host's cache, observe an internal cache hit, or prove that a client used the latest catalog. It also cannot make a public cache safe. The server and client remain responsible for choosing and enforcing the correct authorization context.
Does ttlMs guarantee that tools stay unchanged?
No. ttlMs is a freshness hint. A server may change the underlying data before the TTL expires, and a relevant change notification should invalidate the cached result when one is available.
Can an authenticated tools/list response be public?
It can be public only when the returned catalog is genuinely safe to share across authorization contexts. Authentication on the endpoint alone does not make a result private or public.
Is listChanged the same as caching?
No. listChanged is a signal that the catalog changed. ttlMs describes how long a response may be considered fresh. They can work together, but neither replaces authorization or a bounded refresh strategy.
About the publisher
TrackMCP, also written Track MCP
TrackMCP helps teams understand which clients connect to their MCP servers, which tools agents use, and where workflows fail. Learn more about Track MCP.
See this on your own server
TrackMCP turns your MCP server's calls into adoption, workflows, and outcomes. One line to install.