DEV Community

SidClaw
SidClaw

Posted on

How to add human approval to MCP tool calls — no code changes

MCP servers do what agents tell them. There's no policy check between "the agent decided to run this query" and "the query executed." If you're running MCP servers in production, every tool call goes straight through.

We built sidclaw-mcp-guard to fix that. It's a CLI that wraps any MCP server with policy-based guardrails. YAML rules, local approval dashboard, audit trail. No signup, no SaaS dependency. Apache 2.0.

Here's what it looks like.

30-second demo

npx sidclaw-mcp-guard@latest demo
Enter fullscreen mode Exit fullscreen mode

Output:

ALLOW   SELECT * FROM users
  Allowed: read query on users. Read-only queries are safe.

HOLD    DELETE FROM users WHERE id = 42
  Held for approval: delete from users. Data changes need approval.

BLOCK   DROP TABLE users
  Blocked: drop users. Schema changes are never allowed.
Enter fullscreen mode Exit fullscreen mode

Three decisions. Safe reads pass through. Writes wait for a human. Destructive DDL gets blocked outright.

It catches compound statements too. SELECT 1; DROP TABLE users doesn't sneak through as a read -- the destructive part gets flagged.

How it works

mcp-guard is a proxy. It sits between your MCP client (Claude Desktop, Cursor, VS Code, whatever) and the upstream MCP server. Every tools/call request passes through the guard first.

MCP Client  -->  sidclaw-mcp-guard  -->  MCP Server (postgres, filesystem, etc.)
                      |
                 policy.yaml
                      |
                 localhost:9091 (approval dashboard)
Enter fullscreen mode Exit fullscreen mode

The guard reads your policies, classifies the tool call using semantic patterns, and decides: allow, hold for approval, or deny.

Setting it up

npx sidclaw-mcp-guard@latest quickstart
Enter fullscreen mode Exit fullscreen mode

This creates a policy.yaml, writes .mcp.json for your client, and starts the approval dashboard at localhost:9091.

To run it manually against any MCP server:

npx sidclaw-mcp-guard --upstream "npx -y @modelcontextprotocol/server-postgres postgresql://localhost/mydb" --ui
Enter fullscreen mode Exit fullscreen mode

Policy rules

Policies are YAML. Each rule matches a semantic pattern and decides what happens.

rules:
  - name: allow-reads
    description: Read-only queries are safe
    match:
      pattern: sql-read
    action: allow

  - name: approve-writes
    description: Data changes need human review
    match:
      pattern: sql-write
    action: approve

  - name: deny-destructive
    description: Schema changes are never allowed
    match:
      pattern: sql-destructive
    action: deny

default: deny
Enter fullscreen mode Exit fullscreen mode

The patterns aren't regex. sql-read matches SELECT, EXPLAIN, SHOW. sql-write matches INSERT, UPDATE, DELETE. sql-destructive catches DROP, TRUNCATE, ALTER, CREATE. The guard parses the intent, not just the string.

Shell commands too

Works for filesystem and shell MCP servers, not just databases.

  • shell-safe -- ls, cat, echo
  • shell-risky -- curl, wget, ssh
  • shell-destructive -- rm -rf, chmod 777, dd

Same pattern. Same YAML. You can mix SQL and shell rules in one policy file if your agent connects to multiple MCP servers.

The audit trail

Every decision gets logged to .sidclaw/audit.jsonl:

{"timestamp":"...","tool":"query","args":{"sql":"SELECT * FROM users"},"decision":"allow","rule":"allow-reads"}
{"timestamp":"...","tool":"query","args":{"sql":"DELETE FROM users WHERE id=42"},"decision":"approve","rule":"approve-writes","status":"approved"}
{"timestamp":"...","tool":"query","args":{"sql":"DROP TABLE users"},"decision":"deny","rule":"deny-destructive"}
Enter fullscreen mode Exit fullscreen mode

Every tool call, every decision, every approval. JSONL so you can grep it, pipe it, or ship it to whatever log aggregator you already use.

What this doesn't solve

mcp-guard governs tool calls. It doesn't filter LLM outputs, detect prompt injection, or validate agent reasoning. Those are different problems -- Pangea and Lakera handle the input/output layer. This sits at the action layer: the moment the agent decides to do something through an MCP server.

It also doesn't replace proper database permissions. If your postgres user has DROP access and you don't want agents dropping tables, fix the permissions too. mcp-guard is a second layer, not a replacement for the first.


Top comments (0)