MCP is now traffic a firewall can name

MCPCloudflareSecurityDeveloper ToolsArchitectureNetworkingAI Coding

August 22, 2026

Dark editorial inspect panel with a request card, a dashed line to a lit named-class chip, and the words a firewall can name on a graphite block

MCP traffic detection used to mean grepping a hostname for the letters mcp. That was a guess. The protocol now writes its name on the request, and a TLS-inspecting firewall can match the class without caring what the URL looks like.

Paste a remote server into Claude Code, Cursor, Codex, or VS Code and it still feels like a local config line. Cloudflare's August 2026 writeup is the reason that feeling is now wrong on a managed laptop.

The change is not a new product category. Version already rode later HTTP requests in 2025-11-25. The 2026-07-28 spec put that header on every POST and required method and tool name, so a proxy can sort the mail without opening the envelope.

Installing a remote MCP server is a network event. Treat it like one, or wait for someone else's HTTP log to do it first.

The install still looks local#

Split HUD with a dim CONFIG card on the left feeding a large unlabeled HTTPS slab lettered NO NAME
Same config slot, and the request still has no class on the outside.

Most of the installs that taught people MCP never left the laptop. The 2025-11-25 spec still lists stdioSTDIO transportA way of connecting an MCP client and server over standard input and output streams rather than HTTP, where the spec requires clients to re-send subscriptions after a server crash.See also resource subscription, subscriptions/listen first, and tells clients they SHOULD support it whenever possible. A subprocess on stdin is not a request.

Remote servers showed up as a URL in the same config slot. One line. Same mental bucket as the local filesystem server.

JSON-RPC never helped the firewall. The 2.0 spec is proud of being transport-agnostic, which is another way of saying the envelope has no return address the mail room can read.

Cloudflare says the protocol still does not use a guaranteed hostname and does not require /mcp in the URL. A direct connection can look like any other HTTPS API call, which is exactly how a lot of servers are shipped.

If you already run the parts nobody explains, you have seen this. A Worker at an ordinary /api URL is still MCP if the body is JSON-RPC and the client speaks the transport.

Hostname matching was the fake detector#

A hostname filter catching MCP IN HOST and PATH /mcp while a chip lettered /api SLIPS ducks underneath
The loud servers light up, and the quiet /api request walks around the grep.

Security teams did what you would do with logs and no protocol help. Cloudflare's April 2026 tutorial still teaches the three signals.

  • Hostnames containing mcp
  • URL suffixes like /mcp, /mcp/sse, or /sse
  • DLP regexes on JSON-RPC method fields in the first 1,024 bytes of a POST body

Those queries find the loud servers. mcp.datadog.com. api.stripe.com/mcp. The ones that advertised.

They miss the quiet ones. Cloudflare's later post names the miss as an ordinary URL like https://tools.example.com/api, and admits the grep can also match an unrelated service that happened to use mcp in a hostname.

A podcast CDN named mcp something would light up. A tool server hiding under /api would not. That is not detection. That is a word search.

Body regex is closer, and still a workaround. Cloudflare's old documented recipe used DLP on POST bodies, and only the first 1,024 bytes. That is a weird place to hang an entire agent-governance program.

The spec put the name on the request#

A POST card with three header chips stacked above a dim JSON body, labeled VERSION, METHOD, and TOOL
The name sits outside the envelope so a proxy can sort it shut.

The protocol did this to itself. Session-based Streamable HTTP already required MCP-Protocol-Version on every HTTP request after initialization. The 2025-11-25 transports page says MUST, with an example of MCP-Protocol-Version: 2025-11-25.

That still left the first initialize request walking around unlabeled. Legacy clients, and anything older than 2025-06-18, could omit the header. Presence was a strong yes. Absence proved nothing.

2026-07-28 deleted the handshakeinitialize handshakeThe one-time exchange at the start of an MCP connection where a client and server negotiated protocol version and capabilities before any tool calls happened.See also MCP, Mcp-Session-Id header, protocol version field and stopped pretending the name could wait. Every POST to the MCP endpoint MUST carry MCP-Protocol-Version. Mcp-Method is required on all requests. Mcp-Name is required on tools/call, resources/read, and prompts/get.

The spec is explicit about why. Those fields are mirrored out of the JSON-RPC body so intermediaries can route and inspect without parsing the body. Cloudflare's MCP v2 post says the quiet part. A gateway, rate limiter, or WAF can now make decisions from headers without parsing arbitrary JSON.

Same spec that moved session state onto every request. The _meta tax is the cost. The header stencil is the side effect a firewall can use.

POST /api HTTP/1.1
Host: tools.example.com
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: get_weather

{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_weather"}}

The URL is /api. Host is a generic tools box. The class still sits on the request in three headers a proxy already knows how to read. That is the whole trick.

Gateway turned the header into a bit#

A TLS INSPECT arch around a glowing center chip lettered IS MCP with TRUE under it
One header, one boolean, no private list of MCP-looking domains.

The 12 August changelog shipped the product shape. Cloudflare Gateway classifies MCP by inspecting protocol-specific headers and payload characteristics, and it exposes that as an Is MCP selector in HTTP policies.

experimental.is_mcp is the wire name. The changelog says the selector is in beta and may change before general availability. Hedge once. It is already in Gateway HTTP policies, dated the same week as the blog.

As the blog defines it, the boolean is simple. If Gateway sees MCP-Protocol-Version on a TLS-inspected request, the value is true. You write an allow or a block without maintaining a private list of MCP-looking domains.

Do not upgrade that into a tool-name matcher. Recap posts keep adding Mcp-Method and Mcp-Name as if they were the selector. The official pages do not. Those headers are how the spec invited a firewall. The bit Cloudflare documented is the version header.

TLS decryption is the price of admission. Encrypted traffic that skips inspection stays unlabeled. The same changelog added an AI security report under Insights and Logs, request volume, unique users, unique servers, and a timeseries of servers observed. That is how shadow MCP stops being a vibe.

You'll hit this the first time a managed laptop already running WARP adds a remote MCP URL. The next log line is not "unknown SaaS." It is a named class, with a user stuck to it.

Shadow MCP is not portal bypass#

Two problems get mashed together because both look like an agent talking to a tool. They are not the same rule.

Shadow MCP is a connection to a server the organization has not approved. Someone found it in a README, a product guide, or a Slack paste, and wired it into the client. Security has no idea which tools it exposes or what data just left.

Portal bypass starts with a server you already like. You put it behind an MCP Portal, Access identity, a curated catalog, logging. Then someone pastes the upstream URL into Cursor and skips the front door.

Cloudflare's own portal docs warn that blocked users can still hit the server by using its direct URL. Gateway on the managed network is the control for shadow MCP. Bypass also needs the origin to refuse a direct call, Access, source IP, or the server's own auth.

The baseline they publish is the combined bit. experimental.is_mcp true, traffic source not mcp_portal, action block. Observe first if you want. The logs now carry both signals on decrypted traffic.

The laptop process still does not exist on that wire#

Left a STDIO box with NO PACKET leaving, right a remote request hitting a gate lettered NAMED CLASS
The subprocess never arrives, and the remote URL does.

A firewall that can name MCP is still a firewall. Cloudflare lists the holes in the same post that announces the selector.

  • Local stdio servers, which never generate network traffic
  • Off-network connections and any request that never traverses Gateway
  • Do Not Inspect policies, which skip TLS decryption and leave the headers unread

A client hook still sees the subprocess. Claude Code's managed MCP docs start from the opposite default. Anyone running Claude Code can connect any MCP server they choose, unless you deploy managed-mcp.json or an allowlist.

That file can pin a set, or empty the map and disable MCP besides the VS Code in-process server. It also covers stdio commands. Reproduce that where you control the client. Do not pretend it covers Cursor on the same laptop.

Header absence is still not proof. Cloudflare says presence of MCP-Protocol-Version is a strong positive, and its absence does not prove the request is not MCP. Old clients, custom transports, and the unlabeled initialize of a session-based flow will keep leaking through a header-only rule.

So the decision is smaller than the dashboards. If the next install is a remote URL, assume a TLS-inspecting gateway can name it, and that someone else's policy can block it. If the next install is stdio, the network bit will not save you. Pick the layer that can actually see the thing you just added.

MCP on the wire

Does a remote MCP server have to live at a /mcp URL?

No. Cloudflare's August 2026 writeup is blunt that the protocol does not require /mcp in the URL, so a server at an ordinary address still speaks MCP. The old Gateway tutorial grepped for /mcp and hostnames containing mcp. That is the detector the header replaced.

asked on blog.cloudflare.com
Can a company firewall see stdio MCP on a laptop?

Not on the Gateway view described here. Cloudflare lists local stdio servers with off-network traffic and Do Not Inspect as remaining outside the view. A client allowlist such as Claude Code's managed-mcp.json is the layer that still sees the subprocess.

asked on blog.cloudflare.com
If Claude Code can already lock which servers load, why does a network bit matter?

Claude Code's managed-mcp.json can pin or disable servers, including stdio, but only inside that client. A Gateway selector sees every remote client on a managed, TLS-inspected network without reproducing the allowlist in Cursor, Codex, VS Code, and the next harness. Reproduce the client policy where you can. Treat the remote URL as a network event anyway.

asked on code.claude.com
Is the first initialize request as easy to name as later calls?

On session-based clients, often no. The 2025-11-25 spec requires MCP-Protocol-Version on subsequent HTTP requests, not on initialize. 2026-07-28 requires it on every POST because the handshake is gone. Implementers have been arguing that split since at least the Go SDK issue that expected a reject when the header was missing after initialize.

asked on github.com
Share

Newsletter

New posts land in your inbox when they publish. No spam, unsubscribe anytime.

Prefer RSS