
better-unraid-mcp
io.github.mswdev/better-unraid-mcp
Monitor and manage an Unraid server from any MCP client: array, Docker, VMs, ZFS, logs, and UPS.
Documentation
Better Unraid MCP
Monitor and manage your Unraid server in plain English, from any MCP client.
Features · Quick start · Tools · MCP features · Configuration · HTTP transport · Security · Development
Better Unraid MCP is a Model Context Protocol server for the Unraid GraphQL API. Connect it to Claude Desktop, Claude Code, Codex, Gemini CLI, or any other MCP client and ask things like "why is my array degraded?", "tail the syslog", or "restart the Plex container".
Features
Ask a plain question and get a real answer from your server. "Why is my array degraded?" turns into calls to array_status and disk_list, and you find out which disk is unhappy and what SMART thinks of it without opening an SSH session or clicking through WebGUI tabs.
The 67 tools cover most of what you would normally do over SSH or in the WebGUI:
- Diagnose problems in one conversation: unread alerts, CPU and memory pressure, network errors, disk temperatures, SMART health
- Read any log on the server: list them all, tail the syslog, or page through the middle of a huge file
- Manage Docker: container status and logs, per-container resource usage, start and stop, image updates, port conflict detection, boot autostart order
- Control VMs through libvirt, from a graceful shutdown to a hard reset
- Run the array: start or stop it, manage parity checks, review parity history, check the mover
- Triage notifications: read, archive, and clear the alerts you have been putting off
- Watch the UPS during an outage: battery charge, runtime estimate, load
- Go past the API when you need to (optional, via SSH): read any file on the host, such as
/boot/logs/syslog-previous, or run a confirmed one-off command - Work with ZFS: pool health, datasets, snapshots (create, destroy, rollback), and ARC usage
- Reach the host when the API can't: processes, GPU metrics, disk spin, SMART deep reports, User Scripts, VM snapshots
- Manage the platform itself: API keys, native
.plgplugin installs, mover control, reboot and shutdown - Watch things live: WebSocket-fed resources for parity progress, Docker stats, system metrics (CPU, memory, network, temperature), UPS, array state, notifications, and log following (
unraid://live/*,unraid://logs/{path}) - Reach the rest of the API with raw
graphql_queryand a confirm-gatedgraphql_mutationwhile dedicated tools catch up
The tools are deliberately paranoid. A destructive tool refuses to run unless the request includes confirm: true, so a stray sentence in a chat can't stop your array. The operations that can really hurt (stopping the array, hard-killing a VM, rebooting the host) also need acknowledge_risk: true. If your client supports MCP elicitation, you get an interactive confirmation prompt instead. Every read works with a viewer-level API key, and MCP_READ_ONLY=true takes every state-changing tool out of the listing.
You don't install anything on the Unraid server. The MCP server runs on your machine, talks to Unraid's built-in GraphQL API with one API key, and starts with a single npx command.
Quick start
1. Create an Unraid API key
On your Unraid server (7.2 or newer), go to Settings > Management Access > API Keys and create a key. Or from the Unraid terminal:
unraid-api apikey --create
[!IMPORTANT] The key controls your server, so treat it like a password and give it the least privilege you need. A viewer-level key is enough for every read-only tool.
2. Add the server to your MCP client
Claude Code
claude mcp add better-unraid \
-e UNRAID_API_URL=https://tower.local/graphql \
-e UNRAID_API_KEY=your-api-key \
-- npx -y better-unraid-mcp@latest
Claude Desktop
Add an entry to the mcpServers object in your claude_desktop_config.json:
{
"mcpServers": {
"better-unraid": {
"command": "npx",
"args": ["-y", "better-unraid-mcp@latest"],
"env": {
"UNRAID_API_URL": "https://tower.local/graphql",
"UNRAID_API_KEY": "your-api-key"
}
}
}
}
Codex
codex mcp add better-unraid \
--env UNRAID_API_URL=https://tower.local/graphql \
--env UNRAID_API_KEY=your-api-key \
-- npx -y better-unraid-mcp@latest
Or add a server table to ~/.codex/config.toml:
[mcp_servers.better-unraid]
command = "npx"
args = ["-y", "better-unraid-mcp@latest"]
env = { UNRAID_API_URL = "https://tower.local/graphql", UNRAID_API_KEY = "your-api-key" }
Gemini CLI
Add an entry to the mcpServers object in ~/.gemini/settings.json:
{
"mcpServers": {
"better-unraid": {
"command": "npx",
"args": ["-y", "better-unraid-mcp@latest"],
"env": {
"UNRAID_API_URL": "https://tower.local/graphql",
"UNRAID_API_KEY": "your-api-key"
}
}
}
}
If your server uses a self-signed TLS certificate on the LAN, also set UNRAID_ALLOW_SELF_SIGNED=true. To turn on the SSH-backed tools (reading files like /boot/logs/syslog-previous, ZFS, running one-off commands, and the rest of the tools marked SSH below), also pass the UNRAID_SSH_* variables described under Configuration. @latest keeps you on the newest release; pin a version (for example better-unraid-mcp@0.1.1) if you want fully predictable behavior.
Other ways to install
All of them run the same server, and none of them install anything on the Unraid box.
| Option | Details |
|---|---|
| Claude Desktop, one click | Download better-unraid-mcp-<version>.mcpb from the latest release and open it. Claude Desktop asks for the URL and API key. |
| Claude Code plugin marketplace | claude plugin marketplace add mswdev/better-unraid-mcp, then claude plugin install better-unraid@better-unraid-mcp. The plugin reads UNRAID_API_URL and UNRAID_API_KEY from your environment. |
| Docker (remote HTTP transport) | ghcr.io/mswdev/better-unraid-mcp. See Docker. |
| MCP Registry | Listed as io.github.mswdev/better-unraid-mcp for clients that browse the registry. |
Cautious quickstart (read-only)
Not ready to let a model touch anything? Add MCP_READ_ONLY=true to the environment above and the server registers only the read-only tools. The state-changing tools aren't refused at call time; they never appear at all. Pair that with a VIEWER-role API key and nothing in this server can change your machine.
3. Verify your setup
Before wiring up a client, run the built-in doctor with the same environment:
UNRAID_API_URL=https://tower.local/graphql UNRAID_API_KEY=... npx better-unraid-mcp doctor
It prints one line per check (GraphQL reachability and versions, schema skew against the server's API version, SSH when configured, rate limiting, and read-only mode) and exits non-zero when something is broken:
✓ Reachable in 12 ms — Unraid 7.3.2, API 4.37.4+ad268301, online=true.
✓ Vendored schema matches the server API (4.37.4 vs 4.37.4+ad268301).
⚠ SSH: not configured (optional). Host tools (...) are unavailable; set UNRAID_SSH_* to enable them.
✓ Client-side rate limiting active: 90 request burst, 9/s refill.
✓ read-only mode: disabled — mutating tools are registered (each behind its confirm gate).
doctor: OK
4. Try it
Ask your client:
"What's the status of my Unraid array?"
Tools
Read-only tools never change anything. Destructive tools always require confirm: true; without it they refuse and leave your server alone.
Speed and safety
The SSH channel is one kept-alive connection: it connects lazily, sends keepalive probes, reconnects on its own, and closes after UNRAID_SSH_IDLE_SECONDS of idle time, so commands don't pay for a new handshake each time. GraphQL requests reuse a keep-alive HTTP agent and have a hard 30-second timeout, and idempotent queries get a small jittered retry on transient failures. The busiest reads (system_metrics, array_status, docker_container_list) come from a 5-second snapshot cache, and detailed output includes data_age_ms so you can see how fresh the data is. Long-running shell_exec and docker_container_update calls send MCP progress notifications when the client asks for them (stdio or session-mode HTTP).
All tool output goes through secret redaction: your configured API key, SSH password, and bearer token, plus credential-shaped key/value pairs and JWTs, come back as [redacted]. A client-side rate limiter keeps request bursts inside the Unraid API's throttle, and oversized JSON results are truncated into an envelope that still parses with JSON.parse.
System and storage
14 tools
| Tool | Type | Description |
|---|---|---|
system_info | read-only | OS, kernel, uptime, hostname, and CPU summary. |
array_status | read-only | Array state, capacity, current parity-check status, and per-disk health. |
parity_history | read-only | Recent parity checks (date, status, errors, speed). |
disk_list | read-only | Physical disks with model, size, SMART status, temperature, and partitions. |
disk_smart_report | read-only | Full smartctl attribute report for one disk (SSH). |
disk_spin | mutation | Spins a disk up or down (SSH; confirm): array slots through emhttpd so the state stays consistent, unassigned /dev/sdX through sdspin (ATA only). |
share_list | read-only | User shares with usage; filter by name. |
mover_status | read-only | Whether the mover (cache-to-array migration) is running, plus its schedule. |
system_health | read-only | One severity-scored health rollup (OK / WARNING / CRITICAL) across array, capacity, disks and temperatures, parity, notifications, UPS, and pending container updates. Start here. |
connection_doctor | read-only | Self-test of this MCP server's plumbing: GraphQL reachability and latency, API key validity, versions, schema skew (vendored schema vs the server's API version), SSH connectivity, rate-limit config, and read-only mode. Run it first when something misbehaves; it is also available as npx better-unraid-mcp doctor. |
gpu_metrics | read-only | GPU utilization over SSH: full metrics from nvidia-smi, a one-line summary (frequency, idle share, engine load, GPU clients) parsed from intel_gpu_top, or a clear report when neither is present. |
process_list | read-only | The host's busiest processes by CPU or memory (SSH). |
mover_action | destructive | Starts or stops the mover over SSH (requires confirm: true). Stopping can leave partial files on the destination. |
system_power | destructive | Reboots or shuts down the whole server over SSH. Requires confirm: true and acknowledge_risk: true. |
Array control
Both tools require an API key with the ADMIN role. They report that the request was issued; run array_status afterward to confirm the result.
3 tools
| Tool | Type | Description |
|---|---|---|
array_action | destructive | Starts or stops the array. Stopping takes every share, container, and VM offline, so stop also requires acknowledge_risk: true. |
array_disk_action | destructive | Adds a disk to the array (checks first that the array is STOPPED), mounts or unmounts an array disk, or clears disk statistics. The Unraid API (4.37+) no longer supports removing a disk, so use the web UI for that. Requires confirm + acknowledge_risk. |
parity_check | destructive | Starts (optionally correcting), pauses, resumes, or cancels a parity check. |
Docker
9 tools
| Tool | Type | Description |
|---|---|---|
docker_container_list | read-only | Containers with state, image, and update availability; filter by name. |
docker_container_logs | read-only | Recent log lines for a container, with tail and time-based paging. |
docker_network_list | read-only | Docker networks (driver, scope, IPv6/internal/attachable). |
docker_port_conflicts | read-only | Container and LAN port conflicts. |
docker_stats | read-only | Per-container CPU, memory, network, and block IO usage, hungriest first. Uses a fresh sample from the live WebSocket subscription when one exists and otherwise falls back to docker stats over SSH (see Host shell); the response says which source it used. |
docker_container_action | destructive | Start, stop, pause, unpause, or restart a container (restart uses the API's native restart mutation, API 4.37+). |
docker_container_remove | destructive | Permanently deletes a container (irreversible) and optionally its image. Needs Unraid 7.3+. |
docker_container_update | destructive | Pulls the latest image and recreates containers, by id or all with updates. Needs Unraid 7.3+. |
docker_autostart_set | destructive | Sets which containers start on boot without losing the existing boot order. Needs Unraid 7.3+. |
Virtual machines
4 tools
| Tool | Type | Description |
|---|---|---|
vm_list | read-only | Virtual machines with run state; filter by name. |
vm_action | destructive | Start, stop, pause, resume, forceStop, reboot, or reset a VM by name or id. forceStop and reset are ungraceful and also require acknowledge_risk: true. |
vm_snapshot_list | read-only | Lists a VM's libvirt snapshots through virsh (SSH; the GraphQL API doesn't expose them). |
vm_snapshot_create | destructive | Creates an EXTERNAL disk snapshot through virsh, the same way Unraid 7 does. Requires confirm + acknowledge_risk. Revert and delete stay in the Unraid UI on purpose. |
Notifications
8 tools
| Tool | Type | Description |
|---|---|---|
notification_overview | read-only | Unread and archived counts broken down by importance. |
notification_list | read-only | Notifications by type (unread or archive) with paging; the source of truth for ids. |
notification_alerts | read-only | Deduplicated unread warnings and alerts: the "needs attention now" view. |
notification_archive | mutation | Archives or unarchives notifications (reversible, no confirmation needed). The API swallows per-id failures, so verify with notification_list. |
notification_unread | mutation | Marks one notification as unread again (reversible, no confirmation needed). |
notification_create | mutation | Creates a notification (no confirmation needed). |
notification_recalculate | mutation | Re-syncs cached overview counts from disk (no confirmation needed). |
notification_delete | destructive | Permanently deletes notifications (irreversible). |
Observability
4 tools
| Tool | Type | Description |
|---|---|---|
log_list | read-only | The server's log files with size and last-modified time. |
log_read | read-only | Tails or windows a log file (default 100 lines, max 2000), with paging hints. |
system_metrics | read-only | Point-in-time CPU load, memory pressure, per-interface network rates, and server time. Temperature data is opt-in. |
metrics_history | read-only | Recent CPU, memory, and network history from the opt-in in-memory recorder (MCP_METRICS_HISTORY=true): 30-second min/avg/max buckets for up to 24 hours, with gaps reported as gaps. When recording is off, it says how to turn it on. |
Plugins
4 tools
| Tool | Type | Description |
|---|---|---|
plugin_list | read-only | Installed API plugins and OS .plg plugins. |
plugin_add | destructive | Installs API plugins by npm package name, then restarts the Unraid API. npm install on your server runs package lifecycle scripts, so only install packages you trust. |
plugin_remove | destructive | Uninstalls API plugins by npm package name, then restarts the Unraid API. |
plugin_install_plg | destructive | Installs a native Unraid OS plugin from a .plg URL. A .plg runs arbitrary code as root, so this requires confirm + acknowledge_risk. Only install from sources you trust. |
ZFS (SSH)
ZFS ships with Unraid 6.12+. These tools say so plainly when no pools exist.
4 tools
| Tool | Type | Description |
|---|---|---|
zfs_status | read-only | Pool health and capacity, plus ARC memory usage. |
zfs_dataset_list | read-only | Datasets with used and available space and mountpoints; optional pool filter. |
zfs_snapshot_list | read-only | Snapshots with size and creation time; optional dataset filter. |
zfs_snapshot_action | destructive | Create, destroy, or roll back a snapshot. Rollback discards everything after the snapshot, so this requires confirm + acknowledge_risk. |
Host configuration (SSH)
The GraphQL API has nothing for services, share changes, or unassigned devices, so these tools use the same mechanisms as the web UI: the /etc/rc.d/rc.* scripts, emhttpd's form handler through emcmd, and the Unassigned Devices plugin. Every write is checked by reading the result back.
7 tools
| Tool | Type | Description |
|---|---|---|
service_list | read-only | Running, stopped, or not-installed state of samba, nfs, sshd, docker, libvirt, and tailscale. |
service_action | destructive | restart, start, or stop one of those services, then check its status. stop needs allow_stop: true, and stopping docker or libvirt is refused (use array_action). Requires confirm + acknowledge_risk. |
unassigned_list | read-only | Disks outside the array, pools, and flash, with partitions, filesystems, and mount points. |
unassigned_action | destructive | Mounts or unmounts an unassigned partition through the Unassigned Devices plugin (rc.unassigned) and checks the result with lsblk. Refuses when the plugin isn't installed. Requires confirm + acknowledge_risk. |
share_create | destructive | Creates a user share through emhttpd with a validated subset of settings (comment, allocator, cache mode and pool, SMB export and security), then checks the written .cfg. Requires confirm + acknowledge_risk. |
share_edit | destructive | Changes that same subset of settings on an existing share, leaves the other settings alone, and checks the result by reading it back. Requires confirm + acknowledge_risk. |
share_delete | destructive | Deletes an EMPTY share's configuration. It never deletes files and refuses while the share holds data. Requires confirm + acknowledge_risk. |
Backup (SSH)
1 tool
| Tool | Type | Description |
|---|---|---|
flash_backup | destructive | Archives the flash (USB boot) device into a user share as /mnt/user/<share>/flash-backups/flash-backup-<scope>-<timestamp>.tar.gz: /boot/config by default, or the whole flash with full: true. Each archive is checked with tar -tzf, and keep: N prunes older archives and lists what it removed. The archive contains your license key, API keys, and passwords, so it stays on your array. Requires confirm + acknowledge_risk. |
User Scripts (SSH)
2 tools
| Tool | Type | Description |
|---|---|---|
user_script_list | read-only | Lists scripts managed by the User Scripts plugin. |
user_script_run | destructive | Runs one user script with the plugin's noexec-safe runner. A user script is arbitrary root code, so this requires confirm + acknowledge_risk. |
API keys
These tools hand the model the credentials that control access to your server, so use them with the same care as the keys themselves.
2 tools
| Tool | Type | Description |
|---|---|---|
apikey_list | read-only | Lists configured API keys (name, roles, permissions, created). Key values are never selected or returned. |
apikey_manage | destructive | Creates, updates, adds or removes roles on, or deletes API keys. Requires confirm + acknowledge_risk. The key value is shown exactly once, at creation. Deleting the key this MCP server uses locks it out. |
UPS
1 tool
| Tool | Type | Description |
|---|---|---|
ups_status | read-only | Live UPS telemetry from apcupsd: status (ONLINE, ONBATT, LOWBATT, ...), battery charge and runtime, and power load. When apcaccess has nothing to report, it says "no live UPS data" instead of erroring or inventing a healthy reading. That includes NUT-managed servers, which the Unraid API can't see. |
Host shell (optional, needs SSH)
The GraphQL API can't reach everything: old boot logs under /boot/logs, /proc, one-off commands. These tools fill that gap over SSH. They are off by default and turn on only when you set the UNRAID_SSH_* variables (see Configuration); without them they refuse and explain how to set it up. docker_stats also falls back to this channel when no fresh live sample is available.
2 tools
| Tool | Type | Description |
|---|---|---|
file_read | read-only | Tails any absolute file path on the host (default 200 lines, max 2000). An optional pattern greps on the server first, so searching huge logs stays cheap. |
shell_exec | destructive | Runs an arbitrary command as the SSH user (typically root). Every call requires confirm: true. Output is capped, and timeouts are enforced (default 30 s, max 120 s). |
Raw GraphQL (advanced)
These cover the parts of the Unraid API that no dedicated tool wraps yet, such as users and registration. Use a dedicated tool when one exists, because the dedicated tools handle server quirks that these passthroughs don't. The full schema ships with the package at schema/unraid.graphql.
2 tools
| Tool | Type | Description |
|---|---|---|
graphql_query | read-only | Runs an arbitrary GraphQL query and returns the raw JSON. Query operations only; output is capped. Validated against the vendored schema before sending, with did-you-mean hints; dry_run: true validates without sending. |
graphql_mutation | destructive | Runs an arbitrary GraphQL mutation. Requires confirm: true on every call, plus acknowledge_risk: true when it selects a known-dangerous field (setState, forceStop, reset, configureUps). Verify results with a follow-up read. Validated against the vendored schema before the confirm gate; dry_run: true validates without sending and needs no confirm. |
[!NOTE] Tool behavior is checked against the vendored Unraid API 4.37.4 schema and covered by unit tests at 100% coverage. The read-only tools have been run against a live Unraid 7.3 server, but the write paths haven't been widely exercised on live servers yet. Treat destructive tools with care, and please open an issue if anything misbehaves.
MCP features beyond tools
The server is built on the MCP TypeScript SDK v2 (protocol revision 2026-07-28). Clients on older revisions are negotiated down automatically.
| Feature | Details |
|---|---|
| Interactive confirmation (elicitation) | When your client supports MCP elicitation (over stdio or session-mode HTTP), gated tools show a real confirmation prompt instead of refusing; the riskiest actions show the warning with two checkboxes. The confirm and acknowledge_risk arguments still work everywhere and are the only option on stateless HTTP. Declining the prompt changes nothing on the server. |
| Resources | unraid://schema (the vendored GraphQL SDL this package was built against), unraid://health (the system-health rollup as JSON), unraid://doctor (the connection self-test as JSON), and unraid://live/history (the last hour of recorded CPU, memory, and network history when MCP_METRICS_HISTORY=true, or a hint on how to turn it on). |
| Live resources | Subscribe to unraid://live/parity, unraid://live/docker-stats, unraid://live/metrics, unraid://live/ups, unraid://live/array, and unraid://live/notifications for updates pushed over the Unraid API's WebSocket, or to unraid://logs/{path} to follow a log file. |
| Prompts | Four guided workflows: triage-array-problem, find-resource-hog, safe-container-update (takes an optional container argument), and health-report. |
| Structured output | system_health, system_metrics, array_status, and docker_container_list declare an outputSchema and return structuredContent alongside the human-readable text in both response formats. |
| Complete annotations | Every tool declares readOnlyHint, destructiveHint, idempotentHint, and openWorldHint, and a registry test enforces it. |
Configuration
| Variable | Required | Default | Description |
|---|---|---|---|
UNRAID_API_URL | yes | Full Unraid GraphQL endpoint, including /graphql. | |
UNRAID_API_KEY | yes | API key from Quick start step 1. | |
MCP_TRANSPORT | no | stdio | stdio (local clients) or http (remote clients). |
MCP_HTTP_PORT | no | 3000 | Port for the http transport. |
MCP_HTTP_HOST | no | 127.0.0.1 | Bind address for the http transport. |
MCP_HTTP_ALLOWED_HOSTS | no | Comma-separated Host allow-list; turns on DNS-rebinding protection. | |
UNRAID_ALLOW_SELF_SIGNED | no | false | Set true only for a self-signed TLS certificate on the LAN. |
MCP_READ_ONLY | no | false | Set true to register only the read-only tools. |
MCP_METRICS_HISTORY | no | false | Set true to record CPU, memory, and network history in memory (30-second min/avg/max buckets, 24 hours, cleared on restart) for metrics_history and unraid://live/history. Keeps one WebSocket open to the server. |
MCP_HTTP_BEARER_TOKEN | http | Required for the http transport: clients must send Authorization: Bearer <token>. | |
MCP_HTTP_ALLOW_UNAUTHENTICATED | no | false | Explicit opt-in to run the http transport with no auth (trusted networks only). |
LOG_LEVEL | no | info | fatal, error, warn, info, debug, trace, or silent. |
UNRAID_SSH_HOST | no | Turns on every tool marked SSH above, plus the SSH fallback for docker_stats. Requires a password or key path. | |
UNRAID_SSH_PORT | no | 22 | SSH port. |
UNRAID_SSH_USER | no | root | SSH user. Unraid administration is normally done as root. |
UNRAID_SSH_PASSWORD | no | SSH password. Prefer UNRAID_SSH_KEY_PATH where you can. | |
UNRAID_SSH_KEY_PATH | no | Path to an SSH private key file (readable by the MCP server process). | |
UNRAID_SSH_IDLE_SECONDS | no | 90 | Idle seconds before the persistent SSH connection closes (it reconnects when next needed). |
MCP_HTTP_SESSIONS | no | false | Set true for stateful HTTP sessions (SSE responses), which carry server-initiated messages: progress notifications, elicitation prompts, and live-resource updates. |
See .env.example for a copy-paste template.
Remote HTTP transport
For remote or hosted clients, run over Streamable HTTP instead of stdio:
MCP_TRANSPORT=http \
MCP_HTTP_BEARER_TOKEN=some-long-random-token \
UNRAID_API_URL=https://tower.local/graphql \
UNRAID_API_KEY=your-api-key \
npx -y better-unraid-mcp@latest
The MCP endpoint is then POST http://host:3000/mcp, and every request must carry the token:
curl -X POST http://host:3000/mcp \
-H "Authorization: Bearer some-long-random-token" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
By default the endpoint is stateless: each POST gets a complete JSON response, and server-initiated messages (progress notifications, elicitation prompts, live-resource updates) are unavailable. Set MCP_HTTP_SESSIONS=true for stateful streamable-HTTP sessions. The client initializes once, sends the returned mcp-session-id header on later requests, receives SSE responses that can carry server-initiated messages, and can end the session with a DELETE. Idle sessions expire after 5 minutes.
The server refuses to start in HTTP mode without MCP_HTTP_BEARER_TOKEN. To run an open endpoint on a trusted network on purpose, set MCP_HTTP_ALLOW_UNAUTHENTICATED=true, and the server logs a warning at startup. Requests without a matching token get a 401, and the token comparison is constant-time.
[!WARNING] Every request uses your privileged Unraid API key upstream. The server binds to localhost by default. To expose it further (including to cloud clients such as ChatGPT connectors), put TLS in front with a reverse proxy and set
MCP_HTTP_ALLOWED_HOSTSfor DNS-rebinding protection. Never expose the endpoint unauthenticated to an untrusted network.
Docker (HTTP transport)
The image runs only the HTTP transport (MCP_TRANSPORT=http, bound to 0.0.0.0:3000 inside the container) and, like the CLI, refuses to start without MCP_HTTP_BEARER_TOKEN:
services:
better-unraid-mcp:
image: ghcr.io/mswdev/better-unraid-mcp:latest
ports: ["127.0.0.1:3000:3000"]
environment:
UNRAID_API_URL: https://tower.local/graphql
UNRAID_API_KEY: ${UNRAID_API_KEY}
MCP_HTTP_BEARER_TOKEN: ${MCP_HTTP_BEARER_TOKEN}
MCP_HTTP_ALLOWED_HOSTS: localhost,mcp.example.com # DNS-rebinding protection
# UNRAID_SSH_HOST / UNRAID_SSH_PASSWORD for host tools; MCP_HTTP_SESSIONS=true for subscriptions/elicitation
restart: unless-stopped
Point your client at http://<host>:3000/mcp with Authorization: Bearer <token>, and put TLS in front before it leaves the LAN. Every release publishes multi-arch (amd64/arm64) images.
Security
The Unraid API key controls your server. Give it the least privilege you need, keep it only in environment variables or your client's secret store, and never commit it to version control.
Development
git clone https://github.com/mswdev/better-unraid-mcp.git
cd better-unraid-mcp
npm install
npm run build # or: npm run dev
Run the quality gate before every commit (npm run test:coverage also enforces the coverage threshold that CI checks):
npm run typecheck && npm run build && npm test && npm run lint
To contribute, branch off develop, keep one logical change per commit (conventional commits), and open a draft PR into develop. Never hand-edit the vendored schema (schema/unraid.graphql) or the generated types (src/types/unraid/**); refresh them with npm run schema:update and npm run generate.
License
better-unraid-mcpnpm install better-unraid-mcp