unraid-mcp
Provides tools for managing Docker containers on an Unraid server, including listing, inspecting, viewing logs, networks, port conflicts, update status, and starting/stopping/restarting/pausing/unpausing containers.
Allows interaction with Unraid's official GraphQL API, providing tools for system info, array status and parity operations, Docker and VM management, shares, notifications, metrics, logs, and health checks.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@unraid-mcplist my docker containers and which ones have updates available"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
unraid-mcp
MCP server for Unraid's official GraphQL API (Unraid 7.3.2 / unraid-api 4.35.1).
Connects to /graphql on the standard web port — not port 31337.
Install
claude mcp add unraid -e UNRAID_HOST=tower.local -e UNRAID_API_KEY=your-key -- uvx unraid-mcpRelated MCP server: unraid-code-mode-mcp
Tool surface
One MCP tool: unraid(domain, action, **params).
Domain | Read actions | Write actions (gated) |
|
| — |
|
|
|
|
|
|
|
|
|
|
| — |
|
|
|
|
| — |
|
| — |
|
| — |
Configuration
Env var | Default | Meaning |
| — (required unless | Hostname/IP of the Unraid server |
| — (one of KEY/KEY_FILE; preferred) | Absolute path to a file holding the API key: regular file, mode 0600, owned by the effective uid |
| — (one of KEY/KEY_FILE) | API key from |
|
| Full endpoint override |
|
| Master write switch |
|
|
|
|
| Read timeout in seconds |
|
| Response cap |
|
|
|
|
| Logging level |
HTTP transport variables are read only when UNRAID_TRANSPORT=http. For every
MCP_* variable (and FASTMCP_HOST/FASTMCP_PORT) surrounding whitespace is
trimmed and "" means unset, so empty Unraid template fields are harmless.
Env var | Code default | Image default | Meaning |
|
| — | Bind address |
|
| — | Bind port (0–65535) |
| — (required) |
| Absolute path to the bearer token file (see below) |
| — | — | Refused at startup: environment values leak via |
|
| — |
|
| any source | — | Comma list of source IPs/CIDRs (IPv4-mapped IPv6 peers are matched as IPv4) |
| loopback bind: | — | Comma list of accepted |
| none (any | — | Comma list of exact |
The image sets no bind, so without MCP_HOST/MCP_PORT it binds 127.0.0.1
on port 8000.
HTTP authentication
With UNRAID_TRANSPORT=http the server fails closed: it refuses to start without
a bearer-token file, and every request passes these checks in order before
FastMCP routes it or reads its body:
# | Check | On failure |
1 | Source IP (socket peer) in | 403 |
2 | Exactly one | 403 |
3 |
| 403 |
4 |
| — |
5 |
| 401 with |
Rejections carry a JSON-RPC body ({"jsonrpc":"2.0","error":{"code":-32000,"message":"Forbidden"|"Unauthorized"},"id":null})
and log one WARNING with the peer IP; the Authorization header is never
logged. Unknown routes, POST /health, /health/ and //health all need the
token. WebSocket connections are closed with code 1008.
Token file
Generate a 256-bit token:
(umask 077; openssl rand -base64 48 | tr '+/' '-_' | tr -d '=' > http-token)MCP_AUTH_TOKEN_FILE (and UNRAID_API_KEY_FILE) must be:
an absolute path to a regular file,
mode
0600, owned by the effective uid of the server process (uid 10078 in the container image),non-empty UTF-8 text (surrounding whitespace is stripped).
The token must also be at least 43 characters from the RFC 6750 b64token
charset (A-Z a-z 0-9 . _ ~ + / -, optional trailing =). Only its SHA-256
digest is kept in memory.
Startup refusals
The server exits with an unraid-mcp: http auth: … message when:
no
MCP_AUTH_TOKEN_FILEis set (andMCP_ALLOW_UNAUTHENTICATEDis not1);MCP_AUTH_TOKENis set in the environment (even alongside a file);the token file fails any file check, is shorter than 43 characters or uses characters outside the b64token charset;
MCP_ALLOW_UNAUTHENTICATEDis anything other than0/1, or is1on a non-loopback bind;the bind is
0.0.0.0or::andMCP_ALLOWED_HOSTSis unset;MCP_PORT/FASTMCP_PORT, or an entry ofMCP_ALLOWED_CLIENTS,MCP_ALLOWED_HOSTSorMCP_ALLOWED_ORIGINS, is invalid.
At startup it logs a one-line summary (mode, bind, clients, hosts, origins) and
a warning when, for example, MCP_ALLOWED_CLIENTS is unset on a non-loopback
bind.
Example: container on monolith
The canonical dockerMan template is deploy/my-unraid-mcp.xml.
The container uses host networking, binds only 10.10.10.50:8078 and reads the
API at UNRAID_API_URL=http://127.0.0.1/graphql.
Why host networking: the old br0.500 macvlan container (a dedicated
10.10.10.x IP) could not reach its own host (macvlan host isolation), so
every upstream read hung (#360). On the host network it reaches Unraid's nginx on 127.0.0.1:80.
UNRAID_TRANSPORT=http
UNRAID_API_URL=http://127.0.0.1/graphql
MCP_HOST=10.10.10.50
MCP_PORT=8078
MCP_ALLOWED_HOSTS=10.10.10.50
MCP_ALLOWED_CLIENTS=192.168.0.240
MCP_AUTH_TOKEN_FILE=/config/http-token # image default
UNRAID_API_KEY_FILE=/config/unraid-api.key # image default; remove UNRAID_API_KEYRun flags: --network host --user 10078:10078 --read-only --cap-drop ALL --security-opt no-new-privileges, with /config mounted read-only. The appdata
directory is owned by 10078:10078 with mode 700; http-token and
unraid-api.key are owned by 10078:10078 with mode 600.
Deploy from this repo (dry run first, then for real; MONOLITH overrides the
default root@192.168.0.50):
deploy/deploy.sh --backup-suffix YYYYMMDD-tag --dry-run
deploy/deploy.sh --backup-suffix YYYYMMDD-tagIt builds the image, backs up the live template and autostart file, records
the owners/modes of the appdata directory and both secrets
(/var/lib/docker/unraid-autostart.bak-<suffix>.owners) and tags the running
image unraid-mcp:bak-<suffix>, then ships the new image, installs the
template, fixes secret ownership/modes, keeps unraid-mcp in autostart,
rebuilds the container and verifies the listener is exactly 10.10.10.50:8078
and the container runs the image ID it built. If any step from the backup on
fails it prints the backups and the restore commands (template, autostart,
secret owners/modes back to the recorded ones — the previous image ran as
root — docker tag back, rebuild).
Check it from an allowed client:
curl -s http://10.10.10.50:8078/health # 200 {"status":"ok"}
curl -s -o /dev/null -w '%{http_code}\n' -X POST http://10.10.10.50:8078/mcp # 401Live HTTP smoke (token from UNRAID_MCP_TOKEN_FILE, default
~/.config/unraid-mcp/http-token):
UNRAID_MCP_LIVE_URL=http://10.10.10.50:8078 uv run pytest tests/test_live_http.py --no-covFastMCP native auth vs this guard
FastMCP 4's own options were evaluated and are not used:
HostOriginGuardMiddlewareanswers a bad Host with a plain-text 421, always merges its default hosts and the bound address, accepts wildcards and a same-origin fallback, and skips Host checks on non-loopback binds in "auto" mode. It is pinned off withhost_origin_protection=False.Native auth (
RequireAuthMiddlewarewithStaticTokenVerifierorDebugTokenVerifier) wraps only the/mcproute, leaving custom and unknown routes open; its challenge is RFC 9728resource_metadatarather than a realm;StaticTokenVerifierkeeps plaintext tokens with a non-constant-time lookup;DebugTokenVerifieraccepts any token; nothing filters by source IP.
What is used from FastMCP: custom_route (for /health), middleware= (to put
the pure-ASGI AuthGuard in front of routing) and uvicorn_config= (to force
proxy_headers=False, so X-Forwarded-For never replaces the socket peer).
Safety
Two independent write locks prevent accidental mutations:
Server-level: writes are refused unless
UNRAID_ALLOW_WRITES=1. Default is read-only. Refusal is a structuredwrite_disablederror whosehintnames the env var.Call-level: every write action requires
confirm=trueor an MCP elicitation prompt (choicesyes/no) that the user accepts and answersyes. The match is exact and case-sensitive: accepting withno, an empty or missing answer, or any other value (YES,yes) refuses the write, as do a declined or cancelled prompt. FastMCP 4 offers elicitation only on initialize-handshake (legacy) connections; on a 2026-07-28 connection the prompt cannot be sent, so the write is refused unlessconfirm=trueis passed.
For defence in depth, use a VIEWER-role API key so the Unraid API itself rejects mutations regardless of server configuration.
docker restart is implemented as stop then start (the API has no restart
mutation).
Non-goals (v1)
Not implemented: disk add/remove, clearArrayDiskStatistics, removeContainer,
VM forceStop/reset, API-key mutations, OIDC, rclone, updateSettings, flash
backup, plugin install/remove, GraphQL subscriptions.
Each returns a structured not_implemented error naming the reason.
Errors
A failed call returns an MCP tool error (isError: true) whose text is compact
JSON:
{"code":"write_disabled","message":"Write operations are disabled.","hint":"Set UNRAID_ALLOW_WRITES=1 to enable."}code is always present (write_disabled, confirmation_required,
unknown_action, not_implemented, invalid_params, unauthorized,
not_found, introspection_disabled, upstream_error, connection_failed);
hint and details appear when set. Redaction before the error leaves the
server:
GraphQL
extensionsallowlist: of an upstream GraphQL error'sextensions, onlycode(when a string) is forwarded, asdetails.extensions.code; every other extension key is dropped.By key name (
details, any depth): a dict value is replaced by***REDACTED***when any segment of its key — split on non-alphanumerics and camelCase, singular or plural — iskey,apikey,token,secret,password,passwd,pwd,authorization,auth,cookie,session,credentialorbearer, or when the key, lowercased with non-alphanumerics removed, contains one of the unambiguous wordspassword,passwd,secret,apikey,accesskey,privatekey,sshkey,token,credential,authorization,cookie,bearer,session. SoapiKey,x-api-key,dbpassword,clientsecret,sshkeysmatch;keyword,monkeyCount,authordo not. Subtrees deeper than 20 levels are replaced wholesale.By value (the whole error text —
message,hint,details): the configured API key (when at least 8 characters long) andBearer <token>strings are masked. The same scrubbing applies to the server'sunraid <domain>.<action> failed: <code>: <message>warning log line, where control characters (newlines) are also escaped.
Not detected: other secrets inside free text, or under key names that match
neither rule (for example a database password embedded in a dsn URL).
These guarantees cover structured (UnraidError) failures only. Unexpected
(non-Unraid) exceptions keep FastMCP's generic
Error calling tool 'unraid': … text and are logged with a full traceback;
neither is scrubbed.
Development
uv sync
uv run ruff check .
uv run ruff format --check .
uv run mypy
uv run pytestLive tests against a real Unraid server (read-only):
UNRAID_LIVE=1 uv run --env-file .env pytest tests/test_live.py --no-covLive HTTP smoke against a deployed server (token from UNRAID_MCP_TOKEN_FILE,
default ~/.config/unraid-mcp/http-token):
UNRAID_MCP_LIVE_URL=http://10.10.10.50:8078 uv run pytest tests/test_live_http.py --no-covLicense
MIT
Available Tools
1 toolunraidUnraidC
Single entry point for all Unraid operations.
Use health.capabilities to discover available actions.
| Name | Required | Description | Default |
|---|---|---|---|
| action | Yes | ||
| domain | Yes | ||
| params | No | ||
| confirm | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full behavioral burden. It does not explain the 'confirm' flag's role in gating destructive actions, whether operations mutate state, permission/auth requirements, or rate limits. For a multi-purpose gateway that can perform writes, this is a significant gap.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two short sentences, front-loaded with the tool's role and followed by the discovery instruction. Efficient and free of filler, though too terse given the tool's complexity.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
This is a complex gateway tool with 0% schema coverage and no annotations, yet the description explains only the entry-point concept. It omits the domain/action/params interaction and confirm semantics; the output schema covers return values, but the call contract remains underspecified.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0% and the description adds almost no parameter meaning. It hints that 'action' comes from health.capabilities, but says nothing about 'domain', the free-form 'params' object, or the 'confirm' boolean, leaving four undocumented parameters.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states the tool is a 'single entry point for all Unraid operations', which clarifies its role as a dispatcher rather than a specific verb+resource. However, it never says what kinds of operations exist (storage, VMs, shares, etc.), so an agent still needs to call health.capabilities to understand the tool's actual scope.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives one actionable step: 'Use health.capabilities to discover available actions', which is genuine usage guidance for a discovery-first gateway. There are no sibling tools to distinguish from and no when-not-to-use guidance, so it is implied usage at best.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
1 tool update
v0.1.0- First observed
unraid
TDQS
Scored across 1 tool
With only one tool, there is no possibility of confusion between tools. The tool is a single entry point, so all operations funnel through it, but that is an intra-tool concern rather than inter-tool ambiguity.
The lone tool is named 'unraid', which is trivially consistent since there is no other name to conflict with. However, it does not follow a descriptive verb_noun convention, making it slightly less predictable than ideal.
One tool for an entire Unraid management server is far too few. The domain typically involves discrete operations for array, disk, Docker, VM, and share management, and a single catch-all tool is a poor fit for that scope.
The tool claims to be a single entry point for all Unraid operations with capability discovery, suggesting broad coverage. The opaque surface and lack of explicit operations is a minor gap, but the design intends complete access.
Maintenance
Related MCP Connectors
Monday.com MCP — wraps the Monday.com GraphQL API (BYO API key)
Governed MCP gateway: one endpoint for your tools, with credential custody and audit log.
Manage CloudPepper servers, Odoo instances, backups, and deployments over MCP.
- SupabaseOAuthcom.supabase
MCP server for interacting with the Supabase platform
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceAutomatically generates MCP tools from any GraphQL API by introspecting its schema, supporting queries, mutations, and authentication.5GPL 3.0
- AlicenseAqualityCmaintenanceA code-mode MCP server for the Unraid 7.2+ GraphQL API that exposes search and execute tools, allowing LLM agents to introspect and call any GraphQL field via sandboxed JavaScript.21MIT
- AlicenseNot gradedqualityDmaintenanceExposes the UniFi Network Integration API as MCP tools, dynamically loaded from JSON manifests, with read-only mode by default.MIT
- AlicenseBqualityAmaintenanceA Model Context Protocol server that enables AI clients to inspect and manage an Unraid server via its GraphQL API, with read-only defaults and optional enabled mutations.88MIT