Guide · Anthropic SDK Advanced Features
MCP Server Prompt Caching — cache breakpoints, warmup, cache-read tracking
Three prompt-caching behaviours catch MCP server authors off-guard: cache breakpoints must be placed on the longest static prefix of your prompt — not on the dynamic user-specific suffix — placing cache_control on a message that changes every request defeats caching entirely and still incurs the cache-write cost; the cache TTL for ephemeral cache is 5 minutes and resets on every cache hit — if your MCP tool is called less frequently than once per 5 minutes, every call pays the full input cost; and usage.cache_creation_input_tokens is only non-zero on the first call that populates the cache — subsequent calls show cache_read_input_tokens instead, and both fields are zero when caching is disabled, making it easy to silently confirm that caching is wired correctly.
TL;DR
Add cache_control: { type: 'ephemeral' } to the last element of your static system prompt array, and to any large static documents passed as user content. Never add it to dynamic user-specific messages. Read response.usage.cache_read_input_tokens to measure hit rate — if it stays at zero after the first call, your breakpoint is misplaced. Send a warmup request at server startup to pre-populate the cache so the first real tool call hits rather than creates the cache entry.
Where to place cache_control breakpoints
Anthropic processes messages left-to-right. The cache key is computed from the longest prefix of the request that ends at a cache_control breakpoint. Everything after the breakpoint is excluded from the cache key — so the user's dynamic query can vary freely without busting the cache.
import Anthropic from '@anthropic-ai/sdk';
import { z } from 'zod';
const anthropic = new Anthropic();
// Large, stable system context — ideal candidate for caching.
// Loaded once at module init, never changes between tool calls.
const ANALYSIS_SYSTEM_PROMPT = `You are a code analysis assistant.
You help developers understand code quality, identify bugs, and suggest improvements.
Guidelines:
- Be concise and actionable
- Cite the exact line number when referencing code
- Suggest specific refactors, not generic advice
- Flag security issues with HIGH/MEDIUM/LOW severity labels
Output format: markdown with headers for each finding.`;
// A large reference document also benefits from caching —
// e.g. your API specification, a style guide, or a domain glossary.
const STYLE_GUIDE = `# Code Style Reference (1,500 tokens of stable content)
...`; // truncated for brevity — in practice, fill this with your real content
server.tool(
'analyze_code',
{
code: z.string().min(1).max(50_000),
language: z.enum(['typescript', 'python', 'go', 'rust']),
},
async ({ code, language }) => {
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-6',
max_tokens: 1024,
// System: mark the end of the static section as the cache breakpoint.
// The trailing cache_control covers everything above it.
system: [
{ type: 'text', text: ANALYSIS_SYSTEM_PROMPT },
{ type: 'text', text: STYLE_GUIDE, cache_control: { type: 'ephemeral' } },
// ↑ breakpoint after all stable content
],
messages: [
{
role: 'user',
// Do NOT add cache_control here — this message is dynamic (different code each call)
content: `Analyse this ${language} code:\n\n\`\`\`${language}\n${code}\n\`\`\``,
},
],
});
// Inspect cache performance — log periodically, not on every call
const { input_tokens, cache_creation_input_tokens, cache_read_input_tokens } =
response.usage;
console.log('cache', { input_tokens, cache_creation_input_tokens, cache_read_input_tokens });
// cache_creation_input_tokens > 0 → this call wrote a new cache entry
// cache_read_input_tokens > 0 → this call was a cache hit (90% cheaper)
// both == 0 → caching not active (wrong model, breakpoint missing, or cache expired)
const text = response.content.find(b => b.type === 'text')?.text ?? '';
return { content: [{ type: 'text', text }] };
}
);
The cache_control breakpoint must appear on the last static element. If you add it to the system prompt but then pass 2,000 tokens of per-call context in the user message, those user tokens are still billed at full rate — caching only covers the tokens before the breakpoint. For maximum savings, move all stable context (instructions, reference data, tool schemas) before the breakpoint and keep the dynamic query as short as possible.
Cache warmup at server startup
The cache entry is created on the first request that reaches the Anthropic API with a given breakpoint. If the first user-visible tool call creates the cache entry, that call pays the cache_creation_input_tokens cost — typically 25% more expensive than a normal input token write. For MCP servers with expensive system prompts (4,000+ tokens), sending a dummy warmup request during startup shifts this cost out of the hot path.
import Anthropic from '@anthropic-ai/sdk';
const anthropic = new Anthropic();
// Call this once during server initialisation, before the MCP server starts
// accepting connections. The dummy query is minimal — all we want is for
// Anthropic to record the cache entry for our system prompt prefix.
async function warmupPromptCache(): Promise<void> {
try {
const warmup = await anthropic.messages.create({
model: 'claude-sonnet-4-6',
max_tokens: 1, // we don't need an answer — just populate the cache
system: [
{ type: 'text', text: ANALYSIS_SYSTEM_PROMPT },
{ type: 'text', text: STYLE_GUIDE, cache_control: { type: 'ephemeral' } },
],
messages: [{ role: 'user', content: 'warmup' }],
});
const { cache_creation_input_tokens } = warmup.usage;
if (cache_creation_input_tokens > 0) {
console.log(`Prompt cache warmed: ${cache_creation_input_tokens} tokens written`);
} else {
// Cache already existed from a prior startup within the 5-min TTL window
console.log('Prompt cache already warm');
}
} catch (err) {
// Warmup failure is non-fatal — the first real call will populate the cache
console.warn('Cache warmup failed (non-fatal):', err);
}
}
// In your MCP server setup:
await warmupPromptCache();
server.start(); // now accepting connections
The warmup strategy is most valuable when your MCP server is restarting frequently (blue-green deployments, container restarts) or when the system prompt is very large. If your server runs continuously for hours, the cache stays warm from real traffic and explicit warmup is unnecessary.
Caching large reference documents in user content
Prompt caching is not limited to system prompts. You can place breakpoints in user-turn content too — useful when a tool receives a large reference document (codebase, spec, PDF text) that stays constant across multiple calls in a session but varies by session.
server.tool(
'answer_from_document',
{
document_text: z.string().min(100).max(200_000), // caller provides the doc
question: z.string().min(1).max(1_000),
doc_id: z.string().optional(), // opaque identifier for logging
},
async ({ document_text, question, doc_id }) => {
// Document goes first as a cacheable block; question is the dynamic suffix.
// When the same document_text is passed in multiple calls (same agent session),
// only the first call pays cache-write cost — subsequent calls hit the cache.
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-6',
max_tokens: 512,
system: 'Answer questions based solely on the provided document.',
messages: [
{
role: 'user',
content: [
{
type: 'text',
text: `Document (${doc_id ?? 'unnamed'}):\n\n${document_text}`,
cache_control: { type: 'ephemeral' },
// ↑ marks end of cacheable region — question below is excluded from cache key
},
{
type: 'text',
text: `\n\nQuestion: ${question}`,
// no cache_control — this changes every call
},
],
},
],
});
const { cache_read_input_tokens, cache_creation_input_tokens } = response.usage;
const hitRate = cache_read_input_tokens > 0 ? 'HIT' : cache_creation_input_tokens > 0 ? 'WRITE' : 'MISS';
console.log(`doc_id=${doc_id} cache=${hitRate}`);
const text = response.content.find(b => b.type === 'text')?.text ?? '';
return { content: [{ type: 'text', text }] };
}
);
This pattern is particularly effective for MCP tools that process a fixed codebase, legal contract, or specification across many questions in the same agent session. The document is uploaded once to the Anthropic cache on the first call; subsequent questions about the same document pay only for the question tokens, not the full document.
Minimum cacheable size and supported models
Anthropic enforces a minimum of 1,024 tokens before a cache breakpoint for the cache to activate. Smaller prompts below this threshold are processed normally — the API silently ignores the cache_control marker and returns zero for both cache_creation_input_tokens and cache_read_input_tokens. This is the most common reason developers see zero cache metrics even though their breakpoints look correct.
| Check | Expected result when caching works |
|---|---|
| First call to a new cache key | cache_creation_input_tokens > 0, cache_read_input_tokens == 0 |
| Subsequent calls within 5 min TTL | cache_creation_input_tokens == 0, cache_read_input_tokens > 0 |
| Call after TTL expires (no activity for 5+ min) | cache_creation_input_tokens > 0 again (re-created), cache_read_input_tokens == 0 |
| Prefix smaller than 1,024 tokens | Both == 0 (caching not activated, no error thrown) |
cache_control on dynamic message that changes every call | Both == 0 (different key every time; every call writes, nothing reads) |
Supported models include Claude 3.5 Sonnet, Claude 3.5 Haiku, Claude 3 Opus, and Claude Sonnet 4.6 / Haiku 4.5. Older models (Claude 2, Instant) do not support caching — passing cache_control to them returns an API error. The cache TTL is 5 minutes for ephemeral cache; each cache hit resets the timer, so an active MCP server with frequent calls keeps the cache alive indefinitely.