MCP Security for Enterprise Agents: Why Protocols Need Governed Tools
MCP can connect agents to tools quickly, but enterprise systems need identity, permissions, approvals, and audit behind every tool call.
The short version: Connection protocols such as MCP and A2A solve how agents reach tools and other agents. What enterprises still need is the layer behind those protocols: governed tools that carry identity, enforce permissions, and leave an audit trail on every call without hand-written glue.
An engineer at one company spent an afternoon wrapping the production database in an MCP server, getting the internal agent “connected.” The demo went smoothly; the boss was pleased.
The next day, a salesperson casually asked the agent, “Export all customers’ contract amounts into a table for me — I want to see the big picture.”
The agent complied, including contracts in other regions and under other people’s names that this salesperson had no right to view. It had no malice; it was just very helpful. The problem was that the wrapper thrown together that afternoon had no concept of identity: it did not know who was asking or what that person was allowed to see. What it exposed to the agent was everything the service could touch.
This table was later forwarded into a group chat. By the time someone noticed something was off and the security team stepped in, the damage was done: customer commercial terms and internal performance numbers had been shared with people who should not see them. Afterward, no one could say exactly who had seen what, because those calls never entered any audit ledger at all.
This happened precisely because “connecting” is now too easy.
Connection Got Easier. That Is Where the Risk Starts.
First, acknowledge the big thing: agent connection protocols are becoming a real standardization layer. MCP gives agents a common way to connect to tools and data sources; A2A addresses agent-to-agent interoperability. “How an agent connects” is quickly becoming less of the scarce problem.
But the success itself creates a trap. Connecting suddenly takes a few hours, so the genuinely hard part, once connected, is it allowed to do this and is the action recorded, becomes easy to postpone as a “do it later” item. That opening afternoon is how it happens.
MCP is useful in the same way a network protocol is useful: it standardizes how one side reaches the other. But no one treats raw connectivity as a complete production security model. On top of connectivity, you still need identity, authorization, approval, and evidence. The buzz around connection is real; the governance layer above it still has to be built.
”We’ll Add Our Own Auth” Is Not Enough
A responsible team will push back: that afternoon was cutting corners. We would not do it that way. We will add our own auth to each MCP server and expose only vetted tools.
That’s correct, but it underestimates two things.
First, it does not scale. You can carefully add auth to one or two servers, but an enterprise quickly has dozens of servers and hundreds of tools, each requiring its own hand-written identity verification, permission judgment, and audit recording. That becomes another pile of unread glue code, this time growing on the tool layer.
Second, “we will be careful” is not an architecture. It is a promise that depends on people never rushing and never taking shortcuts. The moment a deadline arrives, “connect first, add auth later” is the easiest path. A security property that relies on everyone being conscientious every time will eventually break. The engineer in the opening story was not ignorant of security; he was short on time.
The real fix is not “hand-write it more carefully.” It is making “carries identity, carries permissions, carries a record” the default form of a tool, unskippable and not hand-written each time.
Wrapping a Raw Interface as a Tool Can Become a Superuser Pass
Why are raw interfaces so dangerous for agents? Because the interfaces being wrapped were often designed for trusted backend services, not for an agent that makes its own decisions. They assume the caller has already been authorized, so they may not verify identity themselves. Exposed directly as MCP tools:
- What the agent gets is an interface with no user identity. It does not “query as this salesperson”; it queries with the service’s permissions;
- Its write operations are counted against the service’s permissions, not the initiator’s, so errors are hard to attribute;
- These calls enter no business audit ledger, so there is nothing reliable to look up afterward.
In one line: wrapping a raw interface as an MCP tool can accidentally issue the agent a superuser pass.
The Missing Layer: Identity, Permissions, and a Record on Every Call
What needs to sit above the protocol is a governed tool layer, where the agent does not call raw interfaces directly but instead calls controlled actions. Each action goes through the same gates a human would on every call:
The raw-interface approach skips the middle steps, and what gets skipped is precisely “is it allowed to do this?” and “is the action recorded?” The step most easily overlooked is identity propagation: the agent must call under the initiator’s identity, not under the service’s own. Once identity is right, “this user can only see their own region’s contracts” becomes a fact the runtime can enforce, not a prompt the model is expected to obey.
When Agents Call Other Agents, Identity Must Travel With Them
Identity propagation becomes even more critical, and more prone to holes, once agents start calling other agents.
Picture this: a salesperson tells a sales assistant agent, “Help me prepare this customer’s renewal materials.” This agent cannot do it alone, so it calls the finance agent to pull payment terms and the contract agent to fetch historical clauses. Now the problem appears: when the finance agent executes, whose permissions is it using? If any hop in the chain loses the initiator’s identity and becomes “query under the finance agent’s own service permissions,” the salesperson may see financials they had no right to see. Privilege escalation may not happen on the first hop; it can happen quietly on the second or third.
In raw-interface-style connection, this “identity evaporating in the chain” is easy to produce. Because there is no unified audit ledger, you cannot reconstruct afterward who in this relay saw what. What a governed tool layer must get right is letting the initiator’s identity propagate all the way down the agent chain, with each hop verified and recorded against the original person’s permissions. A2A handles wiring agents together; the governance layer must ensure permissions are not lost and the record is not broken across the relay.
Five Questions to Check Whether Your Tool Layer Is Healthy
- When the agent calls a tool, does it carry the initiator’s identity, or the service’s?
- Does a privilege-exceeding call get stopped on the spot by the runtime, or does it rely on the model “conscientiously not doing it”?
- Does every tool call enter the audit ledger?
- When an agent calls an agent, does the original person’s identity get passed down?
- To change one permission, does it take effect in one place, or do you have to change it in every server?
Tools Should Grow Out of Definitions
So how do you avoid hand-writing while being secure by default? Let tools grow automatically out of business definitions. You declare objects and permissions, say for a customer-service refund scenario:
export const Refund = ObjectSchema.create({
name: 'support_refund',
fields: { amount: Field.currency({ label: 'Refund Amount', min: 0 }) },
});
export const AgentRole: Security.PermissionSet = {
name: 'support_agent',
objects: {
support_refund: { allowRead: true, allowCreate: true, allowEdit: false, allowDelete: false },
},
};
The runtime (ObjectOS) then automatically projects this into a set of governed tools the agent can call: query refunds and create refunds, each with permission checks built in, executing under the logged-in user’s identity, and landing in a unified audit ledger. “Over-limit refunds go through approval” becomes a process attached to the object, automatically pausing for a signature when the tool reaches it. None of this has to be hand-written in tool code. Change one permission, and all related tools change in one place.
Compare against the dangerous version and the difference is clear:
// Raw tool: hands the risk straight to the model's "conscience"
{ "name": "run_sql", "description": "Execute arbitrary SQL queries", "params": { "sql": "string" } }
You do not write permission code for each tool. You declare permissions, and the tools are a projection of the definition. MCP handles “how to connect.” This layer handles “once connected, is it allowed to do this, and is the action recorded?”
First, Some Cold Water: It Governs “Can,” Not “Should”
Draw a clear boundary here, or it is easy to overstate the claim.
The governed tool layer constrains the blast radius, not the judgment. It can prevent an agent from doing things it has no right to do, which is important; the opening leak would have been stopped by it. But it cannot stop every “within-permission but wrong” action. If a customer-service agent legitimately has the right to issue refunds, and it issues a refund that should not have been issued, the tool layer may allow it because the action is within permission.
In other words, it solves “privilege escalation” and “no record,” not “is the decision right.” The latter still relies on evals, approval gates for high-risk actions, and process design. Anyone who says “connect a governed tool layer and the agent is absolutely safe” is overstating. It is the necessary foundation, not the whole building.
Closing
MCP gives the agent ecosystem a useful connection layer, but connection is not the same as production governance.
That “helpful afternoon” from the opening will become more common because connecting is easy. Once agents start calling other agents, ungoverned connection can bury privilege escalation deeper in the chain. What will be scarce and valuable is not another connection protocol, but the tool layer behind the protocol: one that enforces permissions, preserves identity along the chain, leaves a record, and does not have to be hand-written for every tool. It does not solve everything, but without it, enterprises will hesitate to ship the rest.
npm i -g @objectstack/cli && os start
Declare an object and its permissions, have an agent use a tool to try to exceed its permissions, and watch the runtime stop the attempt and record it to the ledger.