# totalmcp Unified MCP gateway for Jason's ALPHA stack. One Dockerized server, hot-reloadable plugin architecture, three agent clients (Claude Code, Codex, Antigravity) all connecting to one stable endpoint. - **Port:** `8811` - **Static IP:** `10.2.0.35` (Unraid `br0`) - **Registry:** `git.alwisp.com/jason/totalmcp` - **Spec:** see [`PLAN.md`](PLAN.md) for the full architecture and phased roadmap - **Service inventory:** see [`SERVICES.md`](SERVICES.md) for the catalog this gateway plugs into ## Quick Start (local dev) ```bash cp .env.example .env # then fill in tokens npm install npm run prisma:generate npm run dev # starts tsx watch on src/server.ts ``` Verify: ```bash curl http://localhost:8811/health # → { "status": "ok", "version": "0.1.0", "plugins": 0, "enabled": [] } ``` ## Build & run ```bash npm run build # tsc → dist/ npm start # node dist/server.js ``` ## Docker ```bash docker compose up --build -d docker compose logs -f totalmcp ``` ## Endpoints | Method | Path | Auth | Purpose | |--------|-------------|----------|--------------------------------------| | GET | `/health` | none | Liveness — used by Unraid HEALTHCHECK | | GET | `/plugins` | bearer | Loaded plugin list + tool counts | | POST | `/mcp` | bearer | Streamable HTTP — primary MCP transport | | GET | `/mcp` | bearer | Streamable HTTP server-sent stream | | DELETE | `/mcp` | bearer | Streamable HTTP session close | | GET | `/sse` | bearer | Legacy SSE (Antigravity) | | POST | `/message` | bearer | Legacy SSE message channel | ## Authoring a plugin Drop a directory under `src/plugins//index.ts` and `export default` an `MCPPlugin`. Tool names must be fully-qualified (`_`) and unique across all loaded plugins. ```typescript import { z } from "zod"; import type { MCPPlugin } from "../../types/plugin.js"; const plugin: MCPPlugin = { name: "example", version: "0.1.0", description: "Example plugin", minGatewayVersion: "0.1.0", tools: [ { name: "example_ping", description: "Returns pong", inputSchema: z.object({}), handler: async () => ({ result: "pong" }), }, ], }; export default plugin; ``` Then add `example` to `ENABLED_PLUGINS` in `.env`. In dev (`npm run dev`), chokidar picks up the change in ~2s. In prod, push to Gitea and let CI rebuild the image. Reference implementations live at [`src/plugins/gitea/`](src/plugins/gitea/index.ts) and [`src/plugins/unraid/`](src/plugins/unraid/index.ts). ## Repo layout ``` totalmcp/ ├── src/ │ ├── server.ts # Express bootstrap │ ├── registry.ts # Plugin loader + hot-reload │ ├── config.ts # Zod env validation │ ├── logger.ts # Structured JSON logger │ ├── types/plugin.ts # MCPPlugin interface │ ├── auth/bearer.ts # Per-agent token middleware │ ├── mcp/build.ts # MCP server wiring │ ├── transport/ │ │ ├── streamable.ts # Streamable HTTP transport │ │ └── sse.ts # Legacy SSE transport │ ├── util/http.ts # Shared HTTP helper (timeouts, JSON, errors) │ └── plugins/ │ ├── gitea/index.ts # Phase 1 — Gitea REST API │ ├── unraid/index.ts # Phase 1 — Unraid GraphQL API │ ├── docker/index.ts # Phase 2 — Docker socket (dockerode) │ ├── openclaw/index.ts # Phase 2 — OpenClaw/NOVA (Ollama-compatible) │ ├── unifi/index.ts # Phase 3 — UniFi Access (developer REST) │ ├── codex-mrp/index.ts # Phase 3 — CODEX MRP (direct SQLite) │ ├── streamvault/index.ts # Phase 3 — StreamVault download manager │ └── rackmapper/index.ts # Phase 3 — RackMapper datacenter inventory ├── prisma/schema.prisma # Event log schema ├── Dockerfile ├── docker-compose.yml ├── .env.example └── package.json ``` ## Roadmap See [`PLAN.md`](PLAN.md) for the full phased plan. Phase 0 (this scaffold) is intentionally minimal — empty registry, both transports respond, Docker image builds, container boots clean.