pfsense-mcp

pfsense-mcp

Enables AI agents to manage and configure pfSense routers through the pfSense REST API, offering 240 tools across 17 modules with safety features like confirm gates, config backups, and read-only mode.

Category
访问服务器

README

pfSense MCP Server

An MCP server that lets an AI agent (Claude Code, Claude Desktop) manage a pfSense router through the pfSense REST API package (pfrest).

┌─────────────┐   MCP (stdio)   ┌──────────────────┐   HTTPS   ┌─────────────────────┐
│ AI Agent    │ ──────────────► │  pfsense-mcp     │ ────────► │ pfSense router      │
│ (Claude)    │                 │  (Python server) │           │  REST API v1 pkg    │
└─────────────┘                 └──────────────────┘           └─────────────────────┘

Features

  • 240 hand-written tools across 17 modules covering most of the pfSense configuration surface: system, interfaces, firewall, NAT, aliases, DHCP, DNS, routing, diagnostics, services, WireGuard, certificates, users, NTP, IPsec, OpenVPN, and status
  • Safety first:
    • confirm=True required for every destructive operation (delete, reboot, restart, halt)
    • Automatic config backup before destructive changes
    • Read-only mode (PFSENSE_READ_ONLY=true) removes all write tools entirely
    • Audit log with secret redaction
  • Resilience: retry with exponential backoff (3 retries), config-write lock to serialize read-modify-write updates (WireGuard), generous 90 s timeout
  • API-key auth (preferred) or basic auth

Prerequisites

  1. pfSense CE 2.7.x with the REST API package installed:
    • The package serves the v1 API at /api/v1/. There is no v2 API on 2.7.x — this server targets v1 (the last API generation supported by CE 2.7).
    • Install: System → Package Manager → search "api", or from the shell: pkg-static add https://github.com/pfrest/pfSense-pkg-RESTAPI/releases/download/v1.9.0/pfSense-2.7-pkg-API.pkg
    • Create an API key: System → REST API → Keys. The v1 key format is <client_id_hex> <client_secret> (space-separated, no Bearer), e.g. 61646d696e 0123456789abcdef0123456789abcdef (the hex is the username admin; the second part is the secret — never commit a real one)
  2. Python ≥ 3.11 with uv (or pip)

pfSense 2.8+/24.11+ use the v2 API (package v2.x). This server is v1-only; see docs/API_V1_REFERENCE.md for the exact contract and docs/PLAN.md for the version story.

Install

uv sync          # install dependencies
uv run pytest    # run the unit tests (mocked HTTP, no router needed)

Configure

Copy .env.example and fill in the values, or export them in your shell:

Variable Required Description
PFSENSE_URL yes Base URL of the REST API, e.g. https://192.168.1.1
PFSENSE_API_KEY yes* API key as <client_id_hex> <client_secret> (System → REST API → Keys)
PFSENSE_USERNAME / PFSENSE_PASSWORD yes* Basic auth alternative (either API key or these two)
PFSENSE_VERIFY_SSL no true/false; default false (self-signed cert)
PFSENSE_READ_ONLY no true registers only read tools
PFSENSE_AUDIT_LOG no Path to audit log (JSON lines, secrets redacted)
PFSENSE_TIMEOUT no HTTP timeout seconds (default 90; config writes on slow routers may need more)
PFSENSE_MAX_PAGES no Max pages for paginated lists (default 20)

Run

uv run pfsense-mcp

The server speaks MCP over stdio. Point Claude Code / Claude Desktop at it with mcp.json (edit the API key first), or test it with the MCP inspector:

npx @modelcontextprotocol/inspector uv run pfsense-mcp

SSH tunnel (router behind a host)

ssh -L 8443:192.168.1.1:443 user@host
# then PFSENSE_URL=https://127.0.0.1:8443

Tools (240 across 17 modules)

  • System (29) — info, version, uptime, hostname (get/update), DNS settings (get/update, server add/remove), tunables CRUD, packages (list/install/remove), ARP table, system tables, API settings (get/update, errors, version), upgrade status, email notifications, console settings, reboot (confirm), halt (confirm)
  • Interfaces (21) — list/get, statistics, status, available, create/update/delete, apply, VLANs CRUD, bridges CRUD, interface groups CRUD
  • Firewall (22) — rules CRUD + apply, flush, sort, states, states size, schedules CRUD + time ranges, virtual IPs CRUD
  • NAT (13) — port forwards CRUD, one-to-one CRUD, outbound mode get/update, outbound mappings CRUD
  • Aliases (9) — CRUD + advanced settings, alias entry add/delete
  • DHCP (10) — leases, reservations CRUD, dhcpd settings get/update, dhcpd options, start/stop/restart
  • DNS (25) — system DNS, resolver, forwarder, dnsmasq host overrides CRUD + aliases, unbound host overrides CRUD + aliases + flush, unbound access lists CRUD + rows, applies
  • Routing (12) — gateways CRUD + status + detail, default gateway, static routes CRUD, apply
  • Diagnostics (5) — ping, traceroute, DNS lookup, WOL, run command (confirm)
  • Services (16) — list/status, start/stop/restart (confirm), watchdog, sshd settings, syslogd/dpinger control, DDNS
  • WireGuard (10) — tunnels CRUD, peers CRUD (via system-config read-modify-write; the v1 API has no WireGuard endpoints; uses the pfSense 2.7 package schema installedpackages/wireguard/.../item)
  • Certificates (10) — CAs CRUD, certificates CRUD, CRLs CRUD
  • Users (19) — users CRUD, groups CRUD, members, privileges, auth servers, LDAP/RADIUS servers
  • NTP (8) — ntpd settings, time servers CRUD, start/stop/restart
  • IPsec (11) — phase 1 CRUD + encryption entries, phase 2 CRUD, apply, status
  • OpenVPN (13) — servers CRUD, clients CRUD, client-specific overrides CRUD, status
  • Status (7) — CARP, config history, DHCP log, firewall log, system log, log settings

The full per-tool inventory lives in docs/API_V1_REFERENCE.md (§8).

Safety model

  • Confirm gates: delete_*, reboot_system, halt_system, restart_service, install_package, delete_package, run_command, etc. refuse to run unless confirm=True is passed. The gate also covers operations that are destructive by omission: start_service without a service name (starts all services), stop_service (the v1 API ignores the service field and stops everything), stop_*/restart_* for dhcpd/ntpd/syslogd/dpinger, privilege escalation (add_group_member, add_user_privilege), and update_api_settings when disabling the API or enabling read-only (self-lockout).
  • Config backup: before any destructive call, the current config is fetched from /api/v1/system/config/ and saved to ~/.pfsense-mcp/backups/config-<timestamp>.json.
  • Read-only mode: with PFSENSE_READ_ONLY=true the write tools are removed from the server, not just blocked — the agent cannot even see them.
  • Audit log: every write-tool call appends one JSON line (ts, tool, args redacted, ok, detail).

Security model

  • Transport: HTTPS to the pfSense REST API. TLS verification is off by default (PFSENSE_VERIFY_SSL=false) because pfSense ships a self-signed certificate — set it to true and trust the router's CA when you can, and always keep the API reachable only over a private network or SSH tunnel.
  • Authentication: API-key auth (Authorization: <client_id_hex> <client_secret>) is preferred over basic auth. The key comes from the environment/config file and is never logged or echoed by the server.
  • Least privilege: PFSENSE_READ_ONLY=true removes every write tool from the server — the agent cannot even see them, let alone call them.
  • Destructive operations: all delete_*, reboot_system, halt_system, restart_service, package install/remove, and run_command require confirm=True and take a config backup first.
  • Audit trail: every mutation is appended to the audit log (JSON lines) with secrets redacted; point PFSENSE_AUDIT_LOG at a file to enable it. The log is written with O_NOFOLLOW and 0600 permissions, and each entry is fsynced before the call returns.
  • Write verification: config read-modify-write tools (WireGuard) re-read the config after PUT /system/config/ and retry 3× before raising — a pfSense API quirk can return HTTP 200 without persisting (see Known issues), so a silent write loss surfaces as an error instead of a false success. A cross-process file lock serializes concurrent config writes.
  • Command execution surface: run_command executes arbitrary shell commands on the router (root) — it is gated by confirm=True and should be treated as root shell access. ping, traceroute, and dns_lookup build shell commands from their host argument; the argument is validated to reject shell metacharacters so a crafted host cannot inject additional commands.
  • Secrets in config: WireGuard private keys, preshared keys, user passwords, and certificate private keys are written to the router's config.xml (as pfSense itself does) and may appear in tool responses — the audit log redacts known secret fields.

Known issues / operational notes

  • Silently dropped config writes (stale lock): the REST API package guards config writes with /tmp/.api.write_config.lock on the pfSense box. If a PHP request dies mid-write, the lock file is left behind — and while it exists, every config write polls it for ~60 s and then gives up without writing, still returning HTTP 200 with the request payload echoed back (so the change silently never happens). Worse, the lock survives reboots (FreeBSD does not clear /tmp at boot). Symptoms: a write tool returns success but the change is not there. Fix: rm /tmp/.api.write_config.lock on the pfSense router (e.g. via the run_command tool). Mitigation in this server: WireGuard tools verify every config write by re-reading the config after PUT /system/config/ and retry 3× before raising a clear error naming the stale-lock cause. The client's 90 s timeout absorbs the 60 s lock poll so transient contention (a concurrent write) still succeeds.
  • v1 item targeting: no path segments; GET filters via query params, PUT targets via id in the JSON body, DELETE via id in the query string. NAT rules and DHCP reservations use 0-based config indices (id: 0 is valid — treat it as a real id).
  • Empty XML elements come back from the API as "" strings (e.g. "tunnels": ""); the WireGuard tools normalize these.
  • WireGuard schema (2.7 package): config lives under installedpackages/wireguard with lists keyed item (tunnels.item / peers.item). The 2.5-era top-level wireguard.peers.peer schema is invalid on 2.7: peer is not a registered pfSense list tag, so the list serializes as <peer><0> — invalid XML that makes save_config() auto-restore the previous config (and can leave a stale config-write lock). The tools write the 2.7 schema and drop a legacy top-level wireguard section on write.
  • Bounded diagnostics: command_prompt runs commands synchronously, so the diagnostics tools bound runtime — traceroute uses -n -q 1 -w 2 (no per-hop DNS lookups, one probe per hop, 2 s probe wait), keeping even a fully unresponsive 20-hop trace under ~40 s. ping caps each probe at 2 s.

Development

src/pfsense_mcp/
├── server.py        # MCPServer entry point, tool registration
├── config.py        # env config
├── client.py        # REST API v1 HTTP client (httpx, retry/backoff)
├── safety.py        # read-only, confirm gates, backups, audit, RMW lock
└── tools/           # 17 modules, one per pfSense subsystem

Run tests: uv run pytest (mocked HTTP, no router needed) and python scripts/live_integration_test.py against a real router (see the script header for env setup).

CI: .github/workflows/ci.yml runs the unit tests on every push/PR (Python 3.11–3.13). .github/workflows/live-test.yml runs the live integration suite against a real router — manual trigger only, needs a runner that can reach the router and PFSENSE_URL / PFSENSE_API_KEY repository secrets. Dependabot (.github/dependabot.yml) opens weekly update PRs for Python deps and GitHub Actions.

Test environment

The project was developed against a disposable pfSense CE 2.7.2 VM (ID 300) on a Proxmox host, installed via serial-console automation (scripts/install_pfsense.py). See docs/PLAN.md for the full setup walkthrough and docs/API_V1_CONTRACT.md for the generated endpoint contract.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选