2026-01-06 · Authensor

How to Secure MCP (Model Context Protocol) Servers

SafeClaw by Authensor enforces deny-by-default policies on every tool call, resource access, and prompt invocation in MCP (Model Context Protocol) servers. MCP defines a standard interface for AI agents to interact with external capabilities — SafeClaw sits between the MCP client (the AI agent) and the MCP server's tool handlers, evaluating each request against your YAML policy before execution.

How MCP Tool Execution Works

The Model Context Protocol defines three primary capabilities: tools (functions the AI can call), resources (data the AI can read), and prompts (templates the AI can use). When an MCP client sends a tools/call request, the MCP server routes it to the appropriate handler. The protocol itself has no built-in authorization or safety layer — any connected client can call any exposed tool with any arguments. This is by design (MCP focuses on interoperability), but it means the security responsibility falls entirely on the server implementation.

MCP Client → tools/call request → [SafeClaw Policy Check] → Tool handler or Deny

Quick Start

npx @authensor/safeclaw

Creates a safeclaw.yaml in your project. SafeClaw provides MCP middleware that wraps your server's tool handlers.

Step 1: Define MCP Server Policies

# safeclaw.yaml
version: 1
default: deny

policies:
- name: "mcp-tool-access"
description: "Control MCP tool invocations"
actions:
- tool: "query_database"
effect: allow
constraints:
operation: "SELECT"
- tool: "query_database"
effect: deny
constraints:
operation: "DROP|DELETE|TRUNCATE|ALTER"
- tool: "read_file"
effect: allow
constraints:
path_pattern: "data/|docs/"
- tool: "write_file"
effect: allow
constraints:
path_pattern: "output/**"
- tool: "execute_command"
effect: deny

- name: "mcp-resource-access"
description: "Control MCP resource reads"
actions:
- tool: "resource:config"
effect: allow
- tool: "resource:logs"
effect: allow
constraints:
date_range_max_days: 7
- tool: "resource:secrets"
effect: deny
- tool: "resource:credentials"
effect: deny

- name: "mcp-prompt-access"
description: "Control MCP prompt invocations"
actions:
- tool: "prompt:summarize"
effect: allow
- tool: "prompt:generate_code"
effect: allow
- tool: "prompt:system_admin"
effect: deny

Step 2: Integrate with the MCP TypeScript SDK

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { SafeClaw } from "@authensor/safeclaw";

const safeclaw = new SafeClaw("./safeclaw.yaml");

const server = new Server(
{ name: "my-mcp-server", version: "1.0.0" },
{ capabilities: { tools: {}, resources: {} } }
);

// Gate tool calls with SafeClaw
server.setRequestHandler("tools/call", async (request) => {
const { name, arguments: args } = request.params;

const decision = safeclaw.evaluate(name, args ?? {});

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

// Execute the tool
const result = await executeToolHandler(name, args);
return {
content: [{ type: "text", text: JSON.stringify(result) }],
};
});

// Gate resource reads
server.setRequestHandler("resources/read", async (request) => {
const { uri } = request.params;
const resourceName = resource:${extractResourceType(uri)};

const decision = safeclaw.evaluate(resourceName, { uri });

if (!decision.allowed) {
throw new Error(Resource access denied: ${decision.reason});
}

return await readResource(uri);
});

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

Step 3: Build a SafeClaw MCP Proxy

For existing MCP servers you don't control, deploy SafeClaw as a proxy:

import { SafeClaw } from "@authensor/safeclaw";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { Server } from "@modelcontextprotocol/sdk/server/index.js";

const safeclaw = new SafeClaw("./safeclaw.yaml");

// SafeClaw proxy: sits between MCP client and upstream MCP server
const proxyServer = new Server(
{ name: "safeclaw-proxy", version: "1.0.0" },
{ capabilities: { tools: {} } }
);

const upstreamClient = new Client(
{ name: "safeclaw-proxy-client", version: "1.0.0" },
{}
);

// Proxy tool/list — pass through
proxyServer.setRequestHandler("tools/list", async () => {
return await upstreamClient.request({ method: "tools/list" }, {});
});

// Proxy tools/call — gate with SafeClaw
proxyServer.setRequestHandler("tools/call", async (request) => {
const { name, arguments: args } = request.params;
const decision = safeclaw.evaluate(name, args ?? {});

if (!decision.allowed) {
return {
content: [{ type: "text", text: Denied: ${decision.reason} }],
isError: true,
};
}

return await upstreamClient.request(
{ method: "tools/call", params: { name, arguments: args } },
{}
);
});

This proxy pattern lets you add SafeClaw safety to any MCP server without modifying its code.

Step 4: Multi-Client Access Control

MCP servers can be accessed by multiple clients (Claude Desktop, Cursor, VS Code Copilot). Define per-client policies:

policies:
  - name: "claude-desktop-access"
    client: "claude-desktop"
    actions:
      - tool: "query_database"
        effect: allow
      - tool: "write_file"
        effect: allow
        constraints:
          path_pattern: "output/**"

- name: "cursor-access"
client: "cursor"
actions:
- tool: "query_database"
effect: allow
- tool: "write_file"
effect: deny

Step 5: Audit MCP Server Activity

npx @authensor/safeclaw audit --last 100 --filter protocol=mcp

Every MCP request — tool calls, resource reads, prompt invocations — is recorded in the hash-chained audit log with the client identity, tool name, arguments, and decision.

Why SafeClaw

Related Pages

Try SafeClaw

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

$ npx @authensor/safeclaw