SEP-2549: TTL for List Results — rust-sdk implementation
Spec PR: modelcontextprotocol/modelcontextprotocol#2549
Conformance: modelcontextprotocol/conformance#275
Track: Specification · Stage: accepted · Priority: P0 · Theme: Transport Evolution and Scalability
Needs code changes: Yes (Medium) — non-breaking (additive)
Summary
Adds two optional fields to the results of tools/list, prompts/list, resources/list,
resources/read, and resources/templates/list:
ttlMs — how long a client may treat the response as fresh before re-fetching.
cacheScope — controls who may cache the response.
This lets clients cache feature lists and reduce reliance on server-push notifications, while
remaining fully backward compatible. TTL supplements (does not replace) the notification mechanism.
Why this needs code changes in rust-sdk
All the affected list results are generated by a single paginated_result! macro in
crates/rmcp/src/model.rs (~line 1145), used for ListResourcesResult,
ListResourceTemplatesResult, ListPromptsResult, ListToolsResult (and ListTasksResult). That
macro currently emits only _meta + next_cursor + the items field. ReadResourceResult is a
separate struct. None of these carry ttlMs/cacheScope yet.
This is the nice part: adding the fields to the macro covers four of the five result types at once.
Proposed work
Affected areas
crates/rmcp/src/model.rs (the paginated_result! macro, ReadResourceResult, new CacheScope),
handler/server/router.rs, service/client.rs (only if implementing client caching).
Notes / risks
- Purely additive and backward compatible; safe to ship. Because of the macro, the field changes are small and centralized.
- Note the macro currently has
#[expect(clippy::exhaustive_structs)] — adding fields is fine but keep the constructor in sync.
SEP-2549: TTL for List Results — rust-sdk implementation
Spec PR: modelcontextprotocol/modelcontextprotocol#2549
Conformance: modelcontextprotocol/conformance#275
Track: Specification · Stage: accepted · Priority: P0 · Theme: Transport Evolution and Scalability
Needs code changes: Yes (Medium) — non-breaking (additive)
Summary
Adds two optional fields to the results of
tools/list,prompts/list,resources/list,resources/read, andresources/templates/list:ttlMs— how long a client may treat the response as fresh before re-fetching.cacheScope— controls who may cache the response.This lets clients cache feature lists and reduce reliance on server-push notifications, while
remaining fully backward compatible. TTL supplements (does not replace) the notification mechanism.
Why this needs code changes in rust-sdk
All the affected list results are generated by a single
paginated_result!macro incrates/rmcp/src/model.rs(~line 1145), used forListResourcesResult,ListResourceTemplatesResult,ListPromptsResult,ListToolsResult(andListTasksResult). Thatmacro currently emits only
_meta+next_cursor+ the items field.ReadResourceResultis aseparate struct. None of these carry
ttlMs/cacheScopeyet.This is the nice part: adding the fields to the macro covers four of the five result types at once.
Proposed work
paginated_result!macro to add#[serde(skip_serializing_if = "Option::is_none")] pub ttl_ms: Option<u64>andpub cache_scope: Option<CacheScope>(with#[serde(rename_all = "camelCase")]already on the macro output →ttlMs/cacheScope). Update the macro'swith_all_itemsconstructor to default both toNone.ReadResourceResult(not produced by the macro).pub enum CacheScopewith the spec's allowed values +#[cfg_attr(feature = "schemars", derive(JsonSchema))], matching the#[non_exhaustive]/exhaustiveconventions used elsewhere inmodel.rs.with_ttl_ms,with_cache_scope) so server handlers inhandler/server/router.rscan set them ergonomically.service/client.rs.Affected areas
crates/rmcp/src/model.rs(thepaginated_result!macro,ReadResourceResult, newCacheScope),handler/server/router.rs,service/client.rs(only if implementing client caching).Notes / risks
#[expect(clippy::exhaustive_structs)]— adding fields is fine but keep the constructor in sync.