LetterMCP
FeaturesLong read

What MCP Tool Annotations Tell You About Agent Behavior

Annotations signal risk but don't guarantee tool behavior stays honest.

Staff Writer · · 11 min read
Cover illustration for “What MCP Tool Annotations Tell You About Agent Behavior”
Features · September 5, 2026 · 11 min read · 2,467 words

MCP tool annotations are metadata flags, four of them, that tell an agent platform what a tool might do before it fires. They showed up in the March 2025 spec revision and now sit at the center of how enterprises try to govern agent behavior. Most teams treat these flags as promises, and that's the mistake. Any platform that builds real controls on top of them has to start by admitting that difference out loud, not burying it in the fine print.

MCP itself, which Anthropic released in late 2024, standardizes how AI agents call outside tools. It's become the backbone of most agentic workflows built since. Before annotations existed, a client had almost nothing to go on except a tool's name and a plain-English description. Annotations gave servers a way to attach behavior metadata, separate from what a tool does, to what it might do to the world around it.

How annotations function as a risk signal rather than a guarantee

Annotations are hints. Say that plainly, because it's the part everyone gets wrong. A server can declare readOnlyHint: true on a tool and then go delete a file anyway at runtime, because nothing in the base spec stops that. There's no cryptographic binding between the annotation a server publishes and the code that actually runs when the tool gets called. Treat an annotation like a signed guarantee and you've already lost the plot.

That gap isn't a bug waiting on a patch. It's the actual governance problem enterprises deal with right now, today, in production.

Annotations still give you something real, worth naming plainly. They let clients build risk-tiered approval flows: auto-approve the read-only tools, stop and ask on anything destructive, but only if the annotation itself can be trusted in the first place. Security teams get a shared vocabulary to write policy against, instead of parsing free-text descriptions and guessing at intent. And they leave a record: whatever the annotation said at the moment a tool got called becomes part of the audit trail, whether or not it turned out to be true later.

What they can't do matters just as much. They can't stop a bad actor from lying, and they don't survive a server quietly changing its own definitions after the fact. Annotations are a starting point, not a finish line. Anyone selling them as a control system on their own is selling something that falls apart the first time someone tests it.

The attack patterns that exploit the gap between declared and actual tool behavior

Tool poisoning is the headline threat. Malicious instructions get buried inside a tool's description or metadata, and the model reads that text as legitimate context, then does something the user never asked for. OWASP classifies this as MCP03:2025, with a DREAD risk score of 46.5/50 (Critical). Under that umbrella sit a few specific tricks: rug pulls, where a tool's behavior changes after the user has already granted trust, and schema poisoning, where the interface definition itself gets corrupted so the model misreads what a tool is even for. Tool shadowing is a third, where a fake or duplicate tool slips into the agent's context and intercepts calls meant for something else.

Invariant Labs showed in 2025 how bad this gets in practice. A malicious MCP server, sharing context with a legitimate one, used tool poisoning to quietly read and export a user's entire message history. No phishing link, no network exploit. Just poisoned metadata sitting where the agent expected to find instructions.

Rug pulls deserve their own line, because they hit annotations directly. A tool looks read-only at install time, gets approved, and the server silently updates the definition later, no notice, no re-approval prompt in most clients. A CVE disclosed in July 2025, high severity, confirmed exactly this: tool approvals in production AI development environments don't hold up against later server-side changes. Translate that into annotation terms and the exploit writes itself: approve a tool as readOnlyHint: true, wait, then reclassify it as destructive on the server once the client has stopped checking.

Indirect prompt injection sidesteps annotations entirely, since the malicious instructions arrive through data a tool retrieves, not through the tool's declared type. The annotation describes the tool; it says nothing about the content that tool hands back. Session hijacking is a separate failure mode. A confirmed CVE targeting MCP session IDs lets an attacker run commands inside an already-authenticated session, and no annotation at any level protects against an identity that's already been stolen.

The common thread: these attacks live in the semantic layer, in how the model reasons about language, and firewalls or signature scanning don't catch any of it. This isn't theoretical. The first confirmed malicious MCP package showed up in September 2025 and sat undetected in the wild for two weeks before anyone caught it.

What the MCP authentication spec now requires and why it matters for annotation trust

The spec's authentication history reads like a direct answer to these failures, one revision at a time. November 2024, the first release, shipped with no authentication at all. March 2025 brought OAuth 2.1 with PKCE, in the same revision as tool annotations, which says something about how the two were meant to work together from the start. June 2025 formally classified the MCP server as a Resource Server, required servers to implement OAuth 2.0 Protected Resource Metadata, and barred passing client tokens through to upstream APIs, closing off the classic confused-deputy vulnerability. November 2025 made PKCE mandatory across every client app and added incremental scope requests, so agents ask for permissions as they need them instead of holding an over-permissioned token from day one.

The June 2025 split between MCP server and authorization server matters more than it sounds. It creates a checkpoint where a tool's declared annotation can get checked against identity-bound permissions before the tool fires, not after the fact. A tool annotated read-only should only ever carry read scopes, and the spec's least-privilege principle now gives platforms the structure to enforce that alignment instead of just hoping it holds.

The Step-Up Authorization Flow, formalized in November 2025, puts that principle into practice. Hit a tool with destructiveHint: true, and the platform can trigger a fresh authorization challenge on the spot, instead of leaning on the annotation as the only gate between the model and the action.

None of this fixes what came before it, though. Plenty of production MCP servers went live before OAuth was even part of the spec and still run on static, unscoped API keys today. Annotation-based controls sitting on top of that kind of setup have no identity layer underneath them; they enforce rules on a foundation that was never built to hold them.

The ETDI framework, proposed in 2025 research, goes further than the base spec. It binds tool definitions, annotation values included, to signed JWTs, so changing an annotation server-side breaks the signature immediately and the client catches the mismatch before the tool ever runs. That's the closest thing yet to a real technical fix for the rug-pull problem, and it's the direction the rest of the spec should probably follow.

Translating annotation properties into enforceable access policies

Annotations give policy writers something plain-English descriptions never could: a fixed, countable set of properties that maps straight onto access decisions.

The mapping isn't complicated. A tool with readOnlyHint: true is a candidate for auto-approval, broad access, no confirmation gate, as long as its provenance checks out. destructiveHint: true should require a human in the loop every single time, trigger a step-up challenge, and never run under full autonomy, full stop. openWorldHint: true marks the highest-risk category for data leaving the building; it needs egress policy, scope checks, and logging at the level of the individual call. And idempotentHint: false, the default assumption in the spec, means retry logic needs a guard rail: a failed call to a non-idempotent tool should never get auto-retried without a person looking at it first.

Scopes need to live at the tool level, not the server level. A read-only tool sharing a scope with write-capable tools on the same server defeats the entire point of the annotation. The NSA's published guidance backs this with a zoning recommendation: keep tools touching public data separate from tools touching sensitive or regulated data, and annotation properties are the natural line to draw that zone along.

Real enforcement happens at tool-call depth. The policy engine checks annotation values, agent identity, session state, and runtime context on every single call, not once at session login and then never again.

Here's the catch that undoes all of it if ignored: a policy built on annotation values is only as good as whatever verifies those values. A platform that reads annotations live off the server and enforces policy on whatever comes back is running an honor system with extra steps. What's actually needed is a registry: a locked, validated set of tool definitions with annotation values pinned, so policy gets checked against something that can't get quietly rewritten mid-flight.

What annotation-aware audit logs actually need to capture

Logging that a tool got called, without logging what it claimed to be at that moment, leaves a hole in the record. The audit trail needs to show the tool's declared behavior at invocation time, not just its name.

At minimum, each logged call should carry the tool identifier and which server it came from, the annotation values as declared at that exact moment, the agent's identity and the human it was acting on behalf of, the parameters passed, and whatever policy gate got triggered along with its outcome.

That last piece opens the door to something useful: drift detection. If the annotation values for the same tool change across two log entries without a deliberate version bump, that's a signal worth chasing down. Maybe a rug pull. Maybe server-side tampering. Either way, the audit log is what catches it, even if nothing caught it in real time. The NSA's guidance calls for logging every tool and model invocation with exact parameters and identities attached; annotation values belong in that same record, not off to the side as an afterthought.

Nothing here matters if the log can be edited after the fact. Tamper-proof storage is a precondition, because an annotation value sitting in a mutable log is exactly as trustworthy as one read live off a compromised server.

The stakes climb fast in multi-agent setups. Research from Galileo AI on multi-agent systems found a single compromised agent can poison downstream decisions across a connected system within hours, faster than most incident response teams can move. Annotation-aware logs stretched across the full agent graph are the only real way to trace how that kind of failure spread after the fact. Regulatory frameworks, including CISA's joint guidance on AI trustworthiness and data security, increasingly address agent infrastructure, and annotation-captured logs are what turns that monitoring into something an auditor can point to.

Runtime detection: when annotation signals indicate something has gone wrong mid-execution

Annotations set a declared baseline. Runtime monitoring checks whether actual behavior is drifting away from it.

A few patterns are worth watching for directly: a read-only tool suddenly triggering writes downstream, a tool with no openWorldHint reaching out to an external endpoint anyway, a destructive tool getting called over and over without ever hitting its confirmation gate, or an annotation value at call time that doesn't match what got approved during policy evaluation.

There's a subtler failure mode, too: intent drift, where an agent's chain of tool calls wanders away from the user's actual goal even though each individual call looks fine on its own. Annotation properties help define what a normal action profile looks like for a given task, which turns that kind of drift into something you can flag instead of something that just feels off in hindsight.

Indirect prompt injection won't show up at the annotation level at all, since it arrives through content a tool retrieves rather than through the tool's own type. Catching it means inspecting what tools return, not just what they're labeled as. Tool shadowing and tool poisoning are trickier still, because the annotation values involved can look perfectly correct at a glance. Catching them means checking declared values against a verified, out-of-band registry instead of trusting whatever the server hands back in the moment.

None of these threats sit in isolation, either. A supply-chain compromise installs a bad server, the bad server enables tool poisoning, the poisoning steals credentials, and the stolen credentials open the door to more compromise downstream. Runtime detection has to work at the system level to catch chains like that, not tool by tool. Several enterprise governance platforms only added real runtime agent monitoring in 2025 and 2026, and the strongest ones pair annotation-aware policy with behavioral anomaly detection rather than treating the two as independent concerns.

What enterprises actually need from a platform that acts on annotation signals

Annotations without a verification layer underneath them are a policy fiction dressed up as a control. Real enforcement checks annotation values against signed or registry-locked definitions, never against whatever the server happens to return live in the moment. That distinction separates a platform that governs agents from one that just logs what they did after the damage is done.

Three things have to work together for this to hold up. Enablement comes first: broad access to tools across the ecosystem, with OAuth and credential handling built in, because if the governance layer is a hassle, teams will route around it within a week. Control comes next: one inventory of every server, tool, and annotation value across the organization, policy written against that inventory and enforced at tool-call depth, identity tied to every action through SSO and just-in-time credentials. Security closes the loop: real-time detection for the threats that specifically exploit annotations, tool poisoning, rug pulls, intent drift, injection, backed by audit logs that can't be edited and that flag drift the moment annotation values stop matching between calls.

Fragmentation is where most of this quietly falls apart. A team running a separate agent builder, a separate API gateway, and a separate security scanner can't enforce one coherent annotation policy across all three, because each system keeps its own record of the same event. Policy, audit, and detection end up disagreeing with each other simply because they're not looking at the same data.

The NSA's minimum-access principle maps onto this cleanly: tools annotated as open-world or destructive are exactly the ones that need the tightest zoning, the most logging, and the least trust extended by default. Annotations gave the industry a shared language for talking about agent risk. The verification and enforcement layer that makes that language mean something when it counts is what enterprises still have to build, largely from scratch, and largely right now.

Sources

  1. blog.marcnuri.com
  2. blog.modelcontextprotocol.io
  3. stacklok.com

More in Features