Files
docs/services/mcp-gateway.md
T
fkrebs c3c40dc07b docs: add model management UI, catalog API, and 4K improvements to mcp-gateway
- New /ui/models page (sortable model catalog + service assignments)
- model_assignments.json: 35-model catalog with latency/cost/capability metadata
- API: GET/PATCH /api/model-assignments
- Model evaluator agent added to agents.json
- ui.html: widen max-width to 1600px, 4K breakpoints at 1920/2560/3840px

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 02:19:28 +02:00

8.3 KiB

MCP Gateway v2 — Operational

DinD MCP gateway on CT 104. Single OAuth-gated entry point fronting 24 MCP servers (mix of static, spawned, and Docker-catalog-bridged). Public via Zoraxy at https://mcp.nuclide.systems (→ 192.168.1.40:8080).

Architecture

  • Stack: ai/mcp-gateway/ — FastAPI app, container mcp-gateway.
  • Spawn model: Docker-in-Docker. Each spawnable server runs as mcp-<name> on the ai-internal network with no host ports — servers talk over internal DNS.
  • Transport bridge: generic streaming reverse proxy https://mcp.nuclide.systems/<server>/<path>http://<upstream>/<path>. Transport-agnostic (streamable-HTTP /mcp and legacy SSE /sse + /messages). Registered last so specific routes win.
  • Auth: Pocket ID. Two paths off the same OIDC app:
    • MCP clients / API / proxyBearer token, validated via OIDC userinfo (300 s TTL cache). 401 carries WWW-Authenticate + RFC 9728 /.well-known/oauth-protected-resource for discovery.
    • Browser Web UI — Authorization-Code flow with httpOnly session cookie. /ui and /api/* accept cookie or Bearer; /{srv}/ stays Bearer-only.
  • Secrets: per-server OAuth client secrets live in ai/.env (read via os.environ), never hardcoded.

Server inventory (as of 2026-05-22)

26 servers configured in mcp-gateway/config.json. Status: 27 healthy.

Group Server Kind Notes
dev context7 catalog mcp/context7
fetch spawn mcp-server-fetch via uvx
git catalog mcp/git — Docker MCP Catalog; mcp-git bridge + DinD child
gitlab catalog mcp/gitlabGITLAB_API_URL=https://gitlab.dlr.de/api/v4; requires --pass-environment in bridge cmd
kroki static kroki-mcp:8000 (own stack)
markitdown catalog mcp/markitdown
sequential-thinking catalog mcp/sequentialthinking
time spawn mcp-server-time
docling static docling-mcp:8000
coder static coder-mcp:8000
shepard static https://shepard.nuclide.systems/v2/mcp
research crawl4ai spawn unclecode/crawl4ai
paper-search catalog mcp/paper-search — Docker MCP Catalog; replaces old python:3.12-slim inline approach
searxng spawn isokoliuk/mcp-searxng
wikipedia-mcp catalog
youtube-transcript catalog mcp/youtube-transcript
personal bluesky static ariel-mcp:8000
obsidian spawn mcp-obsidian-bridge + mcpvault; vault at Nextcloud fkrebs@nucli.de/files/Notizen (UNAS mount)
gotify spawn
home-assistant static http://192.168.1.60:9583/private_... (HA add-on)
immich spawn
memos spawn uses MEMOS_TOKEN from .env
n8n static https://n8n.nuclide.systems/mcp-server/http
nextcloud spawn ghcr.io/cbcoutinho/nextcloud-mcp-serversingle_user_basic mode, NEXTCLOUD_VERIFY_SSL=false; app-password generated via occ for fkrebs@nucli.de 2026-05-22
unifi spawn
image comfyui static comfyui-mcp:8000 (own stack, see comfyui.md)
storage upload-artifact static upload-artifact-mcp:8000

Catalog bridge env var passthrough: mcp-proxy's stdio_client uses a sanitized env (get_default_environment()), stripping custom vars. For catalog servers that need env vars (e.g. gitlab), the bridge command must include --pass-environment so Docker CLI can resolve -e KEY passthrough from the bridge container's env.

Nextcloud server count: 27 total (26 enabled + nextcloud now re-enabled 2026-05-22). Gateway updated to persist enabled flag across restarts.

Client registration

Easy path — /mcp-config download

The gateway hands back a ready-to-use config blob for the signed-in user:

GET https://mcp.nuclide.systems/mcp-config?format=claude   # default
GET https://mcp.nuclide.systems/mcp-config?format=cursor
GET https://mcp.nuclide.systems/mcp-config?format=raw      # token + URLs
  • Requires Pocket-ID session cookie or existing Bearer (303 → /login otherwise).
  • Mints a fresh long-lived gateway token (mcp_<urlsafe>) bound to the user; persisted in gateway_tokens.json with purpose=client-config:<format> so it's distinguishable from OAuth-issued tokens and revocable.
  • Returns JSON with Content-Disposition: attachment.

Claude Code workflow:

  1. Sign in at https://mcp.nuclide.systems.
  2. Click Claude config in the top nav → browser downloads nuclide-mcp.claude.json.
  3. Merge the mcpServers block into ~/.claude/settings.json. Restart Claude Code.

The downloaded file has one entry per enabled MCP server, with type: http + url: https://mcp.nuclide.systems/<server>/mcp + Authorization: Bearer ... header. No per-server config needed.

Manual

LobeChat — Settings → Skills → Skill Store → Custom → Import JSON:

{ "mcpServers": { "comfyui": { "type": "http",
  "url": "https://mcp.nuclide.systems/comfyui/mcp",
  "auth": { "type": "bearer", "accessToken": "<pocket-id token>" } } } }

(For ComfyUI specifically, LobeChat can also connect straight to http://comfyui-mcp:8000/mcp internally — see comfyui.md — no token.)

Claude.ai — add https://mcp.nuclide.systems/<server>/mcp as a custom connector; auth is discovered via the protected-resource metadata.

Token rotation

Each /mcp-config download mints a NEW token. Old tokens stay valid until removed from gateway_tokens.json (manual prune; future: admin UI button).

Ops

cd /opt/stacks/ai/mcp-gateway && docker compose up -d --build
docker logs mcp-gateway

UI: https://mcp.nuclide.systems/ui (Pocket-ID-gated; /login to sign in).
Models UI: https://mcp.nuclide.systems/ui/models — sortable model catalog + service assignment overrides.
Model assignments data: mcp-gateway/model_assignments.json (bind-mounted rw). API: GET /api/model-assignments, PATCH /api/model-assignments/{service_key}.

Config: mcp-gateway/config.json (live config, persisted). Timestamped backups created on every edit (config.json.bak.YYYYMMDD-HHMMSS).

Add or enable a server

  1. Edit mcp-gateway/config.json — add an mcpServers.<name> entry (or set enabled: true).
  2. Restart the gateway: docker restart mcp-gateway.
  3. Spawn servers come up on demand via POST /api/servers/<name>/start (bearer); or POST /api/servers/bulk/start for all.

Disable a server

Set mcpServers.<name>.enabled = false and add disabled_reason. Stop+rm the container (docker stop mcp-<name> && docker rm mcp-<name>); the gateway will not re-spawn it.

UI check (Playwright)

Live UI is auth-gated, so the local check serves the template + mocks /api/servers:

uv run --with playwright -- python -m playwright install chromium   # once
uv run --with playwright -- python ai/mcp-gateway/ui_check.py        # -> /tmp/mcp_ui.png

Model management (added 2026-05-23)

model_assignments.json tracks the model each AI-consuming service uses, plus the full 35-model catalog with capability metadata (vision, tools, context window, latency, cost tier).

Services tracked: karakeep_text, karakeep_image, paperless_ai, lobechat_default, nextcloud_assistant, home_assistant_voice.

Catalog models: 35 (trimmed from 58 on 2026-05-23; dropped paid API models superseded by free SAIA equivalents).

Model evaluator agent in agents.json — can be triggered via POST /api/agents/model-evaluator/run to audit assignments vs SAIA free baseline.

Env file writes: the PATCH endpoint updates model_assignments.json and attempts to rewrite the service's .env file in-place. Because host paths (e.g. /opt/stacks/karakeep/.env) are not bind-mounted into the gateway container, env_written will be false — apply manually and docker compose up -d --force-recreate <service>.

Verified end-to-end

  • /health 200 (no auth); /.well-known/oauth-protected-resource JSON (no auth).
  • /mcp.json, /<server>/mcp → 401 without token; correct WWW-Authenticate.
  • Static upstreams (bluesky, coder, comfyui, docling, kroki, upload-artifact) — endpoints reachable from inside ai-internal (406/405 to plain GET, which is the correct MCP behavior).
  • All spawn/catalog containers up (docker ps mcp-*): 16/17 (nextcloud disabled; git/gitlab/paper-search added 2026-05-22).