Skip to content

Stamp the required cache hints on modern cacheable results per SEP-2549 - #499

Open
koic wants to merge 1 commit into
modelcontextprotocol:mainfrom
koic:stamp_cache_hints_on_modern_results
Open

Stamp the required cache hints on modern cacheable results per SEP-2549#499
koic wants to merge 1 commit into
modelcontextprotocol:mainfrom
koic:stamp_cache_hints_on_modern_results

Conversation

@koic

@koic koic commented Aug 8, 2026

Copy link
Copy Markdown
Member

Motivation and Context

The 2026-07-28 spec revision makes the SEP-2549 ttlMs / cacheScope cache hints REQUIRED members of the cacheable results (tools/list, prompts/list, resources/list, resources/templates/list, and resources/read). The SDK's existing emission is opt-in (Server.new(ttl_ms:, cache_scope:)), so servers that never opted in violate the 2026-07-28 wire schema, and the conformance suite fails those scenarios on wire-schema validation (ListToolsResult: must have required property 'cacheScope' / 'ttlMs') plus all eight checks of the caching scenario.

The dispatch path now fills the hints centrally for results of requests carrying the SEP-2575 _meta envelope, next to the SEP-2322 resultType stamp: a cacheable result that does not already carry both fields gets the configured ttl_ms / cache_scope values, with ttlMs: 0 (do not cache) and cacheScope: "private" filling anything unset. Values already in the result win, preserving per-result overrides. Only complete results are stamped: an SEP-2322 input_required round trip of resources/read is not a cacheable final result and stays hint-free.

The unset-scope fill is "private" because the spec names no default scope and "private" is the side that cannot leak a user-dependent resources/read result through a shared cache; it is also the unconfigured default of the TypeScript SDK (cacheScope: 'private'), the Python SDK (CacheHint.scope), and this SDK's own server/discover. The opt-in emission of apply_cache_metadata used to fill a missing scope as "public" when only ttl_ms was configured and now fills "private" for the same reason, so both paths agree. Stable protocol versions otherwise keep the opt-in emission unchanged.

How Has This Been Tested?

bundle exec rake test passes with zero failures. New tests in test/mcp/server_test.rb: modern tools/list and resources/read results carry the default hints, configured values win over the defaults, non-cacheable modern results (ping) stay untouched, and legacy cacheable results keep the opt-in emission. The opt-in emission tests assert the "private" fill for a configured ttl_ms without a scope. The previously failing conformance scenarios (tools-list, resources-read-text, and caching) pass at --spec-version 2026-07-28 against the conformance fixture server, and the 2025-11-25 leg is unchanged.

Breaking Changes

The modern-path emission is additive: it applies only to results of requests carrying the SEP-2575
modern _meta envelope. One deliberate change reaches stable versions: a server configured with
ttl_ms: but no cache_scope: now emits cacheScope: "private" instead of "public" on its opt-in results;
declaring cache_scope: "public" explicitly restores the shared-cacheable scope.

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

## Motivation and Context

The 2026-07-28 spec revision makes the SEP-2549 `ttlMs` / `cacheScope` cache hints REQUIRED members of
the cacheable results (`tools/list`, `prompts/list`, `resources/list`, `resources/templates/list`,
and `resources/read`). The SDK's existing emission is opt-in (`Server.new(ttl_ms:, cache_scope:)`),
so servers that never opted in violate the 2026-07-28 wire schema, and the conformance suite fails
those scenarios on wire-schema validation (`ListToolsResult: must have required property 'cacheScope' / 'ttlMs'`)
plus all eight checks of the `caching` scenario.

The dispatch path now fills the hints centrally for results of requests carrying the SEP-2575 `_meta` envelope,
next to the SEP-2322 `resultType` stamp: a cacheable result that does not already carry both fields gets
the configured `ttl_ms` / `cache_scope` values, with `ttlMs: 0` (do not cache) and `cacheScope: "private"`
filling anything unset. Values already in the result win, preserving per-result overrides.
Only complete results are stamped: an SEP-2322 `input_required` round trip of `resources/read` is not
a cacheable final result and stays hint-free.

The unset-scope fill is `"private"` because the spec names no default scope and `"private"` is the side
that cannot leak a user-dependent `resources/read` result through a shared cache; it is also
the unconfigured default of the TypeScript SDK (`cacheScope: 'private'`), the Python SDK (`CacheHint.scope`),
and this SDK's own `server/discover`. The opt-in emission of `apply_cache_metadata` used to fill a missing
scope as `"public"` when only `ttl_ms` was configured and now fills `"private"` for the same reason,
so both paths agree. Stable protocol versions otherwise keep the opt-in emission unchanged.

## How Has This Been Tested?

`bundle exec rake test` passes with zero failures. New tests in `test/mcp/server_test.rb`: modern `tools/list`
and `resources/read` results carry the default hints, configured values win over the defaults,
non-cacheable modern results (`ping`) stay untouched, and legacy cacheable results keep the opt-in emission.
The opt-in emission tests assert the `"private"` fill for a configured `ttl_ms` without a scope.
The previously failing conformance scenarios (`tools-list`, `resources-read-text`, and `caching`) pass at
`--spec-version 2026-07-28` against the conformance fixture server, and the 2025-11-25 leg is unchanged.

## Breaking Changes

The modern-path emission is additive: it applies only to results of requests carrying the SEP-2575
modern `_meta` envelope. One deliberate change reaches stable versions: a server configured with
`ttl_ms:` but no `cache_scope:` now emits `cacheScope: "private"` instead of `"public"` on its opt-in results;
declaring `cache_scope: "public"` explicitly restores the shared-cacheable scope.
@koic koic changed the title Stamp the Required Cache Hints on Modern Cacheable Results per SEP-2549 Stamp the required cache hints on modern cacheable results per SEP-2549 Aug 8, 2026
@koic
koic force-pushed the stamp_cache_hints_on_modern_results branch from cfbda93 to 5864b84 Compare August 8, 2026 16:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant