Drafted with AI help, human-reviewed by The Agent Loop.
Short version: MCP can describe a tool as dangerous. Almost nothing in the stack is required to treat it that way. Annotations get dropped, hosts gate at the UI, servers assume someone else enforced it. I read the spec-shaped complaints, one arXiv study, and a Vercel issue that shipped a checkout tool with zero approval events. The gap is not “we forgot one config flag.” It is that permission was never a protocol primitive.
For skimmers
- MCP auth today ≈ optional transport OAuth, not per-tool policy
-
destructiveHint/readOnlyHintare hints; a client can ignore them (see repro below) - Host “approve?” dialogs live in the UI; MCP servers often never hear about them
- Scanners: 71% of 100 MCP servers scored F on one 2026 framework (vendor research, label it)
- Today’s fix: enforce server-side (or a gateway), default deny for write/delete, log every call
What MCP actually specifies
The Model Context Protocol standardizes how models find and call tools. It does not standardize:
- which agent may call which tool
- read vs write vs destroy as a capability the runtime must enforce
- call budgets, rate limits, or session budgets
- an audit schema every server must emit
A practitioner who shipped five security MCP servers (EDR, proxies, bug-bounty APIs) wrote the same conclusion I keep hitting: every server invents its own ACL because the protocol leaves authorization to the implementer (johnmatrix, Jul 2026). That is a design choice, not a temporary bug.
Tool metadata does include safety-ish fields in the schema (readOnlyHint, destructiveHint, idempotentHint). See the MCP schema. Metadata is not enforcement.
The repro that made me stop
Vercel AI SDK issue #20184 walks through an official-style MCP commerce example:
| Arm | Setup | Result |
|---|---|---|
| 1 | Example wiring as shipped |
submit_order created (2500¢), approval events: 0
|
| 2 | Inspect converted tool | no needsApproval; destructive annotations gone |
| 3 | Explicit policy wrap | approval gate present, execution blocked |
The conversion path kept annotations.title and dropped the rest. The server said “destructive, not idempotent.” The client never asked a human.
That is the whole blog post in three rows: declare ≠ decide ≠ enforce.
Numbers worth knowing (with labels)
| Claim | How to treat it |
|---|---|
| 97.1% of 856 tool descriptions had ≥1 “smell”; 56% unclear purpose | Peer-style arXiv study of 103 servers (2602.14878) |
| 71% of 100 servers scored F; 163 hallucination-based vulns | Vendor scanner research (AgentsID, 2026): directionally useful, not neutral audit |
| ~8.5% of servers implement OAuth | Secondary industry write-up; treat as order-of-magnitude |
| 1,862 public MCP servers answered unauthenticated | July 2025 scan cited in CSA note |
| Tool-poisoning peak success 72.8% (mean 36.5%) | AAAI 2026 paper as summarized in secondary coverage |
I do not need all five to make the point. Even one exploded description field plus one zero-approval checkout is enough: the dangerous path is the default path.
Why host “Allow” is not a security boundary
Interactive hosts (Claude Code, desktop apps, IDEs) ask you to approve tool calls. Good UX. Three leaks:
- Different process. The MCP server may only see “a tool ran,” not “a human clicked Allow on this argument set.”
- Different rules. If the host forgets the gate (example above), nothing downstream re-checks.
- Persuasion. Prompt injection (our last post) aims at the reasoning layer. Policy that lives only in the model’s head can be talked into a “yes.” Execution-time checks do not care how nice the story was.
What to do before MCP grows a real permission model
I would not wait for a standards working group.
-
Default deny writes. Read tools can be broad;
create/delete/send/paystart blocked. -
Enforce outside the model. Gateway or server-side policy (OPA, Cedar, even a boring allowlist in config). Bedrock AgentCore and Vercel’s
@ai-sdk/policy-opaexist because people hit this wall. -
Never ship
mcpClient.tools()straight into the model loop for anything with side effects. Filter, wrap, or gate first. - Pin and re-approve tool lists. Rug pulls change definitions after day-one approval; re-prompt on schema diff.
- Scope credentials per server. One god-API-key in an env var is a prompt injection away from your whole cloud account.
- Log tool name + args + policy verdict. If you cannot answer “what did it call last night,” you cannot incident-respond.
Bottom line: treat MCP like a plugin system for a probabilistic process. Annotations are comments. Something boring and deterministic has to say no.
FAQ
Does MCP have built-in permissions?
No first-class per-tool permission model. Auth work targets transport identity (OAuth), and authorization is largely optional / implementation-defined.
What do destructiveHint and friends actually do?
They declare intent in the tool metadata. Clients are supposed to use them. Issue #20184 shows a conversion path that did not, and a checkout tool ran with no approval.
So is MCP insecure?
MCP is a connection protocol. Security is whatever your host, server, and gateway implement. Most of the ecosystem implements less than the spec’s own “shoulds.”
Where do I start if I run an MCP server tomorrow?
Server-side authz on every mutating tool, unique credentials, deny-by-default for dangerous verbs, audit log. Then argue about protocol politics.
Sources
- Prompt injection in 60 seconds (our post)
- MCP tool schema (annotations)
- vercel/ai #20184: commerce example, annotations dropped, 0 approvals
- MCP permission models are broken (practitioner writeup)
- arXiv 2602.14878: MCP tool description smells (856 tools)
- CSA: MCP security crisis research note (2026)
- Policy-as-code enforcement for agent tool calls
- The MCP authentication and authorization gap
Related on The Agent Loop
Over to you: Has an MCP approval gate ever silently not fired for you? What broke? Reply below — I read every comment.
Top comments (0)