MCP makes it easy for agents to call tools. Too easy.
When your agent calls fs_write
or shell_exec
, something needs to answer: is this allowed? Is this state-changing? Who authorized it? By default, MCP has no answer.
Here's how to add that layer in ~20 lines.
import {
MCPGateway,
buildServerCard,
createRequestIdentity,
isStateChangingTool,
} from "@wasmagent/mcp-firewall";
// Register the server at startup
const card = buildServerCard({
serverId: "filesystem",
tools: await mcpClient.listTools(),
operatorVerified: true,
});
const gateway = new MCPGateway({ serverCards: [card] });
const identity = createRequestIdentity({
principal: "agent:run-abc123",
sessionId: "sess-xyz",
});
// Before every tool call:
const decision = gateway.evaluate({ identity, serverId: "filesystem", tool, args });
if (decision.invocation.decision !== "allow") {
throw new Error(`Blocked: ${decision.invocation.reason}`);
}
const result = await mcpClient.callTool(tool.name, args);
const obs = gateway.wrapResult(tool.name, result, decision); // marks trust level
Four layers run in evaluate()
: vetting → policy → consent → taint. One call, full coverage.
isStateChangingTool({ name: "fs_write", description: "write a file" }) // true
isStateChangingTool({ name: "fs_read", description: "read a file" }) // false
isStateChangingTool({ name: "send_email", description: "send email" }) // true
State-changing tools can be gated behind a ScopeLease
— a time-bounded grant that expires:
import { createScopeLease, isScopeLeaseValid } from "@wasmagent/mcp-firewall";
const lease = createScopeLease({
principalHash: identity.principalHash,
serverId: "filesystem",
grantedTools: ["fs_write"],
ttlSeconds: 300, // 5 min
maxInvocations: 10,
stateChanging: true,
});
if (!isScopeLeaseValid(lease)) throw new Error("Lease expired");
The decision's evidenceRef
slots straight into AEPEmitter
— no manual wiring:
emitter.addAction({
tool_name: decision.invocation.toolName,
state_changing: decision.stateChanging,
capability_decision: {
decision: decision.invocation.decision,
reason_code: decision.evidenceRef.policyDecision,
},
tool_descriptor_digest: decision.evidenceRef.toolManifestDigest,
});
git clone https://github.com/WasmAgent/wasmagent-js
bun test packages/mcp-firewall/
Code: packages/mcp-firewall · packages/mcp-gateway
Series: AEP (part 1) · MCP Trust Pack (part 2) · Trace-to-Training (part 3)