844 lines
35 KiB
Markdown
844 lines
35 KiB
Markdown
# 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:** 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 → /app/data` |
|
||
| Env File | `/mnt/user/appdata/totalmcp/.env` |
|
||
| Docker Socket | `/var/run/docker.sock → /var/run/docker.sock` |
|
||
| Restart Policy | `unless-stopped` |
|
||
| Pids Limit | `2048` |
|
||
|
||
---
|
||
|
||
### Phase 1 — Gitea + Unraid Connectors
|
||
**Goal:** Agents can query repos and Unraid system state.
|
||
**Est:** 1–2 days
|
||
**Prerequisite:** Phase 0 complete
|
||
|
||
#### `plugins/gitea/index.ts`
|
||
- Auth: `GITEA_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:** 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:<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:** 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:<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 1–3 (core)
|
||
ENABLED_PLUGINS=gitea,unraid,docker,openclaw,unifi,codex-mrp,streamvault,rackmapper
|
||
# Add Phase 7+ as plugins land:
|
||
# ,npm,uisp,transmission,syncthing,plex,nyaa # Phase 7 (infra/media)
|
||
# ,home-assistant # Phase 8 (smart home)
|
||
# ,invoiceninja,fabdash,cpas,wfh # Phase 9 (business ops)
|
||
# ,breedr,codedump,ui-tracker,stepview,qrknit,memer,alwisp-web # Phase 10 (personal/niche)
|
||
|
||
# ─── Auth ───────────────────────────────────────────────
|
||
# Format: agentName:token,agentName2:token2
|
||
AGENT_TOKENS=claude-code:TOKEN1,antigravity:TOKEN2,codex:TOKEN3
|
||
|
||
# ─── Gitea ──────────────────────────────────────────────
|
||
GITEA_HOST=https://git.alwisp.com
|
||
GITEA_TOKEN=<pat>
|
||
|
||
# ─── Unraid ─────────────────────────────────────────────
|
||
UNRAID_HOST=http://10.2.0.2
|
||
UNRAID_API_KEY=<key>
|
||
|
||
# ─── OpenClaw / NOVA ────────────────────────────────────
|
||
OPENCLAW_HOST=http://10.2.0.26:18789
|
||
|
||
# ─── UniFi Access ───────────────────────────────────────
|
||
UNIFI_HOST=https://<controller-ip>
|
||
UNIFI_API_KEY=<key>
|
||
UNIFI_SITE_ID=<id>
|
||
|
||
# ─── CODEX MRP ──────────────────────────────────────────
|
||
CODEX_DB_PATH=/mnt/user/appdata/codex/db.sqlite
|
||
|
||
# ─── StreamVault ────────────────────────────────────────
|
||
STREAMVAULT_HOST=http://streamvault:3100
|
||
|
||
# ─── RackMapper ─────────────────────────────────────────
|
||
RACKMAPPER_HOST=http://rackmapper:3200
|
||
|
||
# ─── Chronicle (DEFERRED) ───────────────────────────────
|
||
# CHRONICLE_HOST=http://chronicle:3003
|
||
# CHRONICLE_TOKEN=<bearer>
|
||
|
||
# ─── Obsidian (DEFERRED) ────────────────────────────────
|
||
# OBSIDIAN_REST_HOST=http://obsidian:27123
|
||
# OBSIDIAN_API_KEY=<key>
|
||
|
||
# ─── Phase 7: Infrastructure & Media ────────────────────
|
||
NPM_HOST=http://10.2.0.3:81
|
||
NPM_EMAIL=<email>
|
||
NPM_PASSWORD=<password>
|
||
|
||
UISP_HOST=https://10.2.0.4:443
|
||
UISP_TOKEN=<token>
|
||
|
||
TRANSMISSION_HOST=http://10.2.0.5:9091
|
||
TRANSMISSION_USER=<user>
|
||
TRANSMISSION_PASS=<password>
|
||
|
||
SYNCTHING_HOST=http://10.2.0.2:8384
|
||
SYNCTHING_API_KEY=<key>
|
||
|
||
PLEX_HOST=http://10.2.0.2:32400
|
||
PLEX_TOKEN=<token>
|
||
|
||
NYAA_HOST=http://10.2.0.21
|
||
NYAA_API_KEY=<key>
|
||
|
||
# ─── Phase 8: Smart Home ────────────────────────────────
|
||
HA_HOST=https://10.2.0.12:8123
|
||
HA_TOKEN=<long-lived-access-token>
|
||
|
||
# ─── Phase 9: Business Operations ───────────────────────
|
||
INVOICENINJA_HOST=http://10.2.0.2:8000
|
||
INVOICENINJA_TOKEN=<token>
|
||
|
||
FABDASH_HOST=http://10.2.0.13:8080
|
||
FABDASH_TOKEN=<token>
|
||
|
||
CPAS_HOST=http://10.2.0.14:3001
|
||
CPAS_TOKEN=<token>
|
||
|
||
WFH_HOST=http://10.2.0.18:3000
|
||
WFH_TOKEN=<token>
|
||
|
||
# ─── Phase 10: Personal & Niche ─────────────────────────
|
||
BREEDR_HOST=http://10.2.0.17
|
||
BREEDR_TOKEN=<token>
|
||
|
||
CODEDUMP_HOST=http://10.2.0.34
|
||
CODEDUMP_TOKEN=<token>
|
||
|
||
UITRACKER_HOST=http://10.2.0.29
|
||
UITRACKER_TOKEN=<token>
|
||
|
||
STEPVIEW_HOST=http://10.2.0.33:3000
|
||
STEPVIEW_TOKEN=<token>
|
||
|
||
QRKNIT_HOST=http://10.2.0.9:5000
|
||
QRKNIT_TOKEN=<token>
|
||
|
||
MEMER_HOST=http://10.2.0.30:3000
|
||
MEMER_TOKEN=<token>
|
||
|
||
ALWISP_WEB_HOST=http://10.2.0.8:80
|
||
ALWISP_WEB_TOKEN=<token>
|
||
```
|
||
|
||
---
|
||
|
||
## Dockerfile
|
||
|
||
```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
|
||
|