Secure MCP Servers
The Model Context Protocol (MCP) lets agents connect to data sources and tools via a standardized interface. By default, MCP servers execute any tool call they receive. ShadowAudit's MCP gateway sits between the agent and the MCP server, enforcing policy before any call reaches the server.
The authorization gap
Without ShadowAudit, an MCP server trusts its client completely:
Agent → MCP Client → MCP Server → Filesystem / Database
If the agent is compromised — via prompt injection, hallucination, or a rogue instruction — the MCP server will execute whatever it is told.
The ShadowAudit MCP gateway
Agent → MCP Client → ShadowAudit Gateway → MCP Server → Filesystem / Database
│
Policy check
Audit log
The gateway is a transparent stdio proxy. It intercepts JSON-RPC messages, extracts the tool call arguments, evaluates them against policy, and either forwards the message or returns a standardized error.
Setup
from shadowaudit.mcp.gateway import MCPGatewayServer
from shadowaudit.core.gate import Gate
gateway = MCPGatewayServer(
upstream_command=["python", "-m", "mcp_server_filesystem", "/data"],
gate=Gate(),
policy_path="policies/mcp_policy.yaml",
agent_id="mcp-agent"
)
gateway.run()
MCP policy example
# policies/mcp_policy.yaml
deny:
- capability: filesystem.delete
- capability: filesystem.write
path_prefix: "/etc"
- capability: shell.execute
require_approval:
- capability: filesystem.write
path_prefix: "/data/prod"
allow:
- capability: filesystem.read
- capability: filesystem.write
path_prefix: "/data/staging"
What the gateway blocks
When a blocked call is intercepted, the gateway returns a JSON-RPC error to the client:
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32000,
"message": "AgentActionBlocked: capability=filesystem.delete decision=denied reason=destructive_operation"
}
}
The MCP server never receives the call.
Audit trail
Every MCP tool call — allowed and blocked — is recorded in the audit log with the full JSON-RPC payload hash:
shadowaudit logs --agent mcp-agent