# MCP Trust Pack: a security layer for MCP tool calls

> Source: <https://dev.to/telleroutlook/mcp-trust-pack-a-security-layer-for-mcp-tool-calls-3c3o>
> Published: 2026-06-26 01:50:09+00:00

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.

``` js
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:

``` js
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](https://github.com/WasmAgent/wasmagent-js/tree/main/packages/mcp-firewall) · [packages/mcp-gateway](https://github.com/WasmAgent/wasmagent-js/tree/main/packages/mcp-gateway)

*Series: AEP (part 1) · MCP Trust Pack (part 2) · Trace-to-Training (part 3)*
