unifi-mcp
README.md
# unifi-mcp
A lean, **read-only** [MCP](https://modelcontextprotocol.io) server that surfaces
UniFi network data (a UCG-Fiber console) to Claude over stdio. Written against
Ubiquiti's official API key (`X-API-KEY`); every tool is a GET, so it can only
read — never change — your network.
## Tools
| Tool | Returns | API surface |
|------|---------|-------------|
| `list_hosts` | Consoles on the account (model, IP, firmware, online state) | Site Manager (cloud) |
| `list_sites` | Sites with device-count statistics | Site Manager (cloud) |
| `list_devices` | All adopted devices: status, firmware, update state | Site Manager (cloud) |
| `get_isp_metrics(period, duration)` | WAN health — latency, throughput, packet loss, uptime, per WAN | Site Manager (cloud) |
| `list_clients(site_id?)` | Connected clients (name, IP, MAC, connection type) | Connector proxy (Owner key) |
| `get_device_stats(site_id?)` | Live per-device CPU, memory, load, uptime, uplink throughput | Connector proxy (Owner key) |
`get_isp_metrics` defaults to `period="5m"`, `duration="24h"`; use `period="1h"`
with `duration="7d"`/`"30d"` for longer windows. The proxy tools default to the
console's local site.
## Setup
Requires Python 3.11+ (the system 3.9 is too old for `mcp`). Uses [`uv`](https://docs.astral.sh/uv/).
```bash
cd unifi-mcp
uv venv --python 3.11
uv pip install mcp httpx
```
## Configuration (environment)
- `UNIFI_API_KEY` (**required**) — your UniFi API key. Create it at
[unifi.ui.com](https://unifi.ui.com) → profile → API Keys, or locally under
UniFi Network → Settings → Control Plane → Integrations.
- The proxy tools (`list_clients`, `clients_per_device`, `get_device_stats`)
require an **Owner** key. The cloud tools work with any key, including a
read-only Viewer key.
- `UNIFI_CONSOLE_ID` (**required for the proxy tools**) — your console's id. Find
it as the host `id` in the cloud `GET /hosts` response (or run `list_hosts`).
- `UNIFI_SITE_ID` (**required for the proxy tools**) — the local site id. Find it
as `data[0].id` in the proxy `GET /sites` response (often the "Default" site).
None of these are hardcoded — all are read from the environment at runtime, and
the API key is **never** logged, committed, or written to disk by this code. The
cloud-only tools (`list_hosts`, `list_sites`, `list_devices`, `get_isp_metrics`)
need just `UNIFI_API_KEY`.
## Register with Claude Code
```bash
claude mcp add unifi --scope user \
-e UNIFI_API_KEY="$UNIFI_API_KEY" \
-e UNIFI_CONSOLE_ID="$UNIFI_CONSOLE_ID" \
-e UNIFI_SITE_ID="$UNIFI_SITE_ID" \
-- /ABS/PATH/unifi-mcp/.venv/bin/python -m unifi_mcp.server
```
Registering against the absolute venv interpreter (not `uv run`) keeps cold-start
to a bare `exec`. `--scope user` keeps the registration (and the key) out of any
project repo.
## Skill: network health
`skills/network-health/` is an optional Claude Code skill that turns the tools
into a one-shot health summary — WAN/ISP status, offline devices, pending
firmware updates, and client distribution per AP. Install it by copying the
folder into your skills dir:
```bash
cp -r skills/network-health ~/.claude/skills/
```
Then ask Claude "how's my network?" and it orchestrates the relevant tools.
## Tests
Two layers, both runnable without touching production state (everything is a read):
```bash
# Offline: projection/envelope logic pinned to real captured payloads (no key)
.venv/bin/python test_projections.py
# Live: exercises the tools against the real API; prints raw-keys -> kept so
# field-name mismatches are visible (needs UNIFI_API_KEY; proxy ops also need
# UNIFI_CONSOLE_ID and UNIFI_SITE_ID)
UNIFI_API_KEY=... UNIFI_CONSOLE_ID=... UNIFI_SITE_ID=... .venv/bin/python smoke_test.py
```
## Design notes
- **stdio transport** — Claude launches the process on demand; zero idle cost.
- **Read-only enforced in code** — the HTTP client only ever issues GET
(asserted), so the read-only guarantee holds even if Ubiquiti later ships write
endpoints.
- **Token-efficient** — responses are projected down to monitoring-relevant
fields (`projections.py`); ISP metrics are summarized per-WAN rather than
returned raw.
- **Two API envelopes** — the cloud API uses cursor pagination
(`pageSize`/`nextToken`); the connector proxy uses offset pagination
(`offset`/`limit`) and some endpoints (e.g. `statistics/latest`) return a bare
object with no envelope. `client.py` handles all three shapes.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessSyncing