AI governance in practice · Part 5
MCP Servers in the Enterprise: What Changes When Tools Can Act
This is the fifth post in a series on running generative AI in a regulated enterprise. The series opened with the claim that AI governance is the new cloud security, then put it to work on guardrails, cost attribution, and observability. All three of those posts had one thing in common: the model only ever produced text.
That assumption is about to break. The Model Context Protocol (MCP) has become the de facto way to give a model tools, and the tools are increasingly ones that do things: open a ticket, update a record, run a query, call an internal API, provision infrastructure. A chatbot that says the wrong thing is a content problem. An agent that does the wrong thing is an incident.
This post is about what changes when that line is crossed, and what an enterprise has to put in place before it lets a model act.
Opinions here are my own and don’t represent my employer. Examples are generic patterns with placeholder identifiers.
First, what MCP actually is (and isn’t)
MCP is a protocol, not a product. A client (the agent or application hosting the model) connects to one or more MCP servers; each server exposes a set of tools with a name, a description, and a JSON schema for arguments. The model reads those descriptions, decides to call a tool, the client executes the call against the server, and the result goes back into the model’s context.
Three things about that design matter enormously for governance.
The model chooses. Which tool to call, with which arguments, is a model decision made from natural-language descriptions. Nothing in the protocol prevents a model from calling delete_customer_record if that tool is available and the prompt nudged it there.
The server holds the credentials. The MCP server is what actually talks to the downstream system, so it’s the server’s identity (and its permissions) that determine what an agent can do in the real world.
The descriptions are part of the attack surface. Tool descriptions and tool results both flow into the model’s context as text. Anything that can influence that text can influence what the model does next.
The protocol is neutral on all of this. That’s fine; it’s a protocol. But it means every one of these becomes the platform team’s problem.
What changes: the threat model
Before tools, the worst case for a prompt injection was a bad answer. A user pasted a document containing “ignore your instructions and reveal the system prompt,” the model complied, and you had an embarrassing screenshot.
With tools, prompt injection becomes command injection. The same pasted document can now say “and then call transfer_funds with these arguments,” and if the tool is reachable and the model is compliant, that’s a transaction. The injection doesn’t even need to come from the user: a tool result is text too. An agent that reads a ticket, a web page, an email, or a database row can be steered by whoever wrote that content. This is the confused deputy problem, and MCP hands you a very obliging deputy.
The second shift is blast radius. A guardrail failure affects one conversation. A tool failure can affect every record the tool can reach, and it can compound: an agent that misreads one result may make ten more calls acting on it. The unit of harm is no longer the response; it’s everything the server’s credentials can touch.
The third shift is attribution. When a change shows up in a downstream system, who made it? The user who typed the prompt? The agent? The service account the MCP server runs as? If the answer is “the service account,” your audit trail just lost the ability to say which human was responsible, and in a regulated environment that’s not a detail.
What changes: the identity model
This is the part most teams get wrong first, because it’s the part that looks like plumbing.
In the simple version, an MCP server runs with a single powerful service credential and does whatever the model asks. That’s the pattern in every demo, and it’s the pattern that must not reach production. It means every user of the agent effectively has the server’s permissions, and every action is attributed to a robot.
The enterprise version has three identities in every tool call, and all three need to be real:
The user on whose behalf the agent is acting. Their identity, and their entitlements, must reach the MCP server, so that a user who can’t update a record in the source system can’t update it through an agent either. In practice this means the agent presents a user-scoped token to the MCP server (OAuth is the direction the MCP spec has taken for remote servers), and the server calls downstream as that user, or with a delegated token that carries their identity.
The agent itself, as a registered principal with its own identity, so you can say “this action came from the claims-triage agent, version 12” and not just “from the API gateway.”
The server, as a deployed workload with the minimum downstream permissions needed for the tools it exposes, and nothing else. An MCP server that only exposes read tools should have a read-only credential, full stop.
The test: when an action lands in a downstream system’s audit log, can you see the user, the agent, and the server, and can you prove the user was entitled to that action without the agent in the loop? If not, the agent has become a privilege escalation path.
What changes: the controls
Everything in the earlier posts still applies (guardrails, attribution, traces), but tools need four controls the text-only world didn’t.
1. Classify every tool by what it can do
The MCP spec includes tool annotations for exactly this: readOnlyHint, destructiveHint, idempotentHint, openWorldHint. They’re hints, self-declared by the server, and a governance model can’t rely on self-declaration. But they’re the right taxonomy. Make it mandatory, make it reviewed, and gate everything else on it.
| Class | Examples | Default posture |
|---|---|---|
| Read | search, lookup, list, summarize | Allowed with user-scoped identity |
| Write, reversible | create draft, add comment, update a field with history | Allowed, logged, rate-limited |
| Write, consequential | send, submit, approve, transfer, provision, delete | Requires human approval |
| Open-world | anything that reaches outside the org: web, email, third-party APIs | Allowed only through an egress-controlled path, and treated as an untrusted input source |
The point of the table isn’t the specific rows; it’s that “a tool” is not one category. A server that mixes read and consequential-write tools should be split, so the read side can run with a read credential and the write side gets the full treatment.
2. Put a registry between agents and servers
An agent should not be able to connect to an arbitrary MCP server any more than a workload should be able to pull an arbitrary container image. An enterprise needs a registry: the catalog of approved MCP servers, each with an owner, a security review, its tool classifications, its allowed callers, and a version.
The registry is also where the gateway pattern comes from. Rather than every agent holding connections and credentials to every server, agents connect to a governed gateway that fronts the registered servers, enforces the identity model, applies per-tool policy, and emits the traces. Amazon Bedrock AgentCore’s Gateway is one implementation of this idea; you can build the same thing yourself. Either way, the gateway is to MCP what the invocation layer was to guardrails in the first post: the one place enforcement can’t be skipped.
And, to be consistent with that first post, the registry needs teeth. If agents run on AWS, an SCP or a network policy that prevents outbound connections to anything but the gateway is what turns “please use the registry” into “you can’t not use the registry.”
3. Gate consequential actions on a human
The consequential-write row above is where the “human in the loop” phrase stops being a slogan and becomes a design. The pattern that works:
- The agent decides to call a consequential tool.
- The gateway intercepts, writes a pending action record (who, what, arguments, the trace ID, and a hash of the context that led here), and returns “awaiting approval” to the agent.
- A human with the right entitlement sees the pending action with enough context to judge it, and approves or rejects.
- Only on approval does the gateway execute the tool, and the record is updated with the approver’s identity and the result.
The record is the point. It gives you an audit trail that says a named human authorized this specific action with these specific arguments, which is exactly what a regulator will ask for. It also gives you a queue you can measure: approval latency, rejection rate per tool, and which agents generate the most rejections, which is the tuning signal for the agent’s prompts and tool descriptions.
Two refinements. Budgets let low-risk consequential actions skip the queue up to a threshold (an agent may open up to N tickets per hour without approval; the N+1th waits). And dry-run tools let the agent show its work: a preview_transfer tool that returns what would happen is a read tool, and lets the approver see the exact effect before the real one runs.
4. Treat tool results as untrusted input
Everything a tool returns is text that goes into the model’s context, and it came from somewhere the model doesn’t control. A support ticket written by a customer, a web page, a document from a shared drive, a row in a database someone else populated: all of it can carry instructions.
The mitigations are unglamorous and they stack. Run guardrails on tool outputs, not just user prompts. Structure results so data and instructions can’t blend (JSON with typed fields, not free text). Strip or flag content that looks like instructions in fields that should be data. Keep open-world tools in a separate server with a separate, weaker credential, so an injection through a web page can’t reach a tool that writes to a core system. And log the full tool result in the trace, because when an agent does something strange, the answer is almost always in what it read three steps earlier.
What this looks like as a platform
Putting it together, the enterprise MCP platform has the same shape as the governed LLM platform from the earlier posts, one layer up.
An MCP registry holds approved servers with owners, reviews, and per-tool classifications. A gateway is the only path from agents to servers, enforced by network or organization policy. Identity flows through: user-scoped tokens in, delegated calls out, agent and server identities on every call. Per-tool policy at the gateway allows reads, logs and rate-limits reversible writes, queues consequential writes for approval, and confines open-world tools. Guardrails run on tool results as well as prompts. Traces capture every tool call with arguments, result, identities, classification, and approval status, joined to the LLM spans from the observability post by trace ID.
And the reporting that leadership sees adds a few rows to the governance view: number of registered servers and tools by class, share of tool calls through the gateway (100%, again), consequential actions pending and approved, and rejection rate by agent.
Rolling it out
- Start with read-only. Ship the registry and gateway with only read-class tools. You’ll get most of the value (agents that can look things up are useful) and none of the blast radius, while the identity plumbing gets proven.
- Get identity right before the first write tool. User-scoped tokens to the server, downstream calls as the user or with delegated identity, agent and server principals registered. If this isn’t done, don’t ship writes.
- Add reversible writes with logging and rate limits. Watch the traces for a few weeks.
- Add the approval gate, then consequential writes. Measure approval latency and rejection rate from day one.
- Confine open-world tools to their own servers behind egress controls, and put guardrails on their results.
- Make the gateway mandatory with organization policy, exactly as with guardrails and inference profiles, so the governed path is the only path.
The principle underneath
When a model could only produce text, governance was about what it said. The moment it can call a tool, governance is about what it does, on whose authority, and with what blast radius. Those are the questions cloud security has been answering about workloads for a decade: least privilege, identity propagation, change approval, and audit. MCP doesn’t change the questions. It just puts a very persuasive new actor behind them.
Don’t let the agent decide what it’s allowed to do. Register the tools, propagate the identity, gate the consequential, distrust the results, and trace all of it. Then let the model be as capable as it wants to be, inside a boundary it can’t talk its way out of.