Cache MCP responses without crossing authority
Apply bounded TTLs, endpoint namespaces, private authorization partitions, pagination keys, notification invalidation, and cache modes.
Cacheable surface
Only protocol-defined read operations are cacheable: discovery, Tool, Prompt, Resource and Resource Template lists, plus Resource reads. Tool calls, Sampling, Tasks, elicitation, and any effectful operation are never cached.
The cache sits above transport so in-process, stdio, and Streamable HTTP share semantics. Default TTL zero and private scope preserve uncached behavior until the server opts in.
Cache key
Every key contains a host-selected trusted endpoint namespace, exact MCP method, serialized parameters including pagination cursor, and either public scope or private scope plus an authorization partition.
Server-reported identity is not a namespace. Reuse a private partition only for the same authenticated authority. Public sharing requires an explicit application decision and a shared Store plus endpoint namespace.
Modes and TTL
Use returns a fresh hit or fetches and stores. Refresh skips lookup but
updates storage. Bypass performs neither. Client policy caps every
server-supplied TTL; malformed, future-dated, missing, zero, or expired metadata
is a miss.
TTL expiry is lazy. It must not create background polling, hidden network work, or a retry loop.
Invalidation
List-change notifications invalidate every cached page for that operation. Resource-updated invalidates the exact read URI. A rejected pagination request invalidates the operation's pages so callers cannot combine snapshots from different list generations.
Apply invalidation before delivering the notification to application subscribers.
Security invariants
- cache keys include every request parameter and cursor;
- private entries never cross authorization partitions;
- server TTL cannot exceed the client cap;
- malformed metadata fails closed as a miss;
- effectful results never enter the cache;
- cached content remains untrusted;
- logout, credential rotation, and tenant switch rotate or clear private partitions;
- cache observability excludes sensitive key material and returned content.
Configure a safe client cache
Use a stable endpoint namespace and an authorization-derived private partition. The partition must change on logout, credential rotation, or tenant switch.
use runifold_mcp::{Implementation, InMemoryResponseCache, McpClientConfig};
use std::{sync::Arc, time::Duration};
let config = McpClientConfig::new(Implementation::new("acme-host", "1.0.0"))
.with_response_cache(Arc::new(InMemoryResponseCache::new(256)))
.with_cache_namespace("https://mcp.example.com/v1")
.with_private_cache_partition(format!("tenant:{tenant_id}:subject:{subject_id}"))
.with_max_cache_ttl(Duration::from_secs(60));Use CacheMode::Refresh after an operator requests fresh discovery and
CacheMode::Bypass when debugging. Never build the partition from a display
name or server-returned metadata. A cache hit with the wrong principal is a
security incident: rotate the partition, clear the affected store, and audit
which read operations used it.