2026-01-19 · Authensor

Adding SafeClaw Policy Enforcement to MCP Servers

SafeClaw is action-level gating for AI agents, built by Authensor. The Model Context Protocol (MCP) exposes tools and resources to AI agents via standardized servers. This guide covers adding SafeClaw policy enforcement to any MCP server so that every tool invocation is evaluated against a deny-by-default policy before execution.

Prerequisites

Step-by-Step Instructions

Step 1: Install SafeClaw

npx @authensor/safeclaw

Complete the browser-based setup wizard. SafeClaw has zero third-party dependencies, is backed by 446 tests, and runs in TypeScript strict mode. The client is 100% open source under the MIT license.

Step 2: Add SafeClaw to Your MCP Server Project

npm install @authensor/safeclaw

Step 3: Create a SafeClaw Middleware for MCP

Create safeclaw-mcp-middleware.ts:

import { SafeClaw } from "@authensor/safeclaw";

const safeclaw = new SafeClaw({
apiKey: process.env.SAFECLAW_API_KEY!,
agentId: "mcp-server",
mode: "enforce",
});

interface ToolCallInput {
name: string;
arguments: Record<string, unknown>;
}

interface GateResult {
allowed: boolean;
decision: string;
reason?: string;
}

const toolActionMap: Record<string, string> = {
read_file: "file_read",
write_file: "file_write",
run_command: "shell_exec",
execute_shell: "shell_exec",
fetch_url: "network",
http_request: "network",
};

export async function gateMcpTool(input: ToolCallInput): Promise<GateResult> {
const actionType = toolActionMap[input.name] || "shell_exec";
const target =
(input.arguments.path as string) ||
(input.arguments.url as string) ||
(input.arguments.command as string) ||
input.name;

const result = await safeclaw.evaluate({
actionType,
target,
metadata: {
agent: "mcp-server",
tool: input.name,
timestamp: new Date().toISOString(),
},
});

return {
allowed: result.decision === "ALLOW",
decision: result.decision,
reason: result.reason,
};
}

Step 4: Integrate the Middleware into Your MCP Server

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { gateMcpTool } from "./safeclaw-mcp-middleware.js";

const server = new Server(
{ name: "safeclaw-gated-server", version: "1.0.0" },
{ capabilities: { tools: {} } }
);

server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "read_file",
description: "Read a file from disk",
inputSchema: {
type: "object",
properties: { path: { type: "string" } },
required: ["path"],
},
},
{
name: "write_file",
description: "Write content to a file",
inputSchema: {
type: "object",
properties: {
path: { type: "string" },
content: { type: "string" },
},
required: ["path", "content"],
},
},
{
name: "run_command",
description: "Execute a shell command",
inputSchema: {
type: "object",
properties: { command: { type: "string" } },
required: ["command"],
},
},
],
}));

server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;

// SafeClaw gate check
const gate = await gateMcpTool({ name, arguments: args || {} });

if (!gate.allowed) {
return {
content: [
{
type: "text",
text: Action blocked by SafeClaw: ${gate.decision}. ${gate.reason || ""},
},
],
isError: true,
};
}

// Execute the tool only if SafeClaw allows it
switch (name) {
case "read_file":
return await handleReadFile(args!.path as string);
case "write_file":
return await handleWriteFile(
args!.path as string,
args!.content as string
);
case "run_command":
return await handleRunCommand(args!.command as string);
default:
return { content: [{ type: "text", text: "Unknown tool" }], isError: true };
}
});

const transport = new StdioServerTransport();
await server.connect(transport);

Step 5: Test in Simulation Mode

Set mode: "simulate" in the SafeClaw constructor. Connect a client (Claude Desktop, Cursor, or any MCP host) to the server. All tool calls are logged to the tamper-proof audit trail (SHA-256 hash chain) without blocking. Review at safeclaw.onrender.com.

Step 6: Deploy with Enforce Mode

Set mode: "enforce" and restart the MCP server. Policy evaluation runs in sub-millisecond time. The control plane sees only action metadata, never your keys or data.

Example Policy

# safeclaw.config.yaml
version: "1.0"
agent: mcp-server
defaultAction: deny

rules:
- id: allow-read-project
action: file_read
target: "./project/**"
decision: allow

- id: allow-write-output
action: file_write
target: "./output/**"
decision: allow

- id: deny-write-config
action: file_write
target: "*/.config.*"
decision: deny

- id: gate-shell
action: shell_exec
target: "*"
decision: require_approval

- id: allow-lint
action: shell_exec
target: "npx eslint*"
decision: allow

- id: deny-network
action: network
target: "*"
decision: deny

Example Action Requests

1. ALLOW — Reading a project file:

{
  "actionType": "file_read",
  "target": "./project/src/index.ts",
  "agentId": "mcp-server",
  "tool": "read_file",
  "decision": "ALLOW",
  "rule": "allow-read-project",
  "evaluationTime": "0.3ms"
}

2. DENY — Writing to a config file:

{
  "actionType": "file_write",
  "target": "./tsconfig.config.json",
  "agentId": "mcp-server",
  "tool": "write_file",
  "decision": "DENY",
  "rule": "deny-write-config",
  "evaluationTime": "0.2ms"
}

3. REQUIRE_APPROVAL — Shell command:

{
  "actionType": "shell_exec",
  "target": "docker build -t app .",
  "agentId": "mcp-server",
  "tool": "run_command",
  "decision": "REQUIRE_APPROVAL",
  "rule": "gate-shell",
  "evaluationTime": "0.3ms"
}

4. ALLOW — Running ESLint:

{
  "actionType": "shell_exec",
  "target": "npx eslint ./src",
  "agentId": "mcp-server",
  "tool": "run_command",
  "decision": "ALLOW",
  "rule": "allow-lint",
  "evaluationTime": "0.2ms"
}

5. DENY — Default deny on unregistered action:

{
  "actionType": "network",
  "target": "https://external.api.com/data",
  "agentId": "mcp-server",
  "tool": "fetch_url",
  "decision": "DENY",
  "rule": "deny-network",
  "evaluationTime": "0.2ms"
}

Troubleshooting

Issue 1: MCP server crashes on startup with SafeClaw

Symptom: Error: SAFECLAW_API_KEY is not set.

Fix: Set the environment variable before launching the server: export SAFECLAW_API_KEY=your-key. For MCP server configs in Claude Desktop, add the key to the env block in claude_desktop_config.json.

Issue 2: Tool calls timeout after adding SafeClaw

Symptom: MCP client reports tool call timeout.

Fix: SafeClaw policy evaluation runs in sub-millisecond time and does not cause timeouts. Check the tool implementation itself. Ensure the SafeClaw client is initialized once at startup, not per-request.

Issue 3: All tools return "Unknown tool" after adding middleware

Symptom: Every tool call hits the default case in the switch statement.

Fix: Ensure the name field in CallToolRequestSchema handler matches the tool names defined in ListToolsRequestSchema. The SafeClaw middleware does not modify the tool name.

Cross-References

Try SafeClaw

Action-level gating for AI agents. Set it up in your browser in 60 seconds.

$ npx @authensor/safeclaw