MCP Hub
Back to servers

mcp-unifi

An MCP server for self-hosted UniFi gateway management, providing tools for devices, networks, WiFi, firewall rules, switch port profiles, connected clients, and a one-shot IoT network creation with rollback.

glama
Updated
May 2, 2026

mcp-unifi

CI Release License: MIT Python MCP Coverage

An MCP server for self-hosted UniFi gateway management. Fifteen tools covering devices, networks/VLANs, WiFi SSIDs (full CRUD), firewall rules (full CRUD), switch port profiles, connected clients, plus a one-shot create_iot_network tool that provisions an isolated IoT subnet (VLAN, SSID, and firewall block) in a single call with automatic rollback on partial failure.

Built on FastMCP with Streamable HTTP transport. Talks to a UCG-Fiber, UDM Pro, or any other UniFi OS gateway via the local API key. No Site Manager / cloud account required.

Every tool returns JSON. Errors come back as a structured {"error": "...", "stub_mode": bool} object so the MCP loop never crashes on a gateway hiccup.

Why

Most UniFi automation today means clicking through the controller UI, writing brittle one-off scripts, or pulling in a heavyweight community SDK. mcp-unifi gives any MCP-aware client (Claude Code, Claude Desktop, custom agents) a small, focused, well-typed surface area for the operations you actually do every week: spin up an IoT VLAN, drop a firewall rule, audit your SSIDs, list adopted devices.

The composite create_iot_network tool turns a 15-step UI workflow into a single tool call.

Quick start

Pull the published image and run it:

docker run --rm \
  -p 3714:3714 \
  -e STUB_MODE=true \
  ghcr.io/pete-builds/mcp-unifi:0.2.0

The server starts in stub mode by default, which returns realistic mock data and requires no UniFi hardware. Register it with Claude Code:

claude mcp add unifi --transport http --scope user --url http://localhost:3714/mcp

Then ask Claude Code to "list my UniFi devices" and you should see two stubbed devices come back.

To talk to a real gateway, pass the credentials and flip stub mode off:

docker run --rm \
  -p 3714:3714 \
  -e STUB_MODE=false \
  -e UNIFI_HOST=192.168.1.1 \
  -e UNIFI_API_KEY=<your-local-api-key> \
  ghcr.io/pete-builds/mcp-unifi:0.2.0

Generate the API key under Settings → Control Plane → Integrations on the gateway.

Tool reference

ToolSignatureWhat it does
list_devices()List adopted gateways, APs, and switches with state, uptime, and per-radio info.
list_networks()List all configured networks/VLANs (subnet, DHCP range, VLAN ID).
create_vlan(name, vlan_id, subnet, dhcp_start?, dhcp_stop?, purpose?)Create a new VLAN-tagged network.
update_vlan(network_id, updates)Patch fields on an existing VLAN.
delete_vlan(network_id)Delete a VLAN.
list_wlans()List all WiFi SSIDs.
create_wlan(name, passphrase, network_id, security?, wpa_mode?, is_guest?, hide_ssid?, wlan_band?)Create a new SSID bound to a specific VLAN.
update_wlan(wlan_id, updates)Patch fields on an existing SSID (name, passphrase, hide_ssid, etc.).
delete_wlan(wlan_id)Delete a WiFi SSID.
list_firewall_rules()List all firewall rules.
create_firewall_rule(name, ruleset, action, rule_index?, protocol?, src_address?, dst_address?, src_networkconf_id?, dst_networkconf_id?, enabled?)Create a firewall rule.
delete_firewall_rule(rule_id)Delete a firewall rule.
list_port_profiles()List switch port profiles (PoE mode, native VLAN, forwarding).
list_clients()List currently connected wireless and wired clients (MAC, hostname, IP, signal/satisfaction, AP or switch port, uptime).
create_iot_network(name, vlan_id, passphrase, main_lan_subnet?, subnet?, isolate?, hide_ssid?)One-shot: VLAN + SSID + isolation rule, with rollback on failure.

Every tool returns a JSON string. Errors are returned as a structured {"error": "...", "stub_mode": bool} object so Claude can render the failure without crashing the MCP loop.

Stub mode vs real mode

ModeWhen to useBehavior
Stub (STUB_MODE=true, default)Development, demos, wiring up Claude flows before hardware arrivesIn-memory state machine seeded with one gateway, one AP, one network, one SSID, one firewall rule, two port profiles. Create/update/delete persist within the container's lifetime. Resets on restart.
Real (STUB_MODE=false)Production with a UCG-Fiber/UDM/other UniFi OS gatewayTalks HTTPS to the gateway with your local API key. Requires UNIFI_HOST and UNIFI_API_KEY.

Switching modes is a config change, not a code change. The same eleven tools, the same response shapes.

Configuration

All configuration is read from environment variables (and a .env file when present). Config is validated by Pydantic at startup; invalid values fail fast with a helpful message.

VariableTypeDefaultRequiredNotes
STUB_MODEbooltruenoWhen false, real-mode credentials are required.
UNIFI_HOSTstring""only in real modeGateway IP or hostname (no scheme).
UNIFI_PORTint443noHTTPS port for the gateway.
UNIFI_SITEstringdefaultnoController site identifier.
UNIFI_API_KEYstring""only in real modeLocal API key from Settings → Control Plane → Integrations.
UNIFI_VERIFY_SSLboolfalsenoSet true if you have installed a real cert on the gateway.
IOT_SUBNET_TEMPLATEstring10.0.{vlan_id}.0/24noMust contain the literal {vlan_id} placeholder.
IOT_DHCP_START_OFFSETint (2-254)100noFirst DHCP lease offset within the IoT /24.
IOT_DHCP_STOP_OFFSETint (2-254)200noLast DHCP lease offset within the IoT /24.
MCP_HOSTstring0.0.0.0noBind address.
MCP_PORTint3714noListen port.
LOG_LEVELenumINFOnoOne of DEBUG, INFO, WARNING, ERROR, CRITICAL.
LOG_FORMATenumjsonnojson for production, text for local dev.

A complete example lives in .env.example.

MCP client setup

Claude Code

claude mcp add unifi --transport http --scope user --url http://<host>:3714/mcp

Claude Desktop

Add the following to your claude_desktop_config.json:

{
  "mcpServers": {
    "unifi": {
      "transport": "streamable-http",
      "url": "http://<host>:3714/mcp"
    }
  }
}

Generic config

Streamable HTTP at http://<host>:3714/mcp. Any MCP client that supports the Streamable HTTP transport (spec 2025-03-26+) can connect.

Architecture

+---------------------+         Streamable HTTP         +---------------------+
|  MCP Client         |  -------------------------->    |  mcp-unifi          |
|  (Claude Code, etc) |  <--------------------------    |  (FastMCP server)   |
+---------------------+                                 +----------+----------+
                                                                   |
                                                                   |  HTTPS + X-API-Key
                                                                   v
                                                        +----------+----------+
                                                        |  UniFi OS Gateway   |
                                                        |  /proxy/network/... |
                                                        +---------------------+

The server is a thin async proxy: it translates MCP tool calls into UniFi controller REST calls, shapes the responses, and returns JSON. It does not store state, does not call out to any cloud, and does not authenticate incoming MCP connections (run it on a trusted LAN).

Security notes

  • The UNIFI_API_KEY lives only in the container's environment. It is never logged, never echoed back in MCP responses, and never written to disk by this server.
  • WLAN passphrases are scrubbed ([REDACTED]) on the way out of every tool response, even in stub mode.
  • The container runs as UID 1000, no shell, no home directory, with a read-only root filesystem (/tmp is tmpfs) and no-new-privileges.
  • The base image is pinned by digest. Python deps are installed with pip --require-hashes from a hash-locked requirements.lock.
  • The published image is multi-arch (amd64/arm64) with build provenance attestation and SBOM via docker/build-push-action.
  • The MCP server itself is not authenticated. Place it behind a trusted-LAN boundary, a reverse proxy with auth, or a Tailscale ACL.

For vulnerability reports, see SECURITY.md.

Development

Requires Python 3.13+ and Docker.

# Clone + install dev deps
git clone https://github.com/pete-builds/mcp-unifi.git
cd mcp-unifi
python -m venv .venv && source .venv/bin/activate
pip install --require-hashes -r requirements-dev.lock
pip install -e . --no-deps

# Run the test suite (101 tests, ~95% coverage)
pytest

# Lint and format
ruff check src tests
ruff format src tests

# Type check (mypy strict)
mypy src/mcp_unifi

# Run the server locally in stub mode
python -m mcp_unifi.server

# Or build the image yourself instead of pulling from GHCR
cp docker-compose.example.yml docker-compose.yml
docker compose up --build

Tests

======================= 101 passed in 1.5s =======================

Name                          Stmts  Miss  Branch  BrPart  Cover
-----------------------------------------------------------------
src/mcp_unifi/__init__.py         2     0       0       0   100%
src/mcp_unifi/clients/__init__    3     0       0       0   100%
src/mcp_unifi/clients/stubs.py   70     1       6       0    99%
src/mcp_unifi/clients/unifi.py   82     0      12       0   100%
src/mcp_unifi/config.py          38     1       8       0    98%
src/mcp_unifi/healthcheck.py     18     1       0       0    94%
src/mcp_unifi/logging_setup.py   33     1      12       2    93%
src/mcp_unifi/models.py           6     0       0       0   100%
src/mcp_unifi/server.py         232    15      70       5    92%
-----------------------------------------------------------------
TOTAL                           484    19     108       7    95%

CI gates on 80% coverage minimum, ruff lint, ruff format, mypy strict, and a Trivy fs+image scan that fails on any HIGH or CRITICAL finding.

Updating dependencies

The requirements.lock and requirements-dev.lock files are hash-pinned. Edit requirements.in (or requirements-dev.in), then regenerate:

uv pip compile requirements.in --output-file requirements.lock --generate-hashes --python-version 3.13
uv pip compile requirements-dev.in --output-file requirements-dev.lock --generate-hashes --python-version 3.13

Dependabot opens weekly PRs for requirements.in-level updates and the Docker base image digest.

Acknowledgments

UniFi controller endpoint paths were cross-referenced against the sirkirby/unifi-mcp project. That repo was used as research material for the API surface; no code was copied. The implementation here is an independent FastMCP + httpx build that follows the proven Forge pattern.

License

MIT.

Contributing

Issues and pull requests welcome. Before opening a PR:

  1. Make sure ruff check, ruff format --check, and mypy src/mcp_unifi are clean.
  2. Add or update tests, keep coverage at 80% or above.
  3. Run pytest locally and confirm the suite passes.
  4. Update CHANGELOG.md under an [Unreleased] heading.

Reviews

No reviews yet

Sign in to write a review