Add Opt-In ttlMs / cacheScope Cache Hints to List and Read Results per SEP-2549#436
Open
koic wants to merge 1 commit into
Open
Add Opt-In ttlMs / cacheScope Cache Hints to List and Read Results per SEP-2549#436koic wants to merge 1 commit into
ttlMs / cacheScope Cache Hints to List and Read Results per SEP-2549#436koic wants to merge 1 commit into
Conversation
…s per SEP-2549 ## Motivation and Context SEP-2549 (modelcontextprotocol/modelcontextprotocol#2549, merged for the 2026-07-28 spec release) adds a `CacheableResult` shape with `ttlMs` (freshness lifetime in milliseconds, max-age semantics, 0 = do not cache) and `cacheScope` (`"public"` or `"private"`, whether shared intermediaries may cache) to `ListToolsResult`, `ListPromptsResult`, `ListResourcesResult`, `ListResourceTemplatesResult`, and `ReadResourceResult`, complementing the `listChanged` notifications. The field shape follows the Python SDK's implementation (python-sdk#2824, `ttlMs` + `cacheScope` with `ttlMs: 0` / `cacheScope: "public"` defaults; the TypeScript SDK's earlier `ttl`-seconds prototype #2070 was superseded by this shape). Because this SDK negotiates protocol versions up to 2025-11-25, where the fields do not exist, emission is opt-in: when neither `ttl_ms` nor `cache_scope` is configured, responses are serialized exactly as before. The fields become REQUIRED in 2026-07-28; a follow-up can auto-emit defaults once that version enters `SUPPORTED_STABLE_PROTOCOL_VERSIONS`. - `MCP::Server.new` gains `ttl_ms:` and `cache_scope:` keywords with validating writers modeled on `page_size=` (`ttl_ms` must be nil or a non-negative Integer; `cache_scope` must be nil, `"public"`, or `"private"`). - A private `apply_cache_metadata` wraps the five emission points. When the user sets only one of the two values, the other is filled with the spec defaults (`ttlMs: 0`, the only universally safe freshness value, and `cacheScope: "public"`, the spec and Python default). - A `resources_read_handler` may return a full `{ contents:, ttlMs:, cacheScope: }` hash to override the server-level hints per result; the documented bare-contents return shape keeps working unchanged. - The client's `ListToolsResult` / `ListPromptsResult` / `ListResourcesResult` / `ListResourceTemplatesResult` structs gain `ttl_ms` and `cache_scope` members populated from the response fields. Resolves modelcontextprotocol#387. ## How Has This Been Tested? New tests in `test/mcp/server_test.rb`: - with `ttl_ms: 5000`, all four list results carry `ttlMs: 5000` and the filled `cacheScope: "public"` default - with only `cache_scope: "private"`, `resources/read` carries `ttlMs: 0` - with neither configured, list and read results omit both fields (wire-format regression) - the hints appear alongside `nextCursor` when paginating - a `resources_read_handler` returning `{ contents:, ttlMs: 60_000 }` overrides the server-level value while the missing `cacheScope` is filled - invalid writer values (`-1`, `1.5`, `"internal"`) raise `ArgumentError` New tests in `test/mcp/client_test.rb` assert all four list result structs expose `ttl_ms` / `cache_scope`, and that both are nil when the server omits the fields. ## Breaking Changes None. Emission is opt-in, the new constructor keywords default to nil, and the client structs gain additive members.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation and Context
SEP-2549 (modelcontextprotocol/modelcontextprotocol#2549, merged for the 2026-07-28 spec release) adds a
CacheableResultshape withttlMs(freshness lifetime in milliseconds, max-age semantics, 0 = do not cache) andcacheScope("public"or"private", whether shared intermediaries may cache) toListToolsResult,ListPromptsResult,ListResourcesResult,ListResourceTemplatesResult, andReadResourceResult, complementing thelistChangednotifications.The field shape follows the Python SDK's implementation (python-sdk#2824,
ttlMs+cacheScopewithttlMs: 0/cacheScope: "public"defaults; the TypeScript SDK's earlierttl-seconds prototype #2070 was superseded by this shape). Because this SDK negotiates protocol versions up to 2025-11-25, where the fields do not exist, emission is opt-in: when neitherttl_msnorcache_scopeis configured, responses are serialized exactly as before. The fields become REQUIRED in 2026-07-28; a follow-up can auto-emit defaults once that version entersSUPPORTED_STABLE_PROTOCOL_VERSIONS.MCP::Server.newgainsttl_ms:andcache_scope:keywords with validating writers modeled onpage_size=(ttl_msmust be nil or a non-negative Integer;cache_scopemust be nil,"public", or"private").apply_cache_metadatawraps the five emission points. When the user sets only one of the two values, the other is filled with the spec defaults (ttlMs: 0, the only universally safe freshness value, andcacheScope: "public", the spec and Python default).resources_read_handlermay return a full{ contents:, ttlMs:, cacheScope: }hash to override the server-level hints per result; the documented bare-contents return shape keeps working unchanged.ListToolsResult/ListPromptsResult/ListResourcesResult/ListResourceTemplatesResultstructs gainttl_msandcache_scopemembers populated from the response fields.Resolves #387.
How Has This Been Tested?
New tests in
test/mcp/server_test.rb:ttl_ms: 5000, all four list results carryttlMs: 5000and the filledcacheScope: "public"defaultcache_scope: "private",resources/readcarriesttlMs: 0nextCursorwhen paginatingresources_read_handlerreturning{ contents:, ttlMs: 60_000 }overrides the server-level value while the missingcacheScopeis filled-1,1.5,"internal") raiseArgumentErrorNew tests in
test/mcp/client_test.rbassert all four list result structs exposettl_ms/cache_scope, and that both are nil when the server omits the fields.Breaking Changes
None. Emission is opt-in, the new constructor keywords default to nil, and the client structs gain additive members.
Types of changes
Checklist