- Status: Accepted
- Accepted: 2026-09-25
- Type: Standards Track
- Created: 2026-03-18
- Author(s): Hugues Clouatre (@clouatre)
- Issue: https://github.com/modelcontextprotocol/modelcontextprotocol/issues/2400
- PR: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2419
Imported from upstream. Proposed as modelcontextprotocol#2419 by @clouatre on 2026-03-18, where it was still open on 2026-09-25. Accepted into omcp on its technical merit. The text is the author's; only the status, the sponsor field, and documentation links were changed on import.
Reviewed 2026-09-25 — accepted. The document carries every required section, and a prototype a reviewer can run is cited at https://github.com/clouatre-labs/aptu-coder. Conformance tests and the reference documentation chapter are still required before Final.
Abstract
This SEP standardizes cache_hint as a well-known key in CallToolResult._meta, with values
"no-cache" and "cache". The key allows a server to advise MCP clients whether to cache or
skip caching a particular tool result. This is the first well-known key defined for
CallToolResult._meta on the server-to-client side. It follows the hint-style, non-mandatory
pattern established by progressToken (on request _meta) and the well-known _meta key
convention established by SEP-414. No schema interface changes are required; the key is declared
in specification prose and is backward-compatible.
Motivation
MCP clients may apply caching to tool results -- for example, LLM-backed clients that support prompt caching may insert tool results into their cache to reduce token costs on repeated calls. For stateless, read-only, deterministic tools, this is beneficial in long-running sessions where the same result may be requested multiple times.
However, 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
(DISABLE_PROMPT_CACHING=1 in Claude Code, for example) is a global opt-out that disables
caching for the entire session, not a per-result signal.
This gap is documented in an open upstream request (anthropics/claude-code#34334) asking that
Claude Code honor a per-result _meta.cache_hint: "no-cache" signal.
The MCP spec states:
"The
_metaproperty/parameter is reserved by MCP to allow clients and servers to attach additional metadata to their interactions. Additionally, definitions in the schema may reserve particular names for purpose-specific metadata, as declared in those definitions."
cache_hint is exactly this: a purpose-specific name declared for CallToolResult._meta.
The need generalizes beyond a 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 across clients without requiring bilateral coordination between server and client authors.
Specification
Well-Known Key
The key cache_hint is a well-known key in CallToolResult._meta. Its value is a string. The
following values are defined:
| Value | Meaning |
|---|---|
"no-cache" |
The server advises the client not to cache this result. |
"cache" |
The server advises the client that this result is safe to cache. |
If cache_hint is absent, no caching preference is expressed. Clients MAY apply their default
caching policy.
Semantics
cache_hint is a hint. The receiver is not obligated to honor it. This matches the precedent
established by progressToken -- a hint-style key on request _meta where the spec states
"The receiver is not obligated to provide these notifications." cache_hint applies the same
non-mandatory semantics on the result side.
A server SHOULD set cache_hint: "no-cache" on results that are expensive to cache without
benefit -- for example, large read-only results returned in single-pass contexts where the cache
entry will never be read again.
A server MAY set cache_hint: "cache" to affirmatively signal that a result is safe to cache,
useful when the client's default policy would otherwise skip caching.
Schema
No TypeScript schema interface change is required. The key is declared in specification prose
under the CallToolResult definition. For reference, the existing definition is:
interface CallToolResult {
_meta?: { [key: string]: unknown };
content: ContentBlock[];
isError?: boolean;
structuredContent?: { [key: string]: unknown };
}
The cache_hint key is a well-known name within the open _meta map. Implementations that
do not recognize cache_hint ignore it, as the index signature requires.
Non-Normative Example
A server returning a large file tree result in a single-pass context:
{
"jsonrpc": "2.0",
"id": 5,
"result": {
"content": [{ "type": "text", "text": "..." }],
"isError": false,
"_meta": {
"cache_hint": "no-cache"
}
}
}
A client receiving this result SHOULD skip inserting it into its cache, if it has the capability to do so at the per-result level.
Rationale
Why cache_hint and not io.modelcontextprotocol/cache_hint?
The DNS-prefix convention for _meta keys (proposed in #1788) applies to keys reserved by the
MCP project. SEP-414 establishes a precedent for plain names as an exception when compatibility
or simplicity requires it (traceparent, tracestate, baggage). progressToken is the only
existing plain-name key in the spec and is already in _meta.
cache_hint is proposed as a plain name for two reasons:
- The #1788 draft proposes a
MetaObjectthat would place bare keys in a free-use zone, but #1788 is not yet adopted. Adopting the prefix now would lock in a naming decision that #1788 may revise once ratified. - A plain name is simpler to set, read, and document. The HTTP ecosystem uses
Cache-Control(plain) rather than a namespaced equivalent.
If #1788 is eventually adopted and the community prefers a prefixed name, the transition
(cache_hint -> io.modelcontextprotocol/cache_hint) is non-breaking: both names can coexist
during a deprecation window, as the schema allows arbitrary keys.
Why values "no-cache" and "cache" and not a boolean?
HTTP Cache-Control semantics are widely understood by protocol designers. A string enum is
extensible -- future values (e.g., "immutable") can be added without a breaking change. A
boolean (cache: true/false) cannot be extended.
Why on CallToolResult and not on the tool definition?
Tool-definition annotations (read_only_hint, destructive_hint, etc.) are static -- they
describe the tool's general behavior, not the characteristics of a specific result. A tool may
return cacheable results in some invocations and non-cacheable results in others (e.g., depending
on the size of the output or the session context). Placing cache_hint on the result enables
per-result control.
Alternatives Considered
A new cache_control field on CallToolResult: Adds schema surface and a required schema
version bump. The _meta mechanism is already present and designed for this use case.
Client-side heuristics only: Clients could infer cacheability from result size or tool annotations. This is less precise and requires bilateral convention rather than an explicit signal.
A new capability or negotiation step: Unnecessarily complex for a hint that has no effect if ignored.
Backward Compatibility
This SEP introduces no breaking changes.
Existing clients that do not recognize cache_hint continue to operate correctly. The open
index signature on _meta requires implementations to tolerate unknown keys.
Existing servers that do not set cache_hint are unaffected. Clients apply their existing
default caching policy when the key is absent.
Security Implications
cache_hint is a server-to-client hint with no enforcement requirement. A malicious server
setting cache_hint: "cache" on a mutable or sensitive result cannot force a client to cache
it; a malicious server setting cache_hint: "no-cache" cannot prevent a client from caching.
Clients that choose to honor cache_hint should apply the same validation they apply to any
_meta value: verify the key is a known string, ignore unrecognized values, and do not
propagate the key to the model or to downstream systems.
No authentication, authorization, or data-validation changes are required.
Reference Implementation
https://github.com/clouatre-labs/aptu-coder (Apache-2.0)
All four tool success paths (analyze_directory, analyze_file, analyze_symbol,
analyze_module) set _meta: { "cache_hint": "no-cache" } on every CallToolResult via a
shared helper:
fn no_cache_meta() -> Meta {
let mut m = serde_json::Map::new();
m.insert(
"cache_hint".to_string(),
serde_json::Value::String("no-cache".to_string()),
);
Meta(m)
}
An integration test (crates/aptu-coder/tests/integration_tests.rs,
test_call_tool_result_cache_hint_metadata) validates round-trip serialization:
_meta.cache_hint == "no-cache" in the JSON output.
The implementation uses rmcp 1.5.0. The CallToolResult::with_meta(Some(Meta(map))) API
serializes arbitrary key-value pairs into _meta without schema changes.
This satisfies the SEP "prototype implementation" requirement for Accepted status. Full SDK implementations (TypeScript, Python, Java, C#, Go, Kotlin) are required for Final status and will be tracked in the PR thread.