Metadata-Version: 2.4
Name: tazendor-opnsense-mcp
Version: 0.2.0
Summary: MCP server for the OPNsense REST API
License: MIT
License-File: LICENSE
Requires-Python: >=3.12
Requires-Dist: httpx>=0.27
Requires-Dist: mcp>=1.0
Description-Content-Type: text/markdown

# OPNsense MCP Server

[![CI](https://github.com/tazendor/opnsense-mcp-server/actions/workflows/ci.yml/badge.svg)](https://github.com/tazendor/opnsense-mcp-server/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/tazendor-opnsense-mcp)](https://pypi.org/project/tazendor-opnsense-mcp/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Built with SpecKit](https://img.shields.io/badge/built%20with-SpecKit-6f42c1)](https://github.com/github/spec-kit)

**GitHub**: https://github.com/tazendor/opnsense-mcp-server

A Python [Model Context Protocol](https://modelcontextprotocol.io/) server that exposes the OPNsense REST API to AI clients such as Claude Desktop and Claude Code.

## What it does

The server proxies 43 OPNsense API endpoints across eight domains as MCP tools, letting AI clients query and mutate firewall state through natural language.

| Domain | Tools | Capabilities |
|--------|-------|--------------|
| System | 3 | Status, firmware check, config backup |
| Firewall | 17 | Rule and alias CRUD, NAT port forwards, apply |
| Interfaces | 4 | Interface list, config, ARP/NDP tables |
| DHCP | 3 | Lease list, settings, static mappings |
| Routes | 5 | Static route CRUD and apply |
| DNS | 6 | Unbound settings and host override CRUD |
| IDS | 1 | Ruleset list |
| Services | 4 | Start/stop/restart/status for core modules |

Mutating operations follow OPNsense's staged-then-apply model: changes are staged by `_add`/`_update`/`_delete` tools and committed by the corresponding `_apply` tool.

## Requirements

- Python 3.12+
- [`uv`](https://docs.astral.sh/uv/)
- OPNsense **26.1+** with API access enabled

> **Compatibility**: Tested against OPNsense 26.1.10. The 26.x release series
> made breaking REST API changes — Kea replaced ISC DHCPv4 (`kea/*` paths),
> port-forward NAT moved to Destination NAT (`firewall/d_nat/*`), and the system
> status endpoint changed. Older releases are not supported.

## Installation

```bash
pip install tazendor-opnsense-mcp
```

Or from source:

```bash
git clone https://github.com/tazendor/opnsense-mcp-server.git
cd opnsense-mcp-server
uv sync
```

## Configuration

### Environment variables

```bash
export OPNSENSE_URL="https://192.168.1.1"       # required; must be https://
export OPNSENSE_API_KEY="your-api-key"           # required
export OPNSENSE_API_SECRET="your-api-secret"     # required
export OPNSENSE_VERIFY_TLS="false"               # only for self-signed certs
export OPNSENSE_TRANSPORT="stdio"                # or "http"
```

### Config file

Create `~/.config/opnsense-mcp/config.toml`:

```toml
url = "https://192.168.1.1"
api_key = "your-api-key"
api_secret = "your-api-secret"
verify_tls = false   # omit or set true for valid certificates
transport = "stdio"  # or "http"
```

Environment variables override config file values.

## Running

### stdio (Claude Desktop / Claude Code)

```bash
uv run opnsense-mcp
```

Claude Desktop `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "opnsense": {
      "command": "uv",
      "args": ["run", "--project", "/path/to/opnsense-mcp-server", "opnsense-mcp"],
      "env": {
        "OPNSENSE_URL": "https://192.168.1.1",
        "OPNSENSE_API_KEY": "...",
        "OPNSENSE_API_SECRET": "..."
      }
    }
  }
}
```

### HTTP (remote clients)

```bash
OPNSENSE_TRANSPORT=http OPNSENSE_HTTP_PORT=8000 uv run opnsense-mcp
```

The server validates credentials against OPNsense on startup and exits with a non-zero status if it cannot connect or authenticate, so bad configuration is caught before any client connects.

## Development

```bash
# Run unit and contract tests (no OPNsense instance needed)
uv run pytest -m "not integration"

# Run integration tests against a live instance
OPNSENSE_URL=https://... OPNSENSE_API_KEY=... OPNSENSE_API_SECRET=... \
  uv run pytest -m integration -v

# Quality gates
uv run ruff check src/ tests/
uv run ruff format --check src/ tests/
uv run mypy --strict src/
```

All unit and contract tests pass without a live OPNsense instance (`pytest -m "not integration"`).

## Security notes

- HTTPS is enforced; the server refuses to start with an `http://` URL.
- Credentials are read from environment or config file and never logged.
- Every API call is logged to stderr with method, path, status code, and outcome for auditability.
- When `OPNSENSE_VERIFY_TLS=false`, a warning is printed at startup.
