Model Context Protocol (MCP) server for managing UniFi network infrastructure through Claude Desktop.
This MCP server exposes UniFi management tools to Claude Desktop, allowing natural language interaction with your network. Ask questions like:
- "How many devices are connected to my network?"
- "What's my network health status?"
- "Block the kids iPad"
- "Restart the garage access point"
flowchart TB
subgraph Claude Desktop
CD[Claude Desktop App]
end
subgraph MCP Server
FS[FastMCP Server<br/>server.py]
CR[CLI Runner<br/>cli_runner.py]
end
subgraph UI CLI
CLI[ui_cli.main]
LC[Local Client<br/>UniFi Controller API]
CC[Cloud Client<br/>UniFi Cloud API]
end
subgraph UniFi Infrastructure
UDM[UDM / Controller]
CLOUD[UniFi Cloud API]
end
CD <-->|MCP Protocol<br/>stdio| FS
FS --> CR
CR -->|subprocess<br/>python -m ui_cli.main| CLI
CLI --> LC
CLI --> CC
LC <-->|HTTPS| UDM
CC <-->|HTTPS| CLOUD
- Claude Desktop connects to the MCP server via stdio
- FastMCP Server (
server.py) registers 21 tools with friendly names - CLI Runner (
cli_runner.py) executes./uicommands via subprocess - UI CLI performs the actual API calls and returns JSON
- Results flow back through the chain to Claude
The v2 architecture uses subprocess calls instead of direct Python imports because:
- Single source of truth - CLI handles all logic, formatting, error handling
- Consistent behavior - Same output as terminal usage
- Easier debugging - Test tools by running CLI directly
- Simpler maintenance - Add MCP tool = call existing CLI command
- Python 3.11+ with conda environment
ui-cli - Claude Desktop installed
- UniFi credentials configured in
.env
# Install MCP server to Claude Desktop
./ui mcp install
# Verify installation
./ui mcp check
# View current config
./ui mcp showThe installer adds this to Claude Desktop's config:
{
"mcpServers": {
"ui-cli": {
"command": "/path/to/ui-cmd/scripts/mcp-server.sh",
"args": [],
"env": {
"PYTHON_PATH": "/path/to/conda/envs/ui-cli/bin/python"
}
}
}
}| Tool | Description | Example Prompt |
|---|---|---|
network_status |
Check API connectivity | "Is my network API working?" |
network_health |
Site health summary | "What's my network health?" |
internet_speed |
Last speed test result | "What's my internet speed?" |
run_speedtest |
Run new speed test | "Run a speed test" |
isp_performance |
ISP metrics over time | "How's my ISP been performing?" |
| Tool | Description | Example Prompt |
|---|---|---|
client_count |
Count clients by category | "How many devices are connected?" |
device_list |
List UniFi devices | "What UniFi devices do I have?" |
network_list |
List networks/VLANs | "Show my networks" |
| Tool | Description | Example Prompt |
|---|---|---|
find_client |
Find client by name/MAC | "Find my iPhone" |
find_device |
Find device by name/MAC/IP | "Show the living room AP" |
client_status |
Check if client is online/blocked | "Is the TV online?" |
| Tool | Description | Example Prompt |
|---|---|---|
block_client |
Block from network | "Block the kids iPad" |
unblock_client |
Restore access | "Unblock the kids iPad" |
kick_client |
Force disconnect | "Disconnect my laptop" |
restart_device |
Reboot device | "Restart the garage AP" |
create_voucher |
Create guest WiFi code | "Create a guest WiFi voucher" |
| Tool | Description | Example Prompt |
|---|---|---|
list_groups |
List all client groups | "What groups do I have?" |
get_group |
Get group details | "Show the kids devices group" |
block_group |
Block all clients in group | "Block all kids devices" |
unblock_group |
Unblock all clients in group | "Unblock the kids devices" |
group_status |
Live status of group members | "Are the kids devices online?" |
src/ui_mcp/
├── __init__.py # Package init, version
├── __main__.py # Entry point: python -m ui_mcp
├── server.py # FastMCP server + 16 tool definitions
├── cli_runner.py # Subprocess wrapper for CLI calls
└── README.md # This file
Run the test script to validate all tools:
python scripts/test-mcp-tools.py- Add CLI command with
--jsonoutput support - Add tool function in
server.py:
@server.tool()
async def my_new_tool(param: str) -> str:
"""Tool description shown to Claude.
Args:
param: Parameter description
"""
result = run_cli(["lo", "mycommand", param])
if "error" in result:
return format_result(result)
return format_result(result, "Human-readable summary")- Test with
python scripts/test-mcp-tools.py
Check MCP server logs in Claude Desktop:
- macOS:
~/Library/Logs/Claude/mcp*.log
Test CLI runner directly:
from ui_mcp.cli_runner import run_cli
result = run_cli(["lo", "health"])
print(result)The wrapper script couldn't find the ui-cli conda environment. Ensure:
conda activate ui-cli
./ui mcp install # Re-install with correct Python path- Restart Claude Desktop after config changes
- Check that
.envfile exists with credentials - Run
./ui mcp checkto verify setup
Increase timeout in cli_runner.py for slow operations:
result = run_cli(["lo", "health"], timeout=60)- v0.3.0 - Tools layer architecture with quick timeout support
- v0.2.0 - Tools layer architecture (subprocess-based)
- v0.1.0 - Direct API calls (deprecated)