## 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 #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.
15 lines
1 KiB
Ruby
15 lines
1 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module MCP
|
|
class Client
|
|
# Result objects returned by `list_tools`, `list_prompts`, `list_resources`, and `list_resource_templates`.
|
|
# Each carries the page items, an optional opaque `next_cursor` string for continuing pagination,
|
|
# an optional `meta` hash mirroring the MCP `_meta` response field, and the optional SEP-2549
|
|
# cache hints `ttl_ms` (freshness lifetime in milliseconds; 0 means do not cache) and
|
|
# `cache_scope` (`"public"` or `"private"`) mirroring the `ttlMs`/`cacheScope` response fields.
|
|
ListToolsResult = Struct.new(:tools, :next_cursor, :meta, :ttl_ms, :cache_scope, keyword_init: true)
|
|
ListPromptsResult = Struct.new(:prompts, :next_cursor, :meta, :ttl_ms, :cache_scope, keyword_init: true)
|
|
ListResourcesResult = Struct.new(:resources, :next_cursor, :meta, :ttl_ms, :cache_scope, keyword_init: true)
|
|
ListResourceTemplatesResult = Struct.new(:resource_templates, :next_cursor, :meta, :ttl_ms, :cache_scope, keyword_init: true)
|
|
end
|
|
end
|