This is the documentation for the v2 beta — looking for the v1 documentation?
Skip to content

MCP TypeScript SDK (V2) / @modelcontextprotocol/client / client/responseCache

client/responseCache

Classes

ClientResponseCache

Defined in: packages/client/src/client/responseCache.ts:268

Internal

The Client's cache-coordination collaborator.

Owns the per-connection cache state that used to live as five private fields on Client — the backing ResponseCacheStore, the per-method eviction-generation counter, the user-supplied/default flag, and the stamp-memoized derived indices over the tools/list entry. Client holds exactly one instance and never reaches past it to the store.

Not exported from the package index — internal to the client package.

Constructors

Constructor

new ClientResponseCache(_store, _isUserSupplied, _reportError?, _cachePartition?, _now?): ClientResponseCache

Defined in: packages/client/src/client/responseCache.ts:312

Parameters
_store

ResponseCacheStore

_isUserSupplied

boolean

Whether _store was supplied by the caller. A user-supplied store is never clear()ed by resetForReconnect (defeats the only reason to supply one).

_reportError?

(error) => void

Sink for a custom store's set()/evict() failure. write never lets a store rejection cost the caller a result it already fetched — the failure is reported here and the write resolves. The Client wires this to onerror.

_cachePartition?

string = ''

The opaque per-principal identifier for this client (the private-scope storage slot within the connected server's namespace). '' (the default) makes the private slot identical to the server's shared public slot — the safe single-tenant posture. See _partitionFor.

_now?

() => number

Clock seam (testing). The freshness check (entry.expiresAt > now()) and the expiresAt = now() + ttlMs stamp both read it via now. Default Date.now.

Returns

ClientResponseCache

Methods

captureGeneration()

captureGeneration(method, params?): number

Defined in: packages/client/src/client/responseCache.ts:474

Snapshot the eviction generation for {method, params} before issuing the request (a list walk's page 1, or a resources/read for params). Records the key so an interleaved evictKey for the same {method, params} knows there is an in-flight write to suppress and bumps; without the record, evictKey's recorded-only bump would skip and the stale body would be cached.

Parameters
method

string

params?

string

Returns

number

evict()

evict(method): Promise<void>

Defined in: packages/client/src/client/responseCache.ts:412

Bump the per-method generation (so an in-flight write for the same method becomes a no-op) and drop the connected server's two list singletons (own + shared partition; params: ''). The generation bump is unconditional and FIRST — the write race guard relies on the bump, not on the store's deletes completing.

Eviction is scoped to this client's [serverIdentity, principal] partitions (mirroring evictKey) — the method-wide store.evict() is NOT called, so on a shared store one server's list_changed cannot wipe a co-tenant's entry. A custom store's delete() may throw or reject; each partition is guarded independently so a failure on one does not skip the other, the failure is reported via the constructor's sink, and the call resolves so dispatch proceeds.

Parameters
method

string

Returns

Promise<void>

evictKey()

evictKey(method, params): Promise<void>

Defined in: packages/client/src/client/responseCache.ts:452

Drop the single logical entry {method, params} from BOTH the private and public partitions for this client's connected server (mcp.d's invalidateLogical). Used for notifications/resources/updated's per-URI eviction. The per-key generation is bumped FIRST (so an in-flight write for the same {method, params} becomes a no-op and cannot re-cache the now-stale body) but only when the key was already recorded by captureGeneration — bounding the map to keys the client has actually read. A custom store's delete() may throw or reject; each partition's delete is guarded independently so a failure on one does not skip the other, and the call resolves so dispatch proceeds.

Parameters
method

string

params

string

Returns

Promise<void>

now()

now(): number

Defined in: packages/client/src/client/responseCache.ts:344

The clock used for every freshness computation and check.

Returns

number

outputValidator()

outputValidator<V>(name, compile): Promise<V | undefined>

Defined in: packages/client/src/client/responseCache.ts:646

The compiled output-schema validator for tool name, derived from the cached tools/list entry — same source and same stamp-keyed memoization as toolDefinition. The name → validator index re-derives only when the backing entry's stamp changes (a refetched tools/list recompiles; a list_changed eviction drops it). Returns undefined when no tools/list is held, the tool is absent, or it has no outputSchema.

compile is the caller-supplied validator-compile callback (the Client passes its _jsonSchemaValidator wrapper) so this class carries no validator-provider dependency. One tool's uncompilable outputSchema (e.g. an invalid pattern regex or unresolvable $ref) must not poison every other tool's callTool — the callback isolates that compile error per tool by returning a per-tool error variant which the index stores alongside the good ones, and callTool surfaces it as a typed InvalidParams only for that name. Because the error is held on this stamp-keyed substrate (not a parallel map), it inherits the substrate's invalidation lifecycle: a list_changed eviction drops it, a refetched tools/list re-derives it, and resetForReconnect clears the lot.

Type Parameters
V

V

Parameters
name

string

compile

(tool) => V | undefined

Returns

Promise<V | undefined>

read()

read(method, params?): Promise<{ value: unknown; } | undefined>

Defined in: packages/client/src/client/responseCache.ts:562

Serve the fresh cached result for {method, params}, or undefined. Lookup is the two-probe order (own-partition then this server's shared partition, gated on scope === 'public'); freshness is entry.expiresAt > now() (a missing expiresAt is never fresh), checked BEFORE decoding so stale entries cost no parse. Every hit is freshly parsed, so the caller owns the value outright. An entry whose document does not parse or is not an object (corrupted external store) is reported, deleted, and treated as a miss — deleted because a fresh-but-corrupt entry would otherwise re-parse and re-report on every read until its expiresAt passes.

Parameters
method

string

params?

string

Returns

Promise<{ value: unknown; } | undefined>

resetForReconnect()

resetForReconnect(): void

Defined in: packages/client/src/client/responseCache.ts:588

Connection reset. The per-instance default store IS cleared (connection-scoped); a user-supplied store is NOT — that would defeat the only reason to supply one. The generation map and every derived index are dropped regardless: they are connection-scoped even when the backing store survives, so the next read re-derives from whatever the store still holds. The server identity returns to the pre-connect sentinel. The default impl is synchronous, so the MaybePromise<void> return is a plain void here and the caller need not await.

Returns

void

setServerIdentity()

setServerIdentity(identity): void

Defined in: packages/client/src/client/responseCache.ts:355

Record the connected server's identity. Called by Client immediately after a successful connect (serverInfo.name@version, or the transport's sessionId when serverInfo is unavailable). Every partition derived after this call is scoped to this identity; entries written under the pre-connect '' sentinel are no longer reachable.

Parameters
identity

string

Returns

void

toolDefinition()

toolDefinition(name): Promise<{ _meta?: {[key: string]: unknown; }; annotations?: { destructiveHint?: boolean; idempotentHint?: boolean; openWorldHint?: boolean; readOnlyHint?: boolean; title?: string; }; description?: string; execution?: { taskSupport?: "optional" | "required" | "forbidden"; }; icons?: object[]; inputSchema: {[key: string]: unknown; properties?: {[key: string]: string | number | boolean | {[key: string]: string | number | boolean | { [x: string]: string | number | boolean | ... | (string | number | boolean | ... | (string | number | boolean | ... | (string | number | boolean | ... | (string | ... 4 more ... | null)[] | null)[] | null)[] | null)[] | null; } | (string | ... 4 more ... | null)[] | null; } | string | number | boolean | { [x: string]: string | number | boolean | ... | (string | number | boolean | ... | (string | number | boolean | ... | (string | number | boolean | ... | (string | ... 4 more ... | null)[] | null)[] | null)[] | null)[] | null; } | (string | ... 4 more ... | null)[] | null[] | null; }; required?: string[]; type: "object"; }; name: string; outputSchema?: {[key: string]: unknown; $schema?: string; }; title?: string; } | undefined>

Defined in: packages/client/src/client/responseCache.ts:606

The descriptor for tool name taken from the cached tools/list entry. The name → Tool index is memoized against the entry's stamp and re-derived only when the backing entry changes (mcp.d's cachedTool). Returns undefined only when no tools/list response is held at all, or the held list does not contain name.

Consumed by callTool()'s SEP-2243 _resolveXMcpHeaderScan (mirroring) and, via outputValidator, its output-schema validation.

Parameters
name

string

Returns

Promise<{ _meta?: {[key: string]: unknown; }; annotations?: { destructiveHint?: boolean; idempotentHint?: boolean; openWorldHint?: boolean; readOnlyHint?: boolean; title?: string; }; description?: string; execution?: { taskSupport?: "optional" | "required" | "forbidden"; }; icons?: object[]; inputSchema: {[key: string]: unknown; properties?: {[key: string]: string | number | boolean | {[key: string]: string | number | boolean | { [x: string]: string | number | boolean | ... | (string | number | boolean | ... | (string | number | boolean | ... | (string | number | boolean | ... | (string | ... 4 more ... | null)[] | null)[] | null)[] | null)[] | null; } | (string | ... 4 more ... | null)[] | null; } | string | number | boolean | { [x: string]: string | number | boolean | ... | (string | number | boolean | ... | (string | number | boolean | ... | (string | number | boolean | ... | (string | ... 4 more ... | null)[] | null)[] | null)[] | null)[] | null; } | (string | ... 4 more ... | null)[] | null[] | null; }; required?: string[]; type: "object"; }; name: string; outputSchema?: {[key: string]: unknown; $schema?: string; }; title?: string; } | undefined>

write()

write(method, value, capturedGen, freshness?): Promise<void>

Defined in: packages/client/src/client/responseCache.ts:517

Write value under {method} unless the per-method generation moved since capturedGen was taken — a list_changed that landed mid-walk has already invalidated the result the caller is about to write, and overwriting the eviction with the stale aggregate would lose the invalidation.

The value is stored as its JSON-serialized document; serialization doubles as the mutation barrier, so a caller mutating the returned aggregate cannot reach the cache or its derived indices. A value that is not JSON-serializable (reachable only via in-process transports) fails the write loudly into the reportError sink. A custom store whose set() throws or rejects is routed to the same sink and the write resolves — cache bookkeeping never costs the caller a result it already fetched.

freshness carries the client-computed expiresAt (absolute ms epoch, now + ttlMs) and the server-reported cacheScope. The storage partition is derived from the scope via _partitionFor: 'public'[serverIdentity, ''] (shared within this server); 'private'[serverIdentity, cachePartition] (so a shared store never serves a private entry to another identity). Absent freshness preserves the substrate write (no expiresAt, private partition) — the tools/list retain-for-schema posture: never served by read's freshness gate, always readable by toolDefinition.

After storing under the derived partition, the same {method, params} is deleted from the OPPOSITE partition (mirroring evictKey's two-partition posture). A server that flips a result's cacheScope for the same key would otherwise leave the previous entry in the other slot — and since _probe checks own-partition first, a stale private entry would shadow the fresh public one (or a stale public entry would keep serving co-tenants). Both store calls are independently guarded so a custom store's failure on one does not skip the other.

Parameters
method

string

value

unknown

capturedGen

number

freshness?
expiresAt

number

params?

string

scope

CacheScope

Returns

Promise<void>


InMemoryResponseCacheStore

Defined in: packages/client/src/client/responseCache.ts:160

In-memory default. Bounded by an insertion-ordered size cap (default 512; see InMemoryResponseCacheStoreOptions.maxEntries) on the resources/read keyspace so an unbounded stream of distinct URIs cannot grow it without limit; the list-singleton methods are exempt and never evicted by the cap. Map preserves insertion order, so the oldest live capped key is the first matching iteration entry.

Implements

Constructors

Constructor

new InMemoryResponseCacheStore(options?): InMemoryResponseCacheStore

Defined in: packages/client/src/client/responseCache.ts:167

Parameters
options?

InMemoryResponseCacheStoreOptions

Returns

InMemoryResponseCacheStore

Accessors

size
Get Signature

get size(): number

Defined in: packages/client/src/client/responseCache.ts:172

Number of held entries (for diagnostics / bounding tests).

Returns

number

Methods

clear()

clear(): void

Defined in: packages/client/src/client/responseCache.ts:218

Drop every entry (connection reset).

Returns

void

Implementation of

ResponseCacheStore.clear

delete()

delete(key): void

Defined in: packages/client/src/client/responseCache.ts:203

Drop the single entry under key (no-op if absent). Called for both notifications/resources/updated (per-URI) and the list_changed notifications (the list singletons live at {method, params: '', partition}).

Parameters
key

CacheKey

Returns

void

Implementation of

ResponseCacheStore.delete

evict()

evict(method): void

Defined in: packages/client/src/client/responseCache.ts:207

Drop every entry for method across every partition. The Client does NOT call this (its list_changed path issues two partition-scoped delete() calls so co-tenants on a shared store keep their entries); kept on the interface for callers that want a method-wide bulk-clear.

Parameters
method

string

Returns

void

Implementation of

ResponseCacheStore.evict

get()

get(key): CacheEntry | undefined

Defined in: packages/client/src/client/responseCache.ts:176

Parameters
key

CacheKey

Returns

CacheEntry | undefined

Implementation of

ResponseCacheStore.get

set()

set(key, entry): number

Defined in: packages/client/src/client/responseCache.ts:180

Writes entry under key and returns the store-generated stamp the resulting CacheEntry carries. The store owns the stamp counter; callers do not supply one. The caller owns expiresAt and scope (the client-computed freshness metadata); the store MUST persist them and hand them back on get.

Parameters
key

CacheKey

entry
expiresAt?

number

scope?

CacheScope

value

string

Returns

number

Implementation of

ResponseCacheStore.set

Interfaces

CacheEntry

Defined in: packages/client/src/client/responseCache.ts:65

One cached response body. value is the JSON-serialized result document — a store is a dumb string carrier and persists it verbatim; the cache owns both codec halves. stamp is the store-generated monotonically increasing write counter — opaque to callers. Derived views (e.g. a name → Tool index) memoize against it and re-derive only when it changes. expiresAt (absolute ms epoch, now + ttlMs) and scope are the client-computed freshness metadata; the store MUST persist them and hand them back on get so the read path can decide freshness and gate the shared-partition fallback on scope === 'public'.

Properties

expiresAt?

readonly optional expiresAt?: number

Defined in: packages/client/src/client/responseCache.ts:68

scope?

readonly optional scope?: CacheScope

Defined in: packages/client/src/client/responseCache.ts:69

stamp

readonly stamp: number

Defined in: packages/client/src/client/responseCache.ts:67

value

readonly value: string

Defined in: packages/client/src/client/responseCache.ts:66


CacheKey

Defined in: packages/client/src/client/responseCache.ts:48

A logical cache address. params is the canonical result-affecting params key ('' for the four list ops, the uri for resources/read); omitted is equivalent to ''. partition namespaces the entry by connected-server identity AND per-principal scope: the Client writes a JSON-encoded [serverIdentity, principal] pair (so a server-controlled serverInfo string cannot bleed into the principal slot regardless of what characters it contains). A 'public'-scoped entry lives at [serverIdentity, '']; a 'private'-scoped entry at [serverIdentity, cachePartition]. Omitted is equivalent to ''.

Properties

method

readonly method: string

Defined in: packages/client/src/client/responseCache.ts:49

params?

readonly optional params?: string

Defined in: packages/client/src/client/responseCache.ts:50

partition?

readonly optional partition?: string

Defined in: packages/client/src/client/responseCache.ts:51


InMemoryResponseCacheStoreOptions

Defined in: packages/client/src/client/responseCache.ts:120

Options for InMemoryResponseCacheStore.

Properties

maxEntries?

optional maxEntries?: number

Defined in: packages/client/src/client/responseCache.ts:133

Maximum number of held resources/read entries (the only unbounded-keyspace method). When inserting a new resources/read key would exceed this, the oldest such entry (by insertion order) is evicted first. The list-singleton methods (tools/list, prompts/list, resources/list, resources/templates/list, server/discover) are exempt — they hold at most one entry per partition and back the tools/list-derived index, so an unbounded URI working set never displaces them. The default of 512 bounds growth on a long-lived client against template-expanded URIs. 0 disables the bound.


ResponseCacheStore

Defined in: packages/client/src/client/responseCache.ts:92

The pluggable response-cache store. The interface is intentionally narrow; the in-memory default is the only implementation the SDK ships.

Every method is async-ready (MaybePromise) so a Redis-style store can implement the same interface without a later breaking change; the in-memory default stays synchronous (plain values are valid under MaybePromise). The Client awaits every call site.

Entries are keyed by {method, params, partition} where partition is the Client-derived [serverIdentity, principal] JSON pair, so one store instance is safe to share across Client instances connected to different servers and/or principals: writes from distinct connections never collide, the shared-partition read fallback is gated on the stored scope === 'public', and list_changed / HEADER_MISMATCH evictions are scoped to the connected server's two partitions — co-tenants on a shared store are unaffected. The Client constructor still allocates a fresh InMemoryResponseCacheStore per instance by default; supply your own to share or persist.

Methods

clear()

clear(): MaybePromise<void>

Defined in: packages/client/src/client/responseCache.ts:116

Drop every entry (connection reset).

Returns

MaybePromise<void>

delete()

delete(key): MaybePromise<void>

Defined in: packages/client/src/client/responseCache.ts:107

Drop the single entry under key (no-op if absent). Called for both notifications/resources/updated (per-URI) and the list_changed notifications (the list singletons live at {method, params: '', partition}).

Parameters
key

CacheKey

Returns

MaybePromise<void>

evict()

evict(method): MaybePromise<void>

Defined in: packages/client/src/client/responseCache.ts:114

Drop every entry for method across every partition. The Client does NOT call this (its list_changed path issues two partition-scoped delete() calls so co-tenants on a shared store keep their entries); kept on the interface for callers that want a method-wide bulk-clear.

Parameters
method

string

Returns

MaybePromise<void>

get()

get(key): MaybePromise<CacheEntry | undefined>

Defined in: packages/client/src/client/responseCache.ts:93

Parameters
key

CacheKey

Returns

MaybePromise<CacheEntry | undefined>

set()

set(key, entry): MaybePromise<number>

Defined in: packages/client/src/client/responseCache.ts:101

Writes entry under key and returns the store-generated stamp the resulting CacheEntry carries. The store owns the stamp counter; callers do not supply one. The caller owns expiresAt and scope (the client-computed freshness metadata); the store MUST persist them and hand them back on get.

Parameters
key

CacheKey

entry
expiresAt?

number

scope?

CacheScope

value

string

Returns

MaybePromise<number>

Type Aliases

CacheMode

CacheMode = "use" | "refresh" | "bypass"

Defined in: packages/client/src/client/responseCache.ts:35

Per-call cache disposition for the cacheable verbs (listTools() / listPrompts() / listResources() / listResourceTemplates() / readResource()):

  • 'use' (the default) — serve a still-fresh cached entry without a round trip; on miss/stale, fetch and write.
  • 'refresh' — always fetch (ignore any held entry) and write the fresh result.
  • 'bypass' — fetch without consulting OR writing the cache (the result is not stored). The tools/list-derived index (mirroring / output validation) is therefore unaffected by a 'bypass' call.

CacheScope

CacheScope = "public" | "private"

Defined in: packages/client/src/client/responseCache.ts:20

The freshness scope of a cached entry (SEP-2549 cacheHints.scope).


MaybePromise

MaybePromise<T> = T | Promise<T>

Defined in: packages/client/src/client/responseCache.ts:17

A value or a promise of one. The store interface is async-ready; the in-memory default returns plain values.

Type Parameters

T

T

Variables

MAX_CACHE_TTL_MS

const MAX_CACHE_TTL_MS: 86400000 = 86_400_000

Defined in: packages/client/src/client/responseCache.ts:253

Upper bound on the server-supplied ttlMs honoured by ClientResponseCache (24h). A server cannot pin an entry indefinitely.