UniFi Read-Only
A production-grade, read-only Model Context Protocol server for a single
Ubiquiti UniFi OS console. Wraps both the modern Network
Integration API and the broader Classic Controller API on the same console
, pinned to one site, scoped to GET only.
What This Server Does
UniFi Read-Only exposes a curated set of GET-only
tools that wrap two API surfaces on the same console:
- Network Integration API (official,
X-API-KEY), modern UUID-keyed endpoints. Tools prefixedunifi_int_*. - Classic / Internal Controller API (cookie session), broader legacy coverage for alarms, events, rogue APs, DPI stats, daily reports, and more. Tools prefixed
unifi_classic_*.
The Site Manager (cloud) API is intentionally not wrapped, this server talks only to a console you can reach on the LAN.
The instance is pinned to a single site at startup: UNIFI_SITE_ID (Integration UUID) and UNIFI_SITE_NAME (Classic short name). Site-scoped tools take no site parameter at all, the model cannot enumerate or query an unrelated site the admin can also see.
Ships as both a Docker and a Podman container from the same server.py, pick whichever runtime you already run.
What to Ask, in Plain English
A handful of real questions you can put to your AI client once this server is wired in. The model figures out which tools to call, you describe the outcome.
- "List rogue APs detected in the last 24 hours and where they were seen."
- "Which clients are connected to the IoT VLAN right now?"
- "Show me the firewall policies between the LAN and Guest zones, in evaluation order."
- "Which clients failed to connect to the WiFi today, and why?"
- "What SSIDs are configured, and which networks do they sit on?"
- "Give me the per-client DPI breakdown for the busiest user over the last 24 hours."
78 Read-Only Tools Across Both API Surfaces
Build & Configure
Two supported runtimes, pick the one you already run. The docker/ and
podman/ backends ship an identical server.py; only the
container file and the way the client is wired differ. Both keep your UniFi
credentials out of the client's config file.
Step 0, Create a local read-only admin and generate an API key. On the console, go to Settings → Control Plane → Admins & Users → Add Admin. Restrict to Local Access Only, role Limited Admin → Read-Only. Do not use your UI.com cloud account, UI.com enforces MFA, which breaks non-interactive logins with HTTP 499. Sign in as that local admin, go to Settings → Control Plane → Integrations → Create API Key, and copy the key (shown only once). If you also want the Classic-API tools, keep the same admin's username and password handy.
Docker Docker Desktop · MCP Gateway ›
Clone the repository. The Docker backend lives in docker/ and ships a custom-catalog.yaml alongside the Dockerfile.
$ git clone https://github.com/rosarion97/unifi-readonly-mcp-server-public.git $ cd unifi-readonly-mcp-server-public/docker
The image tag must match the image: field in custom-catalog.yaml (unifi-readonly-mcp:latest).
$ docker build -t unifi-readonly-mcp:latest .
Three values are required: UNIFI_HOST, UNIFI_API_KEY, and UNIFI_SITE_ID. Optional: UNIFI_PORT (default 443), UNIFI_SITE_NAME (default default), UNIFI_VERIFY_TLS (default false), and the Classic-API credentials.
Find your UNIFI_SITE_ID by calling unifi_int_list_sites once with a placeholder, then copy the UUID.
$ docker mcp secret set UNIFI_API_KEY $ docker mcp secret set UNIFI_SITE_ID # Optional, only if you want the unifi_classic_* tools: $ docker mcp secret set UNIFI_CLASSIC_USERNAME $ docker mcp secret set UNIFI_CLASSIC_PASSWORD $ docker mcp secret ls # values are masked
UNIFI_HOST, UNIFI_PORT, UNIFI_SITE_NAME,
and UNIFI_VERIFY_TLS are runtime config, set them on the
unifi-readonly entry's env block in the
custom-catalog.yaml you copy in Step 4. The shipped catalog
already declares all secret bindings; you just fill in the runtime fields.
With Option A, continue through Steps 4–7 below.
chmod 600 .env, never commit it. This
path skips the gateway: skip Steps 4–6, point your client at
docker run --env-file (below), then jump to Step 7.
$ cp .env.example .env $ chmod 600 .env # UNIFI_HOST, UNIFI_API_KEY, UNIFI_SITE_ID required; Classic creds optional
{
"mcpServers": {
"unifi": {
"command": "docker",
"args": [
"run", "--rm", "-i",
"--env-file", "/absolute/path/to/docker/.env",
"unifi-readonly-mcp:latest"
]
}
}
}
Steps 4–6 are the Option A (secret store) path. Used Option B? Skip to Step 7. After copying the catalog, open it and fill in the UNIFI_HOST, UNIFI_PORT, UNIFI_SITE_NAME, and UNIFI_VERIFY_TLS values on the unifi-readonly entry.
$ mkdir -p ~/.docker/mcp/catalogs $ cp custom-catalog.yaml ~/.docker/mcp/catalogs/custom.yaml
Add the entry under the single top-level registry: key in ~/.docker/mcp/registry.yaml. Don't overwrite the file if it already exists.
registry:
unifi-readonly:
catalog: custom
enabled: true
Add the gateway block to claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/claude_desktop_config.json). The gateway spawns the UniFi image on demand, reads your catalog and registry, and resolves your UNIFI_* secrets at request time. Replace <your-username> with your macOS username (run whoami to check):
{
"mcpServers": {
"mcp-toolkit-gateway": {
"command": "docker",
"args": [
"run", "-i", "--rm",
"-v", "/var/run/docker.sock:/var/run/docker.sock",
"-v", "/Users/<your-username>/.docker/mcp:/mcp",
"-v", "/Users/<your-username>/Library/Caches/docker-secrets-engine/engine.sock:/root/.cache/docker-secrets-engine/engine.sock",
"docker/mcp-gateway:latest",
"--catalog=/mcp/catalogs/custom.yaml",
"--registry=/mcp/registry.yaml",
"--transport=stdio"
]
}
}
}
All three bind-mounts are required: the Docker socket lets the gateway spawn the server container; ~/.docker/mcp is where it reads your catalog and registry; the docker-secrets-engine socket is the resolver Docker Desktop exposes for the secret store, without it your UNIFI_* URLs resolve to empty strings and the server never starts. On Linux Docker Desktop the path is ~/.docker/desktop/secrets-engine/engine.sock; find it with find ~ -name engine.sock.
claude mcp add -s user mcp-toolkit-gateway -- docker run …; Codex reads the same shape from ~/.codex/config.toml as [mcp_servers.mcp-toolkit-gateway]. Any MCP-capable client, Google's Gemini included, reads an mcpServers block from its own settings file; only the location changes, the entry is identical.
Then quit and reopen the client. claude_desktop_config.json never contains UNIFI_API_KEY, the gateway resolves it from Docker's secret store at request time. As a shortcut, docker mcp client connect claude-desktop (or MCP Toolkit → Clients in Docker Desktop) writes a similar block automatically; the explicit JSON above survives Docker Desktop updates that may rewrite the auto-managed entry.
$ docker mcp server list # unifi-readonly → enabled $ docker mcp tools list
Or run the image directly, it starts and waits silently on stdin (correct for an MCP stdio server). Press Ctrl+C to exit; a missing required value fails fast on stderr.
$ docker run --rm -i --env-file .env unifi-readonly-mcp:latest
Podman Rootless · stdio ›
Clone the repository. The Podman backend lives in podman/.
$ git clone https://github.com/rosarion97/unifi-readonly-mcp-server-public.git $ cd unifi-readonly-mcp-server-public
On macOS / Windows, start a Podman machine first (podman machine init && podman machine start); Linux runs natively.
$ podman build -t unifi-readonly-mcp:latest podman/
Set them in podman/.env; the file is gitignored and chmod 600'd. Three required values, plus optional Classic-API credentials and tuning knobs.
$ cp podman/.env.example podman/.env $ chmod 600 podman/.env # UNIFI_HOST / UNIFI_API_KEY / UNIFI_SITE_ID required
If the UniFi console uses the stock self-signed certificate, leave UNIFI_VERIFY_TLS=false. For production, install a real cert and flip it to true.
Point Claude Desktop at the container with the absolute path to podman/.env:
{
"mcpServers": {
"unifi": {
"command": "podman",
"args": [
"run", "--rm", "-i",
"--env-file", "/absolute/path/to/podman/.env",
"unifi-readonly-mcp:latest"
]
}
}
}
mcpServers block from its own settings.json,
Google's Gemini included. Drop this same entry into that client's
settings.json and the UniFi tools appear there too. Only the
file's name and location change; the server entry is identical.
Claude Code takes the same invocation:
$ claude mcp add -s user unifi -- \
podman run --rm -i \
--env-file /absolute/path/to/podman/.env \
unifi-readonly-mcp:latest
-i is required, MCP stdio needs stdin attached, and every --env-file path must be absolute.
Run the image manually, it starts and waits silently on stdin. Press Ctrl+C to exit; a missing required value fails fast on stderr.
$ podman run --rm -i --env-file podman/.env unifi-readonly-mcp:latest
One container serves one site, run a second container with its own credentials to serve another site. --rm cleans up the container after each session.
Three Required, Five Optional
- UNIFI_HOST, required. Console host or IP (no scheme). LAN IP of your UniFi OS device.
- UNIFI_API_KEY, required. Integration-API bearer key. Generate at Settings → Control Plane → Integrations → Create API Key while signed in as the local read-only admin.
- UNIFI_SITE_ID, required. Integration-API site UUID. Pins this instance to one site.
- UNIFI_PORT, optional. TLS port. Default
443(UDM/UCG/UDR/UDW); use11443for UniFi OS Server. - UNIFI_SITE_NAME, optional. Classic-API short site name. Default
default. - UNIFI_CLASSIC_USERNAME / UNIFI_CLASSIC_PASSWORD, optional. Required only for
unifi_classic_*tools. Without them the server still starts; Classic tools raise a clear error on first call. - UNIFI_VERIFY_TLS, optional. Default
false(stock self-signed cert). Settrueonce you install a real certificate. - UNIFI_MAX_RESPONSE_BYTES, optional. JSON byte cap per tool response. Default
120000(~30k tokens).
Before You Start
- Docker Engine 24+ / Docker Desktop 4.27+ with the MCP Toolkit feature, or Podman 4.x+ on
$PATH. Pick one; the step-by-step for each is above. - A UniFi OS console (UDM / UCG / UDR / UDW or UniFi OS Server) running Network application ≥ 9.0 (9.3+ recommended), reachable on the LAN from the host running the container.
- A dedicated local Limited Admin (Read-Only) on the console: Settings → Control Plane → Admins & Users → Add Admin; Restrict to Local Access Only; role Limited Admin → Read-Only. Do not use a UI.com cloud account, UI.com enforces MFA, which breaks non-interactive logins with HTTP 499.
- Network Integration API key generated as that local admin under Settings → Control Plane → Integrations → Create API Key. Shown only once at creation; copy immediately.
- An MCP-capable client, Claude Desktop, Claude Code, Codex, Gemini CLI, or any client speaking MCP over stdio.
Read-Only by Construction
_request() helper that refuses any verb other than GET;
the only POST anywhere is the one-off Classic-API login. The server
contains no code path that issues a write verb to the console, so it is
incapable of modifying any UniFi configuration regardless of what an LLM or
user asks for. The Site Manager (cloud) API is intentionally not wrapped, this
server talks only to a console you can reach on the LAN. Pull requests that add
additional read-only endpoints are welcome; any change that introduces a
write-capable verb against the console will be rejected. Not affiliated with or
endorsed by Ubiquiti.