# MCP Gateway — Bifrost (migrated 2026-05-26) MCP servers are now aggregated by **Bifrost** at `https://ai.nuclide.systems/mcp`. The legacy FastAPI DinD mcp-gateway (`mcp.nuclide.systems`) is pending decommission. ## Architecture - **Stack**: `ai/bifrost/` on CT 104 — Bifrost LLM+MCP gateway, SQLite state at `data/config.db`. - **Endpoint**: `https://ai.nuclide.systems/mcp` (Zoraxy → `192.168.1.40:14003`) - **Auth**: Virtual Key (`sk-bf-` prefix) via `Authorization: Bearer `. - **Client model**: Each upstream MCP server is a registered client in Bifrost (`auth_type=none`, `allow_on_all_virtual_keys=true`). Tools are auto-discovered and enabled via `tools_to_execute_json`. - **Internal upstreams**: All child MCP containers run on the `ai-internal` Docker network at `http://-mcp:8000/mcp` (streamable-HTTP) or as dedicated stacks. ## Virtual keys | Name | Key prefix | Use | |---|---|---| | `claude-code` | `sk-bf-bfc19117-4c46-4d48-9b10-85d78b1ae2b3` | Claude Code + Claude.ai | | `mcp-dev` | `sk-bf-279d3ecc-9031-41ff-a582-ecf3dca61d52` | Testing / dev | ## Server inventory (as of 2026-05-26) **29 connected clients, ~760 tools total.** | Client name | Upstream | Notes | |---|---|---| | `bluesky` | `http://ariel-mcp:8000/mcp` | | | `coder` | `http://coder-mcp:8000/mcp` | | | `comfyui` | `http://comfyui-mcp:8000/mcp` | Intel Arc image gen | | `context7` | `http://mcp-context7:8000/mcp` | | | `crawl4ai` | `http://mcp-crawl4ai:11235/mcp/sse` (SSE) | | | `docling` | `http://docling-mcp:8000/mcp` | PDF→Markdown | | `fetch` | `http://mcp-fetch:8000/mcp` | | | `git` | `http://mcp-git:8000/mcp` | | | `gitea` | `http://gitea-mcp:8000/mcp` | | | `gitlab` | `http://mcp-gitlab:8000/mcp` | `GITLAB_API_URL=https://gitlab.dlr.de/api/v4` | | `gotify` | `http://mcp-gotify:8000/mcp` | | | `home_assistant` | `http://192.168.1.60:9583/private_ehnWeRl2G3De6NnbcN7teQ` | HA add-on; no TLS | | `immich` | `http://mcp-immich:8000/mcp` | | | `kroki` | `http://kroki-mcp:8000/mcp` | Diagram rendering | | `markitdown` | `http://mcp-markitdown:8000/mcp` | | | `memos` | `http://mcp-memos:8000/mcp` | | | `nextcloud` | `http://mcp-nextcloud:8000/mcp` | | | `ntfy` | `http://mcp-ntfy:8000/mcp` | | | `obsidian` | `http://mcp-obsidian:8000/mcp` | Vault at Nextcloud/UNAS | | `paper_search` | `http://mcp-paper-search:8000/mcp` | | | `paperless` | `http://paperless-mcp:8000/mcp` | | | `proxmox` | `http://mcp-proxmox:8000/mcp` | Read-only (`PVEAuditor`) | | `searxng` | `http://mcp-searxng:8000/mcp` | | | `sequential_thinking` | `http://mcp-sequential-thinking:8000/mcp` | | | `time` | `http://mcp-time:8000/mcp` | | | `unifi` | `http://mcp-unifi:8000/mcp` | | | `upload_artifact` | `http://upload-artifact-mcp:8000/mcp` | S3 via Garage | | `wikipedia` | `http://mcp-wikipedia-mcp:8000/mcp` | | | `youtube_transcript` | `http://mcp-youtube-transcript:8000/mcp` | | ### Not yet connected | Client | Reason | |---|---| | `n8n` — `https://n8n.nuclide.systems/mcp-server/http` | Streamable-HTTP transport: POST returns SSE stream, Bifrost HTTP client times out. | | `shepard` — `https://shepard.nuclide.systems/v2/mcp` | Same streamable-HTTP issue. Bearer token stored in `/tmp/migrate_mcp_oauth.py`. | ## Client configuration ### Claude Code Add to `~/.claude.json` or project `.mcp.json`: ```json { "mcpServers": { "nuclide": { "type": "http", "url": "https://ai.nuclide.systems/mcp", "headers": { "Authorization": "Bearer sk-bf-bfc19117-4c46-4d48-9b10-85d78b1ae2b3" } } } } ``` ### Claude.ai Settings → Integrations → Add MCP server: - URL: `https://ai.nuclide.systems/mcp` - Header: `Authorization: Bearer sk-bf-279d3ecc-9031-41ff-a582-ecf3dca61d52` ## Ops ```bash # On CT 104 cd /opt/stacks/ai/bifrost docker compose up -d --force-recreate docker logs bifrost -f # Inspect config DB sqlite3 data/config.db '.tables' sqlite3 data/config.db 'SELECT name, auth_type, allow_on_all_virtual_keys FROM config_mcp_clients;' # Count active tools via API curl -s http://localhost:14003/mcp \ -H "Authorization: Bearer sk-bf-279d3ecc-9031-41ff-a582-ecf3dca61d52" \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | python3 -m json.tool | grep '"name"' | wc -l ``` ### Add a new MCP client ```bash curl -s -X POST http://localhost:14003/api/mcp/client \ -H "Authorization: Bearer sk-tapirnase" \ -H "Content-Type: application/json" \ -d '{ "name": "", "connection_type": "http", "connection_string": "http://:8000/mcp", "auth_type": "none", "allow_on_all_virtual_keys": true }' ``` Wait ~10 s for tool discovery, then enable tools via `PUT /api/mcp/client/` with `tools_to_execute`. See `/tmp/add_all_mcp_clients.py` on CT 104 for a complete example. ### Key requirements for tools to appear in `/mcp tools/list` 1. `auth_type=none` (not `per_user_oauth`) 2. `allow_on_all_virtual_keys=true` 3. `tools_to_execute_json` populated (tool names without client prefix) 4. VK must have `sk-bf-` prefix ## Legacy mcp-gateway (FastAPI / DinD) **Status: pending decommission.** Container `mcp-gateway` still running at `192.168.1.40:8080` / `https://mcp.nuclide.systems`. Stack: `ai/mcp-gateway/`. Before removing: 1. Confirm Zoraxy route `mcp.nuclide.systems` is no longer referenced anywhere (explicit user confirmation required before touching Zoraxy CT 108). 2. Stop container: rename compose file to `docker-compose.yml.disabled`, update `/opt/stacks/CLAUDE.md`. 3. Remove Pocket-ID client `e73bb7b9` (mcp-gateway) and `78c78998` (Claude MCP callback to mcp.nuclide.systems) after confirming no active tokens. 4. Remove Zoraxy route `mcp.nuclide.systems` → `192.168.1.40:8080`. ## Bifrost OIDC client (Pocket ID) Used for `per_user_oauth` flows (not currently active — all clients use `auth_type=none`): | Field | Value | |---|---| | Client ID | `ec0d15e6-e86d-49b0-ac12-cdfb5afc9086` | | Authorize URL | `https://id.nuclide.systems/authorize` | | Token URL | `https://id.nuclide.systems/api/oidc/token` | | `mcp_external_client_url` | `https://ai.nuclide.systems` |