{
  "markdown": "# mcp-unifi\n\n<!-- mcp-name: io.github.pete-builds/unifi -->\n\n**Safety-first MCP server for self-hosted UniFi. Dry-run previews, JSONL audit log, composite rollback. Network + Protect + Access.**\n\n[![CI](https://github.com/pete-builds/mcp-unifi/actions/workflows/ci.yml/badge.svg)](https://github.com/pete-builds/mcp-unifi/actions/workflows/ci.yml)\n[![Coverage](https://img.shields.io/badge/coverage-91%25-brightgreen)](https://github.com/pete-builds/mcp-unifi)\n[![cosign](https://img.shields.io/badge/cosign-signed-blue)](https://github.com/pete-builds/mcp-unifi/releases)\n[![MCP](https://img.shields.io/badge/MCP-stdio%20%2B%20Streamable%20HTTP-brightgreen.svg)](https://modelcontextprotocol.io/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n\nAn [MCP server](https://modelcontextprotocol.io/) built around the assumption that LLM-driven infrastructure calls need guardrails. Every destructive tool accepts `dry_run=True` and returns the predicted change set without writing. Composite tools (`create_iot_network`, `create_guest_network`, `provision_homelab_service`, `provision_camera`) capture pre-state and roll back applied steps on partial failure. Every call — dry-run or real — lands in a JSONL audit log with secrets scrubbed; the included `mcp-unifi-replay` CLI can re-issue a log against a fresh controller.\n\nBeyond the safety substrate: **Network** tools for devices, AP radio tuning, VLANs, WLANs, firewall, switch ports, port forwards, DHCP reservations, AP groups, observability, Threat Management / IDS-IPS, Honeypot, and Teleport VPN, plus opt-in **Protect** (cameras, motion events, smart detections, recording config) and **Access** (doors, credentials, visitors, badge events, hubs / readers). Every tool accepts a `controller` parameter so one server instance manages multiple UniFi sites. Speaks both **stdio** (Claude Desktop, `uvx`, `.dxt`) and **Streamable HTTP** (Docker, Helm). The full, always-current tool list is in the auto-generated [Tool Manifest](https://pete-builds.github.io/mcp-unifi/tools/). Works on any UniFi OS gateway running UniFi Network 9.x or newer (UDM, UDM Pro, UDM SE, UCG-Fiber, UCG-Ultra, UDR, UDW, UniFi OS Server), authenticated with a local API key from Settings → Control Plane → Integrations. Verified against UCG-Fiber fw 5.1.12.33296. No Site Manager or cloud account required.\n\n## Install\n\nFour supported paths. Pick the one that matches how you run Claude.\n\n### Docker\n\nLong-running container, Streamable HTTP on port `3714`. Best for homelab and multi-client setups.\n\nHTTP transport refuses to start without a bearer token, so supply one:\n\n```bash\nexport MCP_UNIFI_TOKEN=$(openssl rand -hex 32)\ndocker run --rm -p 3714:3714 \\\n  -e STUB_MODE=true \\\n  -e MCP_UNIFI_AUTH_TOKENS=\"$MCP_UNIFI_TOKEN\" \\\n  ghcr.io/pete-builds/mcp-unifi:latest\n```\n\nClients then send `Authorization: Bearer $MCP_UNIFI_TOKEN`. For throwaway local\ntesting on loopback only, `-e MCP_UNIFI_AUTH_REQUIRED=false` skips auth entirely;\nnever use it on an interface reachable by anything else, because every connected\nclient gets admin-equivalent access to the controller.\n\n### Claude Desktop (.dxt) — one-click\n\nDownload `mcp-unifi-<version>.dxt` from the [latest release](https://github.com/pete-builds/mcp-unifi/releases) and double-click. Configuration is through a built-in UI in Claude Desktop. The bundle ships the Python runtime; no separate install needed. Uses stdio transport.\n\n### Helm\n\n```bash\nhelm repo add mcp-unifi https://pete-builds.github.io/mcp-unifi/\nhelm install unifi mcp-unifi/mcp-unifi \\\n  --set unifi.host=192.168.1.1 \\\n  --set unifi.apiKey=<your-local-api-key> \\\n  --set auth.tokens=$(openssl rand -hex 32)\n```\n\nThe chart ships `auth.required: true` with `auth.tokens: \"\"`, so the pod will not\nstart until you set a token (or `--set auth.required=false`, which is only\nappropriate for a trusted single-tenant cluster).\n\n### uvx / pipx\n\nQuick one-off runs straight from the GitHub repo. Stdio transport.\n\n```bash\nuvx --from git+https://github.com/pete-builds/mcp-unifi mcp-unifi\n```\n\nPin a release with `@v0.5.0-rc.2` (or any tag) appended to the URL.\n\nFull guides for each install path live in the [docs site](https://pete-builds.github.io/mcp-unifi/).\n\n## Design\n\n- **Read-only mode.** `MCP_UNIFI_READONLY=true` makes the server structurally unable to change anything: mutating tools are hidden from `tools/list` *and* refused on `tools/call`, so naming a hidden tool gets a normal error envelope instead of a write. Classification is declared per tool at registration (`@audited(\"list_networks\", mutates=False)`), never inferred from tool names — twelve mutating tools, `confirm_destructive_action` among them, carry no `create_`/`update_`/`delete_`/`set_` prefix. Registration fails if a tool has not declared a classification, so a new tool cannot default into being callable. Defense in depth on top of a read-only UniFi API key, not a replacement for it.\n- **Safety primitives.** Every destructive tool accepts `dry_run=True` and returns the predicted change set without writing. Composite tools (`create_iot_network`, `create_guest_network`, `provision_homelab_service`, `provision_camera`) capture pre-state and roll back applied steps on partial failure. Every tool call lands in a JSONL audit log with secrets scrubbed; the included `mcp-unifi-replay` CLI can re-issue a log against a fresh controller.\n- **Single image, multi-controller.** One container runs Network, Protect, and Access together. The same process manages multiple UniFi sites in parallel via the `controller` parameter and a YAML controllers file (`MCP_UNIFI_CONTROLLERS_FILE`). No need to run a separate process per controller.\n- **API-key-first auth.** Uses the local API key from Settings → Control Plane → Integrations against the `/proxy/network/api` endpoint. No username/password storage, no cloud account, no Site Manager dependency.\n- **Multi-channel distribution.** Docker, .dxt one-click for Claude Desktop, Helm chart, uvx. Listed on the official MCP Registry. Container images are cosign-signed (keyless OIDC) with a CycloneDX SBOM attached to each release.\n- **Network + Protect + Access.** Network on by default; Protect and Access opt-in via `MCP_UNIFI_MODULES_ENABLED=network,protect,access`. Access ships read-only (door unlocks and credential issuance require session-token auth and are deferred). UniFi Drive is not in scope.\n\n## Quick start\n\nFastest cold-start: Docker + Claude Code in stub mode, no hardware required.\n\n1. Start the container. Auth is on by default, so mint a token first:\n\n   ```bash\n   export MCP_UNIFI_TOKEN=$(openssl rand -hex 32)\n   docker run -d --rm -p 3714:3714 \\\n     -e STUB_MODE=true \\\n     -e MCP_UNIFI_AUTH_TOKENS=\"$MCP_UNIFI_TOKEN\" \\\n     --name mcp-unifi ghcr.io/pete-builds/mcp-unifi:latest\n   ```\n\n2. Register it with Claude Code, passing the token:\n\n   ```bash\n   claude mcp add --transport http --scope user unifi http://localhost:3714/mcp \\\n     --header \"Authorization: Bearer $MCP_UNIFI_TOKEN\"\n   ```\n\n3. Verify the connection:\n\n   ```bash\n   claude mcp list\n   ```\n\n4. In a Claude Code session, ask: *\"list my UniFi devices\"*. You'll get two stubbed devices back.\n\n5. When you're ready to point at a real gateway, drop stub mode:\n\n   ```bash\n   docker run -d --rm -p 3714:3714 \\\n     -e STUB_MODE=false \\\n     -e UNIFI_HOST=192.168.1.1 \\\n     -e UNIFI_API_KEY=<your-local-api-key> \\\n     -e MCP_UNIFI_AUTH_TOKENS=\"$MCP_UNIFI_TOKEN\" \\\n     --name mcp-unifi ghcr.io/pete-builds/mcp-unifi:latest\n   ```\n\nGenerate the API key under **Settings → Control Plane → Integrations → Create API Key** on the gateway.\n\n## Configuration\n\nAll config is read from environment variables (and `.env` when present). The six most common:\n\n| Variable | Default | Notes |\n|---|---|---|\n| `STUB_MODE` | `true` | When `false`, real-mode controller config is required. |\n| `UNIFI_HOST` | (empty) | Gateway IP or hostname. Required in real mode. |\n| `UNIFI_API_KEY` | (empty) | Local API key. Required in real mode. |\n| `MCP_UNIFI_READONLY` | `false` | When `true`, mutating tools are hidden and refused. See the [Security guide](https://pete-builds.github.io/mcp-unifi/guides/security/#read-only-mode). |\n| `MCP_UNIFI_MODULES_ENABLED` | `network` | Set to `network,protect,access` to enable all three modules. |\n| `MCP_UNIFI_CONTROLLERS_FILE` | (unset) | YAML file with named controllers for multi-site. |\n| `MCP_UNIFI_OTEL_ENABLED` | `false` | Optional OpenTelemetry tracing, one span per tool call. Off by default and the SDK is not a dependency. See [Operations](docs/operations.md). |\n\nFull env var reference and the multi-site YAML schema are in the [Configuration docs](https://pete-builds.github.io/mcp-unifi/reference/configuration/).\n\n## How this is built\n\nThe engineering scaffolding around the tool surface (see the [Tool Manifest](https://pete-builds.github.io/mcp-unifi/tools/) for the current count), in case you want to know what's holding it up:\n\n**Test discipline.** ~880 tests across unit, integration, and property-based (Hypothesis) — see `pytest --collect-only` for the current count. HTTP is mocked with respx so tests don't hit a real controller. Coverage gated at 80% branch coverage in CI; current floor is 90%.\n\n**Code quality gates.** Ruff (pycodestyle, pyflakes, isort, flake8-bugbear, pyupgrade, simplify, flake8-bandit security ruleset, comprehensions) plus mypy strict (no implicit Any, unreachable code flagged, unused ignores flagged). Pre-commit hooks run lint, format, types, and regenerate the tool manifest with a drift check, so bad code never reaches CI.\n\n**CI pipeline (5 gated jobs).** Every PR runs lockfile-drift check → lint + type check → tests + coverage → multi-arch Docker build → Trivy filesystem and image scan (HIGH/CRITICAL fails the build). Each gates the next.\n\n**Release pipeline.** A `git tag vX.Y.Z` push triggers a multi-arch (linux/amd64 + linux/arm64) Docker build, cosign keyless signing via sigstore OIDC, SLSA build provenance attestation, CycloneDX SBOM via Syft attached to the GitHub release, a .dxt bundle for Claude Desktop one-click install, GHCR push with `vX.Y.Z` / `X.Y` / `latest` tags, and an auto-bump of the example `docker-compose.yml` on main.\n\n**Dependency hygiene.** Hash-pinned via `pip install --require-hashes`. A custom CI step verifies every pinned dep in `requirements.in` matches `requirements.lock` so no one can bump one without the other. Dependabot auto-merges safe patches. The base image is digest-pinned, not tag-pinned.\n\n**Container hardening.** Runs as non-root UID 1000, no shell, no home directory. Read-only root filesystem enforced via Docker / Helm. `/tmp` is a 16MiB tmpfs. `no-new-privileges` set. All Linux capabilities dropped. Dedicated `/health` endpoint keeps the streamable-HTTP transport from logging 406 noise on every Docker healthcheck.\n\n**Security posture.** Bearer-token authentication on the HTTP transport, secure by default (refuses to start without tokens). Audit log records each authenticated `client_id` per call with secret scrubbing on `api_key`, `passphrase`, `password`, `secret`, `token` substring matches. API keys wrapped in pydantic `SecretStr`. `SECURITY.md` with a private disclosure path.\n\n**Distribution surface.** GHCR (signed multi-arch), Smithery (registered), MCP Registry (listed), Helm chart (Secret/Deployment/Service/Ingress/NetworkPolicy templates), .dxt bundle, uvx / pipx.\n\n**Documentation discipline.** Astro Starlight site auto-deploys to GitHub Pages. The per-tool reference pages are generated from FastMCP introspection by `scripts/generate_tool_manifest.py`, and the pre-commit hook regenerates and drift-checks them so code and docs can't diverge. CHANGELOG follows Keep a Changelog format.\n\n**Version discipline.** `pyproject.toml`, the git tag, the CHANGELOG entry, the Docker image tag, the docker-compose example, and the Helm chart `appVersion` all stay aligned because the release workflow enforces it. There is never a moment where the docs and the code disagree about what version this is.\n\n## Development\n\nClone, install dev dependencies, and wire up the pre-commit hooks:\n\n```bash\ngit clone https://github.com/pete-builds/mcp-unifi.git\ncd mcp-unifi\npython -m venv .venv && source .venv/bin/activate\npip install -e \".[dev]\" pre-commit\npre-commit install\n```\n\nThe pre-commit hooks run ruff (lint + format), mypy strict, and the tool\nmanifest generator. The manifest hook regenerates\n`docs/site/src/content/docs/tools/` whenever any file under\n`src/mcp_unifi/modules/` changes and fails the commit if the on-disk\nmanifest drifts from the registered tool surface. Run the tests with\n`pytest`.\n\nTo regenerate the manifest manually:\n\n```bash\npython scripts/generate_tool_manifest.py        # write\npython scripts/generate_tool_manifest.py --check  # CI-style drift check\n```\n\n## Docs\n\n- [Docs site](https://pete-builds.github.io/mcp-unifi/)\n- [Network tool reference](https://pete-builds.github.io/mcp-unifi/reference/network/)\n- [Protect tool reference](https://pete-builds.github.io/mcp-unifi/reference/protect/)\n- [Access tool reference](https://pete-builds.github.io/mcp-unifi/reference/access/)\n- [Multi-site setup](https://pete-builds.github.io/mcp-unifi/guides/multi-site/)\n- [Access setup](https://pete-builds.github.io/mcp-unifi/guides/access-setup/)\n- [Dry-run and audit log](https://pete-builds.github.io/mcp-unifi/guides/dry-run-audit/)\n- [Security model](https://pete-builds.github.io/mcp-unifi/guides/security/)\n- [Migration from v0.x](https://pete-builds.github.io/mcp-unifi/guides/migration/)\n- [Changelog](CHANGELOG.md)\n- [Security policy](SECURITY.md)\n\n## License\n\n[MIT](LICENSE).\n",
  "bytes": 13657,
  "sha": "783298a1864310b0af3fb283dd9831ead722b33dca5cca12be48412d055cc043",
  "repo_slug": "pete-builds/mcp-unifi",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_pete_builds_unifi_0aeda69c/readme"
}