Skip to content

Authentication

Starting in v0.9.0, the HTTP transport authenticates every incoming MCP request with an Authorization: Bearer <token> header. The server is secure by default: it refuses to start over streamable-http without tokens configured. Stdio transport is unaffected — the parent process owns the security boundary there, so adding bearer auth would be theatre.

mcp-unifi exposes 57 Network tools, 11 Protect tools, and 18 Access tools. Many in Network and Protect are destructive: delete a VLAN, rewrite a firewall rule, disable Threat Management, block a client, change a camera’s recording mode. Before v0.9.0 the server trusted the LAN, which meant a single compromised IoT device on the same network had admin-equivalent access to the controller. v0.9.0 closes that gap. Access is read-only in v0.10, but the same bearer-token gate still applies to it.

Terminal window
openssl rand -hex 32

Use one token per client (Claude Code, n8n, Home Assistant, etc.) so you can revoke one without rotating all.

Set MCP_UNIFI_AUTH_TOKENS as a comma-separated list. Each entry is either:

  • a bare token (auto-assigned client_id client-0, client-1, …)
  • a name:token pair for named clients (recommended — the name shows up in the audit log)
MCP_UNIFI_AUTH_TOKENS=claude:7f3a...,n8n:c812...,homeassistant:a4d9...
MCP_UNIFI_AUTH_REQUIRED=true

On boot, the server logs the configured client_ids (never the tokens) and starts. Without tokens and with MCP_UNIFI_AUTH_REQUIRED=true, the server raises at startup with a clear error.

Add the Authorization header in your MCP server config:

{
"mcpServers": {
"unifi": {
"type": "http",
"url": "http://nix1:3714/mcp",
"headers": {
"Authorization": "Bearer 7f3a..."
}
}
}
}

Every request to /mcp must carry:

Authorization: Bearer <token>

Missing or wrong → 401.

Opting out (single-host trusted boundary only)

Section titled “Opting out (single-host trusted boundary only)”

If the server is bound to 127.0.0.1 and only reachable from the same host inside a private network namespace, you can disable auth:

MCP_UNIFI_AUTH_REQUIRED=false

The server boots with a loud WARNING log line every time. Don’t do this on a multi-host LAN, a tailnet, or anywhere a guest network could reach the port.

Every tool call records the authenticated client_id in the JSONL audit log:

{
"ts": "2026-05-27T18:42:11.000Z",
"controller": "default",
"tool": "delete_firewall_rule",
"client_id": "n8n",
"args": {...},
"success": true,
"latency_ms": 142.3
}

client_id is null on stdio or when auth is disabled. Older entries from v0.8.0 and earlier omit the field entirely; replay tooling tolerates both shapes (no schema version bump — the field defaults to null when missing).

  • Per-tool scopes: every authenticated client can call every tool. Per-client RBAC is a v1.x decision.
  • Token rotation API: rotate by editing the env var and restarting. No live rotation endpoint.
  • OAuth flows: out of scope. The static-token model fits homelab single-admin use; if you need OAuth, front mcp-unifi with an authenticating reverse proxy (Authelia, Caddy + auth_request, etc.) and keep MCP_UNIFI_AUTH_REQUIRED=false.
  • Rate limiting: not implemented.