Files
fkrebs a0f1707e33 decommission Wallabag: remove from portmap, databases, mcp-gateway docs
Wallabag replaced by crawl4ai in paywall-bypass n8n flow.
Container, DB, MCP server, and Bifrost client all removed 2026-05-28.
2026-05-28 00:31:13 +02:00

8.2 KiB

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 <vk>.
  • 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://<name>-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
open-webui sk-bf-7e6999fc-2d86-48f9-8ac9-0ba558893d87 Open WebUI (LITELLM_API_KEY in ai/.env)

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
n8nhttps://n8n.nuclide.systems/mcp-server/http Streamable-HTTP transport: POST returns SSE stream, Bifrost HTTP client times out.
shepardhttps://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:

{
  "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

LLM Inference & Governance

Bifrost proxies LLM inference at /v1 (OpenAI-compatible). enforce_auth_on_inference=1 — all /v1 calls require a valid sk-bf-* VK.

Providers

Provider Internal name Base URL Notes
SAIA (GPU cluster) openai https://chat-ai.academiccloud.de/v1 Rate limited (see below)
Google Gemini gemini default
Mistral AI mistral default
Cerebras cerebras default
claude-max-bridge openrouter http://claude-max-bridge:8000 Claude Opus/Sonnet/Haiku via max subscription

SAIA rate limits

SAIA enforces per-account quotas. Bifrost is configured with a global provider-level limit (config_providers.rate_limit_id='saia-minute'):

Window SAIA limit Bifrost enforcement
Per minute 30 req active (saia-minute row)
Per hour 200 req row exists (saia-hour), not linked
Per day 1000 req row exists (saia-day), not linked
Per month 3000 req not trackable across restarts

Note: Bifrost only supports one rate limit window per provider. The minute window is linked because it provides burst protection. The saia-hour / saia-day rows are in governance_rate_limits and can be linked by updating config_providers SET rate_limit_id='saia-hour' if needed.

To change the active window:

sqlite3 /opt/stacks/ai/bifrost/data/config.db \
  "UPDATE config_providers SET rate_limit_id='saia-day' WHERE name='openai';"
docker compose -f /opt/stacks/ai/bifrost.yml up -d --force-recreate

Rate limit API: GET /api/governance/rate-limits is read-only. POST/PUT return 405. Use direct SQLite to create new entries.

Virtual key governance

All VKs (governance_virtual_key_provider_configs) have:

  • allow_all_keys=1 — set via SQL (Bifrost API PUT silently ignores this field)
  • allowed_models — explicit JSON model list as text in DB (SQL NULL = deny all with enforce_auth_on_inference=1)

If models stop working after a Bifrost upgrade/restore, re-run /tmp/fix_vk_final.py on CT 104 and then:

sqlite3 /opt/stacks/ai/bifrost/data/config.db \
  "UPDATE governance_virtual_key_provider_configs SET allow_all_keys=1;"
docker compose -f /opt/stacks/ai/bifrost.yml up -d --force-recreate

Ops

# 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

curl -sc /tmp/bfcookies http://localhost:14003/api/session/login \
  -H "Content-Type: application/json" \
  -d '{"username":"fkrebs","password":"tapirnase"}' > /dev/null

curl -s -X POST http://localhost:14003/api/mcp/client \
  -b /tmp/bfcookies \
  -H "Content-Type: application/json" \
  -d '{
    "name": "<name>",
    "connection_type": "http",
    "connection_string": "http://<host>: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/<id> 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: DECOMMISSIONED 2026-05-26.

  • Zoraxy route removed (config renamed to .DECOMMISSIONED-2026-05-26 on CT 108)
  • Container stopped; compose file renamed to docker-compose.yml.DECOMMISSIONED-2026-05-26
  • Pocket-ID clients e73bb7b9 (litellm/mcp-gateway) and 78c78998 (Claude MCP) deleted from CT 110 DB

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