MCP's Security Gap: Why Agent Frameworks Skip Receipts and Guardrails
Most MCP implementations ignore security fundamentals: transaction receipts, capability bounds, and audit trails. Here's what's missing and how to fix it.
MCP's Security Gap: Why Agent Frameworks Skip Receipts and Guardrails
Most Model Context Protocol (MCP) implementations treat security as an afterthought. They focus on tool connectivity while ignoring fundamental security patterns that production systems require.
The Missing Pieces
No Transaction Receipts
MCP servers typically execute tools without generating verifiable receipts. When an agent deletes files or makes API calls, there's no cryptographic proof of what happened.
What's missing:
- Cryptographic signatures of tool invocations
- Tamper-proof execution logs
- Verifiable state transitions
Example gap: An agent calls a file deletion tool. The MCP server confirms deletion, but there's no receipt proving which files were deleted, when, or by which agent session.
Unbounded Capability Exposure
Frameworks expose entire tool sets without granular permissions. An agent gets all-or-nothing access instead of scoped capabilities.
Current pattern:
{
"tools": [
{"name": "file_operations", "description": "Full file system access"}
]
}
What's needed:
{
"tools": [
{
"name": "file_read",
"scope": {
"paths": ["/workspace/*"],
"max_size": "10MB",
"rate_limit": "100/hour"
}
}
]
}
No Audit Trails
Most implementations don't maintain searchable logs of agent actions. When something goes wrong, there's no investigation path.
Security Patterns That Work
1. Implement Tool Receipts
Generate signed receipts for every tool invocation:
def execute_tool(tool_name, params, agent_id):
result = tool_registry.execute(tool_name, params)
receipt = {
"tool": tool_name,
"params": params,
"result_hash": hash(result),
"timestamp": time.now(),
"agent_id": agent_id,
"session_id": current_session.id
}
signed_receipt = crypto.sign(receipt, server_private_key)
audit_log.append(signed_receipt)
return result, signed_receipt
2. Add Capability Boundaries
Implement fine-grained permissions before tool execution:
class CapabilityGuard:
def check_permission(self, agent_id, tool_name, params):
policy = self.get_policy(agent_id)
if tool_name not in policy.allowed_tools:
raise PermissionDenied(f"Tool {tool_name} not allowed")
if not self.validate_params(tool_name, params, policy):
raise PermissionDenied("Parameter validation failed")
return True
3. Build Audit Infrastructure
Maintain structured logs for compliance and debugging:
audit_entry = {
"timestamp": iso_timestamp(),
"agent_id": agent.id,
"tool": tool_name,
"input_hash": hash(params),
"output_hash": hash(result),
"duration_ms": execution_time,
"status": "success|failure|blocked"
}
audit_store.insert(audit_entry)
Why Frameworks Skip Security
Developer Experience Priority
Frameworks prioritize quick prototyping over production security. Adding guardrails increases complexity and slows initial development.
Unclear Threat Models
Most teams don't define what they're protecting against. Without clear threat models, security feels optional.
Performance Concerns
Signing receipts and checking permissions adds latency. Teams skip these steps to maintain responsiveness.
Implementation Checklist
- Generate cryptographic receipts for all tool invocations
- Implement capability-based access control
- Maintain structured audit logs
- Add rate limiting per agent/tool combination
- Validate tool parameters against schemas
- Monitor for anomalous usage patterns
- Create incident response procedures
The Bottom Line
MCP's tool execution model needs security by design, not as an afterthought. Receipts, guardrails, and audit trails should be table stakes for any production MCP implementation.
Without these fundamentals, you're building sophisticated agents on brittle foundations.