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
- Node.js 18 or later
- An existing MCP server (or the
@modelcontextprotocol/sdkpackage to build one) - A SafeClaw account at safeclaw.onrender.com (free tier, 7-day renewable keys, no credit card)
- SafeClaw API key from the browser dashboard
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
- SafeClaw Action Types Reference
- Glossary: Model Context Protocol (MCP)
- FAQ: Does SafeClaw Work with MCP Servers?
- SafeClaw vs MCP Built-in Permissions
- Use Case: Securing MCP Tool Servers in Production
Try SafeClaw
Action-level gating for AI agents. Set it up in your browser in 60 seconds.
$ npx @authensor/safeclaw