Skip to main content
Glama
MCP Action Firewall

MCP Action Firewall

by starskrime

πŸ”₯ MCP Action Firewall

Python 3.12+ License: MIT MCP Compatible

Works with any MCP-compatible agent

Claude Cursor Windsurf OpenAI Gemini OpenClaw

A transparent MCP proxy that intercepts dangerous tool calls and requires OTP-based human approval before execution. Acts as a circuit breaker between your AI agent and any MCP server.

How It Works

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    stdin/stdout    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    stdin/stdout    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ AI Agent β”‚ ◄────────────────► β”‚   MCP Action     β”‚ ◄────────────────► β”‚ Target MCP Serverβ”‚
β”‚ (Claude) β”‚                    β”‚   Firewall       β”‚                    β”‚ (e.g. Stripe)    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                        β”‚
                                   Policy Engine
                                  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                  β”‚ Allow? Block? β”‚
                                  β”‚ Generate OTP  β”‚
                                  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

MCP servers don't run like web servers β€” there's no background process on a port. Instead, your AI agent (Claude, Cursor, etc.) spawns the MCP server as a subprocess and talks to it over stdin/stdout. When the chat ends, the process dies.

The firewall inserts itself into that chain:

Without firewall:
  Claude ──spawns──► mcp-server-stripe

With firewall:
  Claude ──spawns──► mcp-action-firewall ──spawns──► mcp-server-stripe

So you just replace the server command in your MCP client config with the firewall, and tell the firewall what the original command was:

Before (direct):

{ "command": "uvx", "args": ["mcp-server-stripe", "--api-key", "sk_test_..."] }

After (wrapped with firewall):

{ "command": "uv", "args": ["run", "mcp-action-firewall", "--target", "mcp-server-stripe --api-key sk_test_..."] }

Then the firewall applies your security policy:

  1. βœ… Safe calls (e.g. get_balance) β†’ forwarded immediately

  2. πŸ›‘ Dangerous calls (e.g. delete_user) β†’ blocked, OTP generated

  3. πŸ”‘ Agent asks user for the code β†’ user replies β†’ agent calls firewall_confirm β†’ original action executes

Related MCP server: sovr-mcp-proxy

Installation

pip install mcp-action-firewall
# or
uvx mcp-action-firewall --help

Quick Start β€” MCP Client Configuration

Add the firewall as a wrapper around any MCP server in your client config:

{
  "mcpServers": {
    "stripe": {
      "command": "uv",
      "args": ["run", "mcp-action-firewall", "--target", "mcp-server-stripe --api-key sk_test_abc123"]
    }
  }
}

That's it. Everything after --target is the full shell command to launch the real MCP server β€” including its own flags like --api-key. The firewall doesn't touch those args, it just spawns the target and sits in front of it.

More Examples

{
  "mcpServers": {
    "stripe": {
      "command": "uv",
      "args": [
        "run", "mcp-action-firewall",
        "--target", "uvx mcp-server-stripe --api-key sk_test_...",
        "--name", "stripe"
      ]
    },
    "database": {
      "command": "uv",
      "args": [
        "run", "mcp-action-firewall",
        "--target", "uvx mcp-server-postgres --connection-string postgresql://...",
        "--name", "database",
        "--config", "/path/to/my/firewall_config.json"
      ]
    }
  }
}
{
  "mcpServers": {
    "github": {
      "command": "uvx",
      "args": [
        "mcp-action-firewall",
        "--target", "npx @modelcontextprotocol/server-github"
      ]
    }
  }
}

The OTP Flow

When the agent tries to call a blocked tool, the firewall returns a structured response:

{
  "status": "PAUSED_FOR_APPROVAL",
  "message": "⚠️ The action 'delete_user' is HIGH RISK and has been locked by the Action Firewall.",
  "action": {
    "tool": "delete_user",
    "arguments": { "id": 42 }
  },
  "instruction": "To unlock this action, you MUST ask the user for authorization.\n\n1. Show the user the following and ask for approval:\n   Tool: **delete_user**\n   Arguments:\n{\"id\": 42}\n\n2. Tell the user: 'Please reply with approval code: **9942**' to allow this action, or say no to cancel.\n3. STOP and wait for their reply.\n4. When they reply with '9942', call the 'firewall_confirm' tool with that code.\n5. If they say no or give a different code, do NOT retry."
}

Argument visibility guarantee: The arguments shown to the user are frozen at interception time β€” they are taken from the original blocked call, not from what the agent passes to firewall_confirm. The agent cannot change the arguments after the OTP is issued.

The firewall_confirm tool is automatically injected into the server's tool list:

{
  "name": "firewall_confirm",
  "description": "Call this tool ONLY when the user provides the correct 4-digit approval code to confirm a paused action.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "otp": {
        "type": "string",
        "description": "The 4-digit code provided by the user."
      }
    },
    "required": ["otp"]
  }
}

Configuration

The firewall ships with sensible defaults. Override with --config:

{
  "global": {
    "allow_prefixes": ["get_", "list_", "read_", "fetch_"],
    "block_keywords": ["delete", "update", "create", "pay", "send", "transfer", "drop", "remove", "refund"],
    "default_action": "block",
    "otp_attempt_count": 1
  },
  "servers": {
    "stripe": {
      "allow_prefixes": [],
      "block_keywords": ["refund", "charge"],
      "default_action": "block"
    },
    "database": {
      "allow_prefixes": ["select_"],
      "block_keywords": ["drop", "truncate", "alter"],
      "default_action": "block"
    }
  }
}

Rule evaluation order:

  1. Tool name starts with an allow prefix β†’ ALLOW

  2. Tool name contains a block keyword β†’ BLOCK (OTP required)

  3. No match β†’ fallback to default_action

otp_attempt_count β€” maximum number of failed OTP attempts before the pending action is permanently locked out. Defaults to 1 (any wrong code cancels the request). Increase for more forgiving UX, keep at 1 for maximum security.

Per-server rules extend (not replace) the global rules. Use --name stripe to activate server-specific overrides.

CLI Reference

--target (required)

The full command to launch the real MCP server. This is the server you want to protect:

mcp-action-firewall --target "mcp-server-stripe --api-key sk_test_abc123"
mcp-action-firewall --target "npx @modelcontextprotocol/server-github"
mcp-action-firewall --target "uvx mcp-server-postgres --connection-string postgresql://localhost/mydb"

--name (optional)

Activates per-server rules from your config. Without it, only global rules apply:

mcp-action-firewall --target "mcp-server-stripe" --name stripe

--config (optional)

Custom config file path. Without it, uses firewall_config.json in your current directory, or the bundled defaults:

mcp-action-firewall --target "mcp-server-stripe" --config /path/to/my_rules.json

-v / --verbose (optional)

Turns on debug logging (written to stderr, won't interfere with MCP traffic):

mcp-action-firewall --target "mcp-server-stripe" -v

Project Structure

src/mcp_action_firewall/
β”œβ”€β”€ __init__.py          # Package version
β”œβ”€β”€ __main__.py          # python -m support
β”œβ”€β”€ server.py            # CLI entry point
β”œβ”€β”€ proxy.py             # JSON-RPC stdio proxy
β”œβ”€β”€ policy.py            # Allow/block rule engine
β”œβ”€β”€ state.py             # OTP store with TTL
└── default_config.json  # Bundled default rules

Try It β€” Interactive Demo

See the firewall in action without any setup:

git clone https://github.com/starskrime/mcp-action-firewall.git
cd mcp-action-firewall
uv sync
uv run python demo.py

The demo simulates an AI agent and walks you through the full OTP flow:

  1. βœ… Safe call (get_balance) β†’ passes through instantly

  2. πŸ›‘ Dangerous call (delete_user) β†’ blocked, OTP generated

  3. πŸ”‘ You enter the code β†’ action executes after approval

Known Limitations

Argument Inspection

The firewall matches on tool names only, not argument values. This means a tool like get_data({"sql": "DROP TABLE users"}) would pass if get_ is in your allow list, because the policy engine only sees get_data.

Workaround: Use explicit tool names in your allow/block lists and set "default_action": "block" so unrecognized tools require approval.

🚧 Roadmap: Argument-level inspection (scanning argument values against block_keywords) is planned for a future release.

Development

# Install dev dependencies
uv sync

# Run tests
uv run pytest tests/ -v

# Run the firewall locally
uv run mcp-action-firewall --target "your-server-command" -v

License

MIT

Available Tools

14 tools
echoEcho ToolB

Echoes back the input string

Inputs: message (required).

firewall_confirmB

Call this tool ONLY when the user provides the correct 4-digit approval code to confirm a paused action.

Inputs: otp (required).

get-annotated-messageGet Annotated Message ToolC

Demonstrates how annotations can be used to provide metadata about content.

Inputs: messageType (required), includeImage.

get-envPrint Environment ToolA

Returns all environment variables, helpful for debugging MCP server configuration

Inputs: no named parameters.

get-resource-referenceGet Resource Reference ToolC

Returns a resource reference that can be used by MCP clients

Inputs: resourceType, resourceId.

get-structured-contentGet Structured Content ToolB

Returns structured content along with an output schema for client data validation

Inputs: location (required). Returns: temperature, conditions, humidity

get-sumGet Sum ToolB

Returns the sum of two numbers

Inputs: a (required), b (required).

get-tiny-imageGet Tiny Image ToolA

Returns a tiny MCP logo image.

Inputs: no named parameters.

gzip-file-as-resourceGZip File as Resource ToolA

Compresses a single file using gzip compression. Depending upon the selected output type, returns either the compressed data as a gzipped resource or a resource link, allowing it to be downloaded in a subsequent request during the current session.

Inputs: name, data, outputType.

simulate-research-querySimulate Research QueryA

Simulates a deep research operation that gathers, analyzes, and synthesizes information. Demonstrates MCP task-based operations with progress through multiple stages. If 'ambiguous' is true and client supports elicitation, sends an elicitation request for clarification.

Inputs: topic (required), ambiguous.

toggle-simulated-loggingToggle Simulated LoggingB

Toggles simulated, random-leveled logging on or off.

Inputs: no named parameters.

toggle-subscriber-updatesToggle Subscriber UpdatesA

Toggles simulated resource subscription updates on or off.

Inputs: no named parameters.

trigger-long-running-operationTrigger Long Running Operation ToolC

Demonstrates a long running operation with progress updates.

Inputs: duration, steps.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    C2.9/5.0

    Scored across 14 tools

    Disambiguation4/5

    Most tools have clearly distinct purposes, but some get-* tools like get-annotated-message and get-structured-content could be confused, and get-resource-links vs get-resource-reference are similar.

    Naming Consistency2/5

    Tool names mix hyphens (get-env) and underscores (firewall_confirm), and verbs vary inconsistently (echo, get-, toggle-, simulate-, trigger-, gzip-), lacking a uniform pattern.

    Tool Count3/5

    14 tools is a reasonable count, but the server name implies a firewall/pause/confirm focus, while most tools are demo utilities, creating a scope mismatch.

    Completeness2/5

    The server name suggests a firewall use case, but only one tool (firewall_confirm) supports that; the remaining tools are unrelated demos, leaving a significant gap between promise and functionality.

    Maintenance

    ActivityNo data
    ResponsivenessOutdated data

    Related MCP Connectors

    Related MCP Servers

    • F
      license
      Not graded
      quality
      Not graded
      maintenance
      A transparent proxy and execution firewall that intercepts and audits AI agent tool calls against configurable security policies before forwarding them to downstream MCP servers. It provides safe execution environments with features like data redaction, anti-loop protection, and unified alert dispatching.
      -
    • A
      license
      Not graded
      quality
      D
      maintenance
      A policy-enforcing MCP gateway that intercepts all tool calls to downstream MCP servers, applying allow/deny/ask rules with human approval and audit logging for safe access to dangerous tools.
      10 npm
      MIT
    • F
      license
      Not graded
      quality
      C
      maintenance
      Provides a secure MCP boundary for AI agents, intercepting and validating tool calls, redacting secrets, and requiring human approval for sensitive actions with a tamper-evident audit trail.
      -