unas-mcp
MCP server for managing a Ubiquiti UNAS Pro NAS, providing health monitoring, storage and disk inventory, NFS export visibility, read-only file inspection, and gated lifecycle operations via SSH and the UniFi Drive API.
README
unas-mcp
MCP server for managing a Ubiquiti UNAS Pro 8 NAS (6×26 TB RAID-6, ~104 TB
usable) — health monitoring, storage/disk inventory, NFS export visibility,
read-only file inspection, and gated lifecycle operations. 20 tools, deployed
as a container on VM 102 (http://10.0.40.25:8490/mcp).
What it is
The UNAS Pro 8 exposes two independent surfaces, and this server is a thin, safety-gated layer over both:
- SSH (
root@<host>, key-only,src/unas_mcp/ssh.py) —/proc/mdstat,smartctl,df,rpcinfo,exportfs,uptime/last,uname, and a handful of read-only filesystem commands (ls,du). This is the same transportdocs/reference/unas-pro-8-capabilities.md(in thehomelabrepo) documents as voiding Ubiquiti's warranty — used here only where the UI and the Drive API cannot answer the question. - UniFi Drive API (
src/unas_mcp/api.py, unofficial/undocumented — findings indocs/drive-api-findings.md) — physical bay mapping (slotId), vendorhealthScore/riskReasons, and per-RAID-groupcurrentProtectionvsexpectedProtection. SSH's/proc/mdstatandsmartctlare enough for pass/fail health, but they don't carry which physical bay to pull at the rack or the vendor's own risk scoring — that's the entire reason the Drive API transport exists in this server.
Every parser in this project is "tolerant but honest": a field genuinely
absent from a live reply is surfaced as None (or raises, for the small set
of fields health decisions are gated on), and is never silently defaulted
to a value that reads as healthy (0, "ok", False, or a label count
standing in for a verdict actually read). See api.py, parsers/smart.py,
and tools/storage.py's module docstrings for the specific traps this
pattern was built to close.
Tool inventory (20 tools, 5 groups)
| Group | Tool | Tier | What it does |
|---|---|---|---|
| Health (SSH) | unas_health_summary |
read | Rolled-up healthy | unhealthy | unknown verdict across array, SMART, capacity, and NFS — unknown on any transport/parse failure or incomplete reply, never silently healthy |
unas_array_status |
read | Parsed /proc/mdstat — per-array [n/m] state, healthy flag, rebuild/resync/reshape progress |
|
unas_smart_status |
read | One SMART record per drive bay (sda-sdf): verdict + the three failure-predicting counters | |
unas_smart_details |
read | Full SMART health + identity record for one drive bay | |
unas_capacity |
read | Media volume capacity in bytes (total/used/avail) + used_pct, from df -B1 |
|
unas_nfs_state |
read | Whether nfsd/mountd are registered with the local RPC portmapper | |
unas_uptime_boot |
read | uptime + last 5 last -x boot/shutdown records |
|
unas_system_info |
read | uname -a + /etc/os-release |
|
| Lifecycle (SSH) | unas_smart_test_start |
direct | Start a SMART self-test (short/long) on a drive bay |
unas_smart_test_status |
direct | Read a drive bay's SMART self-test log | |
unas_firmware_status |
read | Firmware/OS release identity | |
unas_service_status |
read | Running systemd services | |
unas_reboot |
confirm | Reboot the appliance — ~9.5 min of NFS downtime for every client; requires confirm="yes" |
|
| Storage (Drive API) | unas_pool_status |
read | Every pool's status/type/capacity/usage + per-RAID-group protection state |
unas_disk_inventory |
read | Every physical disk + cache slot: slotId, model, serial, temp, healthScore, riskReasons, error counters |
|
unas_fan_status |
read | Raw fan-control status | |
| NFS (SSH) | unas_nfs_exports |
read | Live exportfs -v table — export paths, client allow-lists, squash options |
unas_fix_permissions |
confirm | Recursive chown 977:988 under a share's .data root (the UNAS's hardcoded NFS all_squash uid/gid) |
|
| Files (SSH) | unas_list_dir |
read | ls -lah on a path under a share |
unas_disk_usage_by_dir |
read | du -h --max-depth=N on a path under a share (bounded depth, 180s timeout) |
Tiers: read = no gate. direct = additive/trivially reversible, no
gate. confirm = requires confirm="yes" (safety.requires_confirmation) —
anything else raises PermissionError and issues no remote command.
Deliberate exclusions
Two categories of tool are absent by design, not merely unimplemented — their absence is the actual protection, not a confirmation prompt sitting in front of them:
- No write file operations. Not move, delete, copy, upload, or mkdir.
The same bytes are already reachable through nine NFS client mounts with
ordinary tools (
mv,rm,cp,rsync, ...) — a second write path to the same ~75 TB of data only doubles the ways to corrupt it. Seetools/files.py's module docstring. (unas_fix_permissionsis the one narrow exception: achown, not a data-touching write, and the only survivor of an investigated-and-cut NFS-allow-list write path — seetools/nfs.py's module docstring for why that one was rejected.) - No pool or disk lifecycle tools at all — no create/destroy/format/
reshape/expand for pools, RAID groups, or disks.
safety.pycalls this theabsenttier: "a confirmation prompt is a speed bump; absence is a wall."is_forbidden_tool_name()insafety.pyis defence in depth on top of that absence (it blocks an obviously-named destructive tool from being registered by accident), but the real guarantee is the exact-match allowlist test (tests/test_health_tools.py:: test_registers_exactly_the_twenty_expected_tool_names) — any new tool, named anything, fails it until a human deliberately updates the expected set.
Environment contract
| Variable | Purpose |
|---|---|
UNAS_HOST |
Hostname/IP of the UNAS Pro 8 |
UNAS_SSH_KEY |
Path to the SSH private key used to reach the UNAS |
UNAS_API_KEY |
UniFi Drive API key (X-API-Key header) for the storage tools — see docs/drive-api-findings.md |
MCP_PORT |
Port the MCP server listens on (default 8490) |
Development
python3.12 -m venv .venv
.venv/bin/pip install -e '.[dev]'
.venv/bin/pytest -v
Docker
docker build -t unas-mcp .
The image includes an openssh-client for the SSH transport.
Deployed
Live on VM 102 (10.0.40.25:8490), registered for Claude Code:
claude mcp add unas --transport http http://10.0.40.25:8490/mcp --scope user
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。