Files
totalmcp/INSTALL-UNRAID.md
jason 7e73e0de71
Build and Push Docker Image / build (push) Successful in 7s
update docs
2026-05-09 23:31:14 -05:00

16 KiB
Raw Permalink Blame History

Unraid Install Guide — totalmcp

Step-by-step walkthrough for deploying the totalmcp gateway on Unraid using the Docker Add Container GUI. Targets Unraid 6.12+ / 7.0+.

After installing, totalmcp is reachable at:

  • LAN: http://10.2.0.35:8811
  • Health: http://10.2.0.35:8811/health (no auth)
  • MCP endpoint: http://10.2.0.35:8811/mcp (Bearer token required)
  • Public proxy (optional, after NPM entry): https://mcp.alwisp.com once the existing gitea-mcp proxy is repointed

0. Prerequisites

Gitea Container Registry login

The image lives in your private Gitea registry, so the Unraid host needs credentials.

  1. Open the Unraid web UI → Console (top right) or SSH into the box.
  2. Run:
    docker login git.alwisp.com
    
  3. Enter your Gitea username and a personal access token (with read:packages scope). Credentials are saved to /root/.docker/config.json.

Confirm br0 and pick the IP

  • br0 should already exist (every other ALPHA container uses it).
  • Reserve 10.2.0.35 for totalmcp (next free above codedump @ .34).
  • If .35 is taken when you go to assign it, pick the next free IP and update PLAN.md + SERVICES.md to match.

Pre-create the appdata directory

mkdir -p /mnt/user/appdata/totalmcp/data
mkdir -p /mnt/user/appdata/totalmcp/logs
chown -R 99:100 /mnt/user/appdata/totalmcp

(99:100 = nobody:users, the standard Unraid container ownership.)

Gather your tokens

Have these in a scratch file before starting — easier than tab-switching during the GUI install.

Token Where to get it
AGENT_TOKENS Generate fresh: openssl rand -hex 32 (run 3× — one per agent: claude-code, antigravity, codex). Save them — you'll paste the same values into agent configs after install.
GITEA_TOKEN git.alwisp.com → your avatar → SettingsApplicationsGenerate New Token. Scopes: repo, issue, read:user. Copy the token immediately (Gitea won't show it again).
UNRAID_API_KEY On the Unraid host: unraid-api start, then unraid-api key to print the key. (Requires the unraid-api plugin from Apps.)
OPENCLAW_HOST Already known: http://10.2.0.26:18789 (NOVA). No token — LAN-only.
UNIFI_API_KEY UniFi Access UI → SettingsSecurityAPI TokensCreate. Scopes: read all, plus the writes you want.
UNIFI_SITE_ID UniFi Access UI URL contains it (/locations/<UUID>). Optional.
CODEX_DB_PATH Container path is /app/codex/db.sqlite (after the volume mount). Host file: /mnt/user/appdata/codex/db.sqlite.
RACKMAPPER_TOKEN RackMapper UI → API tokens. Optional if RackMapper has no auth.
STREAMVAULT_* Skip — service not deployed yet.

1. Add Container — top-level fields

Docker tab → Add Container

Field Value
Name totalmcp
Overview Unified MCP gateway for ALPHA — exposes every backend service to Claude Code, Codex, Antigravity
Repository git.alwisp.com/jason/totalmcp:latest
Docker Hub URL (leave blank — private registry)
Icon URL (optional — set later)
WebUI http://[IP]:[PORT:8811]/health
Extra Parameters --pids-limit=2048 --user 0:0 (the --user 0:0 is needed for the docker plugin — see §3)
Post Arguments (leave blank)
Network Type Custom: br0
Use IPv4 only ☑ checked
Fixed IP address (optional) 10.2.0.35
Privileged ☐ unchecked
Console shell command Shell
Auto update / Restart Policy unless-stopped

Why br0 instead of bridge? br0 gives the gateway its own LAN IP so agents on workstations connect directly without going through Unraid's port-mapped 10.2.0.2:8811. Matches the rest of the ALPHA service catalog.


2. Port mappings

Click Add another Path, Port, Variable, Label or DevicePort.

Container Port Host Port Connection Type Description
8811 8811 TCP MCP gateway (HTTP + SSE)

With br0 and a fixed IP, the host-port column is mostly cosmetic — the container has its own IP. Listed for completeness.


3. Path mappings

Click Add Path for each row.

Required

Container Path Host Path Access Mode Description
/app/data /mnt/user/appdata/totalmcp/data Read/Write SQLite event log + plugin runtime data
/app/logs /mnt/user/appdata/totalmcp/logs Read/Write Structured JSON logs (Phase 5)

Required if docker plugin is enabled (Phase 2)

Container Path Host Path Access Mode Description
/var/run/docker.sock /var/run/docker.sock Read/Write Docker socket — needed for start/stop/restart

Read-only is enough for docker_list_containers/get_logs/get_stats, but start/stop/restart require RW.

⚠️ Permission gotcha: the container runs as the unprivileged mcp user, but Unraid's /var/run/docker.sock is owned by root:root (mode 660). The mcp user has no access by default — docker_list_containers will return EACCES. The simplest fix is to add --user 0:0 to the Extra Parameters field at the top of the Add Container form (run the container as root). For a LAN-only gateway behind bearer auth on a single-tenant box, this tradeoff is acceptable. See troubleshooting §7 for stricter alternatives.

Required if codex-mrp plugin is enabled (Phase 3)

Container Path Host Path Access Mode Description
/app/codex/db.sqlite /mnt/user/appdata/codex/db.sqlite Read/Write CODEX SQLite database

Plugin reads in WAL mode so concurrent reads with CODEX itself are safe. Writes (codex_create_work_order) bypass CODEX validation — use the CODEX UI for routine work-order creation.


4. Environment variables

Click Add Variable for each row. Group by phase — only add variables for plugins you're enabling now.

Core (always required)

Variable Value Description
NODE_ENV production
PORT 8811 Must match the port mapping above
LOG_LEVEL info debug / info / warn / error
GATEWAY_VERSION 0.1.0
PLUGINS_DIR ./dist/plugins (default — only override for advanced setups)
ENABLED_PLUGINS gitea,unraid,docker,openclaw,unifi,codex-mrp,streamvault,rackmapper Comma-separated. Trim to only the plugins you've configured.
AGENT_TOKENS claude-code:TOKEN1,antigravity:TOKEN2,codex:TOKEN3 Generate with openssl rand -hex 32. Save the same tokens in your agent configs.

Phase 1 — Gitea + Unraid

Variable Value Required for plugin
GITEA_HOST https://git.alwisp.com gitea
GITEA_TOKEN (Gitea PAT with repo + issue + admin scopes) gitea
UNRAID_HOST http://10.2.0.2 unraid
UNRAID_API_KEY (generated via unraid-api start on the host) unraid

Phase 2 — Docker + OpenClaw

Variable Value Required for plugin
OPENCLAW_HOST http://10.2.0.26:18789 openclaw (NOVA)

The docker plugin needs no env vars — it uses the mounted socket.

Phase 3 — Service Connectors

Variable Value Required for plugin
UNIFI_HOST https://<your-unifi-controller> unifi
UNIFI_API_KEY (UniFi Access → Settings → Security → API tokens) unifi
UNIFI_SITE_ID (your site UUID, optional) unifi
CODEX_DB_PATH /app/codex/db.sqlite codex-mrp (must match the path mapping above)
STREAMVAULT_HOST http://streamvault:3100 streamvault (skip until StreamVault is deployed)
STREAMVAULT_TOKEN (if StreamVault requires auth) streamvault (optional)
RACKMAPPER_HOST http://10.2.0.23 rackmapper
RACKMAPPER_TOKEN (if RackMapper requires auth) rackmapper (optional)

Phase 6 — Deferred

Variable Value Required for plugin
CHRONICLE_HOST (after deploy) chronicle (deferred)
CHRONICLE_TOKEN (after deploy) chronicle (deferred)
OBSIDIAN_REST_HOST http://10.2.0.2:27123 obsidian (deferred)
OBSIDIAN_API_KEY (Obsidian Local REST API plugin) obsidian (deferred)

Phase 7+ (defer until Phase 7 ships)

The full set is documented in .env.exampleNPM_*, UISP_*, TRANSMISSION_*, SYNCTHING_*, PLEX_*, NYAA_*, HA_*, INVOICENINJA_*, FABDASH_*, CPAS_*, WFH_*, BREEDR_*, CODEDUMP_*, UITRACKER_*, STEPVIEW_*, QRKNIT_*, MEMER_*, ALWISP_WEB_*. Add only when those plugin phases land.


5. Apply & verify

  1. Click Apply at the bottom of the Add Container form.
  2. Unraid pulls the image from git.alwisp.com/jason/totalmcp:latest and starts the container.
  3. Watch the Docker tab — totalmcp should show started within 510 seconds.

Liveness check

From the Unraid console (or any LAN host):

curl http://10.2.0.35:8811/health

Expected:

{
  "status": "ok",
  "version": "0.1.0",
  "plugins": 8,
  "enabled": ["gitea","unraid","docker","openclaw","unifi","codex-mrp","streamvault","rackmapper"]
}

Plugin diagnostics

curl -H "Authorization: Bearer <CLAUDE_CODE_TOKEN>" http://10.2.0.35:8811/plugins

You'll see a per-plugin breakdown with tool counts. If a plugin is missing, check the container logs:

docker logs totalmcp | grep -E "plugin_(loaded|connect_failed|invalid)"

Connect Claude Code

claude mcp add --scope user --transport http totalmcp http://10.2.0.35:8811/mcp \
  -H "Authorization: Bearer <CLAUDE_CODE_TOKEN>"

Then run /mcp inside Claude Code — the totalmcp tools should appear in the catalog.

Per-plugin smoke tests

Run these from Claude Code in order of risk (lowest first). Each row = the safest first call to confirm a plugin actually works end-to-end.

Plugin First call Expected on success Likely failure mode
gitea gitea_list_repos { repos: [...] } including the totalmcp repo 401 → GITEA_TOKEN missing/wrong scopes
unraid unraid_host_summary { host, os, uptime, cpu, memory, array } GraphQL field shape mismatch — adjust queries in src/plugins/unraid/index.ts to match your unraid-api version
docker docker_list_containers ~35 containers from your Unraid stack EACCES on /var/run/docker.sock — confirm --user 0:0 is in Extra Parameters
openclaw openclaw_list_models List of models on NOVA Connection timeout → confirm NOVA at 10.2.0.26:18789 is reachable from 10.2.0.35
unifi unifi_list_sites List of UniFi Access locations 404 on /api/v1/developer/locations — UniFi Access REST paths vary by version; adjust the plugin's endpoint paths
codex-mrp codex_list_work_orders { workOrders: [...] } no such table: work_ordersexpected. Run sqlite3 /mnt/user/appdata/codex/db.sqlite ".schema", then update the SQL in src/plugins/codex-mrp/index.ts to match real CODEX table names
streamvault (skip) n/a Service not deployed — onLoad will warn at startup, tool calls return connection error. Disable by removing from ENABLED_PLUGINS until ready.
rackmapper rackmapper_list_racks { racks: [...] } 404 — RackMapper API path may differ; adjust

What to expect on first run

  • gitea, openclaw, docker are highest-confidence. Built against well-documented APIs that haven't drifted.
  • unraid, unifi are medium-confidence. APIs evolve between versions; field shapes may need adjustment.
  • codex-mrp is low-confidence. Placeholder SQL by design — needs the real CODEX schema before it works.
  • streamvault will be in failure state until the service exists. Disable for now.
  • rackmapper depends on whether RackMapper has a JSON API; adjust if not.

Capture each plugin's failure (if any) and we'll fix the schemas/paths in a follow-up pass.


6. Updates

Manual (one-time)

  1. Docker tab → totalmcp → click the icon → Force Update.
  2. Unraid pulls git.alwisp.com/jason/totalmcp:latest and restarts the container in place.
  3. Re-verify via curl /health.

Automated (Phase 4 — Gitea Actions CI)

Once the CI pipeline lands, every push to main rebuilds the image. To pick up updates without manual intervention:

  • Set Unraid's update polling to a short interval (Settings → Docker → "Check for updates every"), or
  • Hit the Docker tab → Check for Updates button when ready.

7. Troubleshooting

/health returns 500 or container crash-loops

Check the logs:

docker logs totalmcp

Common causes:

  • Invalid environment configurationAGENT_TOKENS malformed or PORT not a number. Fix the Variable in the Unraid GUI and the container will auto-restart.
  • Cannot find module ... — image was built without one of the new deps (e.g., dockerode or better-sqlite3). Force-update to pull the latest image.

docker plugin: docker_connect_failed or EACCES on socket

Most common cause: the container is running as the unprivileged mcp user but /var/run/docker.sock is owned by root:root on the host.

Quickest fix: add --user 0:0 to Extra Parameters on the container (run as root inside the container).

Stricter alternatives if you don't want root in the container:

  1. Find the docker GID on the host: stat -c '%g' /var/run/docker.sock (commonly 281 on Unraid).
  2. Either:
    • Override the container's group with --group-add <GID> in Extra Parameters, or
    • Rebuild the image with that GID baked in (modify the Dockerfile's addgroup step).
  3. Or run a docker-socket-proxy in front and point dockerode at the proxy URL via a socketPath override.

For a LAN-only gateway behind bearer auth on a single-tenant Unraid box, --user 0:0 is the pragmatic choice.

Also confirm the path mapping is /var/run/docker.sock → /var/run/docker.sock with Read/Write access mode.

codex-mrp plugin: codex_mrp_connect_failed

  • Check that /mnt/user/appdata/codex/db.sqlite exists on the host.
  • Confirm the path mapping is /app/codex/db.sqlite (file path, not directory).
  • Confirm CODEX_DB_PATH=/app/codex/db.sqlite matches.

unraid plugin: unraid_connect_failed

  • Run unraid-api status on the host to confirm the API plugin is running.
  • Confirm UNRAID_API_KEY matches the key from unraid-api key.

Authentication 401s from Claude Code

  • Token mismatch — the AGENT_TOKENS value in Unraid must contain the same token Claude Code is sending.
  • Format reminder: agentName:token,agentName2:token2 (no spaces, comma-separated).

Port collision on 8811

If something else on 10.2.0.35 (or your chosen IP) already binds 8811, change PORT and the port mapping to 8812 (or whichever) and update agent configs accordingly. The PLAN.md default is 8811.


8. Reference: minimal viable install

If you just want the gateway running with only the Gitea plugin to start:

Setting Value
Repository git.alwisp.com/jason/totalmcp:latest
Network / IP br0 / 10.2.0.35
Port 8811:8811 TCP
Path /app/data → /mnt/user/appdata/totalmcp/data (RW)
Variable NODE_ENV production
Variable PORT 8811
Variable AGENT_TOKENS claude-code:<your-token>
Variable ENABLED_PLUGINS gitea
Variable GITEA_HOST https://git.alwisp.com
Variable GITEA_TOKEN (your PAT)

Apply, verify with curl /health, then expand by adding more variables and updating ENABLED_PLUGINS as each phase comes online.

Note: even the minimal install does not need --user 0:0 since the docker plugin isn't enabled. Add --user 0:0 only when you turn docker on.