Files

848 lines
35 KiB
Markdown
Raw Permalink Normal View History

2026-05-09 22:18:00 -05:00
# 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<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)
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:** 12 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` |
2026-05-09 23:10:59 -05:00
| 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`) |
2026-05-09 22:18:00 -05:00
| Restart Policy | `unless-stopped` |
2026-05-09 23:10:59 -05:00
| 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.
2026-05-09 22:18:00 -05:00
---
### Phase 1 — Gitea + Unraid Connectors
**Goal:** Agents can query repos and Unraid system state.
**Est:** 12 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 <CLAUDE_CODE_TOKEN>"
```
**Antigravity** (`mcp_config.json`):
```json
{
"mcpServers": {
"totalmcp": {
"url": "http://10.2.0.35:8811/sse",
"transport": "sse",
"headers": { "Authorization": "Bearer <ANTIGRAVITY_TOKEN>" }
}
}
}
```
**Codex:**
```json
{
"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:** 12 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:** 23 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:** 23 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:** 12 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:** 23 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:<port>`, `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:** 12 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:** 23 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:** 23 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_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 13 (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
```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