
MCP has carried a destructiveHint field since 2025, and the spec tells clients not to trust it, because the server declaring a tool safe is the same party doing the write.
Cloudflare's WriteGuard, in private beta as of last week, sorts every tool call into four tiers and runs policy before the handler executes, moving the judgment from the server to the operator.
If you ship MCP servers, fill in the annotation hints anyway, they help the model behave. If you consume them, never wire an auto-approve rule to a field the other side controls.
What shipped
Cloudflare put WriteGuard into private beta through its MCP server portal. The portal centralises connections to multiple MCP servers, and WriteGuard sits in the middle of that traffic as a policy, attribution, and audit layer.
The classification is four tiers. Read Only covers searching issues, reading merge requests, checking pipeline status. Minimal Impact covers adding reactions, marking notifications read, subscribing to an issue. Contained Write covers adding comments, creating merge requests, updating issue fields. Critical covers merging code, triggering a production deploy, bulk-deleting records.
Anything past Read Only goes through policy before it runs. WriteGuard can let the call through untouched, stamp it with agent attribution and then let it through, or block it before the handler is ever invoked. Audit events go asynchronously to an internal Worker with the server, tool, tier, outcome, user, client, and duration, values scrubbed.
Attribution is where the design earns its keep. Agents already inherit the employee's permissions through Cloudflare Access and OAuth, so an engineer who cannot close an issue has an agent that cannot close it either. WriteGuard adds the MCP client and session on top of the human identity, so a write reads as "agent session acting for this person" rather than just the person.
The field that already existed
This is where it stops being routine plumbing. MCP has carried a vocabulary for exactly this since 2025. Tool definitions carry ToolAnnotations, and inside it four booleans: readOnlyHint, destructiveHint, idempotentHint, openWorldHint.
The defaults are deliberately pessimistic. readOnlyHint defaults to false, so an unannotated tool is assumed to modify things. destructiveHint defaults to true, so an unannotated modification is assumed to be destructive. On paper that is a sane failure mode. An MCP server that ships with no annotations at all should be treated as a pile of live wires.
That is not what happens in practice, for two reasons.
The first is coverage. Plenty of servers ship without annotations, and clients vary in how strictly they honour the pessimistic defaults. The MCP project's own write-up on annotations puts it plainly, and the one adoption number it offers is not encouraging: GitHub's read-only mode, which the annotations enable, is used by roughly 17% of users. The vocabulary exists. The population of it is thin, and the enforcement of it is thinner.
The second reason is structural, and no amount of adoption fixes it. The hint is self-declared. It travels inside the tool definition, which comes from the server, which is the thing performing the write. The spec is explicit that clients must treat annotations as untrusted unless they come from a trusted server. A server that wants its tool auto-approved simply declares readOnlyHint: true and the client that trusts the field waves it through.
The MCP maintainers are not confused about this. Their own framing is that annotations "aren't enforcement," and that if you need a guarantee a tool cannot exfiltrate data, that is a job for network controls or sandboxing, not a boolean.
Why an external classifier is the right shape
Cloudflare did not extend the annotation system. They built a classifier that lives with the operator and ignores what the server claims about itself.
That is the correct trust boundary. The party that bears the cost of a bad write should be the party that decides how dangerous the write is. In WriteGuard's model, tier assignment comes from the tool's configuration plus the request context, held on the portal side, which the connecting organisation controls. A server can lie about itself all day and the tier does not move.
It also puts enforcement in the only place enforcement can live. Annotations inform a model's decision about whether to ask for confirmation. They run before the model acts, inside a system that a prompt injection can talk to. A middleware that blocks the call before the handler runs is downstream of the model entirely. Nothing the model was tricked into wanting matters if the call never reaches the tool.
This is a familiar pattern wearing new clothes. HTTP has had safe and idempotent method semantics since forever, and nobody builds an authorisation layer that trusts the client to have picked the right verb. The verb is a description. The policy engine is the control. MCP annotations are the verb. WriteGuard is the policy engine.
What to actually do
Fill in the annotations on servers you ship. This is not security work, it is ergonomics. A client deciding whether to interrupt a human gets a much better answer from a populated readOnlyHint than from a pessimistic default, and users stop drowning in confirmation prompts for tools that read a row. The four fields cost a few lines each.
Do not build approval logic on the other side of that. If you are the client, and you have an auto-approve path keyed on readOnlyHint, you have handed the decision about what gets approved to every server operator you have ever connected to. That is the same failure as trusting a JWT without checking the signature, and it will fail the same way, quietly, in favour of whoever wrote the payload.
Classify by blast radius, not by name. Cloudflare's tiers are worth stealing verbatim because they separate two things people usually collapse. Adding a comment and merging a branch are both writes, and they belong in different tiers. A four-tier model gives you somewhere to put "reversible, log it and move on," which is where most agent activity actually belongs. A two-state read-write model forces everything into either free or blocked, and teams pick free.
Log the session, not just the user. Once several agents run concurrently under one person's credentials, "who did this" stops being answerable from the identity alone. Capturing the MCP client and session alongside the human is cheap at write time and impossible to reconstruct later.
The broader shape here keeps repeating. A protocol ships a field that describes intent, the field gets treated as a control, and eventually someone builds the actual control next to it. The field was never wrong. It was just never the thing standing between an agent and your production deploy.