UniFi MCP Server
MCP server providing Claude Code with full UniFi network management capabilities -- devices, clients, ports, bandwidth auditing, firewall policies, and traffic rules -- all through natural language.
README
UniFi MCP Server
MCP server providing Claude Code with full UniFi network management capabilities -- devices, clients, ports, bandwidth auditing, firewall policies, and traffic rules -- all through natural language.
Overview
The UniFi MCP Server connects Claude Code to one or more UniFi network controllers via the UDM REST API. It exposes 19 tools that let you query, audit, and configure your network infrastructure conversationally.
Key capabilities:
- Self-healing authentication -- automatic re-auth on expiry or 401, with proactive refresh and mutex protection against concurrent auth storms
- Automated TOTP -- generates MFA codes from a stored seed, no manual authenticator app interaction
- 19 management tools covering devices, clients, ports, port profiles, networks, bandwidth auditing, traffic routes, and firewall policies
- Encrypted session persistence using AES-256-GCM
- Resilient API client with 429 throttle handling, 5xx exponential backoff, and 401 transparent re-auth
- Dual-controller support -- cloud-hosted controllers (two-step MFA) and UDM Pro Max (single-step MFA)
Architecture
graph LR
CC[Claude Code] <-->|stdio| MCP[MCP Server<br/>StdioServerTransport]
MCP --> Tools[19 Tools<br/>Zod-validated inputs]
Tools --> UC[UniFi Client<br/>Axios + retry interceptors]
UC --> SM[Session Manager<br/>Self-healing auth]
SM --> SC[Session Cache<br/>AES-256-GCM]
SM --> TOTP[TOTP Generator<br/>RFC 6238]
UC -->|HTTPS| API[UniFi Controller<br/>REST API]
style CC fill:#4A90D9,color:#fff
style MCP fill:#6C5CE7,color:#fff
style Tools fill:#00B894,color:#fff
style UC fill:#FDCB6E,color:#000
style SM fill:#E17055,color:#fff
style API fill:#2D3436,color:#fff
Features
Self-Healing Authentication
The server never requires manual re-authentication during operation. The session manager implements a multi-layered strategy:
| Layer | Trigger | Behavior |
|---|---|---|
| Proactive refresh | Session < 15 min remaining | Fire-and-forget re-auth in the background; current request uses existing session |
| Expired session | Session TTL = 0 | Blocking re-auth before the request proceeds |
| 401 retry | Controller returns HTTP 401 | Interceptor triggers re-auth, then transparently retries the original request |
| Mutex protection | Concurrent requests during auth | All callers await the same in-flight auth promise (no auth storms) |
| Failure circuit breaker | 3 consecutive auth failures | Auth disabled until server restart (prevents credential lockout) |
Two-Step MFA with Automated TOTP
Cloud-hosted UniFi controllers require two-step MFA:
sequenceDiagram
participant S as MCP Server
participant C as UniFi Controller
participant T as TOTP Generator
S->>C: POST /api/auth/login {username, password}
C-->>S: 200 MFA_AUTH_REQUIRED + UBIC_2FA cookie
S->>T: Generate TOTP from base32 seed
T-->>S: 6-digit code
S->>C: POST /api/auth/login {username, password, token}<br/>Cookie: UBIC_2FA=...
C-->>S: 200 + Set-Cookie: TOKEN=jwt...
S->>S: Parse JWT for CSRF token + expiry
S->>S: Encrypt and persist session to disk
For UDM Pro Max controllers, authentication completes in a single step (username + password + TOTP in one request).
Resilient API Client
Every API call passes through axios interceptors that handle transient failures:
- 429 Too Many Requests -- waits for
Retry-Afterheader (or 5s default), then retries - 5xx Server Error -- exponential backoff (1s, 2s, 4s) up to 3 retries
- 401 Unauthorized -- triggers self-healing re-auth, refreshes headers, retries the request once
Tools Reference
Device Tools (3)
| Tool | Description | Key Parameters |
|---|---|---|
unifi-device-list |
List all devices (switches, APs, gateways) with name, model, IP, state, firmware | type: filter by usw, uap, ugw, udm, or all |
unifi-device-detail |
Full detail for a single device by MAC address, including port count and uptime | mac: device MAC address |
unifi-device-ports |
All ports on a switch with status, speed, PoE, connected MACs, and assigned port profile | mac: switch MAC address |
Client Tools (3)
| Tool | Description | Key Parameters |
|---|---|---|
unifi-client-list |
List active clients connected to the network | filter: all, wired, or wireless; limit: max results (1-500) |
unifi-client-detail |
Detailed info for a specific client by MAC | mac: client MAC address |
unifi-client-history |
All known clients including offline (historical database) | limit: max results (1-500) |
Port Profile Tools (6 -- 2 read, 4 write)
| Tool | Description | Key Parameters |
|---|---|---|
unifi-port-profiles |
List all port profiles (bandwidth/VLAN configs) | -- |
unifi-port-profile-detail |
Full raw detail for a port profile by ID | profileId |
unifi-port-profile-create |
Create a new port profile with bandwidth limits | name, forward, nativeNetworkId, egressRateKbps, ingressRateKbps, confirm |
unifi-port-profile-update |
Update an existing profile (fetch-then-merge) | profileId, + any field to change, confirm |
unifi-port-profile-delete |
Delete a port profile (fails if ports still assigned) | profileId, confirm |
unifi-port-set-profile |
Assign a port profile to a specific switch port | deviceMac, portIdx, profileId, portName, confirm |
All write tools require confirm: true to execute. Set confirm: false for a dry-run preview of what would change.
Network Tools (2)
| Tool | Description | Key Parameters |
|---|---|---|
unifi-network-list |
List all configured networks (VLANs, corporate, guest) | -- |
unifi-network-detail |
Full configuration for a specific network | networkId |
Audit Tools (1)
| Tool | Description | Key Parameters |
|---|---|---|
unifi-bandwidth-audit |
Per-room bandwidth audit across all switches. Shows port name, bandwidth limits, active status, connected device count. Designed for cross-referencing with billing systems. | switchMac (optional), includeUnnamed |
Route & Firewall Tools (2)
| Tool | Description | Key Parameters |
|---|---|---|
unifi-traffic-routes |
List traffic routes/rules with bandwidth shaping data (raw response) | -- |
unifi-firewall-policies |
List firewall policies from v2 API including traffic rules (raw response) | -- |
Utility Tools (2)
| Tool | Description | Key Parameters |
|---|---|---|
unifi-auth-status |
Check authentication status, session expiry, and controller connectivity | -- |
unifi-api |
Generic API escape hatch -- make any UniFi API call not covered by other tools | method, path, useAbsolutePath, params, body |
Setup
Prerequisites
- Node.js >= 20.0.0
- Access to a UniFi controller (cloud-hosted or UDM Pro / UDM Pro Max)
- TOTP seed from your authenticator app setup (for automated MFA)
- WireGuard VPN (if targeting a UDM Pro / UDM Pro Max on a private LAN)
VPN Setup (WireGuard)
If your UniFi controller is a UDM Pro / UDM Pro Max on a private network, you need a WireGuard VPN tunnel to reach it. Cloud-hosted controllers are publicly accessible and do not require VPN.
Required routed subnets:
Your WireGuard AllowedIPs must include every subnet the MCP server and your tools need to reach:
| Subnet | Purpose |
|---|---|
| UDM management subnet | Controller API access (required) |
| Switch management subnet | SSH access for device operations (if switches are on a separate subnet) |
| Tenant/client subnets | Client data and VLAN networks (if querying client tools) |
Example WireGuard peer config:
[Peer]
PublicKey = <udm-public-key>
AllowedIPs = <udm-subnet>/24, <switch-subnet>/24, <tenant-supernet>/8
Endpoint = <udm-wan-ip>:51820
Verifying connectivity:
# Check the tunnel routes all required subnets
wg show <tunnel-name> allowed-ips
# Verify controller reachable
ping <your-udm-ip>
# Verify switches reachable (if on a separate management subnet)
ping <switch-ip>
Troubleshooting: devices unreachable but controller responds
The WireGuard Windows app stores tunnel configs internally. Editing the .conf file on disk does not update the running tunnel. If your config file has the correct AllowedIPs but wg show is missing a subnet:
- Open the WireGuard app
- Remove the tunnel
- Import tunnel from file (re-import the updated
.conf) - Activate the tunnel
Verify with wg show <tunnel-name> allowed-ips.
Installation
# Clone the repository
git clone <repo-url>
cd mcp-servers/unifi-mcp-server
# Install dependencies
npm install
# Configure environment
cp .env.example .env
# Edit .env with your controller details (see Configuration section)
# Build
npm run build
# Authenticate (first time)
npm run auth # Interactive mode
npm run auth -- --auto # Non-interactive (requires UNIFI_USERNAME, UNIFI_PASSWORD, UNIFI_TOTP_SEED)
# Start the server
npm run start
Claude Code Configuration
Add the server to your Claude Code MCP settings (~/.claude/settings.json or project-level .mcp.json):
{
"mcpServers": {
"unifi": {
"command": "node",
"args": ["/path/to/unifi-mcp-server/dist/index.js"],
"env": {
"UNIFI_HOST": "your-controller-ip",
"UNIFI_SITE": "default"
}
}
}
}
The server communicates over stdin/stdout using the MCP StdioServerTransport.
Configuration
All configuration is loaded from .env at the project root via Zod-validated schemas.
Core Settings
| Variable | Required | Default | Description |
|---|---|---|---|
UNIFI_HOST |
Yes | -- | Hostname or IP of the UniFi controller |
UNIFI_PORT |
No | 443 |
HTTPS port |
UNIFI_SITE |
No | default |
UniFi site ID (visible in the controller URL) |
UNIFI_VERIFY_SSL |
No | false |
Set true only if the controller has a valid TLS certificate |
Authentication
| Variable | Required | Default | Description |
|---|---|---|---|
UNIFI_USERNAME |
For self-healing | -- | UniFi SSO or local admin username |
UNIFI_PASSWORD |
For self-healing | -- | UniFi SSO or local admin password |
UNIFI_TOTP_SEED |
For self-healing | -- | Base32-encoded TOTP secret from authenticator setup |
Session Storage
| Variable | Required | Default | Description |
|---|---|---|---|
TOKEN_CACHE_PATH |
No | ./data/unifi-session.encrypted.json |
Path to the encrypted session cache file |
TOKEN_ENCRYPTION_KEY |
No | -- | 64-character hex key for AES-256-GCM encryption. Generate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))". Falls back to base64 encoding if unset. |
Logging
| Variable | Required | Default | Description |
|---|---|---|---|
LOG_LEVEL |
No | info |
Log verbosity: debug, info, warn, error |
Webhook Push (scripts/webhook-push.ts only)
| Variable | Required | Default | Description |
|---|---|---|---|
WEBHOOK_URL |
For webhook | -- | Make.com or other webhook URL for monthly bandwidth audit |
VPN_TUNNEL_NAME |
No | -- | WireGuard tunnel name for automatic VPN connect/disconnect |
VPN_GATEWAY |
No | -- | IP to ping for VPN health check (e.g., your gateway behind the tunnel) |
MAX_RETRIES |
No | 3 |
Number of retry attempts for webhook push |
RETRY_DELAY_MS |
No | 60000 |
Initial delay between retries in ms (linear backoff: 1x, 2x, 3x) |
Authentication
How It Works
UniFi controllers use cookie-based authentication with JWT tokens. The server supports two authentication flows depending on the controller type:
Cloud-hosted controller (two-step MFA):
- POST username + password to
/api/auth/login-- returnsUBIC_2FAcookie - Generate TOTP code from the stored base32 seed (RFC 6238, HMAC-SHA1, 30-second period, 6 digits)
- POST username + password + TOTP code with the
UBIC_2FAcookie -- returnsTOKENinSet-Cookie - Parse the JWT payload for the
csrfTokenandexp(expiry) claims - Encrypt and persist the session to disk
UDM Pro / UDM Pro Max (single-step):
- POST username + password + TOTP code in a single request -- returns
TOKENinSet-Cookie - Parse and persist as above
Initial Authentication
Run the auth script to establish the first session:
# Interactive -- prompts for username and password
npm run auth
# Non-interactive -- reads credentials from environment
npm run auth -- --auto
The --auto flag is used for scheduled tasks and CI environments. It requires UNIFI_USERNAME, UNIFI_PASSWORD, and UNIFI_TOTP_SEED to be set in .env.
Self-Healing Auth
When UNIFI_USERNAME, UNIFI_PASSWORD, and UNIFI_TOTP_SEED are all present in the environment, the server re-authenticates automatically without any manual intervention:
- Proactive refresh: When the session has fewer than 15 minutes remaining, a background refresh is triggered (fire-and-forget; the current request continues with the existing session)
- Expired re-auth: When the session has fully expired, re-auth blocks until complete before the request proceeds
- 401 retry: If the controller returns 401, the axios interceptor triggers re-auth, refreshes the Cookie and X-CSRF-Token headers, and retries the original request
- Mutex: A promise-based mutex ensures concurrent requests share a single auth attempt
- Circuit breaker: After 3 consecutive authentication failures, auth is disabled until the server is restarted (prevents credential lockout loops)
Scripts
webhook-push.ts
Automated monthly bandwidth audit push to a Make.com webhook. Designed to run as a scheduled task on the 1st of each month.
npx tsx scripts/webhook-push.ts
What it does:
- Connects WireGuard VPN (if
VPN_TUNNEL_NAMEis set) - Authenticates to the UniFi controller with automated TOTP
- Pulls all switch device data and builds a per-room bandwidth payload
- POSTs the payload to the configured
WEBHOOK_URL - Disconnects VPN
- Retries up to
MAX_RETRIEStimes with linear backoff on failure
initial-auth.ts
Interactive or automated initial login to establish a session cache.
npm run auth # Interactive
npm run auth -- --auto # Non-interactive
auto-reauth.ts
Non-interactive re-authentication for use in scheduled tasks and CI. Generates TOTP automatically, saves the session to the encrypted cache.
npx tsx scripts/auto-reauth.ts # re-auth + save session
npx tsx scripts/auto-reauth.ts --quiet # suppress progress logs
npx tsx scripts/auto-reauth.ts --json # output token JSON to stdout for script chaining
Development
Commands
| Command | Description |
|---|---|
npm run build |
Compile TypeScript to dist/ |
npm run dev |
Watch mode (tsc --watch) |
npm run start |
Run the compiled server (node dist/index.js) |
npm run auth |
Run the initial auth script (npx tsx scripts/initial-auth.ts) |
Project Structure
unifi-mcp-server/
├── src/
│ ├── index.ts # Server bootstrap (McpServer + StdioServerTransport)
│ ├── config/
│ │ └── index.ts # Zod-validated env config from .env
│ ├── auth/
│ │ ├── sessionManager.ts # Self-healing auth with proactive refresh + mutex
│ │ ├── sessionCache.ts # AES-256-GCM encrypted session persistence
│ │ └── totp.ts # RFC 6238 TOTP generator (zero external dependencies)
│ ├── unifi/
│ │ ├── unifiClient.ts # Axios client with 401/429/5xx retry interceptors
│ │ └── types.ts # TypeScript interfaces for UniFi API entities
│ ├── tools/
│ │ ├── index.ts # registerTools() — wires all tool modules
│ │ ├── devices.ts # 3 tools: device-list, device-detail, device-ports
│ │ ├── clients.ts # 3 tools: client-list, client-detail, client-history
│ │ ├── ports.ts # 6 tools: profiles CRUD + port-set-profile
│ │ ├── networks.ts # 2 tools: network-list, network-detail
│ │ ├── audit.ts # 1 tool: bandwidth-audit
│ │ ├── routes.ts # 2 tools: traffic-routes, firewall-policies
│ │ └── utility.ts # 2 tools: auth-status, generic api escape hatch
│ └── utils/
│ ├── errors.ts # Error hierarchy: UniFiError → Auth / Api / Config
│ ├── logger.ts # stderr logger with sensitive key redaction
│ └── formatters.ts # Entity formatters for clean LLM output
├── scripts/
│ ├── initial-auth.ts # Interactive/auto auth script
│ ├── auto-reauth.ts # Non-interactive re-auth for scheduled tasks
│ └── webhook-push.ts # Monthly bandwidth audit → webhook
├── data/ # Session cache (gitignored)
├── dist/ # Compiled output (gitignored)
├── .env # Environment variables (gitignored)
├── .env.example # Template with all variables documented
├── .gitignore
├── package.json
└── tsconfig.json
Adding a New Tool
-
Create or edit a file in
src/tools/(group by entity type). -
Export a
register*Tools(server: McpServer)function:import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; import { unifiClient } from '../unifi/unifiClient.js'; import { formatErrorForMcp } from '../utils/errors.js'; export function registerMyTools(server: McpServer): void { server.tool( 'unifi-my-tool', 'Description of what this tool does.', { param: z.string().describe('What this parameter controls'), }, async ({ param }) => { try { const data = await unifiClient.get('/rest/endpoint'); return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }], }; } catch (error) { return { content: [{ type: 'text', text: formatErrorForMcp(error) }], isError: true, }; } } ); } -
Wire it into
src/tools/index.ts:import { registerMyTools } from './mytools.js'; // ... export function registerTools(server: McpServer): void { // ... existing registrations registerMyTools(server); } -
Build and test:
npm run build && npm run start
TypeScript Conventions
- Target: ES2022 with NodeNext module resolution
- ESM:
"type": "module"in package.json -- all imports must use.jsextensions - Strict mode: enabled (
"strict": truein tsconfig) - Output:
dist/with declaration files and source maps
Security
Token Encryption
Session tokens are persisted to disk for survival across server restarts. When TOKEN_ENCRYPTION_KEY is set (64-character hex string = 32 bytes), the session cache is encrypted using AES-256-GCM with:
- Random 16-byte IV per write
- GCM authentication tag for tamper detection
- Format:
iv_hex:auth_tag_hex:ciphertext_hex
If the encryption key is not set, sessions fall back to base64 encoding (a warning is logged on every save).
Generate a key:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
Credential Handling
- Credentials (
UNIFI_USERNAME,UNIFI_PASSWORD,UNIFI_TOTP_SEED) are read from environment variables only -- never stored in code or committed to version control. - The logger automatically redacts any log context key containing
token,secret,password,credential,authorization,bearer,apikey,api_key, orcookie. - All logs are written to stderr (stdout is reserved for MCP protocol communication).
Self-Signed SSL
UniFi controllers typically use self-signed certificates. By default, UNIFI_VERIFY_SSL=false disables certificate verification via https.Agent({ rejectUnauthorized: false }). Set to true only if your controller has a valid certificate from a trusted CA.
Dependencies
| Package | Version | Purpose |
|---|---|---|
@modelcontextprotocol/sdk |
^1.12.0 | MCP server framework (McpServer, StdioServerTransport) |
axios |
^1.7.9 | HTTP client with interceptor support for retry logic |
dotenv |
^16.4.7 | Load .env files into process.env |
zod |
^3.24.1 | Runtime schema validation for config and tool inputs |
Dev dependencies: typescript ^5.7.2, tsx ^4.19.2, @types/node ^22.10.2
Built by Element Zero
This project is built and maintained by Element Zero -- an AI-driven network automation company specializing in multi-tenant building infrastructure.
We use this MCP server daily to manage commercial building networks with 60+ tenant VLANs, automated bandwidth auditing, and billing integration. It's battle-tested in production.
If you manage UniFi networks for multi-tenant buildings, hotels, or coworking spaces -- we'd love to hear from you. Reach out at phil@ezero.ai.
License
MIT
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.