Add ttlMs and cacheScope to list and resource results with a server listCache option (SEP-2549) - #1131
Add ttlMs and cacheScope to list and resource results with a server listCache option (SEP-2549)#1131slachiewicz wants to merge 5 commits into
Conversation
The draft spec introduces TTL caching hints on list and read results. This adds the optional ttlMs (Integer) and cacheScope (CacheScope enum) fields to ListToolsResult, ListPromptsResult, ListResourcesResult, ListResourceTemplatesResult, and ReadResourceResult, following the wire-record evolution rules in CONTRIBUTING.md. Purely additive, schema-layer only: no server or client behavior changes. Server-side TTL configuration hooks are deferred to modelcontextprotocol#578.
The draft spec requires cacheable results to carry ttlMs and cacheScope on the wire. Other SDKs (Go, Python, TypeScript) all stamp defaults after handlers return: ttlMs=0 (immediately stale) and cacheScope=public. This adds the same default stamping in McpAsyncServer and McpStatelessAsyncServer for all five cacheable result types. For list results the defaults are set at the build site. For ReadResourceResult, which is built by user handlers, a withCacheDefaults helper stamps missing fields while preserving any values the handler set explicitly.
Two fixes from review: 1. Widen ttlMs from Integer to Long across all five cacheable result records. The spec defines ttlMs as a non-negative integer with no upper bound, and Jackson rejects values exceeding Integer.MAX_VALUE. Long handles any realistic TTL without interop failures. 2. Default cacheScope to PRIVATE (not PUBLIC) for resources/read in both server classes. The spec's caching guidance says resources/read results that depend on the authenticated user should be private. List results keep the PUBLIC default since they are not user-specific.
Listings previously hardcoded ttlMs to 0, so the server half of SEP-2549 could not be switched on. Listings default to a PRIVATE scope because tools/list has been filtered per caller since modelcontextprotocol#1111. ttlMs is validated non-negative on the affected result builders.
|
One API-level safety issue seems worth resolving before making That becomes awkward once
Calling: .listCache(Duration.ofMinutes(5), CacheScope.PUBLIC)currently marks all four result types public, including the filtered tools listing. A shared intermediary following the server hint could then reuse one principal's filtered tool list for another. The warning reduces misuse, but the type/API shape still permits an unsafe combination and makes the safe mixed-scope configuration impossible. I'd consider either:
A regression could configure That would make the security property enforced rather than advisory while still allowing public caching for caller-invariant lists. |
Closes #1009. Supersedes #1062, whose three commits are kept with @aboullaite's authorship; this PR adds the server hook on top.
Adds
ttlMsandcacheScopetoListToolsResult,ListPromptsResult,ListResourcesResult,ListResourceTemplatesResultandReadResourceResult, with deprecated constructors so existing callers compile unchanged. Servers stampttlMs: 0on listings andcacheScope: privateonresources/readwhen a handler sets neither.McpServerbuilders gainlistCache(Duration, CacheScope)so a server can advertise a positive TTL; without it the server half of SEP-2549 could not be switched on. Listings default toprivatebecausetools/listis filtered per caller since #1111, so a shared cache must never serve one principal's listing to another.Two deliberate deviations from the draft schema:
ttlMsis a nullableLongrather than a required field so results from older servers still deserialize, and the absent-scope default for listings isprivatewhere the schema comment sayspublic.The client-side cache from #1123 follows as a separate PR stacked on this one.
This change was created with AI assistance.