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

posted Originally published at dev.to 3 min read

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

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.

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)

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

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

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

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"}

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.


2 Comments

1 vote
1 vote

More Posts

I’m a Senior Dev and I’ve Forgotten How to Think Without a Prompt

Karol Modelskiverified - Mar 19

The Interface of Uncertainty: Designing Human-in-the-Loop

Pocket Portfolioverified - Mar 10

I Wrote a Script to Fix Audible's Unreadable PDF Filenames

snapsynapseverified - Apr 20

Comparison: Universal Import vs. Plaid/Yodlee

Pocket Portfolioverified - Mar 12

I spent years trying to get AI agents to collaborate. Then Opus 4.6 and Codex 5.3 wrote the rules

snapsynapseverified - Apr 20
chevron_left

Related Jobs

Commenters (This Week)

1 comment
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!