35 KiB
Total MCP Gateway — PLAN.md
Project: totalmcp
Host: Unraid ALPHA · 10.2.0.35 (new static IP, next available above codedump @ 10.2.0.34)
Port: 8811
Stack: Node.js 20 + TypeScript + @modelcontextprotocol/sdk + Express 5
Registry: git.alwisp.com/jason/totalmcp
Last Updated: 2026-05-09
Overview
A single Dockerized MCP server that acts as the unified control plane for every AI agent and tool in the Jason ecosystem. Instead of N×M point-to-point integrations, all three agent interfaces — Claude Code, Codex, and Antigravity — connect to one stable endpoint and get access to every backend service through hot-swappable plugin modules.
Two first-class design concerns:
- Upgradability — new connectors drop in without touching core infrastructure or restarting the container
- Utility — every active service on ALPHA is reachable from any agent from day one
Architecture
┌─────────────────────────────────────────────────────────┐
│ Claude Code / Codex / Antigravity │
└────────────────────┬────────────────────────────────────┘
│ MCP (Streamable HTTP + SSE fallback)
▼
┌─────────────────────────────────────────────────────────┐
│ Total MCP Gateway :8811 │
│ ┌──────────────┐ ┌───────────────┐ ┌─────────────┐ │
│ │ Core Server │ │Plugin Registry│ │ Auth / Rate │ │
│ │ (TypeScript) │ │ (hot-reload) │ │ Limiter │ │
│ └──────────────┘ └───────────────┘ └─────────────┘ │
│ │
│ Active Plugins: │
│ ┌────────┐ ┌────────┐ ┌────────┐ ┌──────────────────┐ │
│ │ gitea │ │ unraid │ │ docker │ │ openclaw │ │
│ └────────┘ └────────┘ └────────┘ └──────────────────┘ │
│ ┌────────┐ ┌────────┐ ┌────────┐ ┌──────────────────┐ │
│ │ unifi │ │ codex │ │stream │ │ rackmapper │ │
│ │ access │ │ mrp │ │ vault │ │ │ │
│ └────────┘ └────────┘ └────────┘ └──────────────────┘ │
│ │
│ Deferred Plugins (install first): │
│ ┌──────────┐ ┌──────────┐ │
│ │ chronicle│ │ obsidian │ │
│ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────┘
│ Unraid br0 · 10.2.0.x
┌───────────────┼──────────────────────────┐
▼ ▼ ▼
git.alwisp.com Unraid host NOVA (OpenClaw)
10.2.0.15 10.2.0.2 10.2.0.26:18789
Transport
Both transports are exposed on the same container at :8811:
| Transport | Endpoint | Use For |
|---|---|---|
| Streamable HTTP (primary) | POST/GET /mcp |
Claude Code, Codex |
| SSE (legacy fallback) | GET /sse + POST /message |
Antigravity |
| Health check | GET /health |
Unraid, monitoring |
| Plugin registry API | GET /plugins |
Admin, debugging |
| Admin UI | GET /admin |
Dashboard, plugin management |
Note: Use Streamable HTTP for Claude Code — there is a known SSE reconnection regression in Claude Code v2.1.83+. Use SSE for Antigravity until it supports Streamable HTTP natively.
Technology Stack
| Layer | Choice | Reason |
|---|---|---|
| Runtime | Node.js 20 LTS + TypeScript | Matches existing stack |
| MCP SDK | @modelcontextprotocol/sdk |
Official Anthropic SDK |
| HTTP Framework | Express 5 | Familiar, lightweight |
| Plugin loading | Dynamic import() + chokidar |
Hot-reload without restart |
| Config | .env + Zod config loader |
Env-var-first, validated at boot |
| Persistence | SQLite via Prisma | Consistent with CODEX/Chronicle pattern |
| Auth | Bearer tokens (static, per-agent) | Simple, no OAuth needed for LAN |
| Containerization | Single Docker image | Unraid-native |
| Registry | git.alwisp.com Gitea Container Registry | Auto-build via Gitea Actions |
| Networking | Unraid br0 static IP | Consistent LAN addressing |
Directory Structure
totalmcp/
├── src/
│ ├── server.ts # Express + MCP SDK bootstrap
│ ├── registry.ts # Plugin loader + hot-reload watcher
│ ├── transport/
│ │ ├── streamable.ts # Streamable HTTP transport
│ │ └── sse.ts # Legacy SSE transport
│ ├── auth/
│ │ └── bearer.ts # Per-agent token validation
│ ├── types/
│ │ └── plugin.ts # MCPPlugin interface contract
│ └── plugins/
│ ├── gitea/
│ │ └── index.ts # Phase 1
│ ├── unraid/
│ │ └── index.ts # Phase 1
│ ├── docker/
│ │ └── index.ts # Phase 2
│ ├── openclaw/
│ │ └── index.ts # Phase 2
│ ├── unifi/
│ │ └── index.ts # Phase 3
│ ├── codex-mrp/
│ │ └── index.ts # Phase 3
│ ├── streamvault/
│ │ └── index.ts # Phase 3
│ ├── rackmapper/
│ │ └── index.ts # Phase 3
│ ├── chronicle/ # DEFERRED — install Chronicle first
│ │ └── index.ts
│ └── obsidian/ # DEFERRED — install Obsidian REST bridge first
│ └── index.ts
├── config/
│ └── catalog.yaml # Enable/disable plugins without code changes
├── prisma/
│ └── schema.prisma # Event log schema
├── admin/ # React + Vite admin dashboard
│ ├── src/
│ └── dist/ # Built, served at /admin
├── Dockerfile
├── docker-compose.yml # Local dev
├── .env.example
├── AGENTS.md # Agent configuration reference
├── README.md
├── PLAN.md # This file
└── CHANGELOG.md
Plugin Interface Contract
Every connector implements this interface. The registry auto-discovers and loads any file matching src/plugins/*/index.ts.
// src/types/plugin.ts
export interface MCPPlugin {
name: string; // e.g., "gitea" — must be unique
version: string; // semver
description: string;
minGatewayVersion: string; // semver — registry skips incompatible plugins
tools: MCPTool[];
resources?: MCPResource[];
prompts?: MCPPrompt[];
onLoad?: () => Promise<void>; // init: connect, test auth
onUnload?: () => Promise<void>; // cleanup: close connections, timers
}
export interface MCPTool {
name: string;
description: string;
inputSchema: ZodSchema;
handler: (input: unknown) => Promise<unknown>;
}
Plugin Isolation Rules
- Each plugin catches its own errors — never throws to core
- No plugin reads another plugin's state directly
- Credentials live in env vars, never in plugin source
- Inter-plugin coordination (if ever needed) goes through the SQLite event log
Adding a New Connector (5-Minute Workflow)
mkdir src/plugins/my-service && touch src/plugins/my-service/index.ts- Implement
MCPPlugininterface, export asdefault - Add required env vars to
/mnt/user/appdata/totalmcp/.env - Add
my-servicetoENABLED_PLUGINSin.env - Hot-reload picks it up in ~2s (dev) OR push to Gitea → CI rebuilds → Unraid force-updates (prod)
Phased Roadmap
Phase 0 — Scaffold & Core Transport
Goal: Container boots, endpoints respond, empty plugin registry loads.
Est: 1–2 days
- Init repo at
git.alwisp.com/jason/totalmcp npm init, install core deps:@modelcontextprotocol/sdk,express,zod,chokidar,dotenv,prisma- Implement
src/server.ts— Express bootstrap, mount both transports - Implement
src/transport/streamable.ts— Streamable HTTP (primary) - Implement
src/transport/sse.ts— SSE legacy fallback - Implement
src/registry.ts— dynamicimport(), chokidar file watcher, plugin lifecycle - Implement
src/auth/bearer.ts— per-agent static token validation - Write
Dockerfile— Node 20 Alpine, non-root user,HEALTHCHECK - Write
docker-compose.yml— local dev with volume mounts - Write
.env.example— all required vars documented - Write
prisma/schema.prisma— event log table - Deploy to ALPHA via Unraid Docker GUI
- Verify:
curl http://10.2.0.35:8811/health→200 OK - Verify:
curl http://10.2.0.35:8811/plugins→{ "plugins": [] }
Unraid Container Settings:
| Setting | Value |
|---|---|
| Container Name | totalmcp |
| Repository | git.alwisp.com/jason/totalmcp:latest |
| Network | br0 |
| IP | 10.2.0.35 (next available static; gitea-mcp already owns 10.2.0.16:8081 and remains undisturbed during transition) |
| Port | 8811:8811 |
| Appdata | /mnt/user/appdata/totalmcp → /app/data |
| Env File | /mnt/user/appdata/totalmcp/.env |
| Docker Socket | /var/run/docker.sock → /var/run/docker.sock |
| Restart Policy | unless-stopped |
| Pids Limit | 2048 |
Phase 1 — Gitea + Unraid Connectors
Goal: Agents can query repos and Unraid system state.
Est: 1–2 days
Prerequisite: Phase 0 complete
plugins/gitea/index.ts
- Auth:
GITEA_TOKENenv var →https://git.alwisp.comREST API - Tools:
gitea_list_repos— list all reposgitea_get_repo— get repo detailsgitea_create_repo— create a new repogitea_list_issues— list issues for a repogitea_create_issue— open a new issuegitea_list_branches— list branchesgitea_get_file— read a file's contentsgitea_commit_file— create or update a file
plugins/unraid/index.ts
- Auth:
UNRAID_API_KEY+UNRAID_HOST - Tools:
unraid_host_summary— CPU, RAM, uptime, array statusunraid_list_containers— all Docker containers + statusunraid_get_container— single container detailsunraid_list_shares— user shares + usageunraid_list_vms— VM list + stateunraid_disk_health— disk S.M.A.R.T. summary
Agent Wiring (after Phase 1)
Claude Code:
claude mcp add --scope user --transport http totalmcp http://10.2.0.35:8811/mcp \
-H "Authorization: Bearer <CLAUDE_CODE_TOKEN>"
Antigravity (mcp_config.json):
{
"mcpServers": {
"totalmcp": {
"url": "http://10.2.0.35:8811/sse",
"transport": "sse",
"headers": { "Authorization": "Bearer <ANTIGRAVITY_TOKEN>" }
}
}
}
Codex:
{
"mcpServers": {
"totalmcp": {
"url": "http://10.2.0.35:8811/mcp",
"transport": "http",
"headers": { "Authorization": "Bearer <CODEX_TOKEN>" }
}
}
}
- Implement
plugins/gitea/index.ts - Implement
plugins/unraid/index.ts - Add both to
ENABLED_PLUGINS - Connect Claude Code, Codex, Antigravity
- Verify tool list shows gitea + unraid tools from each agent
Phase 2 — Docker + OpenClaw
Goal: Agents can manage containers and run local AI inference.
Est: 1–2 days
Prerequisite: Phase 1 complete
plugins/docker/index.ts
- Auth: Docker socket mount (
/var/run/docker.sock) - Tools:
docker_list_containers— running + stopped containersdocker_start_container— start by name or IDdocker_stop_container— stop by name or IDdocker_restart_container— restart by name or IDdocker_get_logs— tail N lines of logsdocker_get_stats— CPU + memory stats
plugins/openclaw/index.ts
- Auth:
OPENCLAW_HOSTenv var → NOVA instance athttp://10.2.0.26:18789 - Tools:
openclaw_chat— chat completion via OpenClaw/Ollamaopenclaw_list_models— list available modelsopenclaw_get_model_info— model details + context size
Note: The
OpenClaw-named container in the Unraid Docker tab serves thenova.alwisp.comproxy entry — same image, "NOVA" is the persona name. Thedonna.alwisp.cominstance at10.2.0.28:18789is a stopped second container set up for a friend; do not target it.
- Implement
plugins/docker/index.ts - Implement
plugins/openclaw/index.ts - Add both to
ENABLED_PLUGINS - Verify container management works from Claude Code
Phase 3 — Service Connectors
Goal: Full parity with all active ALPHA services.
Est: 2–3 days
Prerequisite: Phase 2 complete
plugins/unifi/index.ts
- Auth:
UNIFI_HOST,UNIFI_API_KEY,UNIFI_SITE_ID - Tools:
unifi_list_access_events— recent badge/door events (V2 API)unifi_list_users— access users with namesunifi_get_door_status— current lock state per doorunifi_list_sites— managed UniFi sites
plugins/codex-mrp/index.ts
- Auth: Internal Docker network — direct Prisma client access to CODEX SQLite
- Tools:
codex_list_work_orders— active work orderscodex_get_work_order— single WO detail + BOMcodex_create_work_order— create new WOcodex_get_inventory— inventory levelscodex_list_boms— bill of materials list
plugins/streamvault/index.ts
- Auth:
STREAMVAULT_HOST(internal Docker network) - Tools:
streamvault_list_jobs— all download jobs + statusstreamvault_add_download— queue a new download URLstreamvault_get_job_status— single job detailstreamvault_cancel_job— cancel/remove a job
plugins/rackmapper/index.ts
-
Auth:
RACKMAPPER_HOST(internal Docker network) -
Tools:
rackmapper_list_racks— all racksrackmapper_get_rack— rack layout with devicesrackmapper_list_devices— device inventoryrackmapper_map_service— link a device to a service
-
Implement all four Phase 3 plugins
-
Add all to
ENABLED_PLUGINS -
Verify each tool is callable from agents
Phase 4 — Upgradability Infrastructure
Goal: Admin UI, CI/CD pipeline, zero-downtime plugin updates.
Est: 2–3 days
Prerequisite: Phase 3 complete
- Plugin Admin API
GET /plugins— list all plugins + tool counts, status, versionPOST /plugins/:name/reload— hot-reload a single pluginPOST /plugins/:name/enable— enable a disabled pluginPOST /plugins/:name/disable— disable without removing
config/catalog.yaml— human-readable enable/disable, rate limits per plugin, per-agent tool allowlists- Admin Dashboard (React + Vite, served at
/admin)- Plugin status cards (loaded / error / disabled)
- Tool call counts per plugin
- Last-used timestamps
- Reload button per plugin
- Live event log tail
- Gitea Actions CI Pipeline
- Trigger: push to
main - Steps:
npm ci→npm run build→docker build→docker push git.alwisp.com/jason/totalmcp:latest - Unraid force-updates container on next check or manual trigger
- Trigger: push to
- Semantic versioning for plugin API contracts
- Plugins declare
minGatewayVersion - Registry checks compatibility at load time — skips with warning, never crashes
- Plugins declare
- Health endpoint per plugin:
GET /health/plugins/:name
Phase 5 — Security & Observability
Goal: Production-hardened for daily autonomous agent use.
Est: 1–2 days
Prerequisite: Phase 4 complete
- Per-agent bearer tokens —
AGENT_TOKENS=claude-code:TOKEN1,antigravity:TOKEN2,codex:TOKEN3 - Rate limiting — per-agent, per-tool, configurable in
catalog.yaml - Input schema validation — Zod on every tool call; bad inputs return structured 400 errors, never reach handler
- Structured JSON logging →
/mnt/user/appdata/totalmcp/logs/ - SQLite event log — every tool call: agent, tool name, input hash, duration, success/error, timestamp
- Prometheus metrics endpoint at
/metrics(optional — wire to Grafana if desired) - Plugin error isolation test — verify one crashing plugin cannot bring down the gateway or other plugins
Phase 6 — Deferred Connectors (Install Prerequisites First)
Goal: Add Chronicle and Obsidian after those services are deployed to ALPHA.
Est: 1 day per connector once services are live
Prerequisite: Chronicle and Obsidian deployed + accessible on br0
Chronicle — Prerequisites
- Deploy Chronicle container to ALPHA (see Chronicle build docs)
- Verify
http://chronicle:3003/healthresponds from within the Docker network - Obtain Chronicle bearer token
plugins/chronicle/index.ts
- Auth:
CHRONICLE_HOST,CHRONICLE_TOKEN - Tools:
memory_store— persist a memory entrymemory_recall— retrieve by key or semantic querymemory_search— full-text + semantic search across entriesmemory_list_projects— browse memories by project scopememory_delete— remove a specific entry
Obsidian — Prerequisites
- Install Obsidian Local REST API plugin in the Obsidian container on ALPHA
- Generate API key from Obsidian plugin settings
- Verify
http://obsidian:27123is reachable from Docker network
plugins/obsidian/index.ts
- Auth:
OBSIDIAN_REST_HOST,OBSIDIAN_API_KEY - Tools:
obsidian_note_create— create a new note at a given pathobsidian_note_read— read note contents by pathobsidian_note_update— overwrite note contentsobsidian_note_append— append text to a noteobsidian_note_search— search notes by queryobsidian_list_vault— list notes in a folder
To activate either deferred connector once ready:
- Prerequisites above are complete
touch src/plugins/chronicle/index.ts(orobsidian) and implement- Add env vars to
/mnt/user/appdata/totalmcp/.env - Add to
ENABLED_PLUGINS - Push to Gitea → CI rebuilds → Unraid force-updates
Phase 7 — Infrastructure & Media Connectors
Goal: Cover the remaining third-party services on ALPHA so agents can manage networking, file sync, downloads, and media. Est: 2–3 days Prerequisite: Phase 5 complete (security hardening landed before broadening surface area)
plugins/npm/index.ts — Nginx Proxy Manager
- Auth:
NPM_HOST=http://10.2.0.3:81,NPM_EMAIL,NPM_PASSWORD(NPM token flow) - Tools:
npm_list_proxy_hosts— all configured proxy entriesnpm_get_proxy_host— single entry detailnpm_create_proxy_host— new hostname → backend mappingnpm_update_proxy_host— change backend / SSL settingsnpm_delete_proxy_host— remove entrynpm_renew_cert— force Let's Encrypt renewalnpm_list_certs— cert status across all hosts
plugins/uisp/index.ts — Ubiquiti UISP
- Auth:
UISP_HOST=https://10.2.0.4:443,UISP_TOKEN - Tools:
uisp_list_devices— all UISP-line devicesuisp_get_device_status— per-device state, CPU, RAMuisp_list_sites— managed sitesuisp_get_link_quality— radio link RSSI / capacity / errorsuisp_list_outages— current device outages
plugins/transmission/index.ts — NEBULA
- Auth:
TRANSMISSION_HOST=http://10.2.0.5:9091,TRANSMISSION_USER,TRANSMISSION_PASS - Tools:
transmission_list_torrents— all torrents + statustransmission_add_torrent— magnet or URLtransmission_pause_torrent/transmission_resume_torrenttransmission_remove_torrent— with optional data deletiontransmission_get_stats— global up/down rates, totals
plugins/syncthing/index.ts
- Auth:
SYNCTHING_HOST=http://10.2.0.2:8384,SYNCTHING_API_KEY - Tools:
syncthing_list_folders— all configured folders + sync %syncthing_folder_status— single folder detailsyncthing_list_devices— paired devices + connection statesyncthing_pause_folder/syncthing_resume_foldersyncthing_rescan— force a folder rescan
plugins/plex/index.ts
- Auth:
PLEX_HOST=http://10.2.0.2:32400,PLEX_TOKEN - Tools:
plex_list_libraries— all libraries (movies, TV, music)plex_search_library— search across librariesplex_recently_added— N most recently added itemsplex_now_playing— active sessionsplex_server_status— version, transcoder activity, plugins
plugins/nyaa/index.ts
-
Auth:
NYAA_HOST=http://10.2.0.21:<port>,NYAA_API_KEY(if applicable) -
Tools:
nyaa_list_watches— active search/watch rulesnyaa_add_watch— new auto-download rulenyaa_remove_watch— delete a rulenyaa_get_recent_matches— last N triggered downloadsnyaa_force_check— run watch loop immediately
-
Implement all six Phase 7 plugins
-
Add to
ENABLED_PLUGINS -
Confirm each tool works from Claude Code
Phase 8 — Smart Home
Goal: Bring Home Assistant under MCP control so agents can read sensor state and trigger automations. Est: 1–2 days Prerequisite: Phase 7 complete
plugins/home-assistant/index.ts
- Auth:
HA_HOST=https://10.2.0.12:8123,HA_TOKEN(long-lived access token from HA UI) - Tools:
ha_list_entities— all entities + current stateha_get_state— single entity state + attributesha_call_service— invoke any HA service (light.turn_on,automation.trigger, etc.)ha_list_automations— all configured automationsha_trigger_automation— fire an automation by entity IDha_get_history— historical state changes for an entity
Note:
matter-server(raw Matter protocol bridge on ALPHA) is intentionally not wrapped as a separate plugin — Home Assistant already abstracts Matter, Zigbee, Z-Wave, and other ecosystems behind one API. Add a dedicatedmatterplugin only if a use case emerges that HA cannot serve.
- Implement
plugins/home-assistant/index.ts - Add to
ENABLED_PLUGINS - Verify entity state + service calls work from Claude Code
Phase 9 — Business Operations
Goal: Connect remaining business apps so agents can support shop floor, HR, and finance workflows. Est: 2–3 days Prerequisite: Phase 8 complete
plugins/invoiceninja/index.ts
- Auth:
INVOICENINJA_HOST=http://10.2.0.2:8000,INVOICENINJA_TOKEN - Tools:
invoiceninja_list_invoices— filter by status, client, date rangeinvoiceninja_get_invoice— single invoice detailinvoiceninja_create_invoice— new invoice from line itemsinvoiceninja_send_invoice— email an invoiceinvoiceninja_list_clients— client directoryinvoiceninja_get_payment_status— payment state for invoice
plugins/fabdash/index.ts
- Auth:
FABDASH_HOST=http://10.2.0.13:8080,FABDASH_TOKEN - Tools:
fabdash_get_today_schedule— today's shop schedulefabdash_list_active_jobs— jobs in flightfabdash_machine_status— utilization per machinefabdash_create_job— new shop jobfabdash_update_job— change status / assignment
plugins/cpas/index.ts
- Auth:
CPAS_HOST=http://10.2.0.14:3001,CPAS_TOKEN - Tools:
cpas_list_violations— recent infractionscpas_get_employee_score— running point totalcpas_log_violation— record new infractioncpas_list_at_risk_employees— employees at/near escalation thresholds
plugins/wfh/index.ts
-
Auth:
WFH_HOST=http://10.2.0.18:3000,WFH_TOKEN -
Tools:
wfh_list_tasks— task log per employee or teamwfh_get_employee_log— single employee daily logwfh_submit_form— file required HR/timesheet formwfh_list_pending_submissions— overdue forms
-
Implement all four Phase 9 plugins
-
Add to
ENABLED_PLUGINS
Phase 10 — Personal & Niche Apps
Goal: Round out the catalog with personal and lower-priority custom apps. Est: 2–3 days Prerequisite: Phase 9 complete
plugins/breedr/index.ts
- Auth:
BREEDR_HOST=http://10.2.0.17:<port>,BREEDR_TOKEN - Tools:
breedr_list_dogs,breedr_get_pedigree,breedr_list_upcoming_litters,breedr_log_whelp_event
plugins/codedump/index.ts
- Auth:
CODEDUMP_HOST=http://10.2.0.34:<port>,CODEDUMP_TOKEN - Tools:
codedump_list_projects,codedump_get_project,codedump_update_completion,codedump_add_project
plugins/ui-tracker/index.ts
- Auth:
UITRACKER_HOST=http://10.2.0.29:<port>,UITRACKER_TOKEN - Tools:
uitracker_list_watched,uitracker_add_watch,uitracker_remove_watch,uitracker_get_alert_history
plugins/stepview/index.ts
- Auth:
STEPVIEW_HOST=http://10.2.0.33:3000,STEPVIEW_TOKEN - Tools:
stepview_list_models,stepview_upload_model,stepview_create_share_link,stepview_revoke_share
plugins/qrknit/index.ts
- Auth:
QRKNIT_HOST=http://10.2.0.9:5000,QRKNIT_TOKEN - Tools:
qrknit_create_link,qrknit_get_analytics,qrknit_list_links,qrknit_generate_qr
plugins/memer/index.ts
- Auth:
MEMER_HOST=http://10.2.0.30:3000,MEMER_TOKEN - Tools:
memer_search,memer_upload,memer_get_random,memer_list_tags
plugins/alwisp-web/index.ts
-
Auth:
ALWISP_WEB_HOST=http://10.2.0.8:80,ALWISP_WEB_TOKEN -
Tools:
alwisp_publish_page,alwisp_update_page,alwisp_list_pages -
Implement plugins for the apps that have stable APIs
-
Defer any app that lacks a usable API until one is added
-
Add to
ENABLED_PLUGINSselectively — not all of these need to be on by default
Phase 11 — Future / Conditional
Goal: Track candidates that should not be built yet.
| Plugin | Condition to start |
|---|---|
mrp-qrcode |
Once it stabilizes and its scope clearly differs from codex-mrp in production use |
inven |
Once development converges (currently the third in-flight MRP design) |
matter (raw) |
Only if a use case appears that HA cannot serve |
email-sigs |
Only if an API is added (currently UI-only) |
n8n |
Only if usage materially picks up (currently stopped) |
Skipped (per service triage)
adminer— UI only, no programmatic API neededMariaDB,postgresql16,Redis— services own their own DBs; no direct DB pluginGitea-Runner— managed via the Gitea pluginbx (client UISP)— third-party trust boundary; do not target
Environment Variables
Full reference for /mnt/user/appdata/totalmcp/.env:
# ─── Core ───────────────────────────────────────────────
NODE_ENV=production
PORT=8811
LOG_LEVEL=info
GATEWAY_VERSION=1.0.0
# Comma-separated list of active plugin names
# Phase 1–3 (core)
ENABLED_PLUGINS=gitea,unraid,docker,openclaw,unifi,codex-mrp,streamvault,rackmapper
# Add Phase 7+ as plugins land:
# ,npm,uisp,transmission,syncthing,plex,nyaa # Phase 7 (infra/media)
# ,home-assistant # Phase 8 (smart home)
# ,invoiceninja,fabdash,cpas,wfh # Phase 9 (business ops)
# ,breedr,codedump,ui-tracker,stepview,qrknit,memer,alwisp-web # Phase 10 (personal/niche)
# ─── Auth ───────────────────────────────────────────────
# Format: agentName:token,agentName2:token2
AGENT_TOKENS=claude-code:TOKEN1,antigravity:TOKEN2,codex:TOKEN3
# ─── Gitea ──────────────────────────────────────────────
GITEA_HOST=https://git.alwisp.com
GITEA_TOKEN=<pat>
# ─── Unraid ─────────────────────────────────────────────
UNRAID_HOST=http://10.2.0.2
UNRAID_API_KEY=<key>
# ─── OpenClaw / NOVA ────────────────────────────────────
OPENCLAW_HOST=http://10.2.0.26:18789
# ─── UniFi Access ───────────────────────────────────────
UNIFI_HOST=https://<controller-ip>
UNIFI_API_KEY=<key>
UNIFI_SITE_ID=<id>
# ─── CODEX MRP ──────────────────────────────────────────
CODEX_DB_PATH=/mnt/user/appdata/codex/db.sqlite
# ─── StreamVault ────────────────────────────────────────
STREAMVAULT_HOST=http://streamvault:3100
# ─── RackMapper ─────────────────────────────────────────
RACKMAPPER_HOST=http://rackmapper:3200
# ─── Chronicle (DEFERRED) ───────────────────────────────
# CHRONICLE_HOST=http://chronicle:3003
# CHRONICLE_TOKEN=<bearer>
# ─── Obsidian (DEFERRED) ────────────────────────────────
# OBSIDIAN_REST_HOST=http://obsidian:27123
# OBSIDIAN_API_KEY=<key>
# ─── Phase 7: Infrastructure & Media ────────────────────
NPM_HOST=http://10.2.0.3:81
NPM_EMAIL=<email>
NPM_PASSWORD=<password>
UISP_HOST=https://10.2.0.4:443
UISP_TOKEN=<token>
TRANSMISSION_HOST=http://10.2.0.5:9091
TRANSMISSION_USER=<user>
TRANSMISSION_PASS=<password>
SYNCTHING_HOST=http://10.2.0.2:8384
SYNCTHING_API_KEY=<key>
PLEX_HOST=http://10.2.0.2:32400
PLEX_TOKEN=<token>
NYAA_HOST=http://10.2.0.21
NYAA_API_KEY=<key>
# ─── Phase 8: Smart Home ────────────────────────────────
HA_HOST=https://10.2.0.12:8123
HA_TOKEN=<long-lived-access-token>
# ─── Phase 9: Business Operations ───────────────────────
INVOICENINJA_HOST=http://10.2.0.2:8000
INVOICENINJA_TOKEN=<token>
FABDASH_HOST=http://10.2.0.13:8080
FABDASH_TOKEN=<token>
CPAS_HOST=http://10.2.0.14:3001
CPAS_TOKEN=<token>
WFH_HOST=http://10.2.0.18:3000
WFH_TOKEN=<token>
# ─── Phase 10: Personal & Niche ─────────────────────────
BREEDR_HOST=http://10.2.0.17
BREEDR_TOKEN=<token>
CODEDUMP_HOST=http://10.2.0.34
CODEDUMP_TOKEN=<token>
UITRACKER_HOST=http://10.2.0.29
UITRACKER_TOKEN=<token>
STEPVIEW_HOST=http://10.2.0.33:3000
STEPVIEW_TOKEN=<token>
QRKNIT_HOST=http://10.2.0.9:5000
QRKNIT_TOKEN=<token>
MEMER_HOST=http://10.2.0.30:3000
MEMER_TOKEN=<token>
ALWISP_WEB_HOST=http://10.2.0.8:80
ALWISP_WEB_TOKEN=<token>
Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json tsconfig.json ./
RUN npm ci
COPY src/ ./src/
COPY prisma/ ./prisma/
RUN npx prisma generate
RUN npm run build
FROM node:20-alpine
RUN addgroup -S mcp && adduser -S mcp -G mcp
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/prisma ./prisma
COPY package.json ./
USER mcp
EXPOSE 8811
HEALTHCHECK --interval=30s --timeout=10s --start-period=15s \
CMD wget -qO- http://localhost:8811/health || exit 1
CMD ["node", "dist/server.js"]
Risk Register
| Risk | Mitigation |
|---|---|
| Docker socket exposure | Plugin scoped; consider read-only mount once stable |
| Plugin crash brings down gateway | All plugin calls wrapped in try/catch; registry isolates failures |
| Claude Code SSE reconnect bug (v2.1.83+) | Use Streamable HTTP for Claude Code; SSE only for Antigravity |
| Gitea CI build fails silently | Add Gitea issue notification step to Actions workflow |
| Hot-reload duplicate tool registration | Registry calls onUnload() and removes old entry before re-import |
| br0 Unraid host routing quirk | All clients connect from workstations, not from the Unraid host itself |
| Deferred plugins partially configured | Chronicle and Obsidian env vars commented out until services are live |
Files to Generate Next
| File | Purpose | Priority |
|---|---|---|
AGENTS.md |
Multi-agent config reference, full tool catalog, env var docs | High |
README.md |
Overview, quick-start, Unraid deployment steps | High |
src/server.ts |
Core server bootstrap | Phase 0 |
src/registry.ts |
Plugin loader + hot-reload | Phase 0 |
src/types/plugin.ts |
MCPPlugin interface | Phase 0 |
src/transport/streamable.ts |
Streamable HTTP transport | Phase 0 |
src/transport/sse.ts |
SSE legacy transport | Phase 0 |
src/auth/bearer.ts |
Per-agent token validation | Phase 0 |
Dockerfile |
Production image | Phase 0 |
docker-compose.yml |
Local dev stack | Phase 0 |
src/plugins/gitea/index.ts |
Gitea connector | Phase 1 |
src/plugins/unraid/index.ts |
Unraid API connector | Phase 1 |
src/plugins/docker/index.ts |
Docker socket connector | Phase 2 |
src/plugins/openclaw/index.ts |
OpenClaw/Ollama connector | Phase 2 |
config/catalog.yaml |
Plugin registry config | Phase 4 |
.gitea/workflows/build.yml |
Gitea Actions CI pipeline | Phase 4 |
Upgrade Policy
- Patch (1.0.x): Bug fixes, no plugin API changes — always safe to update
- Minor (1.x.0): New gateway capabilities, backward-compatible plugin API additions
- Major (x.0.0): Breaking plugin API changes — dual-support period, migration guide in
CHANGELOG.md - Plugins declare
minGatewayVersion— incompatible plugins are skipped with a warning log, never crash the server CHANGELOG.mdis updated with every release