Skip to main content
Glama
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.