Agent security has four layers: identity, pre-deploy testing, observability, and runtime defense. Only one of them can refuse a request in flight. The others run before (testing) or after (observability) the action itself. Identity tells you who called, not whether the call is safe.
The Model Context Protocol (MCP) creates a security boundary at the point where an agent’s decision becomes a tool invocation. It sits between the LLM’s output and the system resources that tool touches. This is the only layer where you can stop a poisoned tool call before it executes.
The Gap Between Decision and Execution
An agent decides to call a tool. The LLM emits JSON with a function name and arguments. That JSON crosses a network boundary, enters a runtime, and triggers code that touches files, databases, or APIs. The gap between emission and execution is where runtime defense lives.
Consider a concrete attack: an agent processes a user prompt containing an injection payload. The LLM hallucinates a tool call to delete_records with a WHERE clause of 1=1. Without runtime validation, that call reaches the database and wipes the table. With MCP, the server validates the arguments against a schema, detects the anomalous clause, and refuses the call before the DELETE executes.
MCP formalizes this boundary with a JSON-RPC protocol. Tools are registered with explicit schemas. Invocations are validated server-side, independent of the LLM’s output. The protocol separates tool discovery from execution, which creates injection points for policy enforcement.
Without MCP, tool calls are often ad-hoc. The agent runtime parses LLM output, maps it to a function, and executes. Validation is application-specific. With MCP, the server enforces a contract: every tool has a known argument schema, every invocation is a request with known shape.
How MCP Validates Tool Calls at Runtime
MCP servers validate tool invocations in three stages:
-
Schema validation: The server checks that arguments match the tool’s declared schema. If the LLM hallucinates a parameter or sends the wrong type, the call fails before execution.
-
Capability boundaries: Each tool is registered with a set of capabilities (read, write, execute). The server enforces these boundaries per invocation. A tool registered as read-only cannot write, even if the agent requests it.
-
Rate limiting and quotas: MCP servers can enforce per-tool or per-session rate limits. If an agent loops or gets stuck in a retry cycle, the server refuses additional calls.
This happens server-side, which means the validation logic is independent of the agent’s host process. A compromised agent cannot bypass schema checks by manipulating its own runtime.
Isolation Mechanisms
MCP servers run in a separate process from the agent. Tool execution happens in the server’s address space, not the agent’s. This creates a privilege boundary: the agent cannot escalate by manipulating tool state directly.
The protocol also supports sandboxing at the tool level. A tool can be wrapped in a container, a restricted subprocess, or a capability-limited environment. The MCP server mediates access, so the agent never touches the underlying resource directly.
For example, a file-access tool might run in a chroot jail. The agent requests a file path, the server validates the path against an allowlist, and the tool executes inside the jail. If the agent tries to escape the jail by crafting a malicious path, the server refuses the call before the tool runs.
Audit and Forensics
Every tool invocation generates a structured log entry. MCP servers record:
- Tool name and version
- Arguments (sanitized if sensitive)
- Timestamp and session ID
- Result or error code
- Execution duration
This log is independent of the agent’s own telemetry. If the agent is compromised and tries to hide its actions, the MCP server’s log still captures the tool calls.
The log format is JSON-RPC, which makes it parsable by standard observability tools. You can pipe it to a SIEM, replay it for post-incident analysis, or use it to train anomaly detection models.
Architecture: MCP in the Request Path
Here’s the flow when an agent invokes a tool through MCP:
+-------------+
| Agent |
| (LLM host) |
+------+------+
| JSON-RPC request
| {"method": "tools/call", "params": {...}}
v
+-------------------------------------+
| MCP Server |
| +-------------------------------+ |
| | 1. Schema validation | |
| | 2. Capability check | |
| | 3. Rate limit enforcement | |
| +-------------------------------+ |
| | |
| v |
| +-------------------------------+ |
| | Tool execution (sandboxed) | |
| +-------------------------------+ |
| | |
| v |
| +-------------------------------+ |
| | Audit log | |
| +-------------------------------+ |
+-------------------------------------+
| JSON-RPC response
v
+-------------+
| Agent |
+-------------+
The agent never touches the tool directly. It sends a request, the server validates and executes, and the agent receives a response. The server is the only component with access to system resources.
Policy Injection Points
MCP’s separation of registration and execution creates two policy injection points:
-
Tool registration: When a tool is registered, you can attach policies (allowed users, rate limits, required approvals). These policies are enforced at invocation time.
-
Invocation middleware: The server can run middleware before and after tool execution. Middleware can log, sanitize arguments, or refuse calls based on runtime context (time of day, current load, recent error rate).
This is where runtime defense becomes programmable. You can write a middleware function that checks tool arguments against a blocklist, validates file paths against a known-good set, or refuses calls that match a known attack pattern.
Code Example: Validating a File-Access Tool
Here’s a minimal MCP server that validates file paths before allowing a read operation:
from mcp.server import MCPServer
from pathlib import Path
server = MCPServer()
ALLOWED_PATHS = {Path("/data"), Path("/logs")}
@server.tool("read_file")
def read_file(path: str) -> str:
requested = Path(path).resolve()
# Check if path is under an allowed directory
if not any(requested.is_relative_to(allowed) for allowed in ALLOWED_PATHS):
raise PermissionError(f"Path {path} is outside allowed directories")
# Check for path traversal attempts
if ".." in path or path.startswith("/"):
raise ValueError(f"Invalid path: {path}")
with open(requested, "r") as f:
return f.read()
server.run()
The agent calls read_file with a path. The server validates the path before opening the file. If the agent tries to read /etc/passwd or ../../secrets.txt, the call fails before the file is touched.
Trade-offs and Deployment Considerations
| Aspect | MCP Server-Side | Agent-Level Validation |
|---|---|---|
| Isolation | Separate process, privilege boundary | Same process as agent |
| Bypass risk | Agent cannot manipulate server logic | Agent can patch validation code |
| Latency | Network hop per tool call | In-process function call |
| Auditability | Structured logs independent of agent | Agent controls logging |
| Complexity | Additional service to deploy | No extra infrastructure |
| Failure mode | Server down = agent cannot call tools | Validation bug = silent bypass |
The main trade-off is latency. Every tool call crosses a network boundary. For high-frequency tools (e.g., a vector search called 100 times per agent turn), this adds measurable overhead. For low-frequency, high-risk tools (e.g., database writes, API calls with side effects), the overhead is acceptable.
The failure mode is also different. If the MCP server goes down, the agent cannot call tools at all. This is a safe failure: the agent degrades to read-only or stops. If application-level validation has a bug, the agent might bypass it silently.
When to Use MCP Runtime Defense
Use MCP runtime defense when:
- Tools touch sensitive resources: Databases, file systems, external APIs with side effects.
- Agents are exposed to untrusted input: User-supplied prompts, third-party data sources.
- You need audit trails independent of the agent: Compliance, forensics, incident response.
- Tool execution must be sandboxed: Containers, restricted subprocesses, capability-limited environments.
Avoid MCP runtime defense when:
- Latency is critical: High-frequency tool calls where network hops are unacceptable.
- Tools are read-only and low-risk: Lookups, calculations, stateless transformations.
- You control the agent’s input completely: Internal tools with trusted prompts only.
- You cannot deploy a separate service: Resource-constrained environments, edge deployments.
Technical Verdict
MCP’s runtime layer solves a specific problem: stopping a poisoned tool call before it executes. It does this by creating a privilege boundary between the agent and system resources, enforcing schemas and capabilities server-side, and logging every invocation independently.
This is not a replacement for identity, observability, or pre-deploy testing. It is the only layer that can refuse a request in flight. If your agent touches sensitive resources or processes untrusted input, MCP runtime defense is the missing piece. If your tools are low-risk or latency-sensitive, application-level validation is simpler.
The protocol is young, but the pattern is proven: the same techniques that protect HTTP request boundaries (allowlist, sanitize, refuse) port directly to the agent boundary. A tool with a known argument schema is a request with known shape. MCP makes that shape explicit and enforceable.