pfsense-mcp-server
A security-focused local MCP server exposing 41 strongly typed pfSense REST API tools. It gives an MCP client operational visibility into one managed pfSense Plus appliance while keeping the production path GET-only.
Key properties:
- explicit capability gates and typed Pydantic responses;
- credential fields excluded from models, schemas, logs, errors, and fixtures;
- optional sensitive metadata omitted by default;
- fail-closed configuration and TLS verification by default;
- 41 READ tools, zero WRITE tools, and an empty WRITE endpoint allow-list.
Status
v0.2.2 is the current release state. It completes project, packaging, documentation, and defense-in-depth hardening while preserving the 41-tool READ API. It is not yet published on PyPI. No mutating capability is active, and the accepted Tier 0 WRITE infrastructure remains inert.
Scope (current phase)
- REST API only — SSH is out of scope.
- The production server is read-only. Accepted Tier 0 WRITE infrastructure exists as dormant library code, but it is not constructed by production bootstrap, has no allow-listed endpoint, and registers no MCP tool.
- 34 capabilities, 41 tools, spanning system, interfaces, gateways,
firewall, users, certificates, DHCP, DNS, NTP, SSH, cron, ACME,
FreeRADIUS, HA/CARP, and diagnostics. See
src/pfsense_mcp/capabilities.pyfor the authoritative list.
Architecture
Transport (swappable: HttpTransport / MockTransport)
↓
RestApiClient GET-only enforcement, API-version resolution,
↓ JSON parsing, error mapping, duration logging
PfSenseClient semantic methods, raw JSON → typed models
↓
ToolRegistry / MCP Tools thin, capability-gated
Application (application.py) owns startup, dependency construction,
and lifecycle — via factory.py for client construction and
profiles.py for capability-set selection. server.py's only job is
Application().run().
diagnostics.py reports local server health (configuration validity,
TLS mode, active API version, registered capabilities, transport
type) without ever contacting pfSense.
The authoritative capability profile gates registration before a tool can be exposed. The default auditor profile contains the accepted READ set; the engineer placeholder contains no capabilities. Endpoint registries independently enforce GET-only access and an empty WRITE allow-list. An optional exact-name restriction can further reduce the tools authorized by the selected profile; it can never add a tool or capability.
Credentials
This project never stores, logs, or contains an API key. The key is
loaded at runtime from a local file outside this repository — only
its first line is read — path supplied via PFSENSE_API_KEY_FILE.
The server fails closed if the key cannot be loaded.
Configuration (missing/invalid values fail closed)
| Variable | Required | Example |
|---|---|---|
PFSENSE_API_URL |
yes | https://pfsense.example.invalid |
PFSENSE_IDENTITY |
yes | api-mcp-admin |
PFSENSE_API_KEY_FILE |
yes | /path/outside/repository/pfsense-api.key |
PFSENSE_TLS_MODE |
no (default strict) |
strict / auto / insecure |
PFSENSE_TLS_CA_FILE |
required if PFSENSE_TLS_MODE=auto |
path to a CA bundle |
PFSENSE_API_VERSION |
no (default v2) |
v2 |
PFSENSE_PROFILE |
no (default auditor) |
auditor / engineer |
PFSENSE_ALLOWED_TOOLS |
no | comma-separated exact MCP tool names |
PFSENSE_LOG_MAX_BYTES |
no (default 5000000) |
log-file rotation size |
PFSENSE_LOG_BACKUP_COUNT |
no (default 5) |
rotated log files kept |
PFSENSE_TLS_MODE=insecure disables certificate verification and must
be set explicitly. Switching to auto later (once a CA file exists)
requires no code change — only this configuration.
PFSENSE_PROFILE=engineer currently grants no capabilities — write
tools are not registered or reachable. It is a named placeholder for
a separate, explicitly authorized future phase.
PFSENSE_ALLOWED_TOOLS is an optional restriction applied after the selected
profile. If absent, the auditor profile keeps all 41 tools. If present, only
the comma-separated exact names in both the profile and restriction register.
Whitespace around names is ignored and duplicate names are normalized. An
explicitly empty value registers zero tools. Unknown names, empty list entries,
wildcards, and prefix patterns fail closed at startup. The setting can only
remove tools; it cannot grant a capability, activate WRITE, or override an
endpoint check.
Security policy
Credential material is never part of a public model or MCP schema and
is ignored if pfSense includes it in a READ response. Optional
include_identifying_metadata arguments disclose sensitive operational
metadata only; they never disclose passwords, pre-shared keys, private
keys, or API-key plaintext. See the security model
and vulnerability reporting policy.
The supported MCP transport is local stdio. The process launching and controlling that channel is the caller-authentication boundary; this is not a multi-tenant network service. Public CI has no production configuration and never contacts a pfSense appliance.
Every current tool advertises MCP readOnlyHint=true and
openWorldHint=true: it does not mutate pfSense, but it reads dynamic data
from an external appliance. These annotations are untrusted client hints for
presentation and tool selection, not authorization. They do not relax
capability profiles, the optional exact-name restriction, GET-only or endpoint
enforcement, credential handling, auditing, or WRITE inactivity.
Installation from source
Linux is the supported production platform because secure credential loading depends on descriptor-bound Unix file semantics. Python 3.11 or newer is required. Clone the repository, create an isolated environment, and install the project:
git clone https://github.com/night4me/pfsense-mcp-server.git
cd pfsense-mcp-server
python -m venv .venv
.venv/bin/python -m pip install --upgrade pip
.venv/bin/python -m pip install .
The project is not currently published on PyPI. Do not use a similarly named package from a package index. After the owner publishes the authenticated release, the exact-version installation command will be:
python -m pip install 'pfsense-mcp-server==0.2.2'
Until the project page and release provenance are publicly verifiable, install from the reviewed source tree as shown above.
Quick start
Create the API-key file outside the repository and restrict it to the account that runs the MCP server:
install -m 600 /dev/null /absolute/private/path/pfsense-api.key
Place the key on the first line without printing it in shell history or logs. Then configure your MCP client to launch the console entry point with these environment variables:
{
"command": "/absolute/path/to/.venv/bin/pfsense-mcp-server",
"env": {
"PFSENSE_API_URL": "https://pfsense.example.invalid",
"PFSENSE_IDENTITY": "api-mcp-admin",
"PFSENSE_API_KEY_FILE": "/absolute/private/path/pfsense-api.key",
"PFSENSE_TLS_MODE": "strict"
}
}
The exact outer MCP-client configuration key varies by client. Use one of the
verified client examples, then confirm that the client
shows 41 READ tools and no WRITE tools. A first safe call is
pfsense_get_system_status. The server communicates over stdio and produces
no web interface or screenshotable UI.
For development, install the project with its test and analysis tools:
.venv/bin/python -m pip install -e ".[dev]"
make quick
make validate
Additional release checks are documented in the release checklist. Live private-infrastructure acceptance is separate, opt-in, and never part of public CI.
Direct launch
Direct launch is useful for confirming configuration and MCP startup. The process waits for MCP messages on stdin when configuration is valid.
PFSENSE_API_URL=https://pfsense.example.invalid \
PFSENSE_IDENTITY=api-mcp-admin \
PFSENSE_API_KEY_FILE=/absolute/private/path/pfsense-api.key \
PFSENSE_TLS_MODE=strict \
pfsense-mcp-server
Troubleshooting
The server exits with a configuration error
Configuration fails closed. Confirm every required variable is present, the API URL is an HTTPS origin without a path, and the identity contains no control characters. Error messages identify the invalid setting but never print the key value.
The API-key file is rejected
The file must be a regular non-symlink file owned by the process user, with no group or other permission bits, and its first line must be non-empty and bounded. Parent directories should normally be mode 0700.
TLS verification fails
Prefer strict with the system trust store. For an internal CA, set
PFSENSE_TLS_MODE=auto and point PFSENSE_TLS_CA_FILE to a readable CA
bundle. insecure disables certificate verification and should be limited to
short, explicitly accepted diagnostics.
No tools appear
Use PFSENSE_PROFILE=auditor, the default accepted READ profile. The
engineer placeholder intentionally grants no capabilities in this build.
Also check PFSENSE_ALLOWED_TOOLS: an explicitly empty value intentionally
registers zero tools, and a configured subset hides every unlisted tool.
Can this server manage more than one appliance?
No. One process has one configured upstream identity and appliance. Launch a separate process with separate configuration for another appliance.
Documentation
- MCP tool reference
- Future-major API review
- Client setup examples
- Architecture diagrams
- Architecture decisions
- Offline benchmark methodology
- Threat model
- Security abuse-case catalog
- Security model
- Vulnerability reporting
- Public roadmap
- Future Recovery Contract specification
- Contribution guide
- Release checklist
- PyPI release procedure
- Dependency policy
- v0.2.2 acceptance
- v0.2.1 acceptance
Contributing
Contributions are welcome within the documented security and approval boundaries. Read CONTRIBUTING.md before opening a change. Report vulnerabilities privately as described in SECURITY.md.
License
Licensed under the MIT License. The copyright notice uses the project contributor identity and does not assert ownership by an invented person or organization.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pfsense_mcp_server-0.2.2.tar.gz.
File metadata
- Download URL: pfsense_mcp_server-0.2.2.tar.gz
- Upload date:
- Size: 74.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3e2c17629e807da929af4159e19351815e955a1303824a54aa500b700dd65116
|
|
| MD5 |
5fa13a3023076f679e6906753c452f9b
|
|
| BLAKE2b-256 |
e6e44d0029bd06e0385e3a09f01bbd9db90be35540cded9e57701fc706b94794
|
Provenance
The following attestation bundles were made for pfsense_mcp_server-0.2.2.tar.gz:
Publisher:
publish.yml on night4me/pfsense-mcp-server
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pfsense_mcp_server-0.2.2.tar.gz -
Subject digest:
3e2c17629e807da929af4159e19351815e955a1303824a54aa500b700dd65116 - Sigstore transparency entry: 2381541691
- Sigstore integration time:
-
Permalink:
night4me/pfsense-mcp-server@e4e83833336e2d7287a58bc3d232ee95166a356a -
Branch / Tag:
refs/heads/main - Owner: https://github.com/night4me
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e4e83833336e2d7287a58bc3d232ee95166a356a -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file pfsense_mcp_server-0.2.2-py3-none-any.whl.
File metadata
- Download URL: pfsense_mcp_server-0.2.2-py3-none-any.whl
- Upload date:
- Size: 94.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e06595b393c1a6af0ed10f913e77a493e326a65de0ac3b69d5e94cbe7a0c57a1
|
|
| MD5 |
aec14347cc77d93e63d11910871aab92
|
|
| BLAKE2b-256 |
2150158ed122dd0e6e67198dd44a752d04f4317b8e363f85d91348fb96dacd35
|
Provenance
The following attestation bundles were made for pfsense_mcp_server-0.2.2-py3-none-any.whl:
Publisher:
publish.yml on night4me/pfsense-mcp-server
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pfsense_mcp_server-0.2.2-py3-none-any.whl -
Subject digest:
e06595b393c1a6af0ed10f913e77a493e326a65de0ac3b69d5e94cbe7a0c57a1 - Sigstore transparency entry: 2381541820
- Sigstore integration time:
-
Permalink:
night4me/pfsense-mcp-server@e4e83833336e2d7287a58bc3d232ee95166a356a -
Branch / Tag:
refs/heads/main - Owner: https://github.com/night4me
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e4e83833336e2d7287a58bc3d232ee95166a356a -
Trigger Event:
workflow_dispatch
-
Statement type: