feat(client): minimal response-cache substrate (ResponseCacheStore + aggregate-then-write list*())
Adds a minimal response-cache substrate to Client — the foundation that SEP-2243 header mirroring (#2327) and the future client-side cacheHints honoring both build on.
Motivation and Context
SEP-2243's client algorithm requires the client to know each tool's inputSchema (to find x-mcp-header declarations). #2327's first iteration built a SEP-2243-specific tool-definition cache with its own population/invalidation logic; review rounds repeatedly surfaced lifecycle edges (page-1 wipes, concurrent races, listChanged interactions). This PR moves to the right abstraction: a general response cache that listTools()/listPrompts()/listResources() write to, and _toolDefinition(name) reads as a stamp-memoized derived view. Lifecycle is solved once at the cache layer.
How Has This Been Tested?
Client suite (550), full e2e matrix (2545p/205xf), conformance client:all. New responseCache.test.ts covers store contract, partition keying, listMaxPages cap-throw, generation-guard, and the Object.prototype method-name guard.
Breaking Changes
listTools()/listPrompts()/listResources()/listResourceTemplates()called without acursornow aggregate all pages internally and return the complete result. Callers that paginated manually should drop their loop. ExplicitlistTools({ cursor })keeps the per-page contract.- A walk that exceeds
ClientOptions.listMaxPages(default 64) now throws instead of returning a truncated result.
Types of changes
- Bug fix (non-breaking change which fixes an issue)
- New feature (non-breaking change which adds functionality)
- Breaking change (fix or feature that would cause existing functionality to change)
- Documentation update
Checklist
- I have read the MCP Documentation
- My code follows the repository's style guidelines
- New and existing tests pass locally
- I have added appropriate error handling
- I have added or updated documentation as needed
Additional context
Public surface added: ResponseCacheStore (interface, MaybePromise<…> returns), InMemoryResponseCacheStore (default), CacheKey, CacheEntry, CacheScope, ClientOptions.responseCacheStore. The interface is shaped for the eventual cacheHints work to be additive: set(key, { value, expiresAt?, scope? }) carries the freshness slot now, even though nothing populates it yet.
Do not share a store across Client instances in v2.0.x — the store is keyed without a server-identity or principal partition, so cross-instance sharing causes server-identity confusion and clear()/evict() cross-talk. Per-principal partitioning that enables safe sharing is the follow-up work.
Lifecycle model: list_changed notifications evict the matching entry (they do not eagerly refetch). A per-method generation counter prevents an in-flight aggregation from caching a stale result after an eviction has fired. _resetConnectionState clears only the per-instance default store, never a user-supplied one.