Client Caching
Purpose and Scope
Client response caching reduces repeated MCP round trips for cacheable read-style operations. In this SDK, caching has two cooperating sides: a server sends freshness hints on supported results, and every Client has a response cache that can serve a still-fresh entry locally. The important design point for client authors is that caching is not a separate feature you enable around individual calls. A connected client already owns a cache; the server decides whether a result is reusable by attaching a nonzero ttlMs and an appropriate cacheScope.
Sources: docs/clients/caching.md, docs/clients/connect.md
This page focuses on the client-facing contract: which calls use the response cache, how cacheMode changes behavior per request, how to swap the backing store, and how to partition a shared store when different users or authorization contexts share infrastructure. It also explains where caching intersects with adjacent client workflows. connect() must complete before cacheable calls can run, list and read calls are the verbs that participate, and authentication determines whether a cached response is safe to share across callers.
Sources: docs/clients/caching.md, docs/clients/calling.md, docs/clients/oauth.md, docs/clients/machine-auth.md
Relevant Source Files
docs/clients/caching.md- Primary how-to for response caching, server cache hints,cacheMode, custom stores, and user partitioning.docs/clients/calling.md- Defines the client calls that caching affects, includinglistTools,listPrompts,listResources,listResourceTemplates, andreadResource.docs/clients/connect.md- Establishes the connectedClientlifecycle and explains that transport choice should not change downstream client behavior.docs/clients/machine-auth.md- Provides machine authentication context for cache partitioning when service accounts, bearer tokens, or cross-app access are involved.docs/clients/middleware.md- Explains HTTP client middleware, useful for observing transport requests and confirming whether cached calls avoid the wire.docs/clients/oauth.md- Defines user OAuth provider responsibilities and reinforces why cache entries derived from an authorization context should be partitioned or private.
Core Caching Model
The cacheable verbs check the response cache before sending a request. With the default cacheMode of 'use', the client returns a fresh local entry when one exists; otherwise it sends the request and stores the server response if the server supplied a usable hint. The documented cacheable verbs are listTools(), listPrompts(), listResources(), listResourceTemplates(), and readResource(). These are discovery and read operations, not mutating calls, which makes the freshness model easy to reason about.
Sources: docs/clients/caching.md, docs/clients/calling.md
Server hints control whether a response can be served from cache. ServerOptions.cacheHints names cacheable results such as 'tools/list' and 'resources/read' and attaches ttlMs plus cacheScope. Without a hint, the SDK emits ttlMs: 0, so the client never serves that result from cache. Resource reads can also receive a per-resource cacheHint through registerResource; that hint wins field by field over the general resources/read entry for that resource result.
Sources: docs/clients/caching.md
A cacheScope of 'public' is only appropriate when the result is identical for every caller. Anything derived from a user, tenant, token, policy decision, or other authorization context should remain 'private', which is the documented default. The client also protects itself against excessive freshness by capping any ttlMs at 24 hours through MAX_CACHE_TTL_MS, so a server cannot pin a cache entry forever. Treat hints as performance metadata, not as a substitute for authorization or correctness checks.
Sources: docs/clients/caching.md, docs/clients/oauth.md, docs/clients/machine-auth.md
Execution Flow
A typical flow begins with a connected client. The client is constructed with a name and version, a transport such as StreamableHTTPClientTransport or StdioClientTransport is selected, and connect() performs the initialize handshake. After the handshake, the client has negotiated protocol information, server capabilities, and instructions. From that point on, cacheable client methods behave consistently regardless of transport. This matters because an application can start with in-process tests, move to stdio, and later use Streamable HTTP without changing caching call sites.
Sources: docs/clients/connect.md, docs/clients/caching.md
const tools = await client.listTools(); // network, then cached for the server's ttlMs
const again = await client.listTools(); // served from cache while still fresh
await client.listTools(undefined, { cacheMode: 'refresh' }); // always refetch and re-store
await client.readResource({ uri: 'config://app' }, { cacheMode: 'bypass' }); // no cache read or writeThe request count example in the client guide demonstrates the practical effect: after an initial listTools(), a second listTools() can be served locally, while a later 'refresh' call crosses the wire again. A readResource() with 'bypass' also reaches the server because bypass intentionally avoids both cache reads and cache writes. This makes cacheMode useful both for correctness-sensitive operations and for diagnostics, because you can force fresh data without discarding unrelated entries.
Sources: docs/clients/caching.md
Cache Modes and Store Options
cacheMode has three values. 'use' is the default: serve a fresh entry if possible, otherwise fetch and store. 'refresh' always fetches and stores the fresh response, replacing whatever was present. 'bypass' fetches without reading or writing, leaving the cache byte-untouched. The bypass detail is especially important for tool calls, because the SDK may read the cached tools/list entry for output validation when a tool has an advertised outputSchema. Bypassing a resource read should not accidentally disturb that tool metadata cache.
Sources: docs/clients/caching.md, docs/clients/calling.md
The default backing store is a fresh InMemoryResponseCacheStore per client. The guide documents that it holds at most 512 resources/read entries by default. Applications that need a larger in-memory cache can construct InMemoryResponseCacheStore with maxEntries; applications that need process-shared or distributed caching can pass a custom responseCacheStore. Every method on the ResponseCacheStore interface may return a promise, so Redis-style or other asynchronous stores can implement the same five-method contract.
Sources: docs/clients/caching.md
const store = new InMemoryResponseCacheStore({ maxEntries: 2048 });
const client = new Client(
{ name: 'my-client', version: '1.0.0' },
{ responseCacheStore: store }
);Entries are keyed by connected-server identity, allowing one store to back many clients without collisions between different servers. That does not automatically solve multi-user safety, however. When several principals share one store, set cachePartition to a stable identity for the authorization context, such as the authenticated subject. This is the client-side complement to server-side cacheScope: private data should not be reused across users simply because they reached the same MCP endpoint through the same gateway or worker fleet.
Sources: docs/clients/caching.md, docs/clients/oauth.md, docs/clients/machine-auth.md
Observability and Adjacent Client Features
Client middleware is the easiest way to observe whether a call went to the network when using HTTP transports. Middleware wraps the fetch used by StreamableHTTPClientTransport, so it can add headers, log requests, or inspect responses for every HTTP request the SDK sends, including initialize. The built-in withLogging middleware can show which requests crossed the wire; a cached listTools() served locally should not produce a corresponding HTTP request. Keep logging off stdout when using stdio transports, because stdout may carry MCP frames.
Sources: docs/clients/middleware.md, docs/clients/connect.md, docs/clients/caching.md
Authentication guides provide the operational boundary for cache design. With OAuth, the provider stores tokens and discovery state for an end user. With machine authentication, a transport can use client credentials, bearer tokens, private-key JWT, or cross-app access. In all of those cases, cached results may reflect the identity and permissions attached to the request. Use 'public' only for invariant server data, use 'private' for authorization-dependent results, and add cachePartition when a shared store serves more than one principal.
Sources: docs/clients/oauth.md, docs/clients/machine-auth.md, docs/clients/caching.md
Compact Reference
| Item | Contract | Notes |
|---|---|---|
| Cacheable methods | listTools(), listPrompts(), listResources(), listResourceTemplates(), readResource() | These check the cache before sending when cacheMode is 'use'. |
cacheMode: 'use' | Default behavior | Serve fresh local entry, otherwise fetch and store. |
cacheMode: 'refresh' | Force revalidation | Always fetch and store the fresh result. |
cacheMode: 'bypass' | Ignore cache for this call | Fetch without reading or writing any entry. |
ServerOptions.cacheHints | Server result hints | Uses ttlMs and cacheScope for named cacheable results. |
registerResource cacheHint | Per-resource override | Wins field by field over the general resources/read hint. |
responseCacheStore | Client option | Replaces the default per-client InMemoryResponseCacheStore. |
cachePartition | Client option | Separates shared-store entries by stable authorization-context identity. |
MAX_CACHE_TTL_MS | Client-side cap | Limits server-provided TTLs to 24 hours. |
Next Steps
Start by letting the default cache work: connect a Client, call listTools() twice, and use middleware or a test harness to confirm that only the first call reaches the server while the hint is fresh. Then add cacheMode: 'refresh' around user-visible reload actions and cacheMode: 'bypass' around operations where you explicitly need uncached reads. If you introduce a shared store, decide on cachePartition before production traffic. Read the calling guide next to understand the cached verbs, and the OAuth or machine-auth guides before sharing stores across identities.
Sources: docs/clients/caching.md, docs/clients/calling.md, docs/clients/middleware.md, docs/clients/oauth.md, docs/clients/machine-auth.md