# Optimizing Prompt Caching in Anthropic Bedrock for Complex Schemas

*By the OnlineCloud Engineering Team — LLM Infrastructure Engineers*
—
## 1. Introduction: The Quiet Tax on Large Tool Surfaces
Every agentic system hits the same inflection point: the moment the tool belt outgrows the model’s default economics. Early demos carry two or three tools and prefixes stay small. Then production happens. Your MCP server exposes forty tools, each carrying a name, a prose description, and a JSON Schema — and the tools wrapping your Oracle or CRM objects run to hundreds of tokens alone. Add a system prompt with governance rules and output contracts, and every request now opens with a 30,000-to-80,000-token preamble.
Here is the part that surprises platform teams: **that preamble is re-billed and re-processed on every request.** Each invocation of the Messages API is a fresh prefill; the provider charges full input price for every token it processes, system prompt and schemas included. At $3 per million input tokens, a 50,000-token prefix is $0.15 of dead weight before the model emits a single useful word. At millions of requests a month, the tool surface becomes a six-figure line item — and because time to first token (TTFT) grows with prompt-processing cost, the biggest tool set is also the slowest one.
The good news: this cost is almost entirely avoidable. Anthropic’s prompt caching on Bedrock is built for exactly this pattern — a large, stable, repeated prefix — and a well-structured MCP deployment is the ideal workload. This post covers how the cache works, how to structure MCP tool schemas so you hit it, what the 90% input discount looks like in production arithmetic, how to benchmark the TTFT win, and how to govern prompt changes so the cache stays warm on purpose rather than by accident.
## 2. How Anthropic Prompt Caching Actually Works
Prompt caching is a **prefix cache with exact-match semantics at the token level**. You annotate positions in the request with cache breakpoints — a `cachePoint` marker in the Bedrock Converse API (`cache_control` in the raw Anthropic API) — and the platform caches everything preceding each breakpoint. The next request that opens with an identical token prefix is served from cache instead of re-running the model’s prefill stage.
Three mechanics drive every engineering decision:
**Prefix, not semantic, matching.** The cache key is the token stream, not the meaning of the request. One changed byte upstream — a reworded system-prompt sentence, a tool inserted mid-list, a JSON object whose keys serialized in a different order — produces a different stream and a complete miss. Matching is strictly positional: once the stream diverges, everything after it is uncached.
**Minimum size and TTL tiers.** A breakpoint must sit at least 1,024 tokens into the prompt for the 5-minute TTL tier (the 1-hour tier requires a longer prefix). Entries are sliding-window: each read refreshes the TTL, so a hot prefix stays resident indefinitely while a cold one expires minutes after its last request. A prefix that sees traffic only once an hour will never stay warm on the 5-minute tier — that is what the 1-hour tier is for.
**Pricing asymmetry.** Cache reads cost a fraction of the base input price; writes cost a premium:
| Tier | Cache write (per input token) | Cache read (per input token) |
|—|—|—|
| 5-minute TTL | 1.25× base input price | 0.1× base input price |
| 1-hour TTL | 2.0× base input price | 0.1× base input price |
Reads are where the 90% headline comes from; writes are the price of admission, paid once per TTL window per distinct prefix. The strategy is therefore to maximize the read-to-write ratio by keeping the prefix byte-identical across as many requests as possible.
A Bedrock Converse request with breakpoints after the system prompt and after the tool list:
“`python
response = bedrock.converse(
modelId=”anthropic.claude-sonnet-…:0″,
system=[
{“text”: SYSTEM_PROMPT}, # static, versioned, cached
{“cachePoint”: {“type”: “default”}},
],
toolConfig={
“tools”: [
*serialize_tools(TOOL_REGISTRY), # byte-stable, ordered
{“cachePoint”: {“type”: “default”}},
]
},
messages=[{“role”: “user”, “content”: [{“text”: user_query}]}],
)
“`
Bedrock supports multiple breakpoints per request on compatible model versions, so you can cache the fully static region and keep volatile content — per-tenant instructions, retrieved context — behind your own breakpoint. The rule of thumb: **everything static goes before a breakpoint; everything that varies per request goes after one.**
## 3. Structuring MCP Tool Definitions and Schemas for Cache Hits
Getting cache hits is not a configuration task — it is a serialization discipline. In an MCP-native architecture, the MCP server is the source of truth for tool definitions, and an adapter layer converts them into the tool payload of every model request. That conversion must be deterministic, or you will silently invalidate the cache on every request.
**Canonical, byte-identical serialization.** The cache matches on tokens, so two semantically identical requests that serialize differently are two different prefixes. Pick one canonical serialization — stable tool ordering plus fixed JSON key order — and enforce it in every process that builds requests. Beware runtimes that hash-map keys into arbitrary order, and never build tool dicts ad hoc in request handlers. Serialize once from the registry and reuse the bytes.
**Freeze ordering; grow append-only.** Token streams are positional: inserting a tool mid-list invalidates every cached segment after it, fleet-wide. New tools go at the end of the registry; reordering is a cache-invalidation event, not a cosmetic change. Removing a tool is a breaking deployment — version-bump, warm the new prefix, then shift traffic.
**Keep dynamic content out of the cached region.** Tool descriptions and system text must not be templated with per-request, per-tenant, or time-varying values — no “as of {date}”, no environment names, no user clauses inside the cached segment. The most common cache-killer we audit is a system prompt interpolating a timestamp at the top, quietly changing the prefix on every request.
**Curate the giant schemas.** Enterprise tools wrapping real database objects carry most of your prefix tokens — which is precisely why caching matters for them. Trim redundant `description` prose, drop field documentation duplicated between description and `inputSchema`, and avoid scattering the same large schema across near-duplicate tools. Every cached token is billed at write premium and saved at read discount; verbosity has a price either way.
**Right-size the tool surface per agent.** If your MCP server exposes 60 tools but an agent class uses eight, every request pays for the other 52. Partition tool sets by domain — order-status agents get the read-only CRM and order tools, reconciliation agents get finance tools — so each agent class has a smaller, cheaper, more stable prefix, with less drift surface and cheaper re-warm after changes.
**Lint the wire format in CI.** Add a golden-file test that builds the exact request payload and diffs it against the committed artifact. Any change fails the build and forces an explicit decision — which is what you want, because in a cached system a prompt edit is a production change with cost and latency consequences.
## 4. Cost Analysis: Measuring the 90% Discount in Production
The discount is real, but it should be evaluated end-to-end on your own workload. Take a representative profile: a tool-heavy agent with a 12,000-token system prompt and 33,000 tokens of MCP schemas — 45,000 input tokens per request — serving 100,000 requests/day against a Claude-class model at $3/MTok input, $15/MTok output, ~800 output tokens per request.
**Without caching**, every request pays full price for the prefix: 100,000 × 45,000 × $3/MTok = **$13,500/day** in input cost, on top of ~$1,200/day of output tokens.
**With a stable cached prefix**, each 5-minute window pays one 1.25× write and the rest read at 0.1×. At roughly uniform traffic, that is ~288 writes and ~99,700 reads per day:
– Writes: 288 × 45,000 × $3 × 1.25 ≈ **$49/day**
– Reads: 99,700 × 45,000 × $3 × 0.10 ≈ **$1,345/day**
– Total input cost: **~$1,394/day — a 90% reduction**; output cost unchanged.
That ratio holds only while the prefix stays byte-identical and traffic keeps the TTL alive. Fail either and every request pays write premiums — which is why instrumentation is mandatory.
Bedrock exposes the counters you need — `cacheReadInputTokens` and `cacheWriteInputTokens` in the invocation metrics — which you can stream to CloudWatch alongside raw token counts. Track **hit ratio = reads / (reads + writes)** per prompt version, model, and region. Healthy systems sit above 95%; anything below is almost always serialization drift, tool reordering, region-split traffic, or TTL expiry during low-traffic hours. If inter-request gaps routinely exceed five minutes, the 1-hour tier (2× write, same 0.1× read) is worth the premium — run the arithmetic with your actual cadence before choosing.
## 5. Benchmarking TTFT with Cached Context
Cost is half the story. TTFT is dominated by prefill — the model processing the entire prefix before emitting anything — so a 45,000-token preamble is not just expensive, it is slow. Caching removes most of that work from the critical path, and on long prefixes the latency win matches the cost win.
Methodology matters. Measure TTFT from request submission to the first streamed token, and confirm cache hits per request via the read-token counters — otherwise you are comparing a hit population against a miss population and calling it a model improvement. Run an A/B against production-shaped traffic: the same system prompt and tool registry, many different conversations, so you measure real prefix reuse rather than identical repeated queries.
Representative numbers for a 45,000-token prefix on Bedrock (p50/p95 across a production mix):
| Metric | Cache miss (cold prefix) | Cache hit (warm prefix) |
|—|—|—|
| TTFT p50 | 4.9 s | 0.8 s |
| TTFT p95 | 8.2 s | 1.6 s |
| Input cost per request | $0.135 | ~$0.014 |
An 80–85% TTFT reduction is typical at this prefix size, and the absolute gap grows with prefix depth — the larger your schemas, the bigger the win. Two consequences follow. First, **interactive quality changes**: a tool-calling agent that responds in under a second instead of after a five-second pause feels fundamentally different, and gateway timeout budgets stop being a design constraint.
Second, **savings compound across multi-step loops.** An agent resolving a ticket may run a ten-step tool chain, and nearly every step re-sends the full prefix. Cache each step and the 90% input discount and ~85% prefill reduction apply ten times per resolved task — the difference between a loop that costs dollars per completion and one that costs cents. That is why we treat prompt caching as a first-class architectural constraint when designing MCP tool surfaces, not a billing optimization applied afterward.
## 6. Governance: Automating Prompt Versioning for Cache Stability
A cached deployment turns every prompt edit into an infrastructure event. Change one system-prompt sentence and you invalidate the cache for the entire fleet: the next request from every agent pays a write premium and eats a cold prefill. Edit prompts by hand in request handlers and you will do this silently and constantly. The fix is to treat the system prompt plus tool registry as a versioned, deployable artifact with the same discipline as application code.
**The artifact.** The canonical prefix — system text, ordered tool list, serialized schemas — lives in version control (or an S3-backed registry) as one immutable bundle. A build step produces the byte-serialized prefix and its content hash. Runtimes load a pinned version; nothing constructs prompts at request time.
**The CI gates.** Changes to the bundle run through checks encoding the caching rules: a golden-file test asserting byte-identical serialization; an ordering lint enforcing append-only tool growth; a scan rejecting time-varying or per-request interpolation inside the cached region; a token check confirming the prefix still exceeds the 1,024-token minimum. A prompt that fails these gates cannot reach production.
**Versioned rollout with cache warm-up.** Because a release is a cache-invalidation event, deploy it like a risky change: release the new prefix version, run synthetic warm-up traffic to populate the cache before users arrive, then ramp 5% → 100%. This prevents the classic post-release symptom — a normal traffic day billing like a cold start because every request is a cache write. Tag request observability with the prompt version so hit rate and cost are attributable per release.
**Rollback is nearly free.** Entries persist for the TTL after their last read, so reverting to the previous version often restores a still-warm cache within minutes — the platform rolls back with your artifact. That asymmetry (cheap rollback, expensive forward drift) argues for small, reviewed prompt changes over large rewrites.
**The SLO.** Track cache hit ratio per prompt version and alert on drops. A sudden hit-rate cliff is the earliest signal of serialization drift, an accidental reorder, or a rogue templated value — visible in the token counters hours before it shows up in the cost report.
None of this is exotic. It is the release engineering you already run for services, applied to the one artifact most teams still edit by hand — and it is the difference between prompt caching as a lucky discount and prompt caching as a reliable production lever.
## Conclusion: Make Your Schema the Asset
Large tool surfaces and long system prompts are not a mistake — they are the shape of real enterprise agentic systems talking to real schemas. The mistake is paying full price for them on every request. Bedrock prompt caching turns the tax on complex schemas into a 90%-discounted, dramatically faster repeating prefix — but only if you engineer for it: canonical serialization, frozen tool ordering, volatile content quarantined behind breakpoints, and prompt changes governed like deployments.
At OnlineCloud we build MCP-native agentic systems for a living, designing the tool surface, serialization layer, and caching strategy as one system — measured by cost per resolved task and p95 TTFT rather than demo quality. If your agent fleet is paying full freight for a tool belt it re-sends on every call, talk to us. We are a US-based engineering studio of senior engineers — no offshore handoffs — shipping evaluated, observable systems to production in 6–10 weeks.
**Contact the OnlineCloud team today** — [onlinecloud.dev](https://www.onlinecloud.dev/) · [contact@onlinecloud.dev](mailto:contact@onlinecloud.dev) · Free initial consultation, response within 24 hours.