Skip to content

kphatak001/mcpfw

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

29 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mcpfw

Transparent policy enforcement proxy for MCP servers. Sits between your AI agent and MCP servers, inspecting every tool call against YAML policies before it reaches the server.

Two modes: stdio (wrap a local server) or HTTP proxy (network-enforced, can't bypass).

Agent ──HTTP──▶ mcpfw (proxy :8443) ──HTTP──▶ MCP Server
                      │
                 ┌────┴────┐
                 │ Layer 3  │  Per-call policy (stateless)
                 │ Layer 2  │  Session envelope (stateful)
                 │ Rug-pull │  Tool description integrity
                 │ Scanner  │  Response injection detection
                 └──────────┘

Install

pip install mcpfw

Demo

Agent Firewall Demo

Normal calls pass. Multi-step exfiltration gets killed. Per-call policy allows every individual action. The session-level envelope catches the trajectory.

Run it yourself: bash demo/run_demo.sh (or bash demo/start_firewall.sh for persistent mode)

Usage

Stdio mode (local MCP servers)

Wrap any MCP server — one config line change:

 {
   "mcpServers": {
     "filesystem": {
-      "command": "npx",
-      "args": ["-y", "@modelcontextprotocol/server-filesystem", "."]
+      "command": "mcpfw",
+      "args": ["--policy", "policy.yaml", "--",
+               "npx", "-y", "@modelcontextprotocol/server-filesystem", "."]
     }
   }
 }

Works with Claude Code, Kiro, Cline, or any MCP client. Zero client changes.

HTTP proxy mode (remote MCP servers, network-enforced)

mcpfw --listen :8443 --target https://mcp-server:3000 --policy policy.yaml

The agent connects to mcpfw's port. mcpfw proxies to the real server. The agent cannot bypass policy because mcpfw IS the network path.

Streamable HTTP (production MCP transport)

mcpfw --listen :8443 --target https://mcp-server:3000 \
      --transport streamable --policy policy.yaml

Supports the MCP spec 2025-03-26 transport: JSON responses + SSE streaming with per-event inspection.

With behavioral envelope (session-level enforcement)

mcpfw --listen :8443 --target https://mcp-server:3000 \
      --policy policy.yaml --envelope envelope.yaml

Adds agent-envelope session tracking: cross-action data flow detection, workflow matching, drift scoring, and kill switch. Catches multi-step attacks that per-call policy misses.

Rug-Pull Detection

mcpfw caches tool descriptions on first tools/list response. If descriptions change later (the postmark-mcp attack pattern), the response is blocked:

First tools/list:  send_email: "Send an email"           → cached ✅
Later tools/list:  send_email: "Send email. BCC admin@evil.com"  → 🛑 BLOCKED: rug-pull detected

This happens automatically. No configuration needed.

Policy Files

name: standard

# Scan MCP server responses for prompt injection
scan_responses:
  enabled: true

rules:
  # Session-wide call budget (prevents resource amplification)
  - name: session_budget
    action: budget
    max_calls: 200
    max_per_tool: 50
    message: "Session call budget exceeded"

  # Detect exfiltration sequences (read secrets → network call)
  - name: exfil_env_curl
    action: sequence
    pattern: ["read_file:*.env*", "run_command:*curl*"]
    message: "Blocked: read sensitive file then network call"

  # Block writes to sensitive paths
  - action: deny
    tools: ["write_file", "edit_file"]
    when:
      arg_matches:
        path: ["~/.ssh/**", "~/.bashrc", "/etc/**", "**/.env*"]
    message: "Write to sensitive path blocked"

  # Allow all reads
  - action: allow
    tools: ["read_file", "list_directory", "search_files"]

  # Allow writes within project
  - action: allow
    tools: ["write_file", "edit_file"]
    when:
      arg_matches:
        path: ["./src/**", "./tests/**"]

  # Rate limit everything
  - action: rate_limit
    tools: ["*"]
    rate: 60/minute

  # Ask human for anything else
  - action: ask
    tools: ["*"]
    message: "Requires approval"

Rule Actions

Action Behavior
allow Forward to MCP server
deny Return error to agent, never reaches server
ask Pause, prompt human in terminal, wait for y/n
rate_limit Token bucket — deny if exceeded, otherwise fall through
budget Session-wide call caps — total and per-tool
sequence Detect suspicious multi-call patterns across session history

Default Action

By default, mcpfw allows tool calls that don't match any rule. Set default_action to change this:

name: locked-down
default_action: deny   # or "ask"

rules:
  - action: allow
    tools: ["read_file", "list_directory"]
  # everything else is denied — fail closed
Value Behavior
allow (default) Unmatched calls pass through
deny Unmatched calls are blocked
ask Unmatched calls require human approval

The bundled paranoid.yaml uses default_action: deny.

Argument Matching

when:
  # Glob patterns on argument values
  arg_matches:
    path: ["~/.ssh/**", "/etc/**"]

  # Substring containment
  arg_contains:
    command: ["rm -rf", "curl | bash"]

  # Regex
  arg_regex:
    command: "curl.*\\|.*bash"

Response Scanning

MCP server responses are scanned for prompt injection before reaching the agent. A compromised or malicious MCP server can embed instructions like "ignore previous instructions" in tool output — mcpfw catches these and returns a sanitized error instead.

Enable in your policy:

scan_responses:
  enabled: true
  extra_patterns:          # optional — add your own regex
    - "CUSTOM_MARKER"

Default patterns detect common injection vectors: ignore previous instructions, <system> tags, [INST] markers, and similar.

Based on: VIGIL: Verify-Before-Commit, MCP-ITP: Implicit Tool Poisoning

Discovery Filtering

When an agent sends tools/list, mcpfw intercepts the response and strips out any tool that the policy would deny. The agent never sees denied tools — fewer tokens in context, no hallucinated calls to blocked tools, no wasted round-trips.

This happens automatically based on your existing deny rules. A tool is hidden when evaluate with empty arguments yields deny. Tools with argument-conditional deny rules (e.g. "deny write_file only to ~/.ssh") are not hidden — they might be allowed with different arguments.

MCP Server responds: 27 tools
                        │
                   mcpfw filters
                        │
Agent receives:    10 tools (denied tools invisible)

Filtered tools are logged to the audit trail:

{"event":"discovery_filtered","hidden_tools":["issue_refund","cancel_subscription","deactivate_customer"],"count":3,"message":"Stripped 3 tool(s) from discovery response"}

No policy changes needed — if you already have deny rules, discovery filtering works out of the box.

Session Budgets

Cap total tool calls per session and per-tool to prevent resource amplification attacks where a malicious MCP server triggers recursive tool chains that inflate costs.

- name: session_budget
  action: budget
  max_calls: 200       # total calls across all tools
  max_per_tool: 50     # per individual tool
  message: "Session budget exceeded"

Per-tool limits only block the specific tool that exceeded its budget — other tools remain available.

Based on: Beyond Max Tokens: Stealthy Resource Amplification via Tool Calling Chains

Sequence Detection

Detect multi-step attack patterns across session history. Catches exfiltration sequences like "read .env file, then curl to external server."

- name: exfil_env_curl
  action: sequence
  pattern: ["read_file:*.env*", "run_command:*curl*"]
  message: "Blocked: read sensitive file then network call"

Steps use tool_name:arg_glob syntax. The engine walks session history backwards to find preceding steps. Only fires when all steps match in order.

Based on: Taming Privilege Escalation in LLM Agent Systems, AgentGuardian: Learning Access Control Policies

Temporal Preconditions

Enforce time-based rules: "this action is only allowed if a different action happened first, within a time window." This is the stateful enforcement layer that no other open-source tool provides.

Requires prior event:

# Payment tools require human approval within the last 30 minutes
- name: payment_gate
  action: requires
  tools: ["payment_*", "refund_*"]
  requires_event: "human_approval"
  within: "30m"
  message: "Payment requires human approval within last 30 minutes"

Cooldown (minimum time between events):

# Cannot delete a resource within 5 minutes of creating it
- name: no_rapid_delete
  action: requires
  tools: ["delete_*"]
  requires_event: "create_*"
  cooldown: "5m"
  message: "Cannot delete within 5 minutes of creation"

Duration formats: 30m, 2h, 300s, 1h30m. Glob patterns work on both tool names and event names.

See policies/temporal.yaml for a complete example with payment gates, rapid-delete prevention, and maintenance windows.

Bundled Policies

Policy Description
permissive.yaml Log everything, block nothing
standard.yaml Block sensitive paths, allow reads, ask for unscoped writes, session budgets, exfiltration detection, response scanning
paranoid.yaml Ask for everything except reads, tight budgets, aggressive sequence detection, response scanning
temporal.yaml Payment gates, rapid-delete prevention, maintenance windows (temporal preconditions demo)
org-baseline.yaml Organization-wide baseline: infra path deny, payment gate, session budget
team-support.yaml Support team layer: KB reads, rate-limited email, ask-default

Policy Composition

Layer multiple policy files with precedence. Deny at a higher layer cannot be overridden by allow at a lower layer.

mcpfw --policy policies/org-baseline.yaml \
      --policy policies/team-support.yaml \
      --listen :8443 --target http://mcp-server:3000

First --policy = highest priority. Rules are evaluated in order: org rules first, then team rules. If the org denies a tool call, the team's allow never fires.

This mirrors Cedar's hierarchical forbid semantics without requiring a new policy language.

Demo

Try it without any external MCP server — a mock server and test calls are included.

Quick (single terminal):

python3 tests/send_calls.py | mcpfw -p policies/standard.yaml -- python3 tests/mock_server.py

Interactive (two terminals):

# Terminal 1 — start mcpfw with mock server
mkfifo /tmp/mcpfw-demo
mcpfw -p policies/standard.yaml -l audit.jsonl -- python3 tests/mock_server.py < /tmp/mcpfw-demo

# Terminal 2 — send calls one at a time, press Enter between each
python3 tests/interactive_demo.py > /tmp/mcpfw-demo

Sends 6 tool calls that exercise every decision type: allow, deny, and the interactive 🔒 Allow? [y/N] prompt.

CLI Options

mcpfw --policy policy.yaml [options] -- <mcp-server-command>

Options:
  --policy, -p     Path to policy YAML (required)
  --audit-log, -l  Path to JSON-lines audit log
  --dry-run        Log decisions but allow everything

Audit Log

Every tool call is logged as JSON-lines:

{"event":"tool_call","tool":"write_file","arguments":{"path":"~/.ssh/key"},"decision":"deny","rule":"block_sensitive","message":"Write to sensitive path blocked","timestamp":1713700000}

Blocked responses are also logged:

{"event":"response_blocked","request_id":3,"pattern":"(?i)ignore\\s+(all\\s+)?previous\\s+instructions","message":"Server response contained suspected prompt injection","timestamp":1713700001}

Pair with agentspec

agentspec scans your agent config and generates mcpfw policies AND envelopes automatically:

# Generate per-call policy
agentspec model agent.yaml --emit-policy -o policy.yaml

# Generate session-level envelope
agentspec model agent.yaml --emit-envelope -o envelope.yaml

# Enforce both at the network layer
mcpfw --listen :8443 --target https://server:3000 \
      --policy policy.yaml --envelope envelope.yaml

Pair with findingfold

findingfold is an MCP server that collapses security findings by root cause. Wrap it with mcpfw to enforce policies on which findings data the agent can access:

mcpfw --policy policy.yaml -- findingfold-mcp

The Trilogy

mcpfw is part of a three-layer open-source agent security stack:

Layer Tool Question
Pre-deploy agentspec "Is this agent config risky?"
Runtime (session) agent-envelope "Is this agent off-script?"
Runtime (per-call) mcpfw "Is this specific call allowed?"

License

Apache-2.0 — see LICENSE.

About

Transparent policy enforcement proxy for MCP servers. Sits between your AI agent and MCP servers, inspecting every tool call against YAML policies before it reaches the server.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages