Your MCP Server Is Now a Normal HTTP Service
Everyone read the 2026-07-28 MCP spec as a scalability win. It is a governance bill — and the two headers it made mandatory are the most useful thing in it. Here is what to fix.

On 28 July the Model Context Protocol shipped its 2026-07-28 specification, and nine days ago the core maintainers published the roadmap that follows it. Every summary I have read leads with the same line: MCP went stateless, so your server scales now. That is true, and it is the least interesting thing in the release, because you get it by upgrading an SDK. The consequential changes are quieter. MCP adopted a formal feature lifecycle with a minimum twelve-month deprecation window, made two routing headers mandatory, and required cache metadata on list results. Read together, those three say something the scalability headline does not: your MCP server is no longer an AI integration. It is an HTTP service with a maintenance calendar, and the tooling you already own can finally operate it.
Why this matters now
The 2026-07-28 release is the largest revision since remote MCP launched. The initialize and initialized handshake is gone, and so is the Mcp-Session-Id header; every request now carries its protocol version and client capabilities in _meta, which means any request can land on any instance behind a plain round-robin load balancer. Server-initiated calls were replaced by Multi Round-Trip Requests, where a server that needs something mid-call returns a result of type input_required and the client retries the original request with the answers attached. Tasks moved out of the core into an official extension. The scale behind those decisions is not small: the maintainers report close to half a billion monthly downloads across the Tier 1 SDKs, with the TypeScript and Python SDKs each past a billion in total.
Then, on 22 August, the new roadmap. Five priority areas, one of which names the remaining gap without flinching: MCP authorization today is built around a person approving access in a browser, and increasingly the caller is an agent running as a cloud workload with its own identity, acting for a user who is not present. The remediation the maintainers name is entirely existing standards — proof-of-possession tokens, workload identity federation, the identity-assertion grant behind Enterprise-Managed Authorization, ordinary token exchange — rather than anything MCP-shaped. That is worth noticing, because it is the same instruction both documents give from different angles: stop inventing agent-specific infrastructure and start using the API infrastructure you already run.
The most useful line in the changelog is a minor change
Buried under Minor changes, SEP-2243 now requires the Mcp-Method and Mcp-Name headers on every Streamable HTTP POST.
An MCP server is a public, unversioned API into whatever you connected it to. The 2026-07-28 spec is the first version where your gateway can see what it is being asked to do without opening the envelope.
Before this, an edge layer that wanted to rate-limit one tool, or block a destructive one, had to buffer and parse a JSON-RPC body to find out which tool was being called. That is expensive, awkward in most gateways, and the reason almost nobody did it. Now the method and the tool name travel in headers, so your WAF, your rate limiter, your authorization layer and your metrics pipeline can all act on them directly. Two further changes compound it. List results are now required to carry ttlMs and cacheScope, which makes a shared cache in front of your server a supported decision rather than a gamble. And the spec documents OpenTelemetry trace context conventions in _meta, so MCP calls land in the same trace waterfall as the rest of your traffic instead of in a separate dashboard nobody opens.
Here is what that buys, in about forty lines.
// mcp-edge-policy.ts — a deny-by-default edge policy for a remote MCP server,
// written against the 2026-07-28 spec. It runs as ordinary HTTP middleware on
// whatever already fronts your API. Nothing in it is AI-specific, which is the
// entire point.
//
// Before SEP-2243 an edge layer had to buffer and parse the JSON-RPC body to
// learn which tool was being invoked — expensive, awkward in most gateways, and
// the reason almost nobody bothered. `Mcp-Method` and `Mcp-Name` are now
// REQUIRED on every Streamable HTTP POST, so this is a header lookup.
declare const metrics: {
histogram(name: string, value: number, tags: Record<string, string>): void
}
type Sensitivity = 'read' | 'write' | 'destructive'
// The tool catalogue is policy, not documentation. A name absent from this map
// never reaches the handler — which is also how you find out the day someone
// ships a tool without telling you.
const CATALOGUE: Record<string, Sensitivity> = {
findPosts: 'read',
findProjects: 'read',
findMedia: 'read',
createPosts: 'write',
updatePosts: 'write',
deletePosts: 'destructive',
}
// Requests per hour, per caller. Agents make a great many reads and very few
// legitimate deletes; one budget for "the MCP server" hides exactly that.
const HOURLY_BUDGET: Record<Sensitivity, number> = {
read: 600,
write: 60,
destructive: 0, // opt-in per caller, never a default
}
// 2026-07-28 partitions the JSON-RPC server-error range: -32020..-32099 is
// reserved for the specification, -32000..-32019 stays implementation-defined.
const POLICY_DENIED = -32010
const deny = (message: string, status = 403) =>
new Response(
JSON.stringify({
jsonrpc: '2.0',
id: null,
error: { code: POLICY_DENIED, message },
}),
{ status, headers: { 'content-type': 'application/json' } },
)
interface Caller {
subject: string
scopes: string[]
/** Increments and returns this caller's count for the current hour. */
spend(bucket: string): Promise<number>
}
export async function mcpEdgePolicy(
req: Request,
caller: Caller,
forward: (req: Request) => Promise<Response>,
): Promise<Response> {
if (req.method !== 'POST') return forward(req)
const method = req.headers.get('Mcp-Method')
const name = req.headers.get('Mcp-Name')
// A POST without these is either a pre-2026-07-28 client or something
// imitating one. Both are worth a log line; neither gets through.
if (!method) return deny('Mcp-Method header required (MCP 2026-07-28)', 400)
// tools/list is a read of the catalogue itself. Let it through — and note its
// response is now required to carry ttlMs and cacheScope, so a shared cache
// in front of this is legitimate rather than a gamble.
if (method !== 'tools/call') return forward(req)
const sensitivity = name ? CATALOGUE[name] : undefined
if (!sensitivity) return deny(`unknown tool: ${name ?? '(none)'}`)
if (
sensitivity === 'destructive' &&
!caller.scopes.includes(`mcp:destructive:${name}`)
) {
return deny(`${name} requires an explicit grant`)
}
const used = await caller.spend(`${caller.subject}:${sensitivity}`)
if (used > HOURLY_BUDGET[sensitivity]) {
return deny(`${sensitivity} budget exhausted this hour`, 429)
}
// Trace context now has documented _meta conventions (SEP-414), so this call
// lands in the same waterfall as the rest of your traffic rather than in a
// separate "AI stuff" dashboard.
const started = performance.now()
const res = await forward(req)
metrics.histogram('mcp.tool.duration_ms', performance.now() - started, {
tool: name!,
sensitivity,
subject: caller.subject,
status: String(res.status),
})
return res
}Nothing in that file is AI-specific. It is the same allowlist-and-budget pattern you would put in front of any public API, and until 28 July you could not write it cheaply.
There is a cost on the other side of the ledger and it deserves stating plainly, because the release notes are quieter about it. Stream resumability was removed along with the Last-Event-ID header and SSE event IDs. A broken response stream now loses the in-flight request, and the client must re-issue it as a new request with a new request ID. In exchange for horizontal scalability you have accepted that retries are the normal path rather than an edge case, which means every tool that writes anything has to be idempotent. That is the same discipline I argued for on the webhook ingestion side — persist, deduplicate, then act — and it matters here for the same reason: at-least-once delivery is only survivable when the receiving end was built for it. If your tools are not idempotent today, the stateless core is not a scalability upgrade. It is a duplicate-writes bug you have not hit yet.
What a founder or CTO does with this
The practical question is not which SEP to read. It is: who owns this thing?
Most MCP servers I encounter were built by one engineer in a week to make a demo work, and they are now sitting in front of a production database with a pasted token and no named owner. That was defensible when MCP was eighteen months old and moving weekly. It is not defensible against a protocol that has just published a deprecation registry. The twelve-month minimum window is a promise to you, but it is also an invoice: anything deprecated on 28 July can be removed no earlier than late July 2027, and something in your server is on that list. Work that has a date is work you can schedule, which is the whole reason mature protocols publish these policies — and the whole reason ignoring them is now a choice rather than an oversight.
The migration ledger
Five items, in the order I would work them. The first two are already live; the last three have a clock.
- Sessions and the handshake are removed, not deprecated. Mcp-Session-Id, initialize and notifications/initialized are gone, and so are ping, logging/setLevel and notifications/roots/list_changed. Upgrade the SDK. If you were keeping state in the transport session, mint an explicit handle from a tool and let the model pass it back as an ordinary argument — the maintainers note this works better anyway, because the model can see the handle and thread it between tools.
- Stream resumability is removed. A dropped stream loses the request and the client re-issues it under a new ID. Audit every write tool for idempotency before you enjoy the horizontal scaling. This is the change most likely to reach production as a data bug rather than an error message.
- New obligations arrive with the upgrade. Mcp-Method and Mcp-Name on every Streamable HTTP POST, ttlMs and cacheScope on every list result, a resultType on every result, and server/discover is now something servers must implement. The SDK covers most of it. The cache values are a judgement call you have to make yourself, and they are worth making deliberately rather than accepting a default.
- Dynamic Client Registration is deprecated in favour of Client ID Metadata Documents. It keeps working for backwards compatibility with authorization servers that have not moved, and it will be removed in a future version. Alongside it, clients must now validate the issuer on authorization responses and must not reuse credentials across authorization servers. Not urgent; not optional either.
- Roots, Sampling, Logging and the HTTP+SSE transport are deprecated with a twelve-month floor. The migrations are named for you: pass directories through tool parameters or resource URIs instead of Roots, call the model provider's API directly instead of Sampling, log to stderr or OpenTelemetry instead of Logging, and move to Streamable HTTP. Put this on a calendar rather than trusting that you will notice.
My perspective
Here is the opinion I will defend: treating your MCP server as an AI project is the mistake, and the 2026-07-28 spec is the protocol telling you so about as clearly as a protocol can.
I run this site's content workflow through an MCP server sitting on top of its Payload CMS. It can create, update and delete posts, projects, testimonials and media. For a while I thought of it as tooling — a convenience for me, on my own site, at low stakes. Then I wrote down what it actually is: an authenticated write path into the production database of my professional front door, reachable by a model, with no per-tool budget and no distinction at the edge between reading a draft and deleting one. Nothing in that description mentions AI. It is a description of an API, and not a reassuring one.
The fix was not an AI fix. It was the catalogue and the budget in the file above, plus the same posture I took on agent egress: deny by default, and make the destructive path require an explicit grant rather than inherit an implicit one. On Keaz I run twelve services behind one gateway with no platform team, and the only reason that is sustainable is that every service looks the same to the edge. As of 28 July, MCP servers look the same too. That is the release. The stateless core is how it happened; being able to govern the thing with the boring tools you already own is what you actually got.
Recommended action this quarter
Three things, in order. Upgrade to an SDK that speaks 2026-07-28 first, because the removals are already live and the compatibility path only gets more expensive from here. Then write your tool catalogue down as a policy file, even if you never enforce a single budget — the exercise of classifying every tool as read, write or destructive reliably surfaces at least one tool nobody remembered exposing, and that finding alone usually justifies the afternoon. Finally, put the deprecated-features registry on a calendar for the first quarter of 2027; if your server touches Roots, Sampling, Logging, HTTP+SSE or Dynamic Client Registration, that is scheduled work now rather than a surprise later. If you want the sequencing for a specific server, or an argument for why the edge policy belongs in front of it, that review is part of the fractional CTO and architecture work I take on.
Find out who owns your MCP server
If you are running an MCP server against a production system and nobody has yet answered who owns it, the catalogue review takes an afternoon and the findings are usually clarifying. Book a time and we will go through it together.
Keep reading
How I Ingest Webhooks Without Losing Events
A webhook handler that does real work inline is a data-loss bug waiting for a traffic spike. The durable pattern I run on Keaz — persist, queue, process idempotently — and where it breaks.
Your Agent Sandbox Has a Hole in It, and It Is Egress
The agent never escaped the sandbox. It used the internet access it was handed. Filesystem isolation is not containment — egress is the boundary, and here is how to tier it and audit it this week.
Platform Engineering Without a Platform Team
The industry turned platform engineering into a hiring decision. It isn't. The minimum viable platform I run on Keaz across twelve services — no platform team — and when to finally hire one.