Shipping an agent that can call internal APIs without MCP tool security for AI agents is standing privilege with a chat UI. Model Context Protocol (MCP) servers expose tools. If the process hosting those tools uses a long-lived admin token, every prompt injection becomes an admin session.
We frame this as security engineering delivered with AI-assisted tooling — not a generic "AI automation" product, and not an agentic-SOC vendor. Permission models, short-lived credentials, and logs you can sample. Related: AI agent least privilege and AI agent audit logging.
The Trust Boundary Is the Tool Gateway
MCP does not magically scope tools. Your gateway must:
- Authenticate the caller (human session or workload identity) — not "the model."
- Authorize each tool name + argument class (allowlist, not a blocklist of famous bad tools).
- Issue downstream credentials that match the tool, not a shared
ADMIN_PAT. - Log tool name, actor, args hash, result class, timestamp — OWASP LLM risk categories map cleanly here (excessive agency, sensitive information disclosure).
ALLOWED = {
"create_ticket": {"systems": ["jira"], "max_priority": "P3"},
"read_runbook": {"systems": ["wiki"], "paths_prefix": ["/runbooks/"]},
}
def authorize(actor: str, tool: str, args: dict) -> None:
spec = ALLOWED.get(tool)
if spec is None:
raise PermissionError(f"tool not allowlisted: {tool}")
if tool == "read_runbook" and not args["path"].startswith(spec["paths_prefix"][0]):
raise PermissionError("path outside runbook prefix")
# bind downstream creds to actor + tool, never a shared admin token
A tool named run_sql with a production DSN in the server env is a database admin. Split read-only replicas, bind queries, block multiple statements. If you cannot, do not expose SQL as a tool.
Credentials: The Agent Must Not Be an IAM User
The MCP server's AWS access should be a task role or Lambda role, assumed per session where possible, with a permission boundary. Standing keys in the agent host recreate IAM user key findings inside a new runtime.
Human-in-the-loop for irreversible tools (disable user, drop table, pay invoice) is a control, not a UX flourish. Time-box the approval token.
Evidence for SOC 2
CC6 logical access applies to non-human and agent identities. CC7 expects you to notice anomalous tool use. We export:
- Allowlist version (git SHA) in every log line
- Denies as first-class events (not only allows)
- Downstream CloudTrail
sourceIdentitywhen the tool calls AWS
If the GRC platform cannot ingest this, we push files the same way as other internal-tool evidence.
Discuss securing your AI agent build → rutagon.com/contact · 907-841-8407 · contact@rutagon.com.
Runtime Isolation
The MCP server process should not share a filesystem with developer home directories or a world-readable .env. We run it as a dedicated IAM role on ECS/Fargate or Lambda, with secrets from ASM injected at start, never baked into the image. Tool implementations that shell out to bash -c with model-supplied strings are out of scope until they are argument-array APIs.
Rate limits per actor stop a loop that calls create_ticket 8,000 times. We treat unbounded loops as a reliability and a security issue.
Argument Schemas
JSON Schema on tool arguments is an authorization input. A path string without a prefix allowlist is path traversal. A sql string is usually a refuse. A repo enum of in-scope repos is better than a free-text GitHub URL.
We log a hash of arguments, not always the raw payload, when tools can include secrets. The deny log still includes tool name and actor so SOC 2 sampling can see rejected excessive agency.
Human approval tokens are single-use, short TTL, bound to the exact tool+args hash. Approving disable_user:alice must not replay as disable_user:bob.
MCP tool security for AI agents in a production VPC
MCP tool security for AI agents is a gateway problem, not a model-card problem. The process that hosts tools runs as its own IAM role. Secrets come from AWS Secrets Manager at start. The container filesystem is not a developer laptop with a .env and a cloned monorepo. Tools that interpolate model text into bash -c stay out of production until they are argv APIs with an allowlist.
The Model Context Protocol specification is public. It does not define your SOC 2 boundary. You do. We put authn (workload identity, not a shared API key in Slack), per-tool scopes, an allowlist of tool names, and an audit log of tool+actor+arg-hash+result in front of every call. Rate limits per actor stop a loop that opens 8,000 tickets.
Argument JSON Schema is authorization. A free-text path is path traversal. A free-text sql is a refuse. A repo enum of in-scope repos beats a GitHub URL the model invented. Human-approval tokens are single-use, bound to the hash of tool+args, short TTL. Approving disable_user:alice must not replay as disable_user:bob.
OWASP’s LLM top-10 language (excessive agency, sensitive disclosure) is how we label findings for GRC. The engineering artifact is still the gateway log in the log-archive bucket, queryable, retained for the observation window.
What we log when the model asks for a destructive MCP tool
Destructive tools (disable user, delete bucket, wire money) require a human approval token. The log stores tool name, actor workload identity, arg hash, approver, and outcome. We do not store raw account numbers or passwords in the log. Deny events are first-class — SOC 2 sampling should see refused excessive agency, not only successes.
If a tool must return customer data, we treat that as a data-flow in the system description. An unbounded “search the warehouse” tool is a data-exfil API with extra steps. Scope it or do not ship it.
Frequently Asked Questions
Is MCP itself insecure?
MCP is a connector pattern. Insecurity is unbounded tools plus standing admin credentials. The protocol does not replace an authorization layer.
Should every tool require human approval?
No. Read-only runbooks can be automatic. Production mutation should be gated. Putting approvals on search_docs trains people to click through approvals on delete_user.
How do we handle prompt injection that asks for a disallowed tool?
Deny at the gateway even if the model "wants" the tool. Log the attempt. Do not rely on the system prompt as the control.
Can we use a commercial MCP DLP product instead?
You can add one. You still need allowlists and scoped downstream creds. DLP does not fix run_sql with admin DSN.
Does this compete with agentic SOC vendors?
No. We are not selling alert triage. We are shipping permissioned tool access on AWS for teams already building agents.