Files
docs/services/mcp-gateway.md
T

4.6 KiB

MCP Gateway v2 — Operational

Finished implementation of the DinD MCP gateway (Option A). Supersedes the "Phase 1" stub described in mcp-gateway-requirements.md.

What it is

ai/mcp-gateway/ — FastAPI app, container mcp-gateway, public via Zoraxy at https://mcp.nuclide.systems (→ 192.168.1.40:8080). Single OAuth-gated entrypoint fronting several MCP servers.

Architecture (as built)

  • Spawn model: Docker-in-Docker. Each spawnable server runs as mcp-<name> on the ai-internal network with no host ports (fixes the old 8000:8000 collision — servers talk over internal DNS).
  • Transport bridge: generic streaming reverse proxy https://mcp.nuclide.systems/<server>/<path>http://<upstream>/<path>. Transport-agnostic — works for 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 (300s TTL cache). 401 carries WWW-Authenticate + RFC 9728 /.well-known/oauth-protected-resource for discovery.
    • Browser Web UI — full Authorization-Code flow with an httpOnly session cookie: unauthenticated /ui302 /login → Pocket ID authorize (CSRF state cookie) → /sso/callback (state-checked code→token exchange) sets the mcp_session cookie and shows the page + a copyable Bearer token for MCP clients. /logout clears it. The UI and /api/* accept either the cookie or a Bearer header; the /{srv}/ proxy stays Bearer-only. /health + the well-known metadata are open.
  • Secrets: all per-server OAuth client secrets come from ai/.env (env_file: ../.env), read via os.environ — none hardcoded in source.

Server registry (SERVERS in server.py)

name kind upstream notes
comfyui static http://comfyui-mcp:8000 runs as its own stack (ai/comfyui-mcp.yml); gateway only proxies
nextcloud spawn mcp-nextcloud:8000 ghcr.io/cbcoutinho/nextcloud-mcp-server streamable-http
crawl4ai spawn mcp-crawl4ai:11235 unclecode/crawl4ai
mermaid spawn mcp-mermaid:8000 node:20-slim + npx mcp-mermaid (validated working)
markitdown spawn mcp-markitdown:8000 ghcr.io/astral-sh/uv + uvx markitdown-mcp
papersearch spawn mcp-papersearch:8000 python + uv + mcp-proxy → paper_search_mcp

Verified

  • /health 200 (no auth); /.well-known/oauth-protected-resource JSON (no auth).
  • /mcp.json, /<server>/mcp → 401 without token; 401 with bad token (userinfo path); correct WWW-Authenticate.
  • Upstream path proven: gateway → comfyui-mcp /mcp initialize → 200; gateway → spawned mcp-mermaid /mcp → 200 (spawn pattern + proxy).
  • Route precedence correct (specific routes beat the catch-all proxy).

Not yet exercised (needs a real token / operational bring-up)

  • Token-authenticated end-to-end requires an interactive Pocket ID login. Get a token: open https://mcp.nuclide.systems/sso/callback via the OAuth authorize flow (or any Pocket ID token with scopes openid,mcp); the callback page prints the access token.
  • Bring up the 4 remaining spawnable servers (nextcloud/crawl4ai/ markitdown/papersearch): POST /api/servers/<name>/start (bearer) or POST /api/servers/bulk/start. Each may need a transport/flag tweak on first run — mermaid is the proven reference.

Client registration

  • LobeChat: Settings → Skills → Skill Store → Custom → Import JSON, e.g.
    { "mcpServers": { "comfyui": { "type": "http",
      "url": "https://mcp.nuclide.systems/comfyui/mcp",
      "auth": { "type": "bearer", "accessToken": "<pocket-id token>" } } } }
    
    (Or, for ComfyUI specifically, connect LobeChat straight to http://comfyui-mcp:8000/mcp internally — see comfyui-mcp.md — no token.)
  • Claude.ai: add https://mcp.nuclide.systems/<server>/mcp as a custom connector; it will discover auth via the protected-resource metadata.

Ops

  • cd /opt/stacks/ai/mcp-gateway && docker compose up -d --build
  • Logs: docker logs mcp-gateway; UI: https://mcp.nuclide.systems/ui (Pocket-ID-gated; /login to sign in).
  • Config overlay persisted in ai/mcp-gateway/config.json.
  • UI check (Playwright) — the live UI is auth-gated, so the check serves the local template + mocks /api/servers, screenshots, and asserts cards render:
    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