If you’ve ever connected an AI agent to a real API, you know the feeling: excitement turns to dread the moment you realize the agent has permission to do anything that token allows. I’ve seen teams give an LLM write access to a production database because the integration was easier that way. The agent didn’t cause damage, but the anxiety lingered. The core problem isn’t the AI—it’s the access model.
Most tools and APIs were designed for human users who understand context, responsibility, and consequences. AI agents don’t. They process instructions literally and at speed. If a Slack bot can delete messages and someone prompts it to “clean up the channel,” you lose history. If a code assistant can push to main and gets a bad suggestion, you deploy broken code. This isn’t hypothetical—it’s a predictable failure mode.
You need a way to define, enforce, and audit what an agent can do without turning every integration into a custom authorization shim. That’s where the Model Context Protocol (MCP) comes in.
The Core Problem: Unconstrained Access
When you give an AI agent a tool, you’re handing over an API key wrapped in function calls. Most existing systems have binary permissions: read or write, allowed or denied. For a human, that’s often fine. For an agent, it’s a recipe for collateral damage.
I’ve worked with agency teams who built internal agents that hit third-party APIs directly. They’d create a service account with full access because scoping down took extra time. Then they’d deploy the agent to production and hope the rate limits and prompt engineering were enough. They weren’t. One team had an agent accidentally close 200 support tickets because it misread a bot’s message as a command to bulk-resolve pending issues.
The fix isn’t better prompts—it’s better access control. You need a layer where permissions are explicit, granular, and machine-readable. That’s the gap MCP fills.
How MCP Changes the Equation
MCP gives you a structured way to declare what tools an agent can use and what it can do with them. Instead of giving the agent a raw API client and trusting prompt instructions, you define a contract between the agent and the tools. The agent can only invoke tools that are listed in that contract, and each tool can carry its own access restrictions.
This shifts the security burden from “don’t make mistakes with this key” to “you literally cannot do this operation.” Read-only scopes, write-prefixed endpoints, and rate limits become first-class citizens in the definition, not afterthoughts.
A typical MCP tool definition might look like this:
{
"name": "listDatabaseTables",
"description": "Returns the names of all tables in the specified database",
"parameters": {
"type": "object",
"properties": {
"databaseName": { "type": "string" }
}
},
"permissions": {
"scope": "read"
}
}
The agent knows it can call listDatabaseTables, but it has no write scope on the database. Even if a prompt says “delete all tables,” the agent has no tool to do it. The attack surface shrinks to exactly what you define.
Setting Up Read-Only Scopes for Every Integration
Read-only scopes are the simplest, most effective guardrail you can implement. Before you add write capability to any tool, ask: does the agent need this for its primary task? Often the answer is no.
For example, consider an agent that helps users search through a company’s Google Drive. The agent needs to read documents and return summaries. It does not need to delete, rename, or move files. If you give it full API access, a poorly worded request could trigger a destructive action. With MCP, you define a single tool:
{
"name": "searchDocuments",
"description": "Search for files matching a query",
"permissions": {
"scope": "read"
}
}
You can extend that with a getDocumentContent tool that also uses read scope. The agent has no tools for write operations. Even if someone prompts it to delete a file, the agent can’t—it doesn’t have the ability.
The same principle applies to databases, cloud storage, and collaboration tools. Map every action to a tool and set the scope to read until you prove the agent needs write access for a specific, well-defined task.
Permission Granularity: When Read-Only Isn’t Enough
Sometimes you do need write access. An agent that books meetings needs to create calendar events. An agent that triages support tickets needs to update statuses. The goal isn’t to ban write access—it’s to scope it surgically.
MCP lets you define permissions at the field or operation level. Instead of a single “write” scope, you can specify which endpoints or parameters are writable.
For a ticketing system, you might define:
{
"name": "updateTicketStatus",
"description": "Update the status of a support ticket",
"parameters": {
"type": "object",
"properties": {
"ticketId": { "type": "string" },
"status": { "enum": ["open", "in_progress", "resolved"] }
}
},
"permissions": {
"scope": "write",
"restrictedTo": ["status"]
}
}
The agent can change a ticket’s status, but it cannot delete the ticket, reassign it, or modify its internal notes. This granularity keeps the agent useful while limiting blast radius. If a prompt goes sideways, the worst outcome is a wrong status update—not a deleted ticket or a corrupted database.
Implementing Access Control with MCP in Practice
Adopting MCP doesn’t require rewriting your entire stack. Start by defining your interface layer. Each tool your agent exposes should be declared in an MCP-compatible definition file. Then, your agent runtime—whether it’s a custom executor or a framework—reads that definition and enforces permissions before making API calls.
Here’s a minimal implementation pattern in Python:
from mcp import Tool, Permissions
tools = [
Tool(
name="listProjects",
description="List all projects",
permissions=Permissions(scope="read")
),
Tool(
name="createProject",
description="Create a new project with a name and description",
parameters={
"name": "string",
"description": "string"
},
permissions=Permissions(scope="write")
)
]
def call_tool(tool_name, parameters):
tool = next(t for t in tools if t.name == tool_name)
if tool.permissions.scope == "read" and is_write_operation(tool, parameters):
raise PermissionDenied("Read-only tool cannot perform writes")
# ... actual API call logic
The key detail: the permission check happens before the API call, not as a log entry after the fact. If the agent tries to call listProjects with a delete parameter, the runtime rejects it before any request leaves your server.
Version your tool definitions the same way you version your API contracts. When you update permissions, deploy the new definition and test it in staging. An agent that suddenly has write access due to a sloppy JSON merge is a disaster waiting to happen.
A Note on Monitoring and Auditing
MCP definitions are declarative, which means you can version, diff, and audit them. This is a major advantage over ad-hoc permission strings buried in code. Store your tool definitions in a repository. When a permission changes, review it like you would any other code change.
Log every tool invocation at runtime: which tool was called, what parameters were passed, and what the scope was. If an agent misbehaves, you can replay the exact sequence of calls to understand why. Pair this with a rollback mechanism. If a definition update introduces a bug, revert to the previous version and redeploy the agent. No hotfix to the runtime needed—just swap the definition file.
Practical Next Steps
Start by auditing every tool your agent currently uses. For each one, ask: does this tool need write access? If the answer is no, lock it to read-only. If the answer is yes, list the exact fields or operations that require write permissions and document why.
Then, adopt MCP as your definition format. It doesn’t require a specific framework—just a JSON schema and a runtime that enforces it. Build a small wrapper around your existing API clients that checks permissions before making requests. Test in staging with both expected prompts and adversarial ones. A good test: have someone on your team try to trick the agent into exceeding its defined scopes. If they can, your permissions are too loose or your enforcement is too late.
Finally, make your tool definitions part of your deployment pipeline. Treat them like configuration: versioned, reviewed, and deployable independently of the agent code. When you update permissions, you ship a new definition file, not a code change to the agent runtime.
Locking down agent access isn’t about limiting what you can build—it’s about making sure you don’t wake up to a support ticket saying, “Our agent just deleted half our production data.” Start with read-only scopes, add write access surgically, enforce at the protocol level, and audit everything. Your future self will thank you.