MCP in the enterprise: the problem isn't connecting it, it's deciding what permissions to give it
Critical analysis of permissions, scope, auditing, and limits when connecting AI agents to real systems with MCP.

In the previous article on MCP vs Skills I explained what each one is and when it makes sense to use one or the other. I already hinted there that the security aspect deserved its own article. This is that article.
What I’m seeing in companies worries me. Teams connecting AI agents to production databases, code repos, ticketing systems, infrastructure tools… and when you ask “what permissions does this agent have,” the answer is usually an uncomfortable silence or “the same as the user who set it up.” That’s a serious problem.
Connecting an MCP is easy. Deciding what it can do, what it can’t do, who reviews it, and what happens when it makes a mistake is what truly matters. And it’s what almost nobody is addressing.
The principle of least privilege applied to agents
The principle of least privilege isn’t new. Any engineer with security experience knows it: a system component should only have access to the resources it strictly needs to fulfill its function. No more, no less.
With AI agents connected via MCP, this principle becomes more important and harder to apply at the same time. More important because the agent can make unpredictable decisions. Harder because the scope of what it “needs” isn’t always clear.
An agent is not a script. A script does exactly what you program it to do. An agent interprets instructions and decides which tools to use and how. That difference completely changes the risk profile.
When you give a script read access to a database, you know exactly which queries it will run. When you give that same access to an agent, you don’t. It might decide to run a SELECT * on a table with sensitive data because it seemed relevant for answering a question. Not out of malice, but because it has no concept of “you shouldn’t be looking at that.”
Cases that should keep you up at night
I’m going to list situations I’ve seen or that colleagues have described to me. All real. All preventable.
Agent with access to the production database
The classic case. Someone configures an MCP that connects the agent to PostgreSQL so it can “query data and generate reports.” They give it credentials from a user with broad permissions because “it’s only going to read.”
The problem: the agent receives an ambiguous request, builds a query that does a full table scan of the users table (with emails, names, phone numbers), and returns that data in a response that gets logged in the chat history. Now you have personal data exposed in a system that probably doesn’t comply with your company’s data retention policy.
An even worse variant: you gave it write permissions “just in case it needs to create temporary tables.” The agent misinterprets an instruction and executes an UPDATE it shouldn’t have.
Agent that can create Pull Requests
Connecting an agent to the code repo via MCP so it can generate PRs sounds productive. But if the agent can create PRs directly to the main branch, without review, you’re delegating control of your code to a system that doesn’t understand the full context of your architecture.
I’ve seen agents generate code that passes tests but introduces insecure dependencies, changes API contracts without warning, or overwrites security configuration because it “simplified” the code.
Agent with access to infrastructure
This is the most dangerous one. An MCP that connects the agent to Kubernetes, AWS, or your CI/CD tool. “So it can review logs and diagnose problems.” The agent, trying to resolve an issue, scales a deployment, changes an environment variable, or restarts a service.
This isn’t science fiction. It’s what happens when you give powerful tools to a system that optimizes for “resolve the user’s request” without understanding operational consequences.
Permission matrix: what each tool should be able to do
Before connecting any MCP, you should have a clear permission matrix. This isn’t bureaucracy, it’s basic hygiene. A table like this, adapted to your context:
| MCP Tool | Read | Write | Scope | Requires approval | Sensitive data |
|---|---|---|---|---|---|
| Database (prod) | Predefined views only | No | Non-PII tables | No | Yes, restrict |
| Database (staging) | Yes | Temp tables only | Specific schema | No | No |
| Git Repository | Yes | Only feature/ branches | Non-infra repos | Yes (PR review) | No |
| Jira / Linear | Read tickets | Create comments | Specific project | No | No |
| Jira / Linear | - | Create/move tickets | Specific project | Yes | No |
| Slack | Read public channels | Send messages | Designated channels | Yes | Possible |
| AWS / Infra | Log reading only | No | Read-only role | No | Possible (logs) |
| CI/CD | View pipeline status | No | - | No | No |
| CI/CD | - | Trigger/retry builds | Specific repos | Yes | No |
If you can’t fill out this table for every MCP you have connected, you have a governance problem. Not an AI problem, a governance problem.
Some rules I always apply:
1. Production is read-only, with limited scope. Never write access. No exceptions. If the agent needs to modify something in production, it generates a proposal that a human executes.
2. Personal data doesn’t pass through the agent. If a table has PII (personal data), the agent shouldn’t be able to access it. Use views that mask or exclude those fields.
3. Write operations always require approval. Any action that modifies state (creating a ticket, sending a message, making a commit) goes through human review.
Logs and auditing: what it did, when, with what data
If an agent interacts with your systems and you don’t have detailed logs of that interaction, you’re flying blind. It’s not enough to know that “the agent used the database MCP.” You need to know:
- Which MCP tool it used exactly.
- What parameters it sent (the query, the command, the content).
- What response it received (and whether it contained sensitive data).
- Who initiated the request (which user asked the agent to do that).
- When it happened.
- How long it took.
An example audit log structure:
{
"timestamp": "2026-05-18T14:23:45Z",
"event_type": "mcp_tool_call",
"agent_id": "support-agent-01",
"user_id": "user-4521",
"session_id": "sess-abc123",
"tool": {
"name": "database_query",
"mcp_server": "postgres-readonly",
"parameters": {
"query": "SELECT order_id, status, created_at FROM orders WHERE user_id = $1",
"params": ["user-4521"]
}
},
"result": {
"status": "success",
"rows_returned": 3,
"execution_time_ms": 45,
"contains_pii": false
},
"context": {
"user_prompt": "¿Cuál es el estado de mis últimos pedidos?",
"conversation_turn": 2
}
}These logs aren’t optional. They’re your only way to:
- Investigate incidents: if something goes wrong, you need to reconstruct exactly what the agent did.
- Detect anomalies: an agent suddenly making 500 queries per minute when the norm is 10 is a red flag.
- Meet regulatory requirements: GDPR, SOC2, ISO 27001… all require data access traceability.
- Improve the system: without real usage data, you can’t optimize either the prompts or the permissions.
Alerts you should have
Logging alone isn’t enough. You need active alerts:
alertas_mcp:
- nombre: "Anomalous call volume"
condicion: "calls_per_minute > 50 para un mismo agent_id"
severidad: warning
- nombre: "Sensitive data access"
condicion: "result.contains_pii == true"
severidad: critical
- nombre: "Elevated error rate"
condicion: "error_rate > 20% en ventana de 5 minutos"
severidad: warning
- nombre: "Unauthorized tool"
condicion: "tool.name not in allowed_tools[agent_id]"
severidad: critical
- nombre: "Excessive latency"
condicion: "execution_time_ms > 10000"
severidad: infoHuman-in-the-loop: when to require human review
Not everything needs human approval. If every agent action requires someone to hit “accept,” you lose all the value of automation. The key is defining the boundary well.
My general rule: reversible, low-impact actions can be automatic. Irreversible or high-impact actions require approval.
In practice:
Automatic (no approval needed)
- Check the status of a ticket.
- Read logs from a service.
- Search internal documentation.
- Generate a draft response.
- Query metrics from a dashboard.
Requires human approval
- Create or modify a ticket.
- Send a message to a Slack channel or an email.
- Create a Pull Request or make a commit.
- Modify any data in any environment.
- Execute actions on infrastructure.
- Access data that might contain PII.
Requires approval + second review
- Any action in production that modifies state.
- Deployments.
- Changes to security configuration.
- Access to secrets or credentials.
The question isn’t “do I trust the AI.” The question is “if this goes wrong, what’s the impact and how long does it take to revert.” If the answer is “high impact and hard to revert,” you put a human in front. Always.
The typical flow I implement:
User asks the agent for something
→ Agent decides which tool to use
→ Does the action require approval?
→ NO: execute and log
→ YES: generate proposal
→ Notify reviewer (Slack, email, dashboard)
→ Wait for approval (with timeout)
→ Approved: execute and log
→ Rejected: log and notify user
→ Timeout: log as "not executed"Practical implementation: permission middleware
An effective way to implement this is with middleware that intercepts every MCP call before it reaches the server:
class McpPermissionMiddleware:
def __init__(self, policy: PermissionPolicy, audit_log: AuditLogger):
self.policy = policy
self.audit_log = audit_log
async def intercept(self, tool_call: ToolCall, context: AgentContext) -> ToolResult:
# 1. Verify the agent has permission to use this tool
permission = self.policy.check(
agent_id=context.agent_id,
tool_name=tool_call.name,
action_type=tool_call.action_type,
parameters=tool_call.parameters
)
if permission == Permission.DENIED:
self.audit_log.record_denied(tool_call, context)
raise PermissionDeniedError(
f"Agent {context.agent_id} does not have permission for {tool_call.name}"
)
if permission == Permission.REQUIRES_APPROVAL:
approval = await self.request_human_approval(tool_call, context)
if not approval.granted:
self.audit_log.record_rejected(tool_call, context, approval)
return ToolResult.rejected("Action rejected by reviewer")
# 2. Execute the call
result = await self.execute_tool(tool_call)
# 3. Check the result (does it contain sensitive data?)
if self.policy.requires_pii_check(tool_call.name):
result = self.sanitize_pii(result)
# 4. Log everything
self.audit_log.record_execution(tool_call, context, result)
return resultThe permission policy is defined in a configuration file that lives in the repo, versioned and reviewable:
# mcp-permissions.yaml
agents:
support-agent:
allowed_tools:
- name: "database_query"
scope: "readonly"
allowed_tables: ["orders", "products", "order_status"]
blocked_tables: ["users", "payments", "credentials"]
max_rows: 100
- name: "jira_read"
scope: "project:SUPPORT"
- name: "jira_comment"
scope: "project:SUPPORT"
requires_approval: true
blocked_tools:
- "git_*"
- "infra_*"
- "deploy_*"
dev-assistant:
allowed_tools:
- name: "git_read"
scope: "repos:backend-*"
- name: "git_create_pr"
scope: "repos:backend-*"
requires_approval: true
target_branches_blocked: ["main", "release/*"]
- name: "database_query"
scope: "readonly"
environment: "staging"
blocked_tools:
- "infra_*"
- "deploy_*"What nobody tells you: the organizational problem
Technology is the easy part. What’s really hard is the organizational side:
Who defines the permissions. In most companies there’s no clear role responsible for AI agent permissions. It’s not the security team (they don’t understand the usage context). It’s not the product team (they don’t understand the technical implications). It’s not the developer who configures the MCP (they don’t have the global view).
My recommendation: the platform or engineering team defines the base policies, the security team reviews them, and the product team defines the use cases. All three together.
Who reviews the approvals. If you put human-in-the-loop but nobody reviews the requests, the agent gets stuck and users get frustrated. You need a clear process: who reviews, during what hours, with what SLA, and what happens when nobody is available.
How permissions evolve. The permissions you define today won’t be valid forever. Use cases change, teams grow, agents become more capable. You need periodic review. I suggest quarterly at minimum.
Checklist before connecting an MCP to a real system
Before giving the OK to any MCP integration in your company, go through this list:
- There is a documented permission matrix for this agent.
- Production access is read-only (no exceptions).
- Personal data is excluded or masked.
- There are audit logs for every MCP call.
- Alerts are configured for anomalies.
- Write actions require human approval.
- There’s a clear process for emergency permission revocation.
- Permissions are versioned in the repository.
- There’s a defined owner responsible for reviewing permissions periodically.
- A test has been done asking “what happens if the agent does the worst possible thing with these permissions.”
That last point is key. Before granting access, ask yourself: if this agent goes haywire and uses all its permissions in the worst possible way, what’s the maximum damage it can do. If the answer scares you, reduce the permissions.
It’s not paranoia, it’s engineering
Nothing I’ve described here is paranoia or anti-AI. Quite the opposite: the only way for agent integration in enterprises to scale sustainably is with serious controls. Companies that are connecting agents today without governance are going to have incidents. Some already are.
AI in the enterprise isn’t a playground. It’s production infrastructure that interacts with real data, real customers, and real money. Treat it accordingly.


