Files
jason fffa421511
Build and Push Docker Image / build (push) Successful in 31s
build lockfile
2026-05-09 23:10:59 -05:00

848 lines
35 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` |
| 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:** 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