Files
docs/services/mcp-gateway.md
T
fkrebs 6309286b96 docs: Nextcloud assistant model config — occ method, current assignments
integration_openai points to LiteLLM at ai.nuclide.systems/v1.
Fixed completion=llama-3.3-70b-instruct, T2I=saia-flux (was devstral).
occ snippet for future model changes.

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

153 lines
8.9 KiB
Markdown

# 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 / proxy** — `Bearer` 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/gitlab``GITLAB_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-server``single_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](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:
```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](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
```bash
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`:
```bash
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>`.
**Nextcloud assistant** uses `integration_openai` app pointing at `https://ai.nuclide.systems/v1` (LiteLLM). No env file — config is in the Nextcloud DB, changed via `occ`:
```bash
# On CT 105 (192.168.1.41):
docker exec nextcloud-aio-nextcloud php occ config:app:set integration_openai default_completion_model_id --value=<model>
# Clear stale model cache after changes:
docker exec nextcloud-aio-nextcloud php occ config:app:delete integration_openai models
```
Current assignments (2026-05-23): completion=`llama-3.3-70b-instruct`, TTS=`tts-1-de`, STT=`whisper-large-v3-turbo`, T2I=`saia-flux`.
## 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).