The bug tickets start closing at noon. Nobody thinks much of it — Joe moved a few tickets to Done, and Joe is having a productive afternoon. By 4 p.m., thousands of tickets have been closed, all by Joe.
That is the opening of Cloudflare's WriteGuard post, and it is the clearest statement yet of the failure mode that actually threatens agent deployments. Not prompt injection. Not data exfiltration. Just an agent doing a permitted thing at machine speed, under a human's name, with nobody watching.
If you are running an MCP server that exposes anything beyond reads, this is your risk profile. And the way the ecosystem currently tells you to handle it does not work.
How to secure MCP write tools, in one sentence: put the policy check on the server in front of the tool handler, classify every tool by blast radius, and stamp agent identity onto the call — because the client-side confirmation dialog the spec relies on is the one layer you do not control. Everything below is the argument for why that is the only placement that survives contact with an autonomous agent.
TL;DR
- The MCP spec puts the guardrail in the wrong place. It says clients SHOULD prompt for confirmation on sensitive operations. The client is the one layer a server operator does not control.
-
Tool annotations are not authorization. The spec itself says clients "MUST consider tool annotations to be untrusted unless they come from trusted servers." A self-declared
read-onlylabel is a claim, not a constraint. - Cloudflare's answer is a server-side interception layer. WriteGuard sits between the MCP client and the tool handler, classifies the call, and can block it before the handler runs.
- Four risk tiers, not per-tool rules: Read Only, Minimal Impact, Contained Write, Critical. Policy is written per tier so new tools inherit governance instead of shipping ungoverned.
- Attribution is the underrated half. Enriching writes with agent identity is what turns "Joe closed 3,000 tickets" into "Joe's agent, session X, closed 3,000 tickets."
- Cloudflare's internal portal went from 13 MCP servers in April to 27 today. The governance problem arrives as a function of server count, and yours is growing too.
The spec asks the wrong layer to hold the line
Read the MCP specification's security guidance for tools carefully and you find two lists. Servers MUST validate inputs, implement access controls, rate limit invocations, and sanitize outputs. Clients SHOULD prompt for user confirmation on sensitive operations and log tool usage for audit.
Notice which verb goes where. The hard requirement on the server is generic hygiene. The specific, load-bearing protection — a human sees this and can say no — is a SHOULD, aimed at the client.
That is a reasonable protocol design. It is a terrible operational plan, for one reason: you do not own the client. Your MCP server will be called by Claude Code, by an IDE extension, by an internal orchestrator someone built in an afternoon, by a cron job. Every one of them decides for itself whether to render a confirmation dialog. Some are headless by design and structurally cannot. The spec even acknowledges this — it says there SHOULD "always be a human in the loop with the ability to deny tool invocations," which is precisely the guarantee an autonomous agent exists to remove.
So the honest reading is this: client-side confirmation is a UX affordance, not a control. If your only protection against a destructive tool call is that some client will probably show a dialog, you have no protection.
Why aren't MCP tool annotations enough?
The usual next suggestion is tool annotations — the optional metadata a server attaches to describe tool behavior, the family of readOnly/destructive style hints people reach for when they want a tool marked safe.
The specification contains an unusually blunt warning about them:
For trust & safety and security, clients MUST consider tool annotations to be untrusted unless they come from trusted servers.
Sit with that. Annotations are self-reported by the server that owns the tool. A compromised or simply careless server can advertise a destructive tool as read-only, and a client that treats the label as a security fact will happily auto-approve it. Annotations are for presentation — deciding what to surface, what to gray out, what to highlight. Treating them as an authorization layer inverts the trust relationship.
This is the same structural mistake as trusting a client-supplied isAdmin: true field. The fix is the same too: enforce where you control the code path.
What does WriteGuard actually do?
Cloudflare describes WriteGuard as "a shared policy, attribution, and auditing layer" for MCP servers. Mechanically, it intercepts tool calls between the MCP client and the server's handler, then uses the tool's configuration plus the request context to pass the call through, enrich it, or block it — before its handler runs.
Three things happen at that interception point.
1. Classification by blast radius. Every tool is assigned one of four tiers:
| Tier | Examples | What the policy should do |
|---|---|---|
| Read Only | search, view | Pass through; log |
| Minimal Impact | reactions, mark-as-read | Pass through; log; rate limit |
| Contained Write | comments, create MR | Attribute; audit; consider volume caps |
| Critical | merge MR, production deploy, bulk delete | Require an explicit approval path |
The design win here is not the specific four names — it is that policy is written per tier, not per tool. A team shipping a new MCP tool declares its tier and inherits a rule that already exists. The alternative, one hand-written rule per tool, guarantees that the fiftieth tool ships ungoverned because nobody got to it. When your portal grows from 13 servers to 27 in four months, per-tool review is not a strategy.
2. Attribution. WriteGuard enriches supported writes with agent identity — the MCP client and session context — and adds agent labels to downstream applications without requiring changes to the downstream server's code.
This is the half that most teams skip, and it is what the Joe story is really about. The ticket system was not wrong; it recorded exactly what it saw, which was a stream of authorized actions carrying Joe's credentials. There was no field in which to record "an agent did this." Attribution adds one. It converts an unexplainable human anomaly into a legible agent event, which is the difference between a four-hour incident review and a one-minute log query.
3. Asynchronous audit. WriteGuard emits a scrubbed audit event per call — it "omits values for keys considered secret or sensitive," and includes the server, tool, risk tier, outcome, user, client, and duration.
That field list is worth copying verbatim, because it is the minimum set that lets you answer the questions you will actually be asked: which agent, doing what, at what risk level, on whose behalf, and did it succeed. Note what is absent — argument values. Logging full tool arguments is how audit trails become the largest secret store in the company.
The reasoning that makes this correct
The sentence in Cloudflare's post that generalizes best is this one: "we knew we could not depend on every employee to configure every agent perfectly or watch every tool call."
That is the whole argument for centralizing the control. Every alternative — per-user agent config, careful client selection, a strong internal norm about which tools you point an agent at — is a policy that degrades with headcount and with time. A gate in the request path does not.
It is the same reason production agents fail at the vibe-to-live boundary: the prototype worked because one careful person was driving it, and the deployment fails because the system assumed that person scales. And it is why designing the agent-to-human handoff is infrastructure work rather than a UI detail — the Critical tier is meaningless unless there is a real path for a human to receive, understand, and answer the request.
How to secure MCP write tools before you have a WriteGuard
WriteGuard is internal today. Cloudflare configures it in TypeScript inside their monorepo, with a private beta planned "in the coming months" and configuration eventually moving into the portal UI. So treat the post as an architecture to copy, not a product to wait for. Every piece of it is a middleware layer in your own server.
If you are standing up an MCP server — or you already deployed one on Cloudflare Workers — here is the order I would build it in:
- Tag every tool with a tier, today. Four buckets, stored next to the tool definition. This takes an hour and is the prerequisite for everything else.
-
Wrap the dispatcher, not the handlers. One interception point that receives
(toolName, args, context)and returns allow/enrich/deny. If the check lives inside each handler, it will be missing from the one handler that matters. -
Deny-by-default for the Critical tier. Not a confirmation prompt — a hard rejection with an error explaining the approval path. The spec's own error guidance is helpful here: a tool execution error with
isError: truegives the model actionable feedback and lets it stop cleanly instead of retrying. - Stamp agent identity on every write. Even before you have policy, having attribution means the next incident is diagnosable.
- Rate limit by tier. The spec makes this a server MUST, and it is the only defense against the actual Joe failure — a thousand individually-legal calls. Per-call permission checks are structurally blind to volume.
- Emit the scrubbed audit event. Server, tool, tier, outcome, user, client, duration. No argument values.
Where do teams get MCP write-tool security wrong?
The first mistake is auditing instead of gating. Logging is cheap and feels like progress, but a perfect audit log of an irreversible bulk delete is an autopsy. Tiers exist so that the small set of genuinely destructive tools gets a blocking check, and everything else gets cheap observability.
The second is treating the agent as a user. Handing an agent a service account and calling it done means every downstream system sees one very busy principal with no way to distinguish its actions from a human's. This is the same category of error as the supply-chain attacks that ride developer trust — the credential is valid, the action is permitted, and the identity model simply has no vocabulary for what actually happened.
The third is waiting for the ecosystem to solve it. The protocol has decided where it stands: servers get MUSTs about access control, clients get SHOULDs about confirmation. Nothing in the pipeline changes the fact that the enforcement point you own is the one in front of your handler.
The take
MCP made it trivially easy to give an agent write access to your systems, and the protocol's own safety story leans on a confirmation dialog that a headless client will never render. Cloudflare's contribution is not a novel security idea — it is interception, classification, and attribution, three things every mature system already does for human traffic. The contribution is noticing that agent traffic never got them.
Classify your tools by blast radius this week. Everything else follows from having those four buckets.
FAQ
How do I secure MCP write tools?
Put the policy check on the server, in front of the tool handler, not in the client's confirmation dialog. Classify every tool by blast radius, require an explicit approval path for the destructive tier, and reject calls that fail policy before the handler executes. Client-side confirmation is a useful second layer, but it is a UX preference that any client can decline to implement, so it cannot be the only gate.
Are MCP tool annotations a security control?
No. The MCP specification is explicit that clients "MUST consider tool annotations to be untrusted unless they come from trusted servers." Annotations are self-reported hints a server attaches to its own tools, so a hostile or buggy server can label a destructive tool as read-only. They are useful for shaping how a client presents a tool; they are not authorization.
What are risk tiers for MCP tools?
Risk tiers are a small fixed set of blast-radius classes that every tool is assigned to, so policy can be written once per tier instead of once per tool. Cloudflare's WriteGuard uses four: Read Only, Minimal Impact, Contained Write, and Critical. The value is that a new tool inherits an existing policy by declaring its tier, rather than shipping ungoverned because nobody wrote a rule for it.
Why does agent attribution matter if I already have audit logs?
Because ordinary application logs record the human whose credentials the agent borrowed, not the agent. When an agent closes a thousand tickets, the downstream system shows a thousand actions by that employee, and the incident looks like a person having an implausible afternoon. Attribution means stamping the agent and session identity onto the write so the downstream record distinguishes agent activity from human activity.
Is WriteGuard available to use today?
Not yet. Cloudflare describes it as internal today, configured in TypeScript inside their monorepo, with a private beta planned in the coming months and configuration moving into the MCP server portal UI over time. Treat the post as an architecture to copy rather than a product to adopt — every mechanism in it can be built with a middleware layer in your own MCP server.
Should my MCP server rate limit tool calls?
Yes, and the specification says so directly: servers MUST validate all tool inputs, implement proper access controls, rate limit tool invocations, and sanitize tool outputs. Rate limiting is the cheapest defense against the specific failure mode where an agent does something individually permitted thousands of times in an afternoon, which no per-call permission check will ever catch.
Sources
- Cloudflare, "WriteGuard: fine-grained controls for MCP Servers" — the ticket-closing scenario, the four risk tiers, the audit event field list, the 13 → 27 server growth, and the private-beta status.
- Model Context Protocol, Tools specification — the human-in-the-loop SHOULD, the untrusted-annotations MUST, and the server/client security-considerations lists.
Written for umesh-malik.com — no-fluff technical writing on AI, Web Dev, and Engineering.
Originally published at umesh-malik.com
Keep reading on umesh-malik.com:
Top comments (0)