Secure MCP server for OPNsense firewall management via AI assistants
The OPNsense MCP server demonstrates solid definition quality with consistent naming patterns, detailed descriptions, and properly typed schemas. All 19 tools start with action verbs (opn_list_*, opn_add_*, opn_update_*, opn_delete_*, opn_ping, opn_traceroute, opn_dns_*, opn_pf_*, opn_reconfigure_*) following verb_noun convention. Tool descriptions are comprehensive (100-300+ chars) and include context about when to use each tool (e.g., 'Use this when the OPNsense instance uses Kea for DHCP'). All input parameters have type definitions and descriptions. However, there are notable gaps: (1) Output schemas are NOT documented in the tool definitions, we can see parameter input schemas but no explicit return type documentation, (2) Error handling guidance is absent, tools do not describe what errors might occur or how the agent should recover, (3) No tool annotations (readOnlyHint/destructiveHint/idempotentHint) despite clear risk levels marked in metadata, (4) Several tools lack adequate depth on parameter constraints (e.g., prefix_len for IPv6 should specify '64' as common/recommended, lease_time should document valid duration formats).
Add an Unbound DNS host override (A/AAAA record) and apply immediately. Use this when you need to create a local DNS record that resolves a hostname to a specific IP address. Useful for split-horizon DNS, internal services, or overriding external DNS for specific hosts. Changes are applied immediately (Unbound is reconfigured automatically). DNS overrides cannot be auto-reverted — verify settings before calling. Use opn_list_dns_overrides to check existing overrides first. Parameters: - hostname: the hostname part (e.g. 'myserver') - domain: the domain part (e.g. 'local.lan') - server: the IP address to resolve to (IPv4 or IPv6) - description: optional description Returns: dict with 'result', 'uuid', 'hostname', 'server', and 'applied' status.
Create a new dnsmasq DHCP range and apply the configuration. Use this to add DHCPv4 or DHCPv6 address ranges with optional Router Advertisement (RA) configuration. Args: interface: Network interface name (e.g. 'lan', 'opt1', 'opt2'). start_addr: Start of address range. IPv4: '192.168.1.100'. IPv6: uses suffix notation — '::100' (OPNsense auto-prepends the delegated prefix from the interface config). end_addr: End of address range. IPv4: '192.168.1.200'. IPv6: '::200'. prefix_len: IPv6 prefix length (e.g. '64'). Include this for IPv6 ranges, omit for IPv4 ranges. ra_mode: Router Advertisement mode for IPv6. Options: - 'slaac' — clients use SLAAC (stateless autoconfiguration) - 'ra-stateless' — RA + stateless DHCPv6 (DNS via DHCP) - 'ra-only' — RA only, no DHCPv6 address assignment Omit for IPv4 ranges. lease_time: Lease duration (e.g. '24h', '1h', '12h'). Default: '24h'. description: Optional description for this range. NOTE: Only one RA daemon should run per interface. Do not configure both dnsmasq RA and radvd on the same interface. NOTE: After creation, the service is automatically reconfigured. Changes take effect immediately. Note: Requires the dnsmasq DNS/DHCP server. Returns: dict with 'result', 'uuid' of the new range, and reconfigure status.
Output schemas not documented. Tool definitions include input parameter schemas but provide no explicit documentation of what each tool returns (field names, types, structure). LLMs need to know the response shape to plan downstream operations and extract relevant data.
No error handling guidance. Tools lack descriptions of failure modes, error codes, and recovery steps. E.g., opn_add_dnsmasq_range does not explain what happens if the interface is invalid, range overlaps with existing pools, or dnsmasq is not installed. Agents cannot self-correct without this guidance.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 65 | 2026-07-28+ | v2 |
Delete an Unbound DNS host override by UUID and apply immediately. The deletion is applied immediately (Unbound is reconfigured automatically). DNS changes cannot be auto-reverted — verify the UUID before calling. Use opn_list_dns_overrides first to find the UUID. Returns: dict with 'result' (str), 'uuid' (str), and 'applied' status.
Delete a dnsmasq DHCP range by UUID and apply the configuration. The deletion is applied immediately (dnsmasq is reconfigured automatically). Use opn_list_dnsmasq_ranges first to find the UUID. Returns: dict with 'result' (str), 'uuid' (str), and 'reconfigure_status'.
Perform a DNS lookup from the OPNsense firewall. Use this when you need to test DNS resolution from the firewall's perspective, verify Unbound is resolving correctly, or check if a specific DNS server returns expected results. Returns: dict with 'result' (str) and 'response' (DNS query results).
Get Unbound DNS resolver statistics (queries, cache hits, uptime). Use this when you need to check DNS resolver performance, cache hit rates, or troubleshoot DNS resolution issues. Returns: dict with resolver statistics fields.
List current DHCPv4 leases from the ISC DHCP server (legacy). Use this when the OPNsense instance uses the ISC DHCP plugin (os-isc-dhcp). ISC DHCP is legacy and being phased out — most 26.x instances use dnsmasq or Kea. Use opn_scan_config first to check which DHCP backend is active. Returns: dict with DHCP lease entries including address, mac, and hostname fields.
List Unbound DNS forward zones (domain-specific DNS servers). Use this when you need to check which domains are forwarded to specific upstream DNS servers or DNS-over-TLS resolvers. Returns: dict with 'rows' (list of forward zones) and 'rowCount' (total).
List Unbound DNS host overrides (local DNS records). Use this when you need to see which hostnames are overridden to specific IP addresses in the local DNS resolver. Returns: dict with 'rows' (list of overrides) and 'rowCount' (total).
List current DHCPv4 and DHCPv6 leases from the dnsmasq DNS/DHCP server. Use this when the OPNsense instance uses dnsmasq for DHCP (default in 26.x). dnsmasq is a lightweight combined DNS/DHCP server that handles both DHCPv4 and DHCPv6. IPv6 leases appear alongside IPv4 leases in the results. Use opn_scan_config first to check which DHCP backend is active. Returns: dict with 'rows' (list of leases) and 'rowCount' (total).
List dnsmasq DHCP ranges (both DHCPv4 and DHCPv6 with RA config). Use this to see configured DHCP address pools and Router Advertisement settings for each interface. Both IPv4 and IPv6 ranges appear in the same list. Key fields: interface, start_addr, end_addr, prefix_len (IPv6) or subnet_mask (IPv4), ra_mode (slaac/ra-stateless/ra-only), ra_priority, lease_time, enabled. Note: Requires the dnsmasq DNS/DHCP server (os-dnsmasq-dns or built-in). Returns: dict with 'rows' (list of ranges) and 'rowCount' (total).
List current DHCPv4 leases from the Kea DHCP server. Use this when the OPNsense instance uses Kea for DHCP (available since 24.7). Kea is the modern replacement for ISC DHCP, recommended for HA setups. Use opn_scan_config first to check which DHCP backend is active. Returns: dict with 'rows' (list of leases) and 'rowCount' (total).
Query the active PF (packet filter) state table. Use this when you need to see active connections through the firewall, debug NAT issues, or identify which hosts are communicating. Returns: dict with 'rows' (list of state entries) and 'rowCount' (total).
Ping a host from the OPNsense firewall to test connectivity. Use this when you need to check if a host is reachable from the firewall, measure round-trip latency, or diagnose network connectivity issues. The ping runs on the firewall itself, not locally. Returns: dict with ping results including loss percentage and RTT stats.
Apply pending dnsmasq DNS/DHCP configuration changes. Use this after manually editing dnsmasq settings to apply the changes to the running dnsmasq service. Note: opn_add_dnsmasq_range auto-reconfigures, so this is only needed for manual edits or troubleshooting. Note: Requires the dnsmasq DNS/DHCP server. Returns: dict with 'status' indicating success or failure.
Apply pending Unbound DNS resolver configuration changes. Use this after making DNS configuration changes (adding overrides, forward zones, etc.) to apply them to the running Unbound service. This restarts Unbound with the new configuration. NOTE: This does not use savepoint protection. DNS changes take effect immediately and cannot be auto-reverted. Verify settings before calling. Returns: dict with 'status' indicating success or failure.
Trace the network path from OPNsense to a destination host. Use this when you need to diagnose routing issues, identify where packets are being dropped, or visualize the network hops to a destination. Returns: dict with 'result' (str) and 'response' (list of hops).
Update an Unbound DNS host override by UUID and apply immediately. Use this when you need to change the hostname, domain, IP address, or other properties of a DNS override. Only the parameters you provide are changed; all other settings are preserved. Changes are applied immediately (Unbound is reconfigured automatically). DNS overrides cannot be auto-reverted — verify settings before calling. Use opn_list_dns_overrides first to find the UUID. Parameters: - uuid: override UUID (from opn_list_dns_overrides) - hostname: new hostname part (e.g. 'myserver') - domain: new domain part (e.g. 'local.lan') - server: new IP address (IPv4 or IPv6) - description: new description - enabled: enable/disable the override Returns: dict with 'result' (str), 'uuid' (str), and 'applied' status.
Update a dnsmasq DHCP range by UUID and apply the configuration. Use this when you need to change the address range, lease time, RA settings, or other properties. Only the parameters you provide are changed; all other settings are preserved. After update, the dnsmasq service is automatically reconfigured. Changes take effect immediately. Use opn_list_dnsmasq_ranges first to find the UUID. Parameters: - uuid: range UUID (from opn_list_dnsmasq_ranges) - interface: network interface name (e.g. 'lan', 'opt1') - start_addr: start of address range - end_addr: end of address range - prefix_len: IPv6 prefix length (e.g. '64') - ra_mode: Router Advertisement mode ('slaac', 'ra-stateless', 'ra-only') - lease_time: lease duration (e.g. '24h', '1h') - description: human-readable description - enabled: enable/disable the range Returns: dict with 'result' (str), 'uuid' (str), and 'reconfigure_status'.
No tool annotations despite explicit risk metadata. Tools declare Risk: READ_ONLY, WRITE, or DESTRUCTIVE in metadata but do not use MCP tool annotations (readOnlyHint, destructiveHint, idempotentHint) to communicate this to clients. This forces clients to manually parse descriptions instead of relying on structured annotations.
Incomplete parameter constraints. Several parameters lack specific validation rules in descriptions: (1) prefix_len for IPv6 ranges should document valid range (1-128) and recommend common values (64, 56); (2) lease_time should document valid formats and constraints (e.g., '1h' to '7d', RFC 3315 format); (3) ra_mode should list enum values in description as well as docs; (4) host parameters for ping/traceroute should clarify if FQDN, IPv4, or IPv6 are accepted.
Missing idempotency guarantees. Update and delete tools do not document whether they are idempotent. If opn_update_dnsmasq_range is called twice with the same UUID and parameters, does it succeed safely or error on second call? Agents need this info to decide whether retries are safe.
No dry-run or confirmation for destructive operations. Tools like opn_delete_dnsmasq_range and opn_delete_dns_override allow immediate deletion without preview or confirmation. In production, agents should be able to preview changes or require explicit confirmation before destructive ops.
Vague descriptions for some discovery tools. opn_pf_states, opn_dns_stats, and opn_dns_lookup have descriptions that lack context about typical use cases and how results relate to other tools. E.g., opn_pf_states should clarify how its results help debug NAT or firewall rules vs. just showing active connections.