plugins/unifi-network/skills/firewall-manager/SKILL.md
Manage UniFi firewall policies using natural language — create, modify, and review firewall rules, content filters, and traffic policies. Use when asked to block traffic, create firewall rules, manage content filtering, set up time-based access controls, or review firewall configuration.
npx skillsauth add sirkirby/unifi-network-mcp firewall-managerInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You manage firewall policies on a UniFi network. Translate the user's intent into the right MCP tool calls, always preview before executing, and snapshot state around every mutation so a rollback path always exists.
There are no helper scripts in this skill — only references and tools. You drive the workflow:
references/firewall-schema.md is the V2 schema reference.references/policy-templates.yaml is a small library of common-scenario payloads you read directly.references/dpi-categories.md maps app names to DPI category groups.This skill requires the unifi-network MCP server. Verify with unifi_tool_index. If it's unavailable, direct the user to the unifi-network-setup skill.
Before doing anything else:
UNIFI_NETWORK_HOST (or UNIFI_HOST) is set. If not: "UNIFI_NETWORK_HOST is not configured. Please run the unifi-network-setup skill before using this skill."unifi_tool_index.You always snapshot the current firewall state before any create / update / delete. The snapshot is your rollback reference and the input to the post-change diff.
Gather state via three parallel tool calls:
unifi_list_firewall_policiesunifi_list_firewall_zonesunifi_list_firewall_groupsCombine the results into one JSON document and write it to disk:
STATE_DIR="${UNIFI_SKILLS_STATE_DIR:-${XDG_STATE_HOME:-$HOME/.local/state}/unifi-mcp/skills}/firewall-snapshots"
mkdir -p "$STATE_DIR"
SNAPSHOT="$STATE_DIR/firewall_$(date -u +%Y%m%dT%H%M%SZ).json"
# Write the combined JSON ($SNAPSHOT_JSON below is what you composed from the three tool results):
printf '%s\n' "$SNAPSHOT_JSON" > "$SNAPSHOT"
echo "Snapshot saved to $SNAPSHOT"
Tell the user the snapshot path. They may want it for manual restore if something goes wrong.
Read references/policy-templates.yaml directly — do not invent new templates. The file lists every template's params, tool, payload, and notes. Walk the user's request against the template list:
| Template | Use when |
|----------|----------|
| iot-isolation | "block IoT from reaching the LAN" / "isolate smart devices" |
| guest-lockdown | "lock down guest WiFi to internet only" |
| kids-content-filter | "block apps after bedtime" / time-based filtering |
| block-bittorrent | "block torrents / P2P" |
| work-vpn-split-tunnel | "VPN but keep printer access" |
| camera-isolation | "lock cameras to NVR only" |
To apply a template:
policy-templates.yaml and locate the matching entry.params value:
unifi_list_firewall_zones.unifi_list_networks.payload block. Substitution is literal: "{iot_zone_id}" → "<actual ID>".tool with the resolved payload — first without confirm=true to get the preview.confirm=true.Do not skip the preview step even when the template is well-known. Templates are starting points, not blanket approval.
Consult references/firewall-schema.md before constructing any V2 payload. It is the authoritative reference for:
ALLOW / BLOCK / REJECT)matching_target / matching_target_type combinationsFor app-aware rules (TikTok, YouTube, Steam, BitTorrent, etc.), consult references/dpi-categories.md to identify the category group, then call unifi_get_dpi_stats to confirm the exact category ID on this controller before building the rule.
Tool selection:
unifi_create_firewall_policy — V2 zone-based create. Wraps the payload in policy_data. Pre-resolve zone IDs (unifi_list_firewall_zones) and network IDs (unifi_list_networks) before constructing it.unifi_update_firewall_policy — partial update via fetch-merge-put. Pass only the fields you want to change in update_data (e.g., {"enabled": true}). This is the canonical way to enable/disable a policy because it preserves all other fields.unifi_toggle_firewall_policy — convenience wrapper for flipping enabled. Prefer unifi_update_firewall_policy with update_data={"enabled": …} — it's the canonical fetch-merge-put path and produces the same result with no special casing.unifi_get_firewall_policy_ordering — read the user-defined policy ordering for a source/destination zone pair. Use this when rule placement matters; do not infer editable order from index.unifi_reorder_firewall_policies — reorder user-defined policies for a source/destination zone pair. Pass the complete orderedFirewallPolicyIds object from the read tool, with only the intended movement applied. Preview first, then confirm.Policy ordering uses UniFi's official integration API and requires an API key (UNIFI_API_KEY or UNIFI_NETWORK_API_KEY). Local username/password controller sessions can read policies and zones, but they cannot call the ordering endpoint.
The ordering endpoint uses integration API zone UUIDs internally. The local MCP manager accepts the normal unifi_list_firewall_zones IDs and translates them by zone name before calling the ordering endpoint.
Take a fresh snapshot using the same procedure as Step 2, then compare to the pre-change snapshot:
diff -u "$BEFORE" "$AFTER" | head -200
For a structural (key-aware) diff that ignores ordering, fall back to:
python3 - "$BEFORE" "$AFTER" <<'PY'
import json, sys
a, b = (json.load(open(p)) for p in sys.argv[1:])
def keyed(items):
# list output uses 'id', detail output uses '_id' — accept either
out = {}
for i, x in enumerate(items):
k = x.get('id') or x.get('_id') or f'__idx_{i}'
out[k] = x
return out
ap, bp = keyed(a.get('policies', [])), keyed(b.get('policies', []))
added = [bp[k] for k in bp.keys() - ap.keys()]
removed = [ap[k] for k in ap.keys() - bp.keys()]
changed = [(ap[k], bp[k]) for k in ap.keys() & bp.keys() if ap[k] != bp[k]]
print(json.dumps({'added': added, 'removed': removed, 'changed': changed}, indent=2))
PY
Read the diff. If it does not match the change you intended (e.g., extra unintended modifications, wrong field touched), stop and report to the user. Do not proceed with further mutations until the unexpected change is understood.
confirm=true. Show the preview verbatim before executing.confirm=true. "Sounds good, do it" counts. Silence does not.UNIFI_POLICY_NETWORK_FIREWALL_POLICIES_CREATE=trueUNIFI_POLICY_NETWORK_FIREWALL_POLICIES_UPDATE=trueUNIFI_POLICY_NETWORK_FIREWALL_POLICIES_DELETE=true (off by default)unifi_list_firewall_policies before creating new rules to detect conflicts and redundancy.references/dpi-categories.md, then confirm the ID with unifi_get_dpi_stats.block-bittorrent), use it (Step 3).action="REJECT" per references/firewall-schema.md.kids-content-filter applies, use it with block_days, block_start, block_end.references/firewall-schema.md and build the rule.unifi_list_firewall_policies.source or destination references the target network or its zone.No mutation, no snapshot needed.
unifi_list_firewall_policies.unifi_get_firewall_policy_details.policy-templates.yaml.unifi_list_firewall_zones, network IDs from unifi_list_networks).unifi_list_firewall_policies.For a comprehensive scored audit, use the firewall-auditor skill instead.
When you cannot reach the MCP server (network issue, server crash), there is no fallback that mutates the controller — there's nothing to mutate against. Tell the user the server is unreachable and direct them to the unifi-network-setup skill for diagnosis.
The "manual procedure" sections of older versions of this skill assumed a separate HTTP transport that no longer exists. Removed for simplicity.
unifi_create_firewall_policy is the canonical create tool. Pre-resolve zone IDs and network IDs before constructing the V2 payload.index is controller-assigned on zone-based policies. Do not try to move policies by updating index; use the dedicated ordering tools.REJECT (sends RST/ICMP unreachable, faster client failure) or BLOCK (silent discard, less informative to the client). REJECT is usually right for internal traffic, BLOCK for external-facing rules. The action comparison table is in references/firewall-schema.md.references/dpi-categories.md.camera-isolation and other multi-rule templates. Confirm ordering with unifi_list_firewall_policies after creation.tools
Configure the UniFi Protect MCP server for Claude Code, Codex, or OpenClaw — set NVR host, credentials, and permissions
testing
How to manage UniFi Protect cameras and NVR — view cameras, smart detections, recordings, snapshots, lights, sensors, Known Faces, and the Alarm Manager. Use this skill when the user mentions UniFi cameras, security cameras, NVR, recordings, motion detection, person detection, face recognition, Known Faces, snapshots, RTSP streams, floodlights, sensors, chimes, arming/disarming the alarm, or any UniFi Protect task.
tools
How to manage UniFi network infrastructure — devices, clients, firewall, VPN, routing, WLANs, and statistics. Use this skill when the user mentions UniFi, Ubiquiti, network management, WiFi configuration, firewall rules, port forwarding, VPN, QoS, bandwidth, connected clients, network devices, or any UniFi networking task.
development
How to manage UniFi Access door control — locks, credentials, visitors, access policies, and events. Use this skill when the user mentions UniFi Access, door locks, door access, building access, NFC cards, PIN codes, visitor passes, access policies, access schedules, door readers, or any UniFi Access task.