Request a callbackBook a call
← All posts

MCP in Production: Gateways, Tool Budgets, and What Changed in the 2026-07-28 Spec

TL;DR
  • Updated for MCP spec 2026-07-28. The initialize handshake and the Mcp-Session-Id header are gone, routing moved into Mcp-Method and Mcp-Name headers, and list results are cacheable via ttlMs and cacheScope.
  • Statelessness is the architectural gift: MCP servers are now ordinary horizontally-scalable HTTP services, and a gateway can route and authorise on headers without parsing the JSON-RPC body.
  • Sixty tool schemas cost about 9,000 tokens on every turn. Anthropic reports that on-demand tool discovery cut context from 77,000 to 8,700 tokens while raising tool-selection accuracy from 79.5% to 88.1% on Opus 4.5.
Tool layer
MCP gateway architecture under the 2026-07-28 stateless specStreamable HTTP · Mcp-Method: tools/call · Mcp-Name: create_ticketallowlist + pinned hashappend, every callcatalogue lookupmiss: tools/listtools/call, scoped tokenmiss: tools/list
Tool registrypinned schema hash per tool, allowlist per agent, always_include set
Agent runtimecontext assembler resolves a tool subset ONCE per run
MCP gatewayauthn > agent identity > allowlist > catalogue cache > schema validate > credential inject > rate limit > forward
Catalogue cachekeyed on cacheScope, TTL from ttlMs — new in 2026-07-28
Audit logagent id, user identity, tool, arg hash, result size, latency
MCP server: Jirathird-party, schema pinned, credential injected at the gateway
MCP server: internalfirst-party, inside the trust boundary
MCP server: Searchthird-party, results treated as untrusted data
Every stage before forward reads HTTP headers rather than the JSON-RPC body, which is what the 2026-07-28 header-routing change makes possible and what makes a policy gateway cheap enough to sit in the hot path. Not drawn, because it is not permitted: a direct connection from the agent to a third-party server. Network policy should make that path impossible, not merely discouraged, because a direct path is an audit gap by construction.

What changed in the 2026-07-28 MCP spec, and does it break your deployment?

Yes, if you stored sessions. The 28 July 2026 revision retired the initialize and initialized exchange along with the Mcp-Session-Id header. Every request now travels on its own, carrying its protocol version, client identity and capabilities in the _meta field, and a new optional server/discover RPC exists for clients that want capabilities up front. The maintainers state the consequence plainly: any request can land on any server instance behind a plain round-robin load balancer without shared storage.

Four other changes matter architecturally. Streamable HTTP requests must now carry Mcp-Method and Mcp-Name headers, so a gateway, rate limiter or WAF can route and meter without parsing a JSON body. Responses from tools/list, prompts/list, resources/list and resources/read now carry ttlMs and cacheScope, which makes the tool catalogue a cache key rather than a per-run fetch. Multi Round-Trip Requests replace server-initiated elicitation, sampling and roots: the server returns a result type of input_required with the questions it needs answered, and the client retries the original call with the answers attached. And authorization hardened: authorization servers should return the iss parameter per RFC 9207, and clients must validate it before redeeming a code.

The deprecation list is long enough to plan around. Dynamic Client Registration is formally deprecated in favour of Client ID Metadata Documents, though it still works. Roots, Sampling and Logging are deprecated and will keep working for at least twelve months. The legacy HTTP+SSE transport is deprecated with a year-long offramp. Tasks moved out of the experimental core into the io.modelcontextprotocol/tasks extension with a poll-based tasks/get and a new tasks/update. There is now a formal deprecation policy with a twelve-month minimum window, the most useful governance change in the release.

The migration itself is small if your servers were already close to stateless, painful if they were not. Remove session storage. Emit the two routing headers from the client. Add a cache layer keyed on cacheScope. Replace server-initiated request handlers with MRTR retry handling. Validate iss. Plan the move off DCR. All four Tier 1 SDKs, TypeScript, Python, Go and C#, shipped support the same day, with the Rust SDK in beta.

ChangeOld behaviourNew behaviourBreaking?What you changeArchitectural consequence
Stateless coreinitialize / initialized handshake; Mcp-Session-Id header pins a client to an instanceEvery request self-describing; protocol version, client identity and capabilities in _metaYesDelete session storage and sticky routingServers become ordinary horizontally-scalable HTTP services behind round-robin
server/discoverCapabilities learned only via the handshakeOptional RPC for clients that want capabilities up frontNo — additiveCall it if you need capabilities before the first tool callDiscovery is now a choice, not a cost on every connection
Mcp-Method / Mcp-Name headersRouting required parsing the JSON-RPC bodyBoth headers mandatory on Streamable HTTPYesEmit both from the client; route on them at the gatewayA policy gateway can authorise and rate-limit on headers alone — this is what makes it cheap
ttlMs / cacheScope on list resultsCatalogue re-fetched per connectionList responses carry cache hints and deterministic orderingNo — additiveAdd a cache keyed on cacheScope, honour ttlMsThe catalogue becomes a cache key, and deterministic order keeps upstream prompt caches stable
Multi Round-Trip RequestsServer-initiated elicitation/create, sampling/createMessage, roots/list over a held-open streamServer returns input_required plus the requests; client retries with inputResponsesYes, for those flowsHandle input_required and re-issue the callNo bidirectional stream needed, so mid-call user confirmation works on stateless infrastructure
RFC 9207 iss validationNot requiredAuthorization servers should return iss; clients must validate before redeeming a codeYes, for clientsValidate iss in the OAuth callbackCloses an authorization-server mix-up hole
DCR to CIMDDynamic Client Registration standardClient ID Metadata Documents standard; DCR deprecated but working; application_type now set during DCRDeprecation clockPlan the move; set application_type so localhost redirects stop being rejectedFewer moving parts in client onboarding; CLI OAuth flows stop failing on redirect_uri
Roots / Sampling / LoggingSupportedDeprecated, working for at least twelve monthsDeprecation clockStop adopting them in new workThe protocol surface narrows, which makes gateways simpler
Legacy HTTP+SSE transportSupported alongside Streamable HTTPDeprecated with a year-long offrampDeprecation clockMove to Streamable HTTPOne transport to secure, meter and audit instead of two

Should agents call MCP servers directly, or through a gateway?

Through a gateway for anything you did not write, and directly only for a first-party server inside your own trust boundary. The 2026-07-28 changes are what make this practical rather than merely advisable, because a gateway that can route and authorise on Mcp-Method and Mcp-Name headers does not have to parse and re-serialise a JSON-RPC body on the hot path.

What breaks without a gateway is a list, and every item on it is discovered late. There is no inventory, so nobody can answer which agents can reach which tools. There is no allowlist, so a server that adds a tool has silently added a capability to your agents. There is no per-agent identity, so calls arrive at downstream systems bearing a standing service credential rather than the scope of the user who caused them. There is no audit trail. There is no rate limiting per tenant and per server. And crucially, a tool schema written by someone else reaches your model completely unvetted.

The gateway's request-order matters and most of it is cheap. Authenticate the caller, resolve the agent identity, check the tool allowlist, look up the catalogue cache, validate and normalise the schema against the pinned hash, inject the scoped credential, rate limit on the tenant and server pair, forward, bound and record the response, write the audit entry. The first seven stages read headers and your own registry. Only schema validation and response bounding touch payloads.

State the cost rather than hiding it. Vendor benchmarks for pure AI-gateway routing put the overhead in microseconds, but that is not what is being measured here: a policy gateway doing credential injection, registry lookups and schema validation is realistically a few milliseconds at p50, more at p95 under a cold catalogue cache. Measure yours, publish the number internally, and put a cache in front of the registry. Then note the offset: the catalogue cache removes a tools/list round trip per run, which usually more than pays for the hop.

Gateway stageReads header or body?What it costsFails how?Skippable?
Authenticate the callerHeaderSub-millisecond with a cached JWKS401; agent cannot call any toolNo
Resolve agent identityHeaderRegistry lookup, cachedFalls back to no permissions, not to all permissionsNo
Check the tool allowlistHeader (Mcp-Name)In-memory set lookup403 with the tool named in the audit logNo — this is the control
Catalogue cache lookupHeader (cacheScope)Saves a tools/list round trip per runMiss: one upstream fetchYes, at the cost of a round trip
Validate + pin schema hashBody1-4ms for a typical schemaBlock or alert, per policy; a changed hash is a rug-pull signalNo — this is the rug-pull defence
Inject scoped credentialNeither — registry referenceOne secret-store fetch, cachedThe agent never holds the credential, so a leak is containedNo
Rate limit per (tenant, server)HeaderSub-millisecond429 with a retry hint the agent can act onOnly if you have one tenant
ForwardBodyNetwork hop to the serverUpstream 5xx surfaces as a tool business error, not an exceptionNo
Bound + record the responseBodyTruncation plus a journal writeOversized result truncated with a pointer, never silently droppedNo
Audit logBothOne appendMissing audit is a compliance finding, not an outageNo
gateway-policy.yaml
version: 1
defaults:
  on_schema_change: block          # block | alert
  max_result_tokens: 2000
  rate_limit: { per: [tenant, server], rps: 20, burst: 40 }

agents:
  support-triage:
    always_include: [request_more_tools, escalate_to_human]
    allow:
      - server: jira
        tools:
          - name: create_ticket
            pinned_schema_hash: "sha256:7c1f...e3a9"
            requires_human_approval: true      # irreversible, externally visible
          - name: search_tickets
            pinned_schema_hash: "sha256:04bd...11c7"
      - server: internal-kb
        tools: ["*"]                            # first-party, inside the boundary
      - server: web-search
        tools:
          - name: search
            pinned_schema_hash: "sha256:9a20...bb41"
            treat_results_as: untrusted         # never as instructions

servers:
  jira:
    url: https://mcp.internal/jira
    credential_ref: secretstore://jira/agent-scoped   # reference, never a literal
    identity: oauth2.1
    token_audience: https://mcp.internal/jira
    on_behalf_of: caller                        # inherit the user's scope
  internal-kb:
    url: http://mcp-kb.svc.cluster.local:8080
    credential_ref: secretstore://kb/read-only
  web-search:
    url: https://mcp.internal/websearch
    credential_ref: secretstore://websearch/metered

network:
  deny_direct_agent_egress: true                # a direct path is an audit gap
The whole policy in one file. pinned_schema_hash is the entire rug-pull defence and it is one field per tool: a third-party server that changes a tool's description or schema after you approved it fails the hash check instead of quietly changing what your agent is willing to do. Note that credentials are references, never literals.

How many tools can an agent actually handle?

Fewer than you have attached, and the limit is an accuracy limit before it is a cost limit. The cost arithmetic is straightforward: sixty tools at roughly 150 tokens of schema each is about 9,000 tokens on every turn, which across a twenty-turn run is 180,000 tokens of catalogue before the agent has done anything. Description length dominates that average, and a verbose description can run to 400 tokens on its own.

The accuracy problem is separate and worse, because it does not show up on a bill. Anthropic's guidance names 15 to 20 tools as the point where a model starts spending significant attention on understanding its options, and describes agents with 20 or more struggling to select the right tool, particularly when tools span unrelated domains and similar operations exist across them. Their own measurements on the Tool Search Tool are the cleanest published numbers here: deferring tool definitions and discovering them on demand cut context consumption from 77,000 tokens to 8,700, an 85% reduction, while tool-selection accuracy on their internal evaluation moved from 49% to 74% on Claude Opus 4 and from 79.5% to 88.1% on Claude Opus 4.5.

The production pattern is tool subsetting resolved at context-assembly time: given a task class, a tenant and an agent identity, query the registry for allowlisted tools, rank by stored embedding similarity against the task description plus a usage prior, and return the top N plus an always-include set. Seven to ten tools per run rather than sixty. The critical implementation detail, and the one that only comes from having done it: resolve the subset once per run, not once per turn. A catalogue that changes between turns invalidates the cached prefix and costs more than the subsetting saved.

Give the wrong subset an escape hatch. A request_more_tools meta-tool that always sits in the always-include set turns a bad subset into one extra turn instead of a failed run. And write tool descriptions as prompt, not documentation: the description is read by the model on every turn, it is the highest-leverage forty words in the tool, and enums beat free strings because an invalid enum is a schema rejection at the gateway rather than a plausible-wrong call at the tool. The tokens this saves show up directly in the tool-catalogue arithmetic inside an agent loop.

Tools attachedSchema tokens/turnTokens across a 20-turn runUncached costCached costRecommendation
575015,000$0.030$0.0047Comfortable. No subsetting needed
121,80036,000$0.072$0.0113Comfortable. Watch for domain confusion if they span unrelated systems
253,75075,000$0.150$0.0236Past the point Anthropic flags for attention cost. Start subsetting
507,500150,000$0.300$0.0473Subset. The catalogue is now 28% of an uncached run
10015,000300,000$0.600$0.0945Subset, and audit the catalogue — nobody needs 100 tools in one agent
7-tool resolved subset1,05021,000$0.042$0.0066The target. Plus request_more_tools as the escape hatch
Selection accuracyMeasure it on your own catalogue with the harness below. Anthropic reports 79.5% to 88.1% on Opus 4.5 with on-demand discovery; your corpus of tools is not theirs
Sixty tools versus a resolved subset of seven
All 60 tools attached
Tools visible to the model
60
Schema tokens per turn
9,000
Across a 20-turn run
180,000 tokens
Uncached catalogue cost per run
$0.360
Cached catalogue cost per run
$0.0567
Attention cost
Past the 15-20 tool point Anthropic flags
Failure when the model picks wrong
Silent — the wrong tool often succeeds
Escape hatch
None
7-tool subset resolved once per run
Tools visible to the model
7 + always_include
Schema tokens per turn
1,050
Across a 20-turn run
21,000 tokens
Uncached catalogue cost per run
$0.042
Cached catalogue cost per run
$0.0066
Attention cost
Well inside the comfortable range
Failure when the subset is wrong
One extra turn via request_more_tools
Escape hatch
request_more_tools, always included
86% fewer catalogue tokens per turn
At two million runs a month, the cached difference alone is roughly $101,000 a month. The accuracy difference is the one that actually decides it: a model choosing among seven relevant tools picks correctly far more often than one choosing among sixty near-neighbours, and the wrong choice is the failure that succeeds silently.
tool-resolver.ts
const ALWAYS = ["request_more_tools", "escalate_to_human"];

export async function resolveToolSubset(
  db: Db, run: { id: string; taskClass: string; tenantId: string; agentId: string },
  n = 7,
): Promise<ToolDef[]> {
  const cached = await db.getRunToolSubset(run.id);
  if (cached) return cached;                    // ONCE per run, never per turn

  const allowed = await db.allowlistedTools(run.agentId, run.tenantId);
  const taskVec = await embed(run.taskClass);

  const ranked = allowed
    .map(t => ({
      t,
      score: 0.75 * cosine(taskVec, t.descriptionEmbedding) +
             0.25 * t.usagePrior,               // prior from your own traces
    }))
    .sort((a, b) => b.score - a.score || a.t.name.localeCompare(b.t.name));

  const picked = [
    ...ranked.slice(0, n).map(r => r.t),
    ...allowed.filter(t => ALWAYS.includes(t.name)),
  ].sort((a, b) => a.name.localeCompare(b.name)); // deterministic order

  await db.setRunToolSubset(run.id, picked);
  return picked;
}

// The escape hatch. Without it, a wrong subset is a failed run.
export const requestMoreTools: ToolDef = {
  name: "request_more_tools",
  description:
    "Call this when no available tool can accomplish the current step. " +
    "Describe what you need to do. You will receive additional tools next turn.",
  inputSchema: {
    type: "object",
    required: ["capability_needed"],
    properties: {
      capability_needed: { type: "string" },
      why_existing_tools_insufficient: { type: "string" },
    },
  },
  annotations: { readOnlyHint: true, idempotentHint: true },
};
The resolver, with the two details that matter. It runs once per run and caches the result on the run row, because a catalogue that shifts between turns destroys the cached prefix. And the ordering is deterministic, a stable sort on the final list, because the 2026-07-28 spec gives you deterministic list ordering upstream and it would be a shame to shuffle it downstream.

How do you secure an MCP deployment?

With five controls, none optional, and one honest admission: the last class of attack is mitigated, not solved.

The core problem is that a tool description is read by the model on every turn and is not sanitised by the protocol. That makes the description field an attack surface: tool poisoning, where instructions are planted in metadata the model reads and the user never sees. It is now well documented. OWASP designates tool poisoning as the third entry in its MCP Top 10, security research published in mid-2026 found command-injection vulnerabilities in a meaningful fraction of public MCP servers, and Microsoft's June 2026 guidance formally classifies MCP tool descriptions as supply-chain assets requiring the same review rigour as production code. A CISA advisory on MCP security was published in the same period.

The five controls that the 2026 guidance converges on: a gateway acting as zero-trust ingress that inspects every schema before it reaches a model; an enforced per-agent tool allowlist with deny by default; OAuth 2.1 identity binding with PKCE and audience-bound tokens, so an agent inherits the calling user's scope instead of carrying a standing credential; version pinning of approved schemas with a defined on-change behaviour; and human-in-the-loop confirmation on irreversible tools. Add audit logging and a network policy that makes public MCP endpoints unreachable, and you have the posture a security reviewer will sign.

Now the admission. Prompt injection delivered through tool results (not descriptions, results) is mitigated, not prevented. You can truncate, wrap results in an explicit delimiter with a system-level statement that the content inside is data and not instructions, and strip anything resembling a tool-call directive. None of that is a proof. Any post claiming the problem is solved in 2026 is wrong. The correct architectural response is to assume a tool result may be adversarial and to require human confirmation on the irreversible actions an adversarial result might try to trigger. If you need that reviewed independently, a technical review of an agent platform's tool surface is a two-week engagement, not a two-quarter one.

ThreatHow it is exploitedControlResidual riskDetection
Tool poisoningInstructions planted in a tool description, which the model reads on every turn and the user never seesGateway inspects every schema before it reaches the model; allowlist deny-by-default; description reviewed like codeA malicious description that reads as legitimate documentation passes reviewSchema diff on change; description entropy and imperative-verb heuristics
Rug-pull updateA server changes a tool's schema or description after you approved itpinned_schema_hash per tool with on_schema_change: blockA compromised server can still fail closed and cause an outage — that is the correct tradeHash mismatch rate, alerted per server
Confused deputyThe agent uses its own standing credential to act on a user's request, exceeding that user's scopeOAuth 2.1 with PKCE, audience-bound tokens, on_behalf_of the caller; credential injected at the gatewayA first-party server with broad scope can still over-reach internallyAudit log: calls where agent scope exceeds caller scope
Prompt injection via tool resultsA retrieved web page or ticket body contains instructions the model followsTruncate, delimit explicitly as data, strip tool-call directives, require confirmation on irreversible actionsReal and unsolved. No 2026 technique fully prevents thisTrajectory eval: did the agent do something the task never asked for?
Over-scoped tokenOne token grants every tool on a serverLeast-privilege credential per (agent, server) pair, injected at the gateway, never held by the agentScope granularity is limited by what the downstream API exposesPeriodic scope audit against actual tool usage from the audit log
Exposed public MCP endpointA server intended for internal use is reachable from the internetNetwork policy; servers are never publicly reachable; the gateway is the only ingressShadow deployments outside the policyExternal attack-surface scanning; egress deny by default
Two paths, one diagram
The tool-poisoning path, with and without a gatewayAttacker3rd-party MCP serverGatewayAgentModel
publish tool whose DESCRIPTION contains instructions
PATH A: tools/list via gateway
tools/list
catalogue incl. poisoned description
schema hash mismatch -> BLOCK + alert
never reaches the model
catalogue minus the rejected tool
PATH B: direct connection, no gateway
catalogue incl. poisoned description
description enters the prompt as trusted text
follows the injected instruction
exfiltration via an innocuous-looking tool call
The only difference between the two paths is whether something inspects the schema before it becomes part of a prompt. Path A fails closed and pages someone. Path B succeeds silently and looks like normal agent behaviour in every metric you have. This is the diagram to put in front of a security reviewer who is asking why the gateway is not optional.

What breaks in production?

Ten things, and the worst of them is a tool call that succeeds. An agent picking a plausible-but-wrong tool from a sixty-tool catalogue does not produce an error, a latency spike or a failed run. It produces a confidently wrong outcome that passes every check you have.

That failure mode is the argument for trajectory evaluation, and the reason tool-selection accuracy belongs in your eval suite rather than your monitoring. Given a task, did the agent choose the right tool? That cannot be answered from outcomes alone, which is why I treat it separately in trajectory evals that catch a wrong-but-successful tool call.

The second-worst is the stale catalogue cache after a revocation, and it is worse than it sounds because its blast radius is security, not availability. You revoke a tool from an agent's allowlist; a gateway node holds a cached catalogue with a ttlMs of five minutes; for those five minutes the agent can still call it. Cache the catalogue by all means, that is the point of the new cache hints, but scope revocation to invalidate rather than wait.

The rest are ordinary distributed-systems failures wearing MCP clothes: a schema drifting under in-flight calls, an oversized tool result, a slow server holding a turn open past the run's wall-clock budget, a cross-tenant credential mistake, and a deprecated transport being removed by a server operator mid-quarter now that the twelve-month clock is running.

FailureBlast radiusDetectionTime to detectAuto-recoverable?Mitigation
Agent picks a plausible-but-wrong tool and it succeedsSingle request, wrong outcomeTrajectory eval only — no metric movesOnly via evalNoTool subsetting; disambiguating descriptions; enums over free strings
Server changes a tool schema; in-flight calls become invalidAll agents using that toolSchema hash mismatch at the gatewaySecondsYes — block and alertpinned_schema_hash with on_schema_change: block
Catalogue cache serves a revoked toolSecurityRevocation timestamp versus cache entry ageUp to ttlMsYesInvalidate on revocation; do not rely on TTL expiry
Tool returns 40K tokensSingle run, large costresult_size p99 by tool nameSecondsYesBound at the gateway before it reaches the agent, with a journal pointer
Slow MCP server exceeds the run's wall-clock budgetSingle runPer-server p95 and p99 latencyMinutesYes — run terminates as timed_outPer-server timeout below the run budget; circuit breaker
One tenant's call authorised with another tenant's tokenSecurity, cross-tenantAudit log: token audience versus caller tenantOnly if you audit for itNoCredential injection keyed on (agent, server, tenant); never a shared token
Gateway is a single point of failureAll tool callsGateway availabilitySecondsYesStateless gateway, three replicas — which the 2026-07-28 spec now makes trivial
MRTR round trip stalls on an unresponsive clientSingle runRate of input_required results with no follow-upMinutesYesTimeout the pending request; terminate the run into waiting_input, not into a hang
A server drops the deprecated HTTP+SSE transport mid-quarterAll agents using that serverTransport negotiation failuresMinutesNoMove to Streamable HTTP now; the offramp is twelve months, not forever
Audit gap on a direct connection someone left openComplianceEgress logs showing traffic that bypassed the gatewayAudit timeNodeny_direct_agent_egress in network policy, not in documentation
Checklist
What to span, and what to measure
  • An execute_tool span nested under the agent spanAttributes: server, tool name, canonical arg hash, result size in tokens, latency, cache status, gateway decision.
  • Tool-selection accuracy against a golden setThe only metric that catches a wrong-but-successful call. Measure it per catalogue size.
  • Tool error rate broken out by server, not just by toolA whole server degrading looks like six unrelated tool failures until you group by server.
  • Result-size distribution, p50 and p99, per toolThis is your early warning for context blowouts, and it moves days before anyone complains.
  • Catalogue cache hit rateNew in 2026-07-28 and directly convertible into dollars. A hit rate under 80% means your cacheScope key is wrong.
  • Arg hash cardinality per toolHigh cardinality on a tool you expected to be repetitive usually means the model is guessing at parameters.
One cardinality rule that will save your metrics backend: high-cardinality identifiers such as run_id and arg hashes belong on spans, never as metric dimensions. Put a run identifier on a metric label and you will find out how your observability bill scales.

What does the MCP layer cost?

Between four cents and sixty cents per twenty-turn run depending almost entirely on how many tool schemas the model can see, plus a gateway hop that is cheap and a catalogue cache that pays for it. The catalogue is the whole story.

At the volumes that matter, the subsetting decision is a budget line, not an optimisation. Going from a sixty-tool catalogue to a seven-tool resolved subset saves about 31.8 cents per uncached run and about 5 cents per cached run. At ten thousand runs a month that is a rounding error. At two million runs a month it is roughly $101,000 a month on cached pricing, the kind of absolute figure that gets a change approved where a percentage would not.

Two smaller lines deserve naming. Gateway compute is genuinely small: a stateless Go or Node service handling header inspection and a registry lookup, sized in single-digit vCPUs at these volumes, which is why the 2026-07-28 statelessness change matters commercially and not just architecturally. Audit log storage grows with tool calls, not runs, so a twenty-turn run with twelve tool calls writes twelve audit rows, and at two million runs a month that is a genuine storage line rather than a footnote.

The catalogue cache is the one component that is cost-negative. Under the old spec you re-fetched tools/list per connection; under the new one, ttlMs and cacheScope let the gateway serve it from memory with a deterministic ordering that also keeps your upstream prompt cache stable. That second-order effect, a stable catalogue meaning a stable cached prefix, is worth more than the round trip it saves, and it is why why a stable catalogue protects your cached prefix is a sentence worth internalising.

LinePer 20-turn run10K runs/mo200K runs/mo2M runs/mo
Catalogue tokens — 60 tools, uncached$0.3600$3,600$72,000$720,000
Catalogue tokens — 60 tools, cached prefix$0.0567$567$11,340$113,400
Catalogue tokens — 7-tool subset, cached prefix$0.0066$66$1,320$13,200
Saving from subsetting (cached basis)$0.0501$501$10,020$100,200
Gateway compute~$0.00002$40 (floor)$120$700
Catalogue cache storagenegligible$5$15$60
Audit log storage (12 calls/run)~$0.00004$10$60$480
Assumptions150 tokens per tool schema; mid tier $2/1M input, cached at $0.20/1M; write 1.25x; prices checked 24 Aug 2026
The absolute figure that gets the change approved
$ per month at 2,000,000 runslower is better
60 tools, no prefix cachingthe accidental default$720,000
60 tools, prefix cached-84%$113,400
7-tool subset, prefix cached-98% vs baseline$13,200
Gateway + cache + audit storagethe cost of the control plane$1,240
The control plane, gateway compute, catalogue cache and audit storage, costs about $1,240 a month at this volume against $100,200 a month saved by the subsetting it enables. That is the argument for the gateway stated in the only unit that reliably wins an architecture review.

When should you not use MCP?

For tools you own, inside your own process, a plain function call is cheaper and safer. MCP buys you a standard interface across a trust boundary. If there is no trust boundary (the tool is your code, in your repository, running in your process), you are paying a network hop, a schema-translation layer and an operational surface for an abstraction that solves a problem you do not have.

On a latency-critical path, the extra hop matters. A tool that must return in under fifty milliseconds inside a voice turn or an interactive UI does not want a gateway, a schema validation step and a network round trip in front of it. Call the function, and expose the MCP version for external consumers separately if you need one.

And before you can enforce an allowlist, do not roll out servers at all. This is the sequencing mistake I see most often: teams stand up eight MCP servers across the organisation, then discover there is no inventory, no per-agent identity and no audit trail, and spend a quarter retrofitting a gateway in front of something already in production. Ship the gateway first. It is a week of work and it is the difference between a rollout and a remediation.

The honest summary: MCP is excellent at exactly one thing, making third-party and cross-team tools available to agents under a common contract with a common security model, and unnecessary for everything else. If your agent's tools are all first-party, you may not need MCP this year at all, and that is a perfectly good answer.

MCP, or a function call?
Should this capability be exposed as an MCP tool?
Your code, your repo, your process
Function call

No trust boundary means no protocol. You would be paying a hop and a schema layer for nothing.

Latency budget under ~50ms for the tool
Function call

Gateway, validation and a network round trip do not fit inside an interactive turn. Expose MCP separately for external consumers.

Third-party service, or another team's system
MCP, through a gateway

This is the case the protocol exists for: a common contract and a common security model across a boundary you do not control.

You have no gateway and no allowlist yet
Ship the gateway first

A week of work now versus a quarter of retrofitting later. Servers before controls is the most common MCP sequencing mistake.

You want other people's agents to use your capability
MCP, and publish it properly

Deterministic list ordering, honest ttlMs, enums over free strings, and a description written as prompt rather than as documentation.

Two of the five say do not use MCP and one says not yet. That distribution is deliberate: MCP is very good at a specific job, and the 2026 enthusiasm for wrapping every internal function in a protocol is how teams acquire an operational surface they did not need.

MCP in production: common questions

What changed in the MCP spec in 2026?

The 28 July 2026 revision made the protocol stateless. The initialize and initialized handshake and the Mcp-Session-Id header were retired, with every request now carrying its protocol version, client identity and capabilities in _meta and an optional server/discover RPC for clients that want capabilities up front. Routing moved into mandatory Mcp-Method and Mcp-Name headers, list results became cacheable via ttlMs and cacheScope, Multi Round-Trip Requests replaced server-initiated elicitation and sampling, RFC 9207 issuer validation became required for clients, and Dynamic Client Registration, Roots, Sampling, Logging and the legacy HTTP+SSE transport were deprecated on a twelve-month minimum clock.

Do you need an MCP gateway in production?

For any server you did not write, yes. Without one there is no tool inventory, no per-agent allowlist, no identity binding, no audit trail, no rate limiting, and a third party's tool schema reaches your model unvetted. The 2026-07-28 header-routing change is what makes the gateway cheap enough to sit in the hot path: seven of its ten stages read HTTP headers and your own registry rather than parsing the JSON-RPC body. For a single first-party server inside your own trust boundary, a direct connection is fine.

How many MCP tools can an agent use at once?

Practically, seven to twelve resolved per task rather than everything you have connected. Anthropic's guidance names 15 to 20 tools as the point where attention starts to degrade and describes agents with 20 or more struggling to select correctly, particularly across unrelated domains. Their published measurement on on-demand tool discovery is the cleanest number available: context fell from 77,000 tokens to 8,700 while tool-selection accuracy rose from 79.5% to 88.1% on Claude Opus 4.5.

What is MCP tool poisoning and how do you prevent it?

Tool poisoning is planting instructions inside a tool's description or metadata, text the model reads on every turn and the user never sees. OWASP lists it third in its MCP Top 10. You prevent the description vector by putting a gateway in front that inspects every schema before it reaches a model, pinning an approved schema hash per tool with a block-on-change policy, and reviewing tool descriptions with the same rigour as production code. You cannot fully prevent injection delivered through tool results: that is mitigated by truncation, explicit data delimiters and human confirmation on irreversible actions, not solved.

Is MCP secure enough for enterprise use?

With five controls, yes; without them, no. The controls that 2026 guidance converges on are a gateway as zero-trust ingress, an enforced per-agent allowlist with deny by default, OAuth 2.1 identity binding with PKCE and audience-bound tokens so the agent inherits the user's scope rather than a standing credential, version-pinned schemas with a defined on-change behaviour, and human-in-the-loop confirmation on irreversible tools. Add audit logging and a network policy that makes servers unreachable except through the gateway.

When should you use MCP instead of a normal API call?

When there is a trust boundary. MCP gives you a common contract and a common security model across third-party services and other teams' systems. For a tool that is your own code running in your own process, a function call is cheaper, faster and has less to secure. For a tool on a latency-critical path with a budget under about fifty milliseconds, the extra hop and validation step do not fit. And if you do not yet have a gateway and an allowlist, ship those before you ship servers.

Ready to talk numbers?

Twenty minutes, straight to the engineer. No sales rep, no deck.