Skip to content

Implement SEP-2549: TTL for List Results #875

@alexhancock

Description

@alexhancock

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

  • Extend the paginated_result! macro to add #[serde(skip_serializing_if = "Option::is_none")] pub ttl_ms: Option<u64> and pub cache_scope: Option<CacheScope> (with #[serde(rename_all = "camelCase")] already on the macro output → ttlMs/cacheScope). Update the macro's with_all_items constructor to default both to None.
  • Add the same two fields to ReadResourceResult (not produced by the macro).
  • Define pub enum CacheScope with the spec's allowed values + #[cfg_attr(feature = "schemars", derive(JsonSchema))], matching the #[non_exhaustive]/exhaustive conventions used elsewhere in model.rs.
  • Add builder-style setters (e.g. with_ttl_ms, with_cache_scope) so server handlers in handler/server/router.rs can set them ergonomically.
  • Client-side: surface the values on results; a TTL-honoring cache is an optional fast follow on service/client.rs.
  • Tests + wire up conformance#275.

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium: important but non-blocking improvementT-enhancementNew features and enhancementsT-modelModel/data structure changesT-transportTransport layer changes

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions