Fail-close: the tool-access default every AI agent should ship with
I spent the better part of sixteen years building payment platforms. The first principle you internalize there, before any framework or pattern, is that the safe state is the closed state. A transaction that can't be verified doesn't get a "probably fine" — it gets declined. A service that loses its dependency doesn't keep serving stale balances — it stops. You learn to design systems where the failure mode is refusal, not improvisation.
Then I started building AI agents wired to real tools through MCP, and I watched the industry reach for the opposite default. Agents ship with broad tool access, allow-by-default, and a vague intention to "add guardrails later." That instinct is backwards, and it's going to cost teams who don't fix it before they go to production.
What "fail-close" actually means for an agent
Fail-close is a default you choose before the first tool is registered, not a guardrail you bolt on after. It means the default answer to "can this agent call this tool right now?" is no, and every yes is an explicit, scoped, auditable grant.
Most agent setups today do the reverse. You register a set of MCP servers, the model gets the full menu, and the only thing standing between a hallucinated plan and a destructive action is the model's own judgment plus maybe a confirmation prompt the user clicks through on autopilot. That's allow-by-default with a speed bump. It works in demos. It fails the first time the model decides that the cleanest path to "tidy up the staging database" runs through a DELETE it was never supposed to reach.
A fail-close agent inverts that. The agent starts with zero tool authority. Access is granted per tool, ideally per operation, against an identity and a context. When something is ambiguous — an unrecognized server, a tool outside the current task scope, a parameter that looks like it touches production — the system denies and surfaces the denial instead of guessing.
The distinction matters because models are non-deterministic and your blast radius is not. You cannot reason about what the model won't do. You can only constrain what the surrounding system will permit.
What breaks when you don't
The failures aren't exotic. They're the boring, predictable consequences of giving a probabilistic system deterministic power without a fence.
Give an agent read and write access to the same datastore and it will eventually write when you wanted it to read, because the boundary between "summarize these records" and "update these records" lives only in the prompt, and prompts are suggestions. Hand it shell access and a filesystem MCP, and somewhere down the line it runs a command that's correct in isolation and catastrophic in context. The same goes for an internal API reached with a service token: a confused enough plan will call the destructive endpoint with full confidence.
None of these require malice or a clever injection attack. They're just the base rate of a system that improvises under uncertainty being handed tools that don't. Add prompt injection from untrusted content the agent ingests, and the allow-by-default posture stops being a reliability problem and becomes a security one. The injected instruction doesn't need to break out of a sandbox if there was never a sandbox to break out of.
When I owned security review for shipping AI features, the question I asked first was never "is the model good." It was "what is the worst single tool call this thing can make, and who decided it was allowed to." If the answer to the second half was "nobody, it just had access," the feature didn't ship.
Governance and RBAC for MCP tools
One thing to be clear about first: the MCP protocol itself does not define roles, authorization, or RBAC. It describes how a client and server talk, not who is allowed to call what. That control plane is yours to build, and the natural place for it is the host application sitting between the model and the servers, the layer that sees every tools/call before it goes out and can decide to refuse it.
RBAC, scoped service identities, and audited grants already run every bank API and every CI deploy key in production. An agent is just another non-human principal asking for access. We have the machinery; we just have to stop pretending agents are a special category that gets to skip it.
Treat every MCP tool as a privileged operation behind role-based access control. The agent runs under an identity. That identity has a role. The role grants a specific, enumerated set of tool operations, scoped to an environment. A research agent gets read access to a knowledge base and nothing that writes. A deployment agent gets a narrow set of operations against staging and a separate, more guarded grant for production that requires a stronger gate.
Three things make this real rather than theatre.
Grants are per-operation, not per-server. Registering a database MCP server should not hand over its entire surface. Read and write are different privileges and belong to different roles, the same way you'd never give an analyst the production write credential just because they need to query.
Logging comes next: every tool call carries the identity, the role, the arguments, and the decision. You want to be able to answer "what did this agent do and what was it allowed to do" after the fact, with a trail, not a guess. The denials matter as much as the approvals; a spike in denied calls is a signal that the agent's plan has drifted from its scope.
And the dangerous grants pass through a human or a stronger policy gate before they're active. The point of fail-close isn't to block everything forever. It's to make sure that anything with real blast radius is a decision someone made on purpose, with their name on it.
The instinct, ported
I maintain an MCP server that runs across several agent clients, including Claude Code and Cursor. Building and operating it taught me how casually broad the default tool surface tends to be, and how little friction there is between a registered server and full access to whatever it exposes. The convenience is real. So is the exposure.
The reliability instinct from payments transfers cleanly: the safe state is the closed state, and you earn your way out of it one explicit grant at a time. We learned to stop treating "the agent can't do that yet" as a limitation. In production, it is the whole point.
The shift is small to describe and easy to defer. Choose the default before you register the first tool, not after the first incident. Everything else in this post is just the consequence of getting that one decision right while it is still cheap.
Sources
- Model Context Protocol — Security Best Practices — the protocol's official guidance on consent, confused-deputy risks, and host-side controls.
- Model Context Protocol — Tools — defines tools as model-controlled and calls for trust boundaries and human-in-the-loop confirmation.
- OWASP — LLM01: Prompt Injection — why untrusted content can redirect an agent's actions.
- OWASP — LLM06: Excessive Agency — the blast-radius risk of granting agents excessive functionality, permissions, or autonomy; recommends least-privilege scoping.
- NIST — Least Privilege (glossary) — the foundational principle behind per-operation, role-scoped tool grants.
Top comments (0)