Introduction to Prompt Caching with MCP
Prompt caching is one of the most powerful cost optimization techniques available to developers working with large language models. When combined with the Model Context Protocol (MCP), it becomes a structured, reusable way to dramatically reduce token usage, latency, and API costs across complex AI applications. This guide walks through everything you need to know to implement prompt caching effectively within an MCP-based architecture.
What Is Prompt Caching?
Prompt caching is the practice of storing the prefix of a conversation or system prompt so that subsequent requests can reuse the already-processed tokens instead of recomputing them from scratch. Most modern LLM providers — including Anthropic, OpenAI, and Google — support some form of prompt caching at the API level. The key insight is that many requests share a common prefix: system instructions, tool definitions, retrieved context, and few-shot examples often remain identical across hundreds or thousands of calls.
Without caching, every API call reprocesses the entire prompt. With caching, the shared prefix is processed once and then referenced on subsequent calls, typically at a fraction of the cost — often 10% of standard input token pricing for cache hits, while cache writes cost slightly more than a standard input token.
Why Prompt Caching Matters in MCP
The Model Context Protocol standardizes how context is delivered to language models. MCP servers expose resources, tools, and prompts that clients consume. Because MCP encourages rich, structured context — large tool schemas, lengthy resource documents, and detailed prompt templates — the token footprint of a single MCP-augmented request can easily reach tens of thousands of tokens before the user's actual query is even appended.
This creates a perfect opportunity for caching. The MCP context is usually stable within a session and often stable across sessions. By caching the MCP-provided context prefix, you can:
- Reduce costs by up to 90% on the cached portion of each request.
- Lower latency because cached prefixes skip recomputation.
- Scale conversations with long context windows without proportional cost growth.
- Improve consistency by keeping tool definitions and instructions stable across calls.
How Prompt Caching Works
At a mechanical level, prompt caching works by designating a prefix of the prompt as cacheable. When the provider receives a request, it checks whether the prefix matches a previously cached version. If it does, the cached computation is reused. If not, the prefix is processed and stored for future use. Caches are typically time-limited — for example, Anthropic's cache has a 5-minute lifetime that refreshes on each hit, while some providers offer extended 1-hour caches.
The critical rule is that the cache key is based on exact token matching. Any change to the cached prefix — even a single character — invalidates the cache. This means the order of messages, the exact tool definitions, and the precise system prompt must remain identical between calls to benefit from caching.
Setting Up an MCP Server with Cacheable Context
Let's build a practical example. We'll create an MCP server that exposes a large, stable set of tools and resources, then configure the client to cache that context on every request.
Installing Dependencies
First, install the MCP SDK and an LLM client. We'll use the TypeScript MCP SDK along with the Anthropic SDK, which has first-class prompt caching support.
npm install @modelcontextprotocol/sdk @anthropic-ai/sdk
npm install -D typescript @types/node tsx
Creating the MCP Server
Here is a minimal MCP server that exposes a knowledge base resource and a search tool. The resource content is large and stable, making it an ideal cache candidate.
import { Server } from "@modelcontextprotocol/sdk/server";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio";
import { ListResourcesHandler, ReadResourceHandler } from "@modelcontextprotocol/sdk/types";
const KNOWLEDGE_BASE = `
You are an assistant for a large enterprise documentation system.
The following documentation covers API endpoints, authentication,
rate limits, error codes, and integration patterns for version 3.2
of the platform. This content is several thousand tokens long and
remains stable across all user sessions within a deployment cycle.
... (truncated for brevity, imagine 8000 tokens here) ...
`;
const server = new Server(
{ name: "docs-server", version: "1.0.0" },
{ capabilities: { resources: {}, tools: {} } }
);
server.setRequestHandler(ListResourcesHandler, async () => ({
resources: [
{
uri: "docs://platform/v3.2",
name: "Platform Documentation v3.2",
description: "Full enterprise platform documentation",
mimeType: "text/plain",
},
],
}));
server.setRequestHandler(ReadResourceHandler, async (request) => {
if (request.params.uri === "docs://platform/v3.2") {
return {
contents: [
{
uri: "docs://platform/v3.2",
mimeType: "text/plain",
text: KNOWLEDGE_BASE,
},
],
};
}
throw new Error("Resource not found");
});
const transport = new StdioServerTransport();
await server.connect(transport);
Consuming MCP Context with Prompt Caching
Now let's build the client side. The client connects to the MCP server, fetches the resources and tool definitions, and assembles a prompt where the stable MCP context is marked as cacheable using Anthropic's cache_control parameter.
Connecting to the MCP Server
import { Client } from "@modelcontextprotocol/sdk/client";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio";
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic();
const transport = new StdioClientTransport({
command: "tsx",
args: ["server.ts"],
});
const mcpClient = new Client(
{ name: "caching-client", version: "1.0.0" },
{ capabilities: {} }
);
await mcpClient.connect(transport);
// Fetch the stable resource content
const resourceResponse = await mcpClient.readResource({
uri: "docs://platform/v3.2",
});
const docsContent = resourceResponse.contents[0].text;
// Fetch tool definitions
const toolsResponse = await mcpClient.listTools();
const mcpTools = toolsResponse.tools;
Building the Cached Prompt
The key to prompt caching is structuring the message array so that the stable prefix comes first and is tagged with cache_control. The user's dynamic query comes after the cached boundary.
const SYSTEM_PROMPT = `You are a helpful enterprise documentation assistant.
Always cite the documentation section you reference in your answers.
Be concise and accurate.`;
// Convert MCP tools to Anthropic tool format
const anthropicTools = mcpTools.map((tool) => ({
name: tool.name,
description: tool.description,
input_schema: tool.inputSchema,
}));
async function askQuestion(userQuery: string) {
const response = await anthropic.messages.create({
model: "claude-sonnet-4-5-20250929",
max_tokens: 1024,
system: [
{
type: "text",
text: SYSTEM_PROMPT,
cache_control: { type: "ephemeral" },
},
{
type: "text",
text: docsContent,
cache_control: { type: "ephemeral" },
},
],
tools: anthropicTools,
messages: [
{
role: "user",
content: userQuery,
},
],
});
console.log("Response:", response.content);
console.log("Cache creation tokens:", response.usage.cache_creation_input_tokens);
console.log("Cache read tokens:", response.usage.cache_read_input_tokens);
console.log("Input tokens:", response.usage.input_tokens);
console.log("Output tokens:", response.usage.output_tokens);
return response;
}
Running Multiple Queries
The caching benefit becomes visible across multiple calls. The first call creates the cache, and subsequent calls read from it.
async function runSession() {
// First call: cache is created
console.log("=== First question ===");
await askQuestion("How do I authenticate API requests?");
// Second call: cache is read, only the new user query is processed fresh
console.log("\n=== Second question ===");
await askQuestion("What are the rate limits for the v3 API?");
// Third call: same cache hit
console.log("\n=== Third question ===");
await askQuestion("Explain the error code 429 retry strategy.");
await mcpClient.close();
}
runSession().catch(console.error);
When you run this, the first call will show a high cache_creation_input_tokens count and a low cache_read_input_tokens. The second and third calls will flip: cache_read_input_tokens will dominate, and the cost will be roughly 10% of what it would have been without caching.
Advanced: Multi-Turn Conversations with Caching
In a multi-turn conversation, you want to cache the growing conversation history along with the MCP context. The trick is to place cache_control on the last message in the cached prefix, and to move that breakpoint forward as the conversation grows.
const conversationHistory = [];
async function chat(userMessage: string) {
conversationHistory.push({ role: "user", content: userMessage });
// Build messages with cache breakpoint at the end of history
const messages = conversationHistory.map((msg, index) => {
const isLast = index === conversationHistory.length - 1;
return {
role: msg.role,
content: [
{
type: "text",
text: msg.content,
...(isLast ? { cache_control: { type: "ephemeral" } } : {}),
},
],
};
});
const response = await anthropic.messages.create({
model: "claude-sonnet-4-5-20250929",
max_tokens: 1024,
system: [
{
type: "text",
text: SYSTEM_PROMPT,
cache_control: { type: "ephemeral" },
},
{
type: "text",
text: docsContent,
cache_control: { type: "ephemeral" },
},
],
tools: anthropicTools,
messages,
});
const assistantText = response.content
.filter((block) => block.type === "text")
.map((block) => block.text)
.join("\n");
conversationHistory.push({ role: "assistant", content: assistantText });
console.log(`Cache reads: ${response.usage.cache_read_input_tokens} tokens`);
console.log(`Cache writes: ${response.usage.cache_creation_input_tokens} tokens`);
return assistantText;
}
This pattern ensures that each turn reuses the cached prefix from the previous turn, adding only the new user message and the previous assistant response to the cache on the next call.
Best Practices for Prompt Caching with MCP
Structure Your Context for Stability
Place all stable content at the beginning of the prompt: system instructions, MCP resource content, and tool definitions. Put dynamic content — user queries, timestamps, session-specific data — at the end. Any dynamic content in the cached prefix will break the cache on every call.
Use Multiple Cache Breakpoints Strategically
Most providers support up to four cache breakpoints. Use them to segment your prompt into layers of stability. For example: cache the system prompt separately from the MCP documentation, which is cached separately from the conversation history. This way, if the conversation history changes but the documentation does not, you still get a partial cache hit on the documentation layer.
Monitor Cache Hit Rates
Always log cache_creation_input_tokens and cache_read_input_tokens from the API response. A healthy caching setup should show cache reads far exceeding cache writes after the first call in a session. If you see cache writes on every call, something in your cached prefix is changing between requests.
Avoid Common Cache-Breaking Mistakes
- Do not include timestamps, random IDs, or session tokens in the cached prefix.
- Do not reorder tool definitions or resources between calls.
- Do not serialize JSON with non-deterministic key ordering — use stable serialization.
- Do not mix cached and uncached content within the same text block.
Consider Cache TTL When Designing Sessions
Anthropic's ephemeral cache lasts 5 minutes and refreshes on each hit. If your application has idle periods longer than 5 minutes between calls, the cache expires and the next call pays the full cache write cost again. For long-idle sessions, consider either sending a lightweight keep-alive request or accepting the re-cache cost. Some providers offer a 1-hour cache tier at a higher write cost but lower amortized cost for sparse usage patterns.
Cache MCP Tool Definitions Separately
Tool schemas from MCP servers can be large. If your tool set is stable but your resource content changes occasionally, cache the tools in their own block with its own cache breakpoint. This way, updating a resource does not invalidate the tool cache.
Measuring Cost Savings
To quantify the impact, track the token usage with and without caching. Here is a simple utility that computes the cost difference.
const PRICING = {
input: 3.0, // per 1M tokens
output: 15.0, // per 1M tokens
cacheWrite: 3.75, // per 1M tokens
cacheRead: 0.3, // per 1M tokens
};
function calculateCost(usage) {
const inputCost = (usage.input_tokens / 1_000_000) * PRICING.input;
const outputCost = (usage.output_tokens / 1_000_000) * PRICING.output;
const cacheWriteCost = (usage.cache_creation_input_tokens / 1_000_000) * PRICING.cacheWrite;
const cacheReadCost = (usage.cache_read_input_tokens / 1_000_000) * PRICING.cacheRead;
const totalWithCache = inputCost + outputCost + cacheWriteCost + cacheReadCost;
// Hypothetical cost without caching: all cached tokens billed at input rate
const totalWithoutCache =
((usage.input_tokens + usage.cache_creation_input_tokens + usage.cache_read_input_tokens) / 1_000_000) * PRICING.input
+ outputCost;
const savings = totalWithoutCache - totalWithCache;
const savingsPercent = (savings / totalWithoutCache) * 100;
return {
totalWithCache: totalWithCache.toFixed(6),
totalWithoutCache: totalWithoutCache.toFixed(6),
savings: savings.toFixed(6),
savingsPercent: savingsPercent.toFixed(1),
};
}
// Example usage with a response
const sampleUsage = {
input_tokens: 50,
output_tokens: 200,
cache_creation_input_tokens: 8000,
cache_read_input_tokens: 0,
};
console.log(calculateCost(sampleUsage));
// First call: small savings due to cache write premium
const subsequentUsage = {
input_tokens: 50,
output_tokens: 200,
cache_creation_input_tokens: 0,
cache_read_input_tokens: 8000,
};
console.log(calculateCost(subsequentUsage));
// Subsequent calls: large savings, ~90% reduction on cached portion
Conclusion
Prompt caching is a high-impact, low-effort optimization that pairs naturally with the Model Context Protocol. Because MCP encourages rich, structured, and stable context — tool schemas, resource documents, and prompt templates — it creates the exact conditions where caching shines. By structuring your prompts with stable prefixes, placing cache breakpoints strategically, monitoring hit rates, and avoiding common cache-breaking mistakes, you can reduce API costs by up to 90% on the cached portion of your requests while also improving response latency. As you build more complex MCP-powered applications, make prompt caching a default part of your architecture rather than an afterthought — the savings compound quickly across sessions, users, and scale.