Skip to content

Docker

The Docker image is the recommended install for homelab and multi-client setups. The server runs as a long-running process and answers MCP calls over Streamable HTTP on port 3714. One container can serve Claude Desktop, Claude Code, Cursor, and Cline simultaneously.

The HTTP transport is secure by default: it refuses to start without a bearer token. Mint one, pass it to the container, and use it on every MCP request. See the Authentication guide for the full model.

Terminal window
export MCP_UNIFI_TOKEN=$(openssl rand -hex 32)
docker run --rm -p 3714:3714 \
-e STUB_MODE=true \
-e MCP_UNIFI_AUTH_TOKENS="$MCP_UNIFI_TOKEN" \
ghcr.io/pete-builds/mcp-unifi:latest

The server boots into stub mode, returns realistic mock data, and needs no UniFi hardware. Register it with any MCP client using Authorization: Bearer $MCP_UNIFI_TOKEN and start calling tools.

For throwaway local testing on loopback only, -e MCP_UNIFI_AUTH_REQUIRED=false skips auth entirely. Never use it on an interface reachable by anything else — every connected client gets admin-equivalent access to the controller.

A working compose file lives in the repo at docker-compose.example.yml:

x-logging: &default-logging
driver: json-file
options:
max-size: "10m"
max-file: "3"
services:
mcp-unifi:
build: .
image: mcp-unifi:dev
container_name: mcp-unifi
restart: unless-stopped
read_only: true
tmpfs:
- /tmp:size=16M
ports:
- "${MCP_PORT:-3714}:${MCP_PORT:-3714}"
env_file:
- .env
environment:
MCP_HOST: "0.0.0.0"
MCP_PORT: "${MCP_PORT:-3714}"
TZ: "${TZ:-UTC}"
LOG_FORMAT: "text"
logging: *default-logging
healthcheck:
test: ["CMD", "python", "-m", "mcp_unifi.healthcheck"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
security_opt:
- no-new-privileges:true

The example builds from local source. For the published image, change build: . to image: ghcr.io/pete-builds/mcp-unifi:latest and drop the build key.

The container runs as UID 1000, no shell, with a read-only root filesystem (/tmp is tmpfs) and no-new-privileges.

To also make the server unable to change anything on the controller, add MCP_UNIFI_READONLY=true to your .env. Mutating tools are then hidden from tools/list and refused on tools/call; reads are unaffected. Pair it with a read-only UniFi API key rather than treating either as sufficient alone — see the Security guide.

Send a tools/list request to confirm the server is alive and the right modules are loaded. Include the bearer token you minted above:

Terminal window
curl -sS -X POST http://localhost:3714/mcp \
-H "Authorization: Bearer $MCP_UNIFI_TOKEN" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

You should see the Network tools listed. Enabling MCP_UNIFI_MODULES_ENABLED=network,protect adds Protect; network,protect,access adds Access on top. The always-current per-module count is in the Tool Manifest.

When you have a gateway and an API key, drop stub mode and add the gateway env vars. Keep the auth token — it’s required for the HTTP transport regardless of stub/real mode:

Terminal window
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> \
-e MCP_UNIFI_AUTH_TOKENS="$MCP_UNIFI_TOKEN" \
ghcr.io/pete-builds/mcp-unifi:latest

Get the API key from Settings → Control Plane → Integrations → Create API Key in the gateway UI.

For multi-site setups (more than one controller), use the MCP_UNIFI_CONTROLLERS_FILE env var instead. See the Multi-Site Setup guide.

Protect and Access are opt-in modules. Add them to MCP_UNIFI_MODULES_ENABLED:

Terminal window
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> \
-e MCP_UNIFI_MODULES_ENABLED=network,protect,access \
-e UNIFI_ACCESS_HOST=192.168.1.1 \
-e UNIFI_ACCESS_API_KEY=<your-access-api-key> \
-e MCP_UNIFI_AUTH_TOKENS="$MCP_UNIFI_TOKEN" \
ghcr.io/pete-builds/mcp-unifi:latest

Access uses a separate API key generated on the Access controller’s developer settings. See the Protect Tools reference and the Access setup guide for the full surface.

Published images are signed with cosign keyless OIDC. See the Security Model guide for the verification command and SBOM download.