Skip to main content
Glama
README.md
# MCP Hub — All-in-One MCP Server & Connector Ecosystem

Self-hosted hub that aggregates MCP (Model Context Protocol) connectors into a single
endpoint for your AI clients — with an App-Store-style web UI, one-click installs,
live logs and health monitoring.

```
Claude Desktop / Cursor ──Bearer key──▶  ┌─────────────────────┐
                                        │   MCP Hub (:3000)    │──▶ github (npx)
                                        │  /mcp   /api  Web UI │──▶ postgres (npx)
Web browser ──session cookie──▶         └─────────┬───────────┘──▶ slack (npx) …
                                                  ▼
                                            PostgreSQL 16
```

## Quick start

### A. Local / plain Docker

```bash
cp .env.example .env
# edit .env — set POSTGRES_PASSWORD (and optionally MASTER_ENCRYPTION_KEY / SESSION_SECRET)
docker compose up -d --build
```

### B. Portainer (web editor)

1. **Stacks → Add stack → Web editor** and paste [`portainer-stack.yml`](portainer-stack.yml).
2. Add an environment variable `POSTGRES_PASSWORD` (under **Environment variables**) before deploying.
3. Deploy — the image is pulled from Docker Hub (`xeniak10/mcp-hub:latest`), no build needed.

Open **http://<host>:3000** — on first visit you'll be asked to create the admin
account (bootstrap). Then:

1. Go to **Marketplace**, install e.g. *GitHub*, paste a token → the connector's child
   process starts in the background.
2. Go to **Settings** → create an API key.
3. Point your AI client at the hub:

```json
{
  "mcpServers": {
    "hub": {
      "url": "http://localhost:3000/mcp",
      "headers": { "Authorization": "Bearer mcp_YOUR_KEY" }
    }
  }
}
```

All connector tools appear namespaced as `github__create_issue`, `postgres__query`, …

## What you get

- **Marketplace** — 71 built-in connectors for developers (GitHub, GitLab, Postgres,
  Kubernetes, Sentry…) *and* business teams — CRM (Salesforce, HubSpot, Pipedrive),
  ERP/accounting (Odoo, QuickBooks, Xero), helpdesk (Zendesk, Freshdesk, ServiceNow),
  communication (Microsoft 365, Email IMAP/SMTP, Twilio, Slack, Teams files),
  productivity (Google Sheets, Notion, Airtable) and observability (Dynatrace).
  One-click install, schema-driven config form, background dependency startup via
  `npx`/`uvx` inside the hub container. Search, category filters and sorting
  (official first / most installed / name).
- **Community connector repos** — paste a public GitHub repo URL in the Marketplace
  ("Community repos" section) and every valid connector manifest in it is fetched,
  validated and merged into your catalog (`community:owner/repo/<id>` namespaced,
  cached offline in the data volume). An `index.json` at the repo root lists files
  explicitly; otherwise root-level `*.json` are probed. New entries appear after a
  hub restart.
- **Custom connectors** — drop a JSON manifest into `./connectors/` (or the
  `connectors` volume) and it appears in the marketplace after a restart. See
  [`connectors/example.json.example`](connectors/example.json.example).
- **Dashboard** — live status badges (running / starting / error / stopped), stats
  tiles, instance search, bulk **Restart all** / **Stop all** with confirmation.
- **Connector detail** — rename instances inline, visual config editor, live tool
  preview (`tools/list` through the running process) with click-to-copy tool names,
  real-time log viewer (WebSocket tail) with level/text filters, follow mode and
  log download.
- **Light & dark theme** — toggle in the sidebar; persisted per browser.
- **Keyboard shortcuts** — `/` focuses search · `g d` dashboard · `g m` marketplace ·
  `g s` settings · `Esc` closes dialogs/blurs search.
- **PWA-ready** — installable manifest + SVG favicon.
- **Backup & restore** — one-click export of all installed connectors + configs to a
  JSON file; import restores them on a fresh instance (Settings page).
- **Account management** — change password (signs out all other devices), list active
  sessions with IP/user-agent, revoke any session.
- **Failure alerts** — set `ERROR_WEBHOOK_URL` and the hub POSTs a JSON alert whenever a
  connector enters the error state (works with Slack, Discord, Teams, generic receivers;
  5-minute per-connector cooldown).
- **Watchdog** — crashed processes restart with exponential backoff; health ping every
  30 s; boot recovery restores everything that was enabled before restart.
- **Updates** — Settings shows your current version and checks GitHub releases /
  Docker Hub tags every 6 h (or on **Check now**). When a new version is published
  you get a one-click copy of the exact upgrade command for your deployment type.
- **Optional auto-update** — enable the Watchtower sidecar with
  `docker compose --profile autoupdate up -d`: it pulls newer `xeniak10/mcp-hub`
  images hourly and recreates only the app container (it is label-scoped, so it
  never touches your database).
- **Monitoring & ops** — unauthenticated `GET /healthz` for uptime probes;
  `GET /api/meta` reports version + git commit (shown in Settings footer);
  API-key traffic on `/mcp` is rate-limited to 120 requests/min per key.
- **Security** — AES-256-GCM encrypted configs at rest, argon2id passwords, cookie
  sessions, hashed API keys, login rate limiting, audit log.

## Configuration

| Variable | Required | Description |
| --- | --- | --- |
| `POSTGRES_PASSWORD` | ✅ | Password for the bundled PostgreSQL |
| `MASTER_ENCRYPTION_KEY` | – | `openssl rand -hex 32`. Auto-generated to `/app/data/master.key` if empty |
| `SESSION_SECRET` | – | Empty = random per boot (users get logged out on restart) |
| `APP_PORT` | – | Host port (default `3000`) |
| `LOG_RETENTION_LINES` | – | Ring buffer size per connector (default 5000) |
| `LOG_RETENTION_DAYS` | – | Log age limit (default 7) |
| `ERROR_WEBHOOK_URL` | – | URL to POST JSON alerts when a connector enters error state |

Volumes: `pgdata` (database), `app-data` (master key + state), `npx-cache`
(connector dependency cache — keeps restarts fast).

### Updating

The hub cannot replace its own container, so updates are a one-liner on the host:

```bash
docker compose pull app && docker compose up -d --no-deps --force-recreate app
```

(Settings → Updates copies this for you when a new version is available. On
Portainer use **Stack → Pull and redeploy**.) Want it fully hands-off?

```bash
docker compose --profile autoupdate up -d
```

That starts Watchtower next to the hub; every hour it checks Docker Hub and
recreates the app container when a newer image exists (`--cleanup` removes old
images). Your data lives in volumes, so updates are lossless.

## Development

```bash
npm install
npm run dev        # server (watch) + web (vite dev server on :5173, proxied /api + /mcp)
npm run typecheck  # all workspaces
npm run build      # shared → server → web
```

Stack: npm workspaces · Fastify 5 · `@modelcontextprotocol/sdk` · PostgreSQL 16 ·
Vite + React 18 + Tailwind CSS v4 + TanStack Query.

### Layout

```
packages/
  shared/   types shared by server & web (manifests, API DTOs, WS events)
  server/   Fastify app: auth, registry (connector manifests), ConnectorManager,
            unified /mcp proxy, log pipeline, SQL migrations
  web/      React SPA served statically by the server in production
```

### Adding a connector

**Without rebuilding** (recommended): copy
[`connectors/example.json.example`](connectors/example.json.example) to
`connectors/<your-id>.json`, fill in the manifest (command argv with `{env.NAME}`
placeholders + JSON-schema config form) and restart the hub. Invalid files are skipped
with a warning in the logs; custom ids that collide with built-ins are ignored.

**From a GitHub repo**: Marketplace → *Community repos* → paste `owner/repo` (or a full
github.com URL). The hub fetches an `index.json` from the repo root (array of file
paths) or falls back to root-level `*.json` files, validates each against the same
schema as volume manifests, and caches them under `community:owner/repo/<id>` ids —
so they can never shadow built-ins and keep working offline after the first fetch.
Manage/remove repos in the same section; changes apply on next restart.

**Built-in**: create `packages/server/src/registry/connectors/<id>.ts` exporting a
`ConnectorManifest` and register it in `src/registry/index.ts`. It appears in the
marketplace immediately after rebuild.

### REST API summary

| Endpoint | Auth | Description |
| --- | --- | --- |
| `GET /healthz` | – | Liveness probe (DB ping only, no details) |
| `POST /api/auth/bootstrap` · `login` · `logout` | –/session | First-run account, sign-in/out |
| `GET /api/meta` | session | `{version, commit}` of the running build |
| `POST /api/auth/change-password` | session | Change password; revokes all other sessions |
| `GET /api/auth/sessions` · `DELETE /:id` | session | List/revoke active sessions |
| `GET /api/marketplace` | session | Catalog entries with install counts |
| `GET/POST /api/community/repos` · `DELETE /:repo` | session | Manage community connector repos |
| `POST /api/connectors` · `PUT/DELETE /:id` | session | Install / rename / uninstall |
| `PUT /api/connectors/:id/config` | session | Save encrypted config |
| `POST /api/connectors/:id/enable·disable·restart` | session | Lifecycle |
| `POST /api/connectors/restart-all` · `stop-all` | session | Bulk operations |
| `GET /api/connectors/:id/tools` | session | Live tool list from the running process |
| `GET /api/logs/:id?after=` + WS stream | session | Log tail |
| `GET/POST /api/keys` · `DELETE /:id` | session | API key management |
| `GET /api/backup` · `POST /api/backup/restore` | session | Config export/import |
| `POST /mcp` | Bearer key or session | Unified MCP endpoint (120 req/min per key) |

All mutating endpoints write to the audit log.

## Notes

- Connectors run as **child processes inside the hub container** (`npx -y pkg@version`
  or `uvx`). No docker socket is mounted. Python-based connectors need `uvx` — it is
  installed into the runtime image.
- The MCP endpoint is **stateless Streamable HTTP** (`/mcp`) — any HTTP MCP client works.
- Resources are exposed under `connector://<slug>/<original-uri>`.