Skip to content

SEP-2419: cache_hint well-known key in CallToolResult._meta#2419

Open
clouatre wants to merge 4 commits intomodelcontextprotocol:mainfrom
clouatre:sep-cache-hint-tool-result
Open

SEP-2419: cache_hint well-known key in CallToolResult._meta#2419
clouatre wants to merge 4 commits intomodelcontextprotocol:mainfrom
clouatre:sep-cache-hint-tool-result

Conversation

@clouatre
Copy link
Copy Markdown

@clouatre clouatre commented Mar 18, 2026

Summary

Standardizes cache_hint as a well-known key in CallToolResult._meta, allowing servers to advise MCP clients whether to cache or skip caching a specific tool result.

Why

MCP clients that support prompt caching (e.g. LLM-backed clients) may insert tool results into their cache to reduce token costs on repeated calls. For single-pass sessions (subagents, benchmarks, one-shot pipelines), the cache entry is written once and never read again: a net token cost with zero benefit. Today the server has no mechanism to advise the client against caching its results. The only available workaround is a global session-level opt-out, not a per-result signal.

The need generalizes beyond any single client. Any MCP client that applies caching at the tool result level faces the same mismatch. Standardizing the key in the MCP specification enables interoperability without requiring bilateral coordination between server and client authors.

This gap is documented in an upstream request (anthropics/claude-code#34334) and in the issue linked below.

What changed

  • seps/2419-cache-hint-tool-result.md: new SEP proposing cache_hint as a plain-name well-known key in CallToolResult._meta with values "no-cache" and "cache"
  • No schema interface changes; the key is declared in specification prose only
  • Backward-compatible: clients that do not recognize cache_hint ignore it; servers that do not set it are unaffected

Design decisions

Plain name, not io.modelcontextprotocol/cache_hint: The draft MetaObject from #1788 (unmerged) places bare keys in the free-use zone. Adopting the prefix before #1788 settles would lock in a naming decision that proposal may revise. The plain name follows the Cache-Control HTTP precedent and remains upgradeable to a prefixed name without a breaking change if #1788 is eventually adopted.

String enum, not boolean: Extensible -- future values (e.g. "immutable") can be added without a breaking change.

On the result, not the tool definition: Tool annotations are static. A tool may return cacheable results in some invocations and non-cacheable results in others (e.g. depending on output size or session context). Per-result placement enables per-result control.

Reference implementation

https://github.com/clouatre-labs/code-analyze-mcp (Apache-2.0) -- all four tool paths set _meta: { "cache_hint": "no-cache" } via a shared helper; integration test validates round-trip serialization.

Closes #2400

…meta

Standardizes cache_hint as a well-known key in CallToolResult._meta with
values 'no-cache' and 'cache'. Allows servers to advise MCP clients whether
to cache or skip caching a specific tool result.

Pattern follows progressToken (hint-style, non-mandatory) and the well-known
_meta key convention from SEP-1686 and SEP-414. No schema interface changes
required; declared in specification prose only.

Reference implementation: https://github.com/clouatre-labs/code-analyze-mcp
Upstream issue: modelcontextprotocol#2400

Signed-off-by: Hugues Clouatre <hugues@linux.com>
Signed-off-by: Hugues Clouâtre <hugues@linux.com>
Assigned PR number is 2419. Rename file per SEP process step 3.

Signed-off-by: Hugues Clouatre <hugues@linux.com>
Signed-off-by: Hugues Clouâtre <hugues@linux.com>
- Run npm run generate:seps to produce docs/seps/2419-cache-hint-tool-result.mdx,
  update docs/seps/index.mdx, docs/docs.json, and regenerate stale
  docs/seps/990-enable-enterprise-idp-policy-controls-during-mcp-o.mdx
- Run prettier --write on seps/2419-cache-hint-tool-result.md

Fixes render-seps and format CI checks.

Signed-off-by: Hugues Clouâtre <hugues@linux.com>
@clouatre clouatre requested review from a team as code owners March 18, 2026 13:34
@clouatre clouatre changed the title docs(sep): add SEP-0000 cache_hint well-known key in CallToolResult._meta docs(sep): add SEP-2419 cache_hint well-known key in CallToolResult._meta Mar 18, 2026
- Abstract: remove SEP-1686 as plain-name precedent (it uses DNS-prefixed
  keys); clarify cache_hint is the first result-side well-known _meta key;
  note progressToken precedent is on request _meta, not result _meta
- Semantics: expand progressToken reference to make the request-vs-result
  distinction explicit
- Rationale: reframe modelcontextprotocol#1788 free-use zone as a proposal, not existing spec
  property

Signed-off-by: Hugues Clouâtre <hugues@linux.com>
@localden localden changed the title docs(sep): add SEP-2419 cache_hint well-known key in CallToolResult._meta SEP-2419: cache_hint well-known key in CallToolResult._meta Mar 18, 2026
@clouatre
Copy link
Copy Markdown
Author

Wanted to flag a few roadmap connections that might not be immediately obvious.

The Result Type Improvements section mentions "reference-based results would let clients decide when to pull large payloads into context rather than polluting it by default." cache_hint addresses that pollution problem from the server side, with no new primitives and no schema changes.

The Agent Communication priority also lists "expiry policies: how long results are retained after completion." cache_hint: "no-cache" is a per-result retention signal in that same problem space.

SEP-1577 (Sampling With Tools, Final) explicitly deferred a "Cache friendliness updates" section listing "introduce cache awareness" as future work. cache_hint on CallToolResult._meta is a concrete minimal step in that direction.

If any of this is useful for routing to the right reviewer or working group, happy to update the abstract to make the connections explicit.

@whiteknightonhorse
Copy link
Copy Markdown

Production data supporting this proposal — running 452 tools with per-tool TTLs across 137 upstream providers.

The "cache" / "no-cache" binary misses the most common case. In practice, almost every tool result is cacheable — but for wildly different durations:

Tool type TTL Why
Crypto prices 5s Stale after seconds
Weather forecast 300s Updates every 5 min
Flight search 1800s Fares shift hourly
Package metadata (npm/PyPI) 3600s Releases are daily
Chemical structures 86400s Stable for months
Prediction markets 0s Must always be live

A "cache" hint without max_age forces the client to guess TTL. Suggesting the enum include a duration value:

{ "_meta": { "cache_hint": "max-age=300" } }

This follows HTTP Cache-Control semantics (which the SEP already references) and gives clients enough information to cache correctly without server-specific knowledge. The "no-cache" value still works for TTL=0 cases.

Interaction with server-side caching and single-flight: When a server caches results internally (as most multi-provider gateways do), the client receives a response that may already be age seconds old. The cache_hint should reflect remaining freshness, not the original TTL. E.g., if server TTL is 300s and the result was cached 200s ago, the hint should be "max-age=100" — otherwise the client caches stale data for another 300s on top.

Single-flight deduplication: When multiple concurrent requests hit the same tool+params, the server returns the same result to all callers (thundering herd prevention). All responses should carry the same cache_hint value. This is already natural if the hint is computed from the result's remaining TTL.

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.

[FEAT] Standardize cache_hint in CallToolResult._meta

2 participants