agent-firewall
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@agent-firewallAdd a rule to ask before any git push"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
agent-firewall
Published on npm as @beernathan87/agent-firewall (the unscoped name was already taken); the command name stays agent-firewall.
A local policy gateway between AI agents and their tools. It sits in front of any MCP server (filesystem, shell, GitHub, browser, …) and applies allow / ask / block rules to every tool call: which tools, which filesystem paths, which shell commands, which network domains. Anything marked ask pops up on a local approval page; everything is written to an audit log.
# before: your MCP client config runs the server directly
"filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/home/me/project"] }
# after: run it through the firewall
"filesystem": { "command": "npx", "args": ["-y", "agent-firewall", "--policy", "/home/me/project/agent-firewall.yml", "--name", "fs", "--",
"npx", "-y", "@modelcontextprotocol/server-filesystem", "/home/me/project"] }Works with Claude Desktop, Claude Code, Cursor, Windsurf, Codex, Continue - anything that launches MCP servers over stdio. No changes to the agent or the server. Zero dependencies except yaml, Node 20+.
What happens on a tool call
The firewall extracts paths, shell commands and hosts from the tool's arguments (heuristic key and value inspection:
path,paths[],command,args[], URLs in strings, …).Built-in dangerous command patterns (
rm -rf /,curl … | sh,git push --force,mkfs,DROP TABLE,--no-verify, …) are blocked outright.The first rule whose
toolglob matches decides. Inside a rule,paths/commands/domainspatterns refine it: ablockpattern always wins, otherwise the longest matching pattern decides (ties prefer ask). Every extracted value contributes its result, including the rule decision or policy default for unmatched values; the most restrictive result wins. Patterns can override the enclosing rule decision. Put narrow tool rules before broad rules: later rules do not run.allow → forwarded. block → the agent receives an
isErrortool result explaining why (so it stops instead of looping). ask → the call waits for you onhttp://127.0.0.1:<port>/<token>(printed to stderr): Approve, Approve for this session, or Deny; unanswered requests are denied afteraskTimeoutseconds.Every decision is appended to
agent-firewall-audit.jsonl(tool, decision, rule, reason, extracted paths/commands/hosts, truncated args).
Related MCP server: cordon
Policy
default: ask # anything no rule covers
askTimeout: 120
rules:
- id: reads
tool: [read_file, list_directory, search_files, "*_read"]
decision: allow
paths: { block: ["**/.env", "**/*.pem", "~/.ssh/**", "**/secrets/**"] }
- id: writes
tool: [write_file, edit_file, "*_write"]
paths:
allow: ["./src/**", "./test/**"] # relative to the firewall's working directory
ask: ["./**"]
block: ["**/.git/**", "~/**"]
- id: deletes
tool: ["delete*", "remove*"]
decision: block
reason: deleting is done by humans in this repo
- id: shell
tool: [execute_command, run_command, shell, bash]
commands:
allow: ["git status*", "git diff*", "npm test*", "ls*"]
ask: ["git commit*", "git push*", "npm install*"]
block: ["sudo *", "rm *", "curl *", "ssh *"]
- id: web
tool: [fetch, http_request]
domains: { allow: ["api.github.com", "*.githubusercontent.com"], ask: ["*"] }
- id: github-writes
server: github # only when started with --name github
tool: [create_issue, create_pull_request, push_files, "merge_*"]
decision: askGlobs: * matches within one path segment, ** crosses segments, ? one character; command and domain patterns are plain globs (* matches anything). A plain list (paths: ["./src/**"]) means "only these, block everything else", including calls with no extracted values for that list. Leading **/ path patterns apply outside the working directory too. **/.env does not match .env.local; include **/.env.*. Paths normalize dot segments, backslashes, file URLs and percent escapes; Windows path inspection also removes alternate-stream suffixes and trailing dots/spaces from segments; ~ uses the firewall process home at policy load and evaluation. Windows path matching ignores case; other platforms use case-sensitive matching, even on case-insensitive macOS volumes. Use conservative tool decisions where filesystem case aliases matter. Domain patterns should use lowercase ASCII/punycode; extracted hosts use URL hostname parsing, including IP addresses and userinfo. A server: rule requires a nonempty --name. Without a policy file the built-in defaults apply: reads allowed (except secrets), writes / deletes / shell / network ask, selected exact shell commands allowed; additional flags require approval. Builtins escalate shell syntax (chains, substitutions, redirection, quoting) and interpreter/wrapper invocations to ask; common direct destructive patterns block case-insensitively. All string arguments of shell-like tool names are inspected as commands. builtins: false disables both protections.
agent-firewall check execute_command '{"command":"git push --force"}' # BLOCK (exit 2); ASK exits 3, ALLOW 0
agent-firewall explain --policy agent-firewall.yml # print the compiled rulesOptions
Flag | Default | Meaning |
|
| Policy file |
| Server name for | |
|
| JSONL audit log ( |
|
| What ask does: local approval page, deny, or auto-approve (unsafe) |
| random | Port of the approval page (127.0.0.1 only) |
| policy | Seconds before an unanswered approval is denied |
|
| Rotate the audit file to |
Limits (read this)
It gates what goes through the MCP server it wraps. A shell tool that is allowed to run node script.js can do anything script.js does; path extraction is heuristic (arguments that look like paths or commands), so a tool with an unusual schema may need an explicit decision. It is not a sandbox - combine it with OS-level permissions for hard guarantees. Approval decisions are per firewall process; "approve for this session" covers the same server, tool and complete serialized arguments until the server exits. The approval page shows complete arguments. Changed content or additional paths require another approval; reordered object keys may also prompt again.
Only individual newline-delimited JSON-RPC objects are accepted from the client. Batches are rejected in full, malformed tool calls are rejected, and tools/call notifications are dropped without execution. CRLF and split UTF-8 chunks are supported. Messages exceeding 8 MiB terminate the connection. An approval wait does not serialize later calls; clients must await results for dependent actions. Stdin EOF waits for pending approvals before closing the server input.
The built-in read-tool names and shell allowances are convenience heuristics, not proof of harmless behavior: a tool called get_* may mutate state, npm test runs project code, and user-authored prefix globs can permit flags that change behavior. Review the wrapped server and use default: ask with explicit tool rules for untrusted schemas. The firewall does not resolve symlinks, inspect script bodies, enforce network redirects/DNS resolution, or constrain processes after a permitted call. Shell analysis is conservative pattern matching, not a shell parser; wrapped destructive commands may ask instead of block. On Windows, .cmd/.bat launchers (the shims npm creates, e.g. mcp-server-filesystem.cmd) are started through cmd.exe; the launcher path and every argument are quoted by the firewall, so paths with spaces work, but arguments containing double quotes or newlines are rejected. Prefer launching node.exe with the server's entrypoint when you can. Bare npx child-command resolution is not guaranteed on Windows.
Audit JSONL escapes embedded newlines. Obvious credentials in the argument summary are masked (Bearer …, token=/password=/api_key= values, sk-/ghp_/AKIA… style keys, PEM private keys), but the audit is not a redacted record: arguments (truncated to 500 characters), extracted paths/commands/hosts and tool names can still contain sensitive content. Protect the audit file with OS permissions and retention appropriate to its contents. Audit write failures warn on stderr and do not stop execution. The approval URL is a local bearer secret; do not share it. The page sends no referrer and rejects foreign Host/Origin headers.
Not in v1 (on purpose)
HTTP/SSE MCP transports, non-MCP agent frameworks, central policy sync, desktop notifications, argument rewriting. Team policies, central audit and AgentGuard integration are the intended paid layer.
Installing into a client
npm install -g @beernathan87/agent-firewall (or npx -y @beernathan87/agent-firewall inside the client's command) and wrap each server entry:
// Claude Desktop / Claude Code / Cursor / Codex: claude_desktop_config.json, .mcp.json, mcp.json ...
"filesystem": {
"command": "agent-firewall",
"args": ["--policy", "C:\\Users\\me\\my project\\agent-firewall.yml", "--name", "fs", "--audit", "C:\\Users\\me\\my project\\agent-firewall-audit.jsonl", "--",
"C:\\Users\\me\\AppData\\Roaming\\npm\\mcp-server-filesystem.cmd", "C:\\Users\\me\\my project"]
}Verified with the official MCP client SDK over stdio on Windows (agent-firewall.cmd shim from a prefix with a space, wrapping @modelcontextprotocol/server-filesystem's .cmd launcher rooted at a directory with spaces) and on Linux/Node 20 (~ in policy paths, case-sensitive matching: a write to SRC/ is not covered by an **/src/** allowance). Approvals were exercised in Chromium and Firefox. When the wrapped server exits, the firewall exits with the same code and the client sees the server as gone; check exits 0 (allow), 2 (block), 3 (ask).
Default policy - what you are trusting
With no policy file the built-ins apply. Read this before relying on them:
Reads are allowed by name: tools whose names look like reads (
read_*,list_*,get_*,search_*, …) run without asking, except on secret-looking paths (.env*, keys,~/.ssh, …). A tool that mutates state but is calledget_somethingis allowed. If you wrap a server you have not read, start withdefault: askand explicit tool rules.Exact shell allowances: only a short list of harmless commands (
git status,git diff,ls,pwd, …) is auto-allowed, and only as exact matches - any extra flag, chain, redirection, substitution or wrapper (bash -c,xargs,sudo,env) asks. Destructive patterns block outright.Writes, deletes, network and everything unknown ask.
askwithout an approval page (--ask deny, headless CI) is a deny.Extraction is heuristic: arguments are scanned for path-, command- and URL-shaped values; a server with an unusual schema can carry a path in a field the firewall does not recognise. Give such tools an explicit
decision.
Security boundary
Trusted: you (the approval page and policy file), the MCP client, the OS user account. The firewall runs with your privileges and enforces policy only on
tools/callmessages that pass through it.Untrusted: the agent (tool-call arguments), the wrapped server's behaviour after a permitted call, and anything a permitted command executes.
Approval page: bound to 127.0.0.1 with an unguessable URL token; rejects foreign
Host/Origin,Origin: nulland cross-siteSec-Fetch-Site;Referrer-Policy: same-origin,X-Frame-Options: DENY, CSPdefault-src 'none'. Do not expose the port.Not a sandbox: no symlink resolution, no script inspection, no network enforcement; combine with OS permissions. Report vulnerabilities privately to the maintainer before disclosure.
Troubleshooting
Symptom | Cause / fix |
Client shows the server as failed immediately | Run the same command in a terminal: the wrapped server's stderr is passed through. On Windows check that |
Every call is blocked "(not approved)" |
|
Approve button does nothing | Old versions sent |
|
|
A path rule does not match on Linux/macOS | Matching is case-sensitive there; write patterns in the real case |
Credits
Created by Nathan Beer. Developed by Nathan Beer with AI-assisted engineering using Claude and ChatGPT.
MIT - see LICENSE; third-party licenses in THIRD_PARTY_NOTICES.md.
This server cannot be deployed
Maintenance
Related MCP Connectors
Security & DLP proxy for MCP: tool-poisoning scans, PII redaction on tool args/results. Beta.
MCP enforcement layer that intercepts AI agent actions and blocks rule violations before execution.
- gatewayOAuthai.sealgate
MCP gateway with runtime security policy, tool-call-level control, and audit of agent actions.
Security firewall for AI agents — scans MCP calls for injection, secrets, and risks.
Related MCP Servers
- AlicenseBqualityCmaintenanceSecurity gateway that wraps any MCP server with per-tool policies, approval gates, and optional Ed25519-signed decision receipts. Shadow mode logs every tool call without blocking; enforce mode applies block, rate-limit, and minimum-tier rules. Receipts are independently verifiable offline with no accounts needed.51,279 npm10MIT
- AlicenseNot gradedqualityAmaintenanceSecurity gateway for MCP tool calls. Sits between your LLM client and MCP servers, enforcing per-tool policies (allow/block/approve/read-only), logging every call, and pausing dangerous operations for human approval in terminal or Slack.4 npm1MIT
- AlicenseNot gradedqualityCmaintenanceA drop-in proxy that guards MCP servers with policy enforcement, secret redaction, prompt-injection screening, rug-pull detection, rate limiting, and audit logging.12 npmApache 2.0
- AlicenseNot gradedqualityBmaintenanceA 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.8 npmMIT