opnsense-mcp-server 0.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. opnsense_mcp_server-0.3.2/.dockerignore +19 -0
  2. opnsense_mcp_server-0.3.2/.github/workflows/ci.yml +52 -0
  3. opnsense_mcp_server-0.3.2/.github/workflows/publish-docker.yml +31 -0
  4. opnsense_mcp_server-0.3.2/.github/workflows/publish-pypi.yml +25 -0
  5. opnsense_mcp_server-0.3.2/.gitignore +37 -0
  6. opnsense_mcp_server-0.3.2/CHANGELOG.md +133 -0
  7. opnsense_mcp_server-0.3.2/CONTRIBUTING.md +143 -0
  8. opnsense_mcp_server-0.3.2/Dockerfile +13 -0
  9. opnsense_mcp_server-0.3.2/LICENSE +21 -0
  10. opnsense_mcp_server-0.3.2/Makefile +47 -0
  11. opnsense_mcp_server-0.3.2/PKG-INFO +463 -0
  12. opnsense_mcp_server-0.3.2/README.md +433 -0
  13. opnsense_mcp_server-0.3.2/docs/README.md +20 -0
  14. opnsense_mcp_server-0.3.2/docs/best-practices/ddns-cloudflare.md +146 -0
  15. opnsense_mcp_server-0.3.2/docs/best-practices/voip-whatsapp.md +224 -0
  16. opnsense_mcp_server-0.3.2/pyproject.toml +84 -0
  17. opnsense_mcp_server-0.3.2/src/opnsense_mcp/__init__.py +3 -0
  18. opnsense_mcp_server-0.3.2/src/opnsense_mcp/__main__.py +5 -0
  19. opnsense_mcp_server-0.3.2/src/opnsense_mcp/api_client.py +668 -0
  20. opnsense_mcp_server-0.3.2/src/opnsense_mcp/config.py +89 -0
  21. opnsense_mcp_server-0.3.2/src/opnsense_mcp/config_cache.py +335 -0
  22. opnsense_mcp_server-0.3.2/src/opnsense_mcp/py.typed +0 -0
  23. opnsense_mcp_server-0.3.2/src/opnsense_mcp/server.py +113 -0
  24. opnsense_mcp_server-0.3.2/src/opnsense_mcp/tools/__init__.py +1 -0
  25. opnsense_mcp_server-0.3.2/src/opnsense_mcp/tools/dhcp.py +189 -0
  26. opnsense_mcp_server-0.3.2/src/opnsense_mcp/tools/diagnostics.py +158 -0
  27. opnsense_mcp_server-0.3.2/src/opnsense_mcp/tools/dns.py +150 -0
  28. opnsense_mcp_server-0.3.2/src/opnsense_mcp/tools/firewall.py +675 -0
  29. opnsense_mcp_server-0.3.2/src/opnsense_mcp/tools/haproxy.py +279 -0
  30. opnsense_mcp_server-0.3.2/src/opnsense_mcp/tools/network.py +181 -0
  31. opnsense_mcp_server-0.3.2/src/opnsense_mcp/tools/security.py +1513 -0
  32. opnsense_mcp_server-0.3.2/src/opnsense_mcp/tools/services.py +297 -0
  33. opnsense_mcp_server-0.3.2/src/opnsense_mcp/tools/system.py +153 -0
  34. opnsense_mcp_server-0.3.2/src/opnsense_mcp/tools/vpn.py +65 -0
  35. opnsense_mcp_server-0.3.2/tests/__init__.py +0 -0
  36. opnsense_mcp_server-0.3.2/tests/conftest.py +186 -0
  37. opnsense_mcp_server-0.3.2/tests/integration/__init__.py +0 -0
  38. opnsense_mcp_server-0.3.2/tests/integration/test_ipv6_analysis.py +658 -0
  39. opnsense_mcp_server-0.3.2/tests/integration/test_live_readonly.py +457 -0
  40. opnsense_mcp_server-0.3.2/tests/test_api_client.py +546 -0
  41. opnsense_mcp_server-0.3.2/tests/test_config.py +169 -0
  42. opnsense_mcp_server-0.3.2/tests/test_config_cache.py +295 -0
  43. opnsense_mcp_server-0.3.2/tests/test_tools/__init__.py +1 -0
  44. opnsense_mcp_server-0.3.2/tests/test_tools/test_dhcp.py +242 -0
  45. opnsense_mcp_server-0.3.2/tests/test_tools/test_diagnostics.py +329 -0
  46. opnsense_mcp_server-0.3.2/tests/test_tools/test_dns.py +216 -0
  47. opnsense_mcp_server-0.3.2/tests/test_tools/test_firewall.py +693 -0
  48. opnsense_mcp_server-0.3.2/tests/test_tools/test_haproxy.py +248 -0
  49. opnsense_mcp_server-0.3.2/tests/test_tools/test_network.py +174 -0
  50. opnsense_mcp_server-0.3.2/tests/test_tools/test_registration.py +98 -0
  51. opnsense_mcp_server-0.3.2/tests/test_tools/test_security.py +1538 -0
  52. opnsense_mcp_server-0.3.2/tests/test_tools/test_services.py +416 -0
  53. opnsense_mcp_server-0.3.2/tests/test_tools/test_system.py +280 -0
  54. opnsense_mcp_server-0.3.2/tests/test_tools/test_vpn.py +96 -0
@@ -0,0 +1,19 @@
1
+ .git/
2
+ .venv/
3
+ tests/
4
+ docs/
5
+ .claude/
6
+ .gitea/
7
+ .github/
8
+ __pycache__/
9
+ *.pyc
10
+ .env
11
+ dist/
12
+ build/
13
+ *.egg-info/
14
+ .pytest_cache/
15
+ .mypy_cache/
16
+ .ruff_cache/
17
+ Makefile
18
+ *.md
19
+ !README.md
@@ -0,0 +1,52 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ lint:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: actions/setup-python@v5
15
+ with:
16
+ python-version: "3.11"
17
+ - name: Install dependencies
18
+ run: pip install -e ".[dev]"
19
+ - name: Ruff lint (includes bandit security rules)
20
+ run: ruff check src/ tests/
21
+ - name: Ruff format check
22
+ run: ruff format --check src/ tests/
23
+ - name: Type checking (mypy strict)
24
+ run: mypy src/
25
+
26
+ test:
27
+ runs-on: ubuntu-latest
28
+ steps:
29
+ - uses: actions/checkout@v4
30
+ - uses: actions/setup-python@v5
31
+ with:
32
+ python-version: "3.11"
33
+ - name: Install dependencies
34
+ run: pip install -e ".[dev]"
35
+ - name: Run tests
36
+ run: pytest -v --tb=short
37
+
38
+ security:
39
+ runs-on: ubuntu-latest
40
+ steps:
41
+ - uses: actions/checkout@v4
42
+ - uses: actions/setup-python@v5
43
+ with:
44
+ python-version: "3.11"
45
+ - name: Install dependencies
46
+ run: pip install -e ".[dev]"
47
+ - name: Dependency vulnerability audit
48
+ run: pip-audit
49
+ - name: Check for hardcoded secrets
50
+ run: ruff check src/ --select S105,S106,S107 --no-fix
51
+ - name: Check for insecure SSL usage
52
+ run: ruff check src/ --select S501 --no-fix
@@ -0,0 +1,31 @@
1
+ name: Publish to Docker Hub
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ jobs:
9
+ publish:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+
14
+ - name: Extract version from tag
15
+ id: version
16
+ run: echo "version=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT"
17
+
18
+ - name: Log in to Docker Hub
19
+ uses: docker/login-action@v3
20
+ with:
21
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
22
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
23
+
24
+ - name: Build and push
25
+ uses: docker/build-push-action@v6
26
+ with:
27
+ context: .
28
+ push: true
29
+ tags: |
30
+ lucamarien/opnsense-mcp-server:latest
31
+ lucamarien/opnsense-mcp-server:${{ steps.version.outputs.version }}
@@ -0,0 +1,25 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ permissions:
9
+ id-token: write
10
+
11
+ jobs:
12
+ publish:
13
+ runs-on: ubuntu-latest
14
+ environment: release
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: actions/setup-python@v5
18
+ with:
19
+ python-version: "3.13"
20
+ - name: Install build tools
21
+ run: pip install --no-cache-dir build
22
+ - name: Build wheel and sdist
23
+ run: python -m build
24
+ - name: Publish to PyPI
25
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,37 @@
1
+ # Claude Code (local dev tooling — not published)
2
+ .claude/
3
+ CLAUDE.md
4
+ .mcp.json
5
+
6
+ # Python
7
+ __pycache__/
8
+ *.pyc
9
+ *.pyo
10
+ *.egg-info/
11
+ dist/
12
+ build/
13
+ .venv/
14
+ .eggs/
15
+
16
+ # Credentials (NEVER commit)
17
+ .env
18
+ *.key
19
+ *_apikey.txt
20
+
21
+ # IDE
22
+ .vscode/
23
+ .idea/
24
+ *.swp
25
+ *.swo
26
+ *~
27
+
28
+ # OS
29
+ .DS_Store
30
+ Thumbs.db
31
+
32
+ # Testing
33
+ .coverage
34
+ htmlcov/
35
+ .pytest_cache/
36
+ .mypy_cache/
37
+ .ruff_cache/
@@ -0,0 +1,133 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ This project follows [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [0.3.2] - 2026-03-17
8
+
9
+ ### Fixed
10
+
11
+ - **Diagnostics:** `opn_dns_lookup` now sends correct API payload (`dns.settings.hostname` instead of `dns.hostname`) — lookups were silently failing with validation error
12
+ - **VPN:** `opn_ipsec_status` now uses POST for `searchPhase1`/`searchPhase2` endpoints — GET was returning empty or failed responses
13
+ - **VPN:** `opn_openvpn_status` now uses POST with pagination for all three search endpoints (instances, sessions, routes)
14
+ - **Services:** `opn_crowdsec_status` now uses POST for decisions/alerts search — consistent with `opn_crowdsec_alerts`
15
+
16
+ ### Security
17
+
18
+ - **Diagnostics:** Expanded hostname validation to reject additional shell metacharacters (`(`, `)`, `{`, `}`, `<`, `>`, `'`, `"`, `\`, space)
19
+ - **Diagnostics:** `opn_dns_lookup` now validates the `server` parameter against the same injection rules as `hostname`
20
+
21
+ ### Changed
22
+
23
+ - **Firewall:** `opn_firewall_log` now supports client-side filtering via `source_ip`, `destination_ip`, `action`, `interface`, and `limit` parameters — reduces context window size when investigating specific devices
24
+
25
+ ## [0.3.1] - 2026-02-25
26
+
27
+ Packaging readiness and documentation sync.
28
+
29
+ ### Fixed
30
+
31
+ - Version numbers synced across pyproject.toml, \_\_init\_\_.py, and CHANGELOG (was stuck at 0.1.0)
32
+ - README updated to document all 60 tools (was 57 — missing firewall categories, ICMPv6 rules, NDP table, IPv6 status)
33
+ - Narrowed broad `except Exception` to specific exceptions in `opn_ipv6_status`
34
+
35
+ ### Added
36
+
37
+ - CLI entry point: `opnsense-mcp` command available after `pip install`
38
+ - `__main__.py` for `python -m opnsense_mcp` support
39
+ - PEP 561 `py.typed` marker for downstream type checking
40
+ - Test registration updated to cover all 60 tools
41
+
42
+ ## [0.3.0] - 2026-02-22
43
+
44
+ Comprehensive security audit overhaul — from 4 surface-level checks to 11 in-depth security areas with compliance framework tagging.
45
+
46
+ ### Changed
47
+
48
+ - **Security:** `opn_security_audit` completely rewritten with 11 audit sections (was 4):
49
+ - **Firewall rules:** Paginated analysis (no more 500-row cap), MVC + legacy config.xml rules, broad source/destination detection, port grouping best practices (SSH isolation, mixed service detection), management port exposure on WAN, insecure protocol detection (FTP, Telnet, plaintext SMTP/POP3/IMAP/LDAP, rsh/rlogin)
50
+ - **NAT port forwarding:** Dangerous port exposure, unrestricted sources, UDP amplification risk, insecure protocol forwarding
51
+ - **DNS security:** DNSSEC validation, DNS-over-TLS forwarding, resolver identity/version hiding
52
+ - **System hardening:** Web GUI HTTPS enforcement, SSH root login/password auth/default port, remote syslog configuration
53
+ - **Services:** Expanded critical service list (7 services, was 4), CrowdSec plugin detection
54
+ - **Certificates:** ACME + system certificate store + CA certificates (not just ACME)
55
+ - **VPN security:** WireGuard config audit with stale peer detection, IPsec status, OpenVPN instances
56
+ - **HAProxy security:** HTTP frontend detection, HTTPS redirect checks, backend health checks, security header audit (HSTS, X-Frame-Options, X-Content-Type-Options)
57
+ - **Gateway health:** Down/offline detection, packet loss >5%, latency >100ms
58
+ - **Compliance tagging:** Every finding tagged with applicable PCI DSS v4.0, BSI IT-Grundschutz, NIST SP 800-41, and CIS Benchmark controls. Manual review items listed separately.
59
+
60
+ ### Added
61
+
62
+ - 6 new read-only API endpoint registrations: WireGuard server/client search, HAProxy frontend/backend/server/action search
63
+ - `_fetch_all_pages` pagination helper for unbounded rule analysis
64
+ - Port parsing, classification, and insecure protocol detection helpers
65
+ - 97 security tests (was 18)
66
+
67
+ ## [0.2.0] - 2026-02-22
68
+
69
+ Expands from 35 to 41 tools with NAT port forwarding, full VPN coverage, DNS write operations, and static route visibility.
70
+
71
+ ### Added
72
+
73
+ - **Firewall:** `opn_list_nat_rules` — list NAT port forwarding (DNAT) rules with search/pagination
74
+ - **Firewall:** `opn_add_nat_rule` — create NAT port forwarding rules with savepoint protection
75
+ - **VPN:** `opn_ipsec_status` — IPsec tunnel status (IKE Phase 1 + ESP/AH Phase 2)
76
+ - **VPN:** `opn_openvpn_status` — OpenVPN instances, sessions, and routes
77
+ - **DNS:** `opn_add_dns_override` — add Unbound host overrides (A/AAAA) with auto-reconfigure and input validation
78
+ - **Network:** `opn_list_static_routes` — list configured static routes with search/pagination
79
+ - 11 new API endpoint registrations with dual camelCase/snake_case support
80
+ - DNS input validation (hostname, domain, IP address regex)
81
+ - NAT protocol validation (TCP, UDP, TCP/UDP)
82
+
83
+ ## [0.1.0] - 2026-02-21
84
+
85
+ Initial release with 35 tools across 9 domains.
86
+
87
+ ### Added
88
+
89
+ #### Phase 1: Foundation
90
+
91
+ - Core infrastructure: config loading, API client with HTTP Basic Auth, SSL verification, 30s timeout
92
+ - OPNsense version auto-detection (camelCase for pre-25.7, snake_case for 25.7+)
93
+ - Dual endpoint registry supporting both API naming conventions
94
+ - Endpoint blocklist: `halt`, `reboot`, `poweroff`, `firmware update/upgrade` are permanently blocked
95
+ - FastMCP server with STDIO transport
96
+ - 16 read-only tools:
97
+ - **System:** `opn_system_status`, `opn_list_services`, `opn_gateway_status`
98
+ - **Network:** `opn_interface_stats`, `opn_arp_table`
99
+ - **Firewall:** `opn_list_firewall_rules`, `opn_list_firewall_aliases`, `opn_firewall_log`
100
+ - **DNS:** `opn_list_dns_overrides`, `opn_list_dns_forwards`, `opn_dns_stats`
101
+ - **DHCP:** `opn_list_dhcp_leases`
102
+ - **VPN:** `opn_wireguard_status`
103
+ - **Services:** `opn_haproxy_status`, `opn_list_acme_certs`, `opn_list_cron_jobs`
104
+
105
+ #### Phase 2: Write Operations
106
+
107
+ - SavepointManager for safe firewall modifications with 60-second auto-rollback
108
+ - Write guard requiring `OPNSENSE_ALLOW_WRITES=true` environment variable
109
+ - 8 write tools:
110
+ - **Firewall:** `opn_confirm_changes`, `opn_toggle_firewall_rule`, `opn_add_firewall_rule`, `opn_delete_firewall_rule`, `opn_add_alias`
111
+ - **DNS:** `opn_reconfigure_unbound`
112
+ - **Services:** `opn_reconfigure_haproxy`
113
+
114
+ #### Phase 3: Advanced Features
115
+
116
+ - **Security:** `opn_security_audit` — comprehensive firewall audit (firmware, permissive rules, disabled rules, critical services, ACME certificates)
117
+ - **Config:** `opn_download_config` — download `config.xml` with XML-aware sensitive data stripping (passwords, keys, secrets redacted by default)
118
+ - **Config:** `opn_scan_config` — session-cached full configuration scan with runtime inventory
119
+ - **Config:** `opn_get_config_section` — retrieve individual config sections as structured JSON
120
+ - **Diagnostics:** `opn_ping` (async job-based with guaranteed cleanup), `opn_traceroute`, `opn_dns_lookup`, `opn_pf_states`
121
+ - **DHCP:** `opn_list_kea_leases` — Kea DHCP server leases
122
+ - **DHCP:** `opn_list_dnsmasq_leases` — dnsmasq DNS/DHCP server leases
123
+ - **Services:** `opn_crowdsec_status`, `opn_crowdsec_alerts` — CrowdSec security engine status and alerts
124
+ - ConfigCache system for session-scoped config caching with automatic invalidation on writes
125
+ - Hostname input validation against shell metacharacter injection
126
+ - `get_text()` API client method for non-JSON responses (XML config backup)
127
+
128
+ ### Security
129
+
130
+ - Bandit security scanning via Ruff (S105/S106/S107 for hardcoded credentials, S501 for SSL verification)
131
+ - Strict mypy type checking
132
+ - All tests use mocked API — no real OPNsense credentials in test suite
133
+ - 242 tests with full coverage of security-critical paths
@@ -0,0 +1,143 @@
1
+ # Contributing to OPNsense MCP Server
2
+
3
+ Thank you for your interest in contributing! This guide covers the coding standards, security requirements, and workflow for adding tools or fixing bugs.
4
+
5
+ ## Development Setup
6
+
7
+ ```bash
8
+ git clone https://github.com/lucamarien/opnsense-mcp-server
9
+ cd opnsense-mcp-server
10
+ pip install -e ".[dev]"
11
+ ```
12
+
13
+ ## Coding Standards
14
+
15
+ - **Python 3.11+** with strict typing (`mypy --strict`)
16
+ - **Linting:** `ruff check src/ tests/` (includes bandit security rules)
17
+ - **Formatting:** `ruff format src/ tests/`
18
+ - **Tests:** `pytest -v` (all tests use mocked API responses, no real OPNsense needed)
19
+ - **No external dependencies** beyond `fastmcp` (which provides `httpx`)
20
+
21
+ Run the full CI pipeline locally before submitting:
22
+
23
+ ```bash
24
+ make validate
25
+ ```
26
+
27
+ This runs: lint, format check, security scan, type check, tests, and dependency audit.
28
+
29
+ ## Adding a New Tool
30
+
31
+ 1. Pick the right domain file in `src/opnsense_mcp/tools/`
32
+ 2. Add your function with the `@mcp.tool()` decorator
33
+ 3. Use Python type hints — FastMCP auto-generates JSON schema from them
34
+ 4. Write a clear docstring (it's the AI's **only** guide to tool selection)
35
+ 5. Return `dict`, not formatted strings
36
+ 6. Add tests in `tests/test_tools/`
37
+ 7. Review against the security checklist below
38
+
39
+ ```python
40
+ @mcp.tool()
41
+ async def opn_example_tool(ctx: Context, search: str = "", limit: int = 50) -> dict[str, Any]:
42
+ """One-line description of what this does.
43
+
44
+ Use this when you need to [specific scenario].
45
+ Returns: dict with 'rows' (list) and 'total' (int).
46
+ """
47
+ api = get_api(ctx)
48
+ return await api.get("endpoint.logical_name")
49
+ ```
50
+
51
+ ### Tool Naming
52
+
53
+ - All tools use the `opn_` prefix
54
+ - Use descriptive names: `opn_list_firewall_rules`, `opn_add_dns_override`
55
+ - Read tools: `opn_list_*`, `opn_*_status`, `opn_get_*`
56
+ - Write tools: `opn_add_*`, `opn_delete_*`, `opn_toggle_*`, `opn_reconfigure_*`
57
+
58
+ ### Endpoint Registry
59
+
60
+ Never hardcode API paths in tools. Use logical endpoint names resolved by the API client:
61
+
62
+ ```python
63
+ # Good — uses the endpoint registry (auto-resolves camelCase/snake_case)
64
+ await api.get("firewall.search_rule")
65
+
66
+ # Bad — hardcodes the endpoint path
67
+ await api.get("api/firewall/filter/searchRule")
68
+ ```
69
+
70
+ Add new endpoints to `ENDPOINT_REGISTRY` in `src/opnsense_mcp/api_client.py` with both naming variants:
71
+
72
+ ```python
73
+ "firewall.search_rule": ("firewall/filter/searchRule", "firewall/filter/search_rule"),
74
+ ```
75
+
76
+ ## Security Requirements
77
+
78
+ These are non-negotiable for all contributions:
79
+
80
+ ### Never Do
81
+
82
+ - Hardcode credentials, API keys, or secrets (enforced by Ruff S105/S106/S107)
83
+ - Log, print, or include API keys in any output or error messages
84
+ - Add API endpoints without checking against the blocklist
85
+ - Disable SSL verification without explicit user configuration
86
+ - Use global mutable state for credential storage
87
+ - Expose internal file paths, stack traces, or sensitive config in tool responses
88
+
89
+ ### Always Do
90
+
91
+ - Load credentials from environment variables only
92
+ - Guard write operations with the `OPNSENSE_ALLOW_WRITES` check
93
+ - Use the savepoint/rollback mechanism for all firewall modifications
94
+ - Keep transport as STDIO only — never expose HTTP/SSE endpoints
95
+ - Validate user-provided inputs (hostnames, IP addresses) against injection
96
+
97
+ ### Blocked Endpoints
98
+
99
+ The following endpoints are **permanently blocked** at the API client level and must never be exposed through any tool:
100
+
101
+ - `core/system/halt` — shuts down the firewall instantly
102
+ - `core/system/reboot` — reboots instantly
103
+ - `core/firmware/poweroff` — powers off instantly
104
+ - `core/firmware/update` — triggers system update
105
+ - `core/firmware/upgrade` — triggers major upgrade
106
+
107
+ ## Testing Requirements
108
+
109
+ - **All tests must use mocked API responses** — never connect to a real OPNsense instance
110
+ - Test both success and error paths
111
+ - Test that blocked endpoints are rejected
112
+ - Test that write guards work (`ALLOW_WRITES=false` must fail)
113
+ - Test both camelCase and snake_case endpoint variants for version compatibility
114
+
115
+ ```bash
116
+ # Run all tests
117
+ pytest -v
118
+
119
+ # Run tests for a specific domain
120
+ make test-domain DOMAIN=firewall
121
+
122
+ # Run with verbose output on failures
123
+ pytest -v --tb=long
124
+ ```
125
+
126
+ ## Quick Decision Trees
127
+
128
+ - **GET vs POST:** Status/read operations use GET. Search with filters uses POST. Modifications/actions use POST.
129
+ - **Write guard?** Read operations don't need one. Firewall changes need write guard + savepoint. Other modifications (DNS, HAProxy) need write guard only.
130
+ - **Savepoint?** Firewall rule changes (add/delete/toggle) use savepoints with 60-second auto-revert. Service reconfigurations (Unbound, HAProxy, dnsmasq) do not.
131
+
132
+ ## Pull Request Checklist
133
+
134
+ - [ ] `make validate` passes locally
135
+ - [ ] New tools have tests with mocked API responses
136
+ - [ ] Docstrings are clear and describe when to use the tool
137
+ - [ ] No hardcoded credentials or sensitive data
138
+ - [ ] Write operations are guarded and use savepoints where appropriate
139
+ - [ ] Total tool count stays under 65 (currently 62)
140
+
141
+ ## License
142
+
143
+ By contributing, you agree that your contributions will be licensed under the MIT License.
@@ -0,0 +1,13 @@
1
+ FROM python:3.13-slim AS builder
2
+
3
+ WORKDIR /build
4
+ COPY pyproject.toml README.md LICENSE ./
5
+ COPY src/ src/
6
+ RUN pip install --no-cache-dir build && python -m build --wheel
7
+
8
+ FROM python:3.13-slim
9
+
10
+ COPY --from=builder /build/dist/*.whl /tmp/
11
+ RUN pip install --no-cache-dir /tmp/*.whl && rm /tmp/*.whl
12
+
13
+ ENTRYPOINT ["opnsense-mcp"]
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,47 @@
1
+ .PHONY: lint format format-check typecheck test test-domain security audit validate clean install build docker
2
+
3
+ install: ## Install project with dev dependencies
4
+ pip install -e ".[dev]"
5
+
6
+ lint: ## Run ruff linter (includes bandit security rules)
7
+ ruff check src/ tests/
8
+
9
+ format: ## Format code with ruff
10
+ ruff format src/ tests/
11
+
12
+ format-check: ## Check formatting without modifying files
13
+ ruff format --check src/ tests/
14
+
15
+ typecheck: ## Run mypy strict type checking
16
+ mypy src/
17
+
18
+ test: ## Run test suite
19
+ pytest -v --tb=short
20
+
21
+ test-domain: ## Run tests for a specific domain (usage: make test-domain DOMAIN=system)
22
+ pytest tests/test_tools/test_$(DOMAIN).py -v --tb=long
23
+ ruff check src/opnsense_mcp/tools/$(DOMAIN).py
24
+ mypy src/opnsense_mcp/tools/$(DOMAIN).py
25
+
26
+ security: ## Run security-specific checks
27
+ ruff check src/ --select S105,S106,S107 --no-fix
28
+ ruff check src/ --select S501 --no-fix
29
+
30
+ audit: ## Run dependency vulnerability audit
31
+ pip-audit
32
+
33
+ validate: lint format-check security typecheck test audit ## Run full CI pipeline locally
34
+ @echo "All checks passed!"
35
+
36
+ build: ## Build wheel and sdist
37
+ python -m build
38
+
39
+ docker: ## Build Docker image locally
40
+ docker build -t opnsense-mcp-server .
41
+
42
+ clean: ## Remove build artifacts
43
+ rm -rf build/ dist/ *.egg-info/ .pytest_cache/ .mypy_cache/ .ruff_cache/ htmlcov/
44
+ find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
45
+
46
+ help: ## Show this help
47
+ @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}'