# 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:** 1. **Upgradability** — new connectors drop in without touching core infrastructure or restarting the container 2. **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`. ```typescript // 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; // init: connect, test auth onUnload?: () => Promise; // cleanup: close connections, timers } export interface MCPTool { name: string; description: string; inputSchema: ZodSchema; handler: (input: unknown) => Promise; } ``` ### 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) 1. `mkdir src/plugins/my-service && touch src/plugins/my-service/index.ts` 2. Implement `MCPPlugin` interface, export as `default` 3. Add required env vars to `/mnt/user/appdata/totalmcp/.env` 4. Add `my-service` to `ENABLED_PLUGINS` in `.env` 5. 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` — dynamic `import()`, 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/data → /app/data` | | Logs | `/mnt/user/appdata/totalmcp/logs → /app/logs` | | Docker Socket | `/var/run/docker.sock → /var/run/docker.sock` (RW, required for Phase 2 docker plugin) | | CODEX DB | `/mnt/user/appdata/codex/db.sqlite → /app/codex/db.sqlite` (Phase 3 codex-mrp plugin) | | Variables | Set per-plugin env vars in the Unraid GUI (see `INSTALL-UNRAID.md`) | | Restart Policy | `unless-stopped` | | Pids Limit | `2048` (via Extra Parameters: `--pids-limit=2048`) | > **Full GUI walkthrough:** [`INSTALL-UNRAID.md`](INSTALL-UNRAID.md) — every field, env var, and verification step. --- ### 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_TOKEN` env var → `https://git.alwisp.com` REST API - Tools: - `gitea_list_repos` — list all repos - `gitea_get_repo` — get repo details - `gitea_create_repo` — create a new repo - `gitea_list_issues` — list issues for a repo - `gitea_create_issue` — open a new issue - `gitea_list_branches` — list branches - `gitea_get_file` — read a file's contents - `gitea_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 status - `unraid_list_containers` — all Docker containers + status - `unraid_get_container` — single container details - `unraid_list_shares` — user shares + usage - `unraid_list_vms` — VM list + state - `unraid_disk_health` — disk S.M.A.R.T. summary #### Agent Wiring (after Phase 1) **Claude Code:** ```bash claude mcp add --scope user --transport http totalmcp http://10.2.0.35:8811/mcp \ -H "Authorization: Bearer " ``` **Antigravity** (`mcp_config.json`): ```json { "mcpServers": { "totalmcp": { "url": "http://10.2.0.35:8811/sse", "transport": "sse", "headers": { "Authorization": "Bearer " } } } } ``` **Codex:** ```json { "mcpServers": { "totalmcp": { "url": "http://10.2.0.35:8811/mcp", "transport": "http", "headers": { "Authorization": "Bearer " } } } } ``` - [ ] 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 containers - `docker_start_container` — start by name or ID - `docker_stop_container` — stop by name or ID - `docker_restart_container` — restart by name or ID - `docker_get_logs` — tail N lines of logs - `docker_get_stats` — CPU + memory stats #### `plugins/openclaw/index.ts` - Auth: `OPENCLAW_HOST` env var → **NOVA** instance at `http://10.2.0.26:18789` - Tools: - `openclaw_chat` — chat completion via OpenClaw/Ollama - `openclaw_list_models` — list available models - `openclaw_get_model_info` — model details + context size > **Note:** The `OpenClaw`-named container in the Unraid Docker tab serves the `nova.alwisp.com` proxy entry — same image, "NOVA" is the persona name. The `donna.alwisp.com` instance at `10.2.0.28:18789` is 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 names - `unifi_get_door_status` — current lock state per door - `unifi_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 orders - `codex_get_work_order` — single WO detail + BOM - `codex_create_work_order` — create new WO - `codex_get_inventory` — inventory levels - `codex_list_boms` — bill of materials list #### `plugins/streamvault/index.ts` - Auth: `STREAMVAULT_HOST` (internal Docker network) - Tools: - `streamvault_list_jobs` — all download jobs + status - `streamvault_add_download` — queue a new download URL - `streamvault_get_job_status` — single job detail - `streamvault_cancel_job` — cancel/remove a job #### `plugins/rackmapper/index.ts` - Auth: `RACKMAPPER_HOST` (internal Docker network) - Tools: - `rackmapper_list_racks` — all racks - `rackmapper_get_rack` — rack layout with devices - `rackmapper_list_devices` — device inventory - `rackmapper_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, version - `POST /plugins/:name/reload` — hot-reload a single plugin - `POST /plugins/:name/enable` — enable a disabled plugin - `POST /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 - [ ] **Semantic versioning for plugin API contracts** - Plugins declare `minGatewayVersion` - Registry checks compatibility at load time — skips with warning, never crashes - [ ] **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/health` responds from within the Docker network - [ ] Obtain Chronicle bearer token #### `plugins/chronicle/index.ts` - Auth: `CHRONICLE_HOST`, `CHRONICLE_TOKEN` - Tools: - `memory_store` — persist a memory entry - `memory_recall` — retrieve by key or semantic query - `memory_search` — full-text + semantic search across entries - `memory_list_projects` — browse memories by project scope - `memory_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:27123` is 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 path - `obsidian_note_read` — read note contents by path - `obsidian_note_update` — overwrite note contents - `obsidian_note_append` — append text to a note - `obsidian_note_search` — search notes by query - `obsidian_list_vault` — list notes in a folder **To activate either deferred connector once ready:** 1. Prerequisites above are complete 2. `touch src/plugins/chronicle/index.ts` (or `obsidian`) and implement 3. Add env vars to `/mnt/user/appdata/totalmcp/.env` 4. Add to `ENABLED_PLUGINS` 5. 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 entries - `npm_get_proxy_host` — single entry detail - `npm_create_proxy_host` — new hostname → backend mapping - `npm_update_proxy_host` — change backend / SSL settings - `npm_delete_proxy_host` — remove entry - `npm_renew_cert` — force Let's Encrypt renewal - `npm_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 devices - `uisp_get_device_status` — per-device state, CPU, RAM - `uisp_list_sites` — managed sites - `uisp_get_link_quality` — radio link RSSI / capacity / errors - `uisp_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 + status - `transmission_add_torrent` — magnet or URL - `transmission_pause_torrent` / `transmission_resume_torrent` - `transmission_remove_torrent` — with optional data deletion - `transmission_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 detail - `syncthing_list_devices` — paired devices + connection state - `syncthing_pause_folder` / `syncthing_resume_folder` - `syncthing_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 libraries - `plex_recently_added` — N most recently added items - `plex_now_playing` — active sessions - `plex_server_status` — version, transcoder activity, plugins #### `plugins/nyaa/index.ts` - Auth: `NYAA_HOST=http://10.2.0.21:`, `NYAA_API_KEY` (if applicable) - Tools: - `nyaa_list_watches` — active search/watch rules - `nyaa_add_watch` — new auto-download rule - `nyaa_remove_watch` — delete a rule - `nyaa_get_recent_matches` — last N triggered downloads - `nyaa_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 state - `ha_get_state` — single entity state + attributes - `ha_call_service` — invoke any HA service (`light.turn_on`, `automation.trigger`, etc.) - `ha_list_automations` — all configured automations - `ha_trigger_automation` — fire an automation by entity ID - `ha_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 dedicated `matter` plugin 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 range - `invoiceninja_get_invoice` — single invoice detail - `invoiceninja_create_invoice` — new invoice from line items - `invoiceninja_send_invoice` — email an invoice - `invoiceninja_list_clients` — client directory - `invoiceninja_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 schedule - `fabdash_list_active_jobs` — jobs in flight - `fabdash_machine_status` — utilization per machine - `fabdash_create_job` — new shop job - `fabdash_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 infractions - `cpas_get_employee_score` — running point total - `cpas_log_violation` — record new infraction - `cpas_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 team - `wfh_get_employee_log` — single employee daily log - `wfh_submit_form` — file required HR/timesheet form - `wfh_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:`, `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:`, `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:`, `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_PLUGINS` selectively — 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 needed - `MariaDB`, `postgresql16`, `Redis` — services own their own DBs; no direct DB plugin - `Gitea-Runner` — managed via the Gitea plugin - `bx (client UISP)` — third-party trust boundary; do not target --- ## Environment Variables Full reference for `/mnt/user/appdata/totalmcp/.env`: ```dotenv # ─── 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= # ─── Unraid ───────────────────────────────────────────── UNRAID_HOST=http://10.2.0.2 UNRAID_API_KEY= # ─── OpenClaw / NOVA ──────────────────────────────────── OPENCLAW_HOST=http://10.2.0.26:18789 # ─── UniFi Access ─────────────────────────────────────── UNIFI_HOST=https:// UNIFI_API_KEY= UNIFI_SITE_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= # ─── Obsidian (DEFERRED) ──────────────────────────────── # OBSIDIAN_REST_HOST=http://obsidian:27123 # OBSIDIAN_API_KEY= # ─── Phase 7: Infrastructure & Media ──────────────────── NPM_HOST=http://10.2.0.3:81 NPM_EMAIL= NPM_PASSWORD= UISP_HOST=https://10.2.0.4:443 UISP_TOKEN= TRANSMISSION_HOST=http://10.2.0.5:9091 TRANSMISSION_USER= TRANSMISSION_PASS= SYNCTHING_HOST=http://10.2.0.2:8384 SYNCTHING_API_KEY= PLEX_HOST=http://10.2.0.2:32400 PLEX_TOKEN= NYAA_HOST=http://10.2.0.21 NYAA_API_KEY= # ─── Phase 8: Smart Home ──────────────────────────────── HA_HOST=https://10.2.0.12:8123 HA_TOKEN= # ─── Phase 9: Business Operations ─────────────────────── INVOICENINJA_HOST=http://10.2.0.2:8000 INVOICENINJA_TOKEN= FABDASH_HOST=http://10.2.0.13:8080 FABDASH_TOKEN= CPAS_HOST=http://10.2.0.14:3001 CPAS_TOKEN= WFH_HOST=http://10.2.0.18:3000 WFH_TOKEN= # ─── Phase 10: Personal & Niche ───────────────────────── BREEDR_HOST=http://10.2.0.17 BREEDR_TOKEN= CODEDUMP_HOST=http://10.2.0.34 CODEDUMP_TOKEN= UITRACKER_HOST=http://10.2.0.29 UITRACKER_TOKEN= STEPVIEW_HOST=http://10.2.0.33:3000 STEPVIEW_TOKEN= QRKNIT_HOST=http://10.2.0.9:5000 QRKNIT_TOKEN= MEMER_HOST=http://10.2.0.30:3000 MEMER_TOKEN= ALWISP_WEB_HOST=http://10.2.0.8:80 ALWISP_WEB_TOKEN= ``` --- ## Dockerfile ```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.md` is updated with every release