AI Agent Gateway for Unraid. Permission-enforcing REST API and MCP server that allows AI agents to manage your server.
UnraidClaw sits between AI agents and your Unraid servers. It provides a REST API, an optional MCP endpoint (off by default) and an unraidclaw command-line client, all using the same tools and the same fine-grained permissions. It combines Unraid's GraphQL API with direct system integration (CLI commands for parity checks, reboot/shutdown, and syslog; filesystem operations for share config editing and notification management; network introspection via ip) to expose capabilities that no single Unraid API covers. Protected API calls require authentication and the relevant permission. Activity logging excludes the public health probe and successful MCP handshakes.
- 55 tools across 13 categories: Health, Docker, Community Applications, Plugins, VMs, Array, Disks, Shares, System, Notifications, Network, Users, Logs
- 30 permission keys in a resource:action matrix, configurable from the WebGUI
- HTTPS with auto-generated self-signed TLS certificate
- SHA-256 API key authentication
- Activity logging with JSONL format, filter, and search
- OpenClaw plugin available on ClawHub and npm (
openclaw plugins install clawhub:unraidclaw --accept-capabilities) - Optional MCP at
/mcpfor Streamable HTTP clients, off by default, using the same API key and permissions as the REST API - Command-line client built into the plugin and available for other machines from npm (
npm install -g unraidclaw-cli) or as a release archive. See CLI - Single-file server, no
node_modulesneeded on Unraid
- Unraid 7.0.0+ (Node.js 22 is built-in)
Search for UnraidClaw in the Unraid CA store and click Install.
# Download and install the plugin
plugin install https://raw.githubusercontent.com/emaspa/unraidclaw/main/packages/unraid-plugin/unraidclaw.plg- Go to Settings > Management Access in the Unraid WebGUI, scroll to the API section, and copy your Unraid API key (must have ADMIN role)
- Go to Settings > UnraidClaw, paste the Unraid API key into the Unraid API Key field
- Generate an UnraidClaw API key (it's hashed with SHA-256; save it, it won't be shown again)
- Configure permissions on the Permissions tab
- Set Service to Enabled and click Apply
The server starts on port 9876 over HTTPS by default. A self-signed TLS certificate is generated on first start; see TLS certificate for what it contains and how clients trust it.
Settings > UnraidClaw > Settings holds the service configuration. Apply writes /boot/config/plugins/unraidclaw/unraidclaw.cfg and restarts the service, or stops it when the service is disabled.
| Row | What it does |
|---|---|
| Enable Service | Starts the gateway (SERVICE="enable") |
| Enable MCP | Serves MCP at /mcp on the same port (MCP_ENABLED="yes"). Off by default. See MCP |
| Listen Port | Port for the REST API and MCP, 9876 by default |
| Listen Host | Bind address, 0.0.0.0 by default. An explicit address is also added to the MCP Origin allowlist |
| Unraid WebUI Port | Port of the Unraid WebGUI, used to build the GraphQL URL |
| Unraid API Key | The Unraid API key with the ADMIN role. Leave blank to keep the stored key |
| Max Log Size (bytes) | Activity log rotation threshold, 10485760 bytes (10 MiB) by default |
The API Key Management section generates the UnraidClaw API key. The TLS Certificate section shows the current certificate's subject, subject alternative names, expiry date and SHA-256 fingerprint, warns when the certificate has no subjectAltName, and has a Regenerate certificate button. TLS certificate says what regenerating does and what clients must do afterwards.
The unraidclaw-cli package provides the unraidclaw command, which manages the gateway from a terminal. It uses the same tool definitions and gateway permissions as OpenClaw and MCP, and runs on Unraid and on Linux, macOS and Windows machines with Node.js 22 or newer.
On Unraid there is nothing to install. The plugin puts unraidclaw on the PATH at /usr/local/bin/unraidclaw.
From npm on another machine:
npm install -g unraidclaw-cli
unraidclaw --versionUpdate it with npm update -g unraidclaw-cli.
From the release archive, for machines without npm: download unraidclaw-cli-<version>.tar.gz and its .sha256 file from the latest release.
sha256sum -c unraidclaw-cli-<version>.tar.gz.sha256 # macOS: shasum -a 256 -c
tar -xzf unraidclaw-cli-<version>.tar.gz
sudo ln -s "$PWD/unraidclaw-cli-<version>/unraidclaw" /usr/local/bin/unraidclawOn Windows, add the extracted folder to PATH and run unraidclaw.cmd. The CLI guide also covers building from source.
unraidclaw config set-key
unraidclaw docker list
unraidclaw array status --output jsonOn Unraid, it discovers the local gateway and certificate. On another machine, set the gateway URL and trust its certificate first, comparing the fingerprint with the WebGUI before accepting:
unraidclaw config set url "https://<server>:9876"
unraidclaw trust
unraidclaw config set-keyMutating commands require confirmation or --yes, except supported dry runs with dryRun set to true. See the CLI guide for credentials, certificate trust, all commands and exit codes.
REST API routes return a consistent envelope (MCP uses JSON-RPC):
{
"ok": true,
"data": { ... }
}REST authentication uses the x-api-key: <api-key> header. /api/health is public and has no permission requirement. Unknown paths return 404 without checking the key, and CORS preflight requests return 204.
| Category | Method | Endpoint | Permission |
|---|---|---|---|
| Health | GET | /api/health |
none |
| Docker | GET | /api/docker/containers |
docker:read |
| GET | /api/docker/containers/:id |
docker:read |
|
| GET | /api/docker/containers/:id/logs |
docker:read |
|
| POST | /api/docker/containers |
docker:create |
|
| POST | /api/docker/containers/:id/:action |
docker:update |
|
| DELETE | /api/docker/containers/:id |
docker:delete |
|
| Community Apps | GET | /api/ca/search?q= |
ca:read |
| GET | /api/ca/app/:name |
ca:read |
|
| POST | /api/ca/app/:name/install |
ca:create |
|
| POST | /api/ca/app/:name/update |
ca:update |
|
| POST | /api/ca/app/:name/remove |
ca:delete |
|
| Plugins | GET | /api/plugins |
plugins:read |
| GET | /api/plugins/:file |
plugins:read |
|
| POST | /api/plugins/install |
plugins:create |
|
| POST | /api/plugins/:file/check |
plugins:update |
|
| POST | /api/plugins/:file/update |
plugins:update |
|
| POST | /api/plugins/:file/remove |
plugins:delete |
|
| VMs | GET | /api/vms |
vms:read |
| GET | /api/vms/:id |
vms:read |
|
| POST | /api/vms/:id/:action |
vms:update |
|
| DELETE | /api/vms/:id |
vms:delete |
|
| Array | GET | /api/array/status |
array:read |
| GET | /api/array/parity/status |
array:read |
|
| POST | /api/array/start |
array:update |
|
| POST | /api/array/stop |
array:update |
|
| POST | /api/array/parity/start |
array:update |
|
| POST | /api/array/parity/pause |
array:update |
|
| POST | /api/array/parity/resume |
array:update |
|
| POST | /api/array/parity/cancel |
array:update |
|
| Disks | GET | /api/disks |
disk:read |
| GET | /api/disks/:id |
disk:read |
|
| Shares | GET | /api/shares |
share:read |
| GET | /api/shares/:name |
share:read |
|
| PATCH | /api/shares/:name |
share:update |
|
| System | GET | /api/system/info |
info:read |
| GET | /api/system/metrics |
info:read |
|
| GET | /api/system/services |
services:read |
|
| POST | /api/system/reboot |
os:update |
|
| POST | /api/system/shutdown |
os:update |
|
| Notifications | GET | /api/notifications |
notification:read |
| GET | /api/notifications/overview |
notification:read |
|
| POST | /api/notifications |
notification:create |
|
| POST | /api/notifications/:id/archive |
notification:update |
|
| DELETE | /api/notifications/:id |
notification:delete |
|
| Network | GET | /api/network |
network:read |
| Users | GET | /api/users/me |
me:read |
| Logs | GET | /api/logs/syslog |
logs:read |
POST /api/docker/containers accepts:
{
"image": "vikunja/vikunja:latest",
"name": "vikunja",
"ports": ["3456:3456"],
"volumes": ["/mnt/cache/appdata/vikunja:/app/vikunja/files"],
"env": ["VIKUNJA_SERVICE_TIMEZONE=Europe/London"],
"restart": "unless-stopped",
"network": "bridge",
"icon": "https://example.com/icon.png",
"webui": "http://[IP]:[PORT:3456]/"
}Only image is required. The container is started immediately and an Unraid dockerMan XML template is created so it appears in the Docker tab.
The CA endpoints search the public catalog, inspect templates, and install apps. Update and removal use the installed container's saved template, not current catalog defaults.
GET /api/ca/search?q=plex matches every query word against names, images, maintainers and descriptions. Add includePlugins=true or includeDeprecated=true to include entries hidden by default.
GET /api/ca/app/:name returns template details, configurable ports, volumes and environment variables, required fields without defaults, and installation blockers. If several templates share a name, the API returns 409 with the candidates. Use ?repo=linuxserver to select a repository.
POST /api/ca/app/Jellyfin/install accepts template overrides and an optional container name:
{
"repo": "linuxserver",
"name": "jellyfin",
"overrides": {
"/config": "/mnt/user/appdata/jellyfin",
"/data/tvshows": "/mnt/user/media/tv",
"/data/movies": "/mnt/user/media/movies",
"PUID": "99"
},
"dryRun": true
}Override keys are the field's display name or container-side target. Unknown keys and missing required values return 400. Review the dry-run template and command preview, then set dryRun to false to install. The app appears on Unraid's Docker tab with its template, icon and WebUI link. The command preview omits some values Unraid adds, including host settings and template labels.
Installation returns 422 for unsupported templates: Extra Parameters, Post Arguments, privileged mode, device passthrough, custom networks, Additional Networks, Tailscale, pinned MAC addresses, legacy configuration, unknown field types, or an incompatible Unraid version. Deprecated, blacklisted and .plg entries are also refused. Use the WebGUI for these cases.
Unraid 7.4 templates can set a memory limit and a list of Additional Networks. On 7.4 the limit is written into the saved template and shown in the command preview, so the container is created with it and later updates keep it. Unraid 7.0 through 7.3 do not read the new <Memory> template field, so a template that sets it returns 422 there instead of installing an app whose limit would never apply. The same happens when the server's version cannot be read. An unreadable limit, a nonzero limit below Docker's 6 MB minimum, or a limit above UnraidClaw's exact numeric range returns 422 before anything is written or run. Additional Networks are refused rather than dropped, because Unraid attaches them with a second docker network connect step that UnraidClaw does not run.
Host paths are used as written. Supply overrides if you relocated appdata; UnraidClaw does not apply CA's path-rewriting rules. A missing /mnt pool or share root returns 400 rather than creating a directory on Unraid's RAM filesystem.
An existing container or my-<name>.xml template returns 409 and is not overwritten. A failed install keeps its template so you can inspect it and finish from the Docker tab.
These endpoints take the installed container name from the Docker tab, which may differ from the catalog name:
POST /api/ca/app/:name/updatePOST /api/ca/app/:name/remove
Both accept {"dryRun": true}. A preview reads the installed configuration but does not pull images or change containers. The container must have a matching saved template and Unraid's net.unraid.docker.managed=dockerman label. Concurrent actions against the same container return 409.
Update pulls the current image tag and preserves saved ports, paths, variables and network mode, plus the container's restart policy, pids limit and attached Docker volumes. Running apps return to running; stopped apps remain stopped. If the image has not changed, no replacement is created.
The replacement is created before stopping the old container. UnraidClaw swaps their names, starts the replacement when needed, verifies its state, then removes the old container without deleting its image or volumes. A failed replacement triggers a rollback. If rollback fails, the error identifies the original container for recovery. Container rollback cannot reverse changes an updated app makes to its data.
Updates refuse paused or unstable containers and configurations they cannot reproduce, including unsupported template fields, device access, custom runtime settings and resource limits. Update previews and errors redact values marked Mask="true" in the saved template.
Remove deletes only the container. Appdata, Docker volumes, the image and the saved template remain. Use Add Container on the Docker tab to recreate it from the saved configuration.
The separate Plugins endpoints manage Unraid .plg plugins without CA. List and inspect require plugins:read; install requires plugins:create; check and update require plugins:update; removal requires plugins:delete. All four permissions default to off.
Install takes an explicit public HTTPS URL ending in .plg. Downloads have size and time limits; private addresses, unsafe URLs and redirects to them are refused. The plugin manager runs the downloaded installer as root, so use sources you trust.
Check and update are separate operations. POST /api/plugins/:file/check downloads the published definition and stages it in /tmp/plugins. POST /api/plugins/:file/update applies that staged version. A check changes the staged files even though it does not install anything. Older, mismatched and unregistered one-shot definitions are refused as updates.
POST /api/plugins/:file/remove runs the plugin's own uninstall scripts. Those scripts may delete configuration or data; unlike CA container removal, data preservation is not guaranteed.
Every mutating Plugins endpoint accepts {"dryRun": true} to return a plan without downloading, writing or executing scripts. Plugin names accept the .plg suffix or omit it. OS plugins are protected. UnraidClaw can list, inspect and check itself, but self-install, self-update and self-removal require the Unraid WebGUI or Unraid's plugin command because they stop the API server. The unraidclaw CLI uses this API and has the same restriction.
POST /api/docker/containers/:id/:action where action is one of: start, stop, restart, pause, unpause
POST /api/vms/:id/:action where action is one of: start, stop, force-stop, pause, resume, reboot, reset
PATCH /api/shares/:name accepts:
{
"comment": "My share description",
"allocator": "highwater",
"splitLevel": "1",
"floor": "0"
}MCP is off by default. In Settings > UnraidClaw > Settings, set Enable MCP to Yes and click Apply. This writes MCP_ENABLED="yes" to /boot/config/plugins/unraidclaw/unraidclaw.cfg and restarts the service. The endpoint is https://<server>:9876/mcp, on the same port as the REST API. While MCP is off, /mcp returns HTTP 404 for every method, with or without a key.
Authenticate with the UnraidClaw API key using x-api-key: <key> or Authorization: Bearer <key>; x-api-key takes precedence if both are sent. Query parameters cannot supply the key. Failed attempts count toward the same per-IP limit as the REST API.
The certificate is self-signed, so copy it to the machine running the client first. Clients that verify it need a copy that matches the server; see TLS certificate for when it changes.
mkdir -p ~/.config/unraidclaw
scp "root@<server>:/boot/config/plugins/unraidclaw/tls/cert.pem" ~/.config/unraidclaw/cert.pemClaude Code (MCP docs) is a Node application, so NODE_EXTRA_CA_CERTS makes it trust the certificate. Set it in the shell that starts claude, for example in your shell profile:
export NODE_EXTRA_CA_CERTS=~/.config/unraidclaw/cert.pem
claude mcp add --transport http unraidclaw "https://<server>:9876/mcp" --header "x-api-key: <key>"Add --scope user to make the server available in every project. Run /mcp inside Claude Code to check the connection.
Codex can add a Streamable HTTP server with codex mcp add --url, but its MCP client cannot be told to trust a self-signed certificate, so that connection fails. Connect through mcp-remote instead, which runs locally as a stdio server and forwards to the gateway using Node:
codex mcp add unraidclaw \
--env NODE_EXTRA_CA_CERTS=$HOME/.config/unraidclaw/cert.pem \
-- npx -y mcp-remote@latest "https://<server>:9876/mcp" \
--transport http-only --header "x-api-key:<key>"This writes the key into ~/.codex/config.toml in plain text. mcp-remote looks for OAuth metadata before connecting; the gateway answers those requests with 404, and it then connects with the key.
Other clients that accept an HTTP MCP server entry:
{
"mcpServers": {
"unraidclaw": {
"type": "http",
"url": "https://<server>:9876/mcp",
"headers": {
"x-api-key": "<key>"
}
}
}
}A client that cannot trust the certificate can use the same mcp-remote command as its stdio server.
Supported protocol versions are 2025-11-25, 2025-06-18 and 2025-03-26. Initialization echoes a supported version or returns the latest for an unknown version. Send Content-Type: application/json, Accept: application/json, text/event-stream and the negotiated MCP-Protocol-Version header. An absent version header defaults to 2025-03-26; an unsupported value returns HTTP 400 with JSON-RPC error -32600 listing the supported versions.
The transport uses stateless JSON responses without SSE or sessions. It supports initialize, ping, tools/list and tools/call; notifications receive HTTP 202 with an empty body. Batches are rejected with -32600 for every version, including 2025-03-26.
| Method | Endpoint | Availability and permissions |
|---|---|---|
| POST | /mcp |
Enabled only with MCP; API key required; each tool uses its existing REST permission |
| GET, DELETE | /mcp |
HTTP 405 when enabled, after Origin and API key checks |
| OPTIONS | /mcp |
HTTP 204 when enabled and Origin is allowed; no key required |
| Any | /mcp |
HTTP 404 when disabled |
The endpoint exposes the same 55 tools as the OpenClaw plugin, without OpenClaw's server argument. Read-only tools carry readOnlyHint; every other tool carries destructiveHint. Each call runs through the gateway's own /api/ route in process, so the permission matrix, body validation, dry-run rules and blockers apply unchanged. OpenClaw keeps using /api/* whether MCP is on or off.
A tool call writes one entry for /mcp with the tool name in tool, and takes resource, action and statusCode from the /api/ route the tool ran, so a call refused by the permission matrix shows 403 even though the MCP response itself is HTTP 200. The route also gets its own entry, with its full path, except for /api/health. A tool call rejected before it reached a route, for example over invalid arguments, keeps resource mcp and action tools/call. Successful initialize, notifications, ping and tools/list requests are omitted from the activity log. Their failures are logged with the method as the action; notification methods use action notification. GET and DELETE responses with status 405 are also omitted. A request refused before its method was read, such as one with a missing key or malformed JSON, uses action rejected. The Activity Log tab and the Recent Activity card on the Dashboard show the tool name, or for other /mcp entries the action, after the path.
If present, Origin must exactly match the gateway's configured scheme and port with a loopback address, a local interface IP or the explicit Listen Host; other origins receive HTTP 403. The allowlist is built at startup without trusting incoming Host or forwarded headers, so a browser frontend or reverse proxy using a different public origin is rejected. Requests without an Origin header, which non-browser clients normally send, are accepted.
The service generates a self-signed certificate on first start and stores it as cert.pem and key.pem in /boot/config/plugins/unraidclaw/tls. The key is EC prime256v1 and the certificate is valid for ten years. Its subjectAltName names the server's host name, <host>.local, localhost, 127.0.0.1, ::1 and the host's stable global IPv4 and IPv6 addresses. Temporary and deprecated IPv6 privacy addresses are left out because they rotate on their own.
A certificate created by an earlier version has no subjectAltName. It is replaced once, on the first service start after the upgrade, and the old pair is kept as cert.pem.bak and key.pem.bak in the same directory. A certificate that already has a subjectAltName is never replaced automatically, including when the server's addresses change. If OpenSSL is missing or generation fails, the service starts with the existing files. On a fresh install with an OpenSSL that cannot add extensions it falls back to a certificate without subjectAltName, and without OpenSSL at all it serves plain HTTP.
To replace a certificate whose names or addresses are out of date, open Settings > UnraidClaw > Settings and use the TLS Certificate section. It shows the certificate's subject, subject alternative names, expiry date and SHA-256 fingerprint, and warns when subjectAltName is missing. Regenerate certificate asks for confirmation, then moves the current pair to cert.pem.bak and key.pem.bak (an existing backup moves to the first free numbered suffix, such as cert.pem.bak.1), restarts the service so it generates a new pair, and refreshes the displayed details. If the files cannot be moved, restoration is attempted and the service is not restarted. A failed restoration reports that manual recovery is needed. If the restart fails or produces no usable certificate, the previous pair stays available as .bak files. Only one regeneration runs at a time. The WebGUI reads the certificate's public details with OpenSSL and never opens the private key.
The CLI displays the same SHA-256 fingerprint during unraidclaw trust. Compare it with the WebGUI before accepting the certificate.
What this means for clients:
- A client that trusts the certificate can verify the server's host name or IP address against the
subjectAltName. Node-based clients such as Claude Code can trust it withNODE_EXTRA_CA_CERTSpointing to a local copy ofcert.pem. After the one-time replacement or a regeneration, the client must replace its copy and restart, because the new certificate has a different key and fingerprint. - The OpenClaw plugin accepts the self-signed certificate with
tlsSkipVerify: true. That disables verification entirely, so it is unaffected by regeneration but also does not check which server it is talking to. - A certificate created before this change has no alternative names until it is replaced, so strict clients cannot verify it even when they trust it. The Settings tab shows a warning in that case.
The OpenClaw plugin exposes all 55 tools to any AI agent that supports the OpenClaw protocol.
openclaw plugins install clawhub:unraidclaw --accept-capabilitiesThe same package is on npm. OpenClaw asks you to confirm installs from outside ClawHub, so installing from npm needs --force:
openclaw plugins install unraidclaw --force --accept-capabilitiesopenclaw plugins update unraidclaw --accept-capabilitiesThen restart the gateway with openclaw gateway restart so it loads the new version.
Edit ~/.openclaw/openclaw.json:
Single server:
{
"plugins": {
"allow": ["unraidclaw"],
"entries": {
"unraidclaw": {
"config": {
"serverUrl": "https://YOUR_UNRAID_IP:9876",
"apiKey": "YOUR_API_KEY",
"tlsSkipVerify": true
}
}
}
}
}Multiple servers:
{
"plugins": {
"allow": ["unraidclaw"],
"entries": {
"unraidclaw": {
"config": {
"servers": [
{
"name": "home",
"serverUrl": "https://<home-server>:9876",
"apiKey": "<api-key>",
"tlsSkipVerify": true,
"default": true
},
{
"name": "work",
"serverUrl": "https://<work-server>:9876",
"apiKey": "<api-key>"
}
]
}
}
}
}
}With multi-server, every tool accepts an optional server parameter (e.g. unraid_docker_list(server: "work")). If omitted, the first server marked default is used, or the first configured server if none is marked.
Set tlsSkipVerify: true to accept the gateway's self-signed certificate. The plugin does not verify the certificate in that mode; see TLS certificate.
Keeping the API key out of the config file: you don't have to hard-code the key in openclaw.json. OpenClaw expands ${VAR} references from the environment at config-load time, so you can point apiKey at an environment variable:
"apiKey": "${UNRAID_API_KEY}"The secret then lives in your environment (shell, systemd EnvironmentFile, or container secret) and never in openclaw.json. This works for servers[].apiKey in the multi-server form too.
Provider-backed secrets (SecretRef): apiKey also accepts an OpenClaw SecretRef object, so the key can come from one of your configured secret providers (file, env, exec). OpenClaw resolves it before the plugin loads, so the plugin only ever sees the resolved string:
"apiKey": { "source": "file", "provider": "default", "id": "/unraidclaw_key" }source is one of file, env, or exec; provider names a provider from your secrets.providers config; id is the lookup key. This also works per-server on servers[].apiKey.
| Category | Tools |
|---|---|
| Health | unraid_health_check |
| Docker | unraid_docker_list, unraid_docker_inspect, unraid_docker_logs, unraid_docker_create, unraid_docker_start, unraid_docker_stop, unraid_docker_restart, unraid_docker_pause, unraid_docker_unpause, unraid_docker_remove |
| Community Apps | unraid_ca_search, unraid_ca_app, unraid_ca_install, unraid_ca_update, unraid_ca_remove |
| Plugins | unraid_plugins_list, unraid_plugin_info, unraid_plugin_install, unraid_plugin_check_updates, unraid_plugin_update, unraid_plugin_remove |
| VMs | unraid_vm_list, unraid_vm_inspect, unraid_vm_start, unraid_vm_stop, unraid_vm_pause, unraid_vm_resume, unraid_vm_force_stop, unraid_vm_reboot |
| Array | unraid_array_status, unraid_array_start, unraid_array_stop, unraid_parity_status, unraid_parity_start, unraid_parity_pause, unraid_parity_resume, unraid_parity_cancel |
| Disks | unraid_disk_list, unraid_disk_details |
| Shares | unraid_share_list, unraid_share_details, unraid_share_update |
| System | unraid_system_info, unraid_system_metrics, unraid_service_list, unraid_system_reboot, unraid_system_shutdown |
| Notifications | unraid_notification_list, unraid_notification_create, unraid_notification_archive, unraid_notification_delete |
| Network | unraid_network_info |
| Users | unraid_user_me |
| Logs | unraid_syslog |
Permissions use a resource:action format. Configure them from the WebGUI Permissions tab or edit /boot/config/plugins/unraidclaw/permissions.json directly.
| Category | Permissions |
|---|---|
| Docker | docker:read, docker:create, docker:update, docker:delete |
| Community Apps | ca:read, ca:create, ca:update, ca:delete |
| Plugins | plugins:read, plugins:create, plugins:update, plugins:delete |
| VMs | vms:read, vms:update, vms:delete |
| Array & Storage | array:read, array:update, disk:read, share:read, share:update |
| System | info:read, os:update, services:read |
| Notifications | notification:read, notification:create, notification:update, notification:delete |
| Network | network:read |
| Users | me:read |
| Logs | logs:read |
The WebGUI includes Read Only, Docker Manager, VM Manager, Full Admin, and None presets. Docker Manager includes all four ca: permissions. Read Only includes plugins:read. Plugin write permissions must be enabled individually or through Full Admin.
GraphQL ──> Unraid API
/ (list queries, array, disks)
┌─────────────┐ HTTPS ┌──────────────────┐──+
│ AI Agent │ ─────────────> │ UnraidClaw │ \
│ (OpenClaw │ x-api-key │ (Fastify) │ CLI ──────> docker, virsh, mdcmd,
│ or MCP) │ │ │ / reboot, ip, ...
└─────────────┘ │ - Auth │──+
│ - Permissions │ \
│ - Activity Log │ Filesystem > share configs, syslog,
└──────────────────┘ notifications
OpenClaw and the CLI call /api/* over HTTPS. MCP clients, when MCP is enabled, call /mcp on the same gateway. The tool definitions live once, in the openclaw-plugin package, which exports them as a transport-neutral registry (unraidclaw/tools, from src/registry.ts). The OpenClaw entry registers that registry with OpenClaw and sends HTTP requests to the gateway. The gateway's MCP adapter imports the same registry and runs each tool call through its own /api/ route in process, so authentication, permission checks and activity logging are shared and no second network connection is made.
This is a pnpm monorepo with four packages:
| Package | Description |
|---|---|
packages/shared |
Shared TypeScript types, permission definitions, API interfaces |
packages/unraid-plugin/server |
Fastify REST API and optional MCP endpoint, bundles to a single CJS file |
packages/cli |
Standalone unraidclaw command, bundled as CommonJS for local Unraid and remote Node.js clients |
packages/openclaw-plugin |
OpenClaw plugin entry plus the transport-neutral tool registry (unraidclaw/tools) that the gateway's MCP adapter and the CLI consume, published to npm as unraidclaw |
- The gateway stores a SHA-256 hash of its API key. A CLI user can save the plaintext client key in a private config file, or on Unraid flash where POSIX file modes are unavailable. See the CLI security notes
- REST requests require
x-api-key, except the public/api/healthprobe. MCP POST, GET and DELETE requests acceptx-api-keyorAuthorization: Bearerand require a key. CORS preflight requests do not require a key. When MCP is off,/mcpdoes not exist - MCP rejects any
Originoutside an allowlist built at startup from loopback, the local interface addresses and the configured Listen Host, which blocks DNS rebinding from a browser. Requests without anOriginheader are allowed, so for non-browser clients the API key is the only protection - Failed authentication is limited per IP to 10 attempts per minute; REST and MCP share the counter. Requests to paths that do not exist return 404 without checking the key and do not count, so MCP clients probing for OAuth metadata do not lock themselves out
- Protected API routes, including calls dispatched by MCP tools, check their required permission before execution. Health has no permission requirement
- Activity logging records requests with timestamps, endpoints, and results, excluding
/api/healthand the MCP connection traffic described above. An MCP tool call is logged as the/mcprequest, with the tool name and the route's outcome, plus a separate entry for the/api/route it ran in process, except health. Successful MCP handshakes and GET/DELETE responses with status 405 are not logged; other failed connection requests are - HTTPS uses a self-signed EC (prime256v1) certificate valid for ten years, with the server's names and stable addresses in
subjectAltName. This lets a client that trusts the certificate verify the host name. It does not protect a client that skips verification, and a client that has not trusted it sees a warning or refuses to connect. See TLS certificate - The server runs locally on your Unraid box, no cloud dependencies
See CONTRIBUTING.md for the build, test and pull-request workflow. AI agents working on this tree should read AGENTS.md first.
MIT
