Skip to content

Hosting and Facade ​

The three MCP servers run together in a Docker Compose stack managed in DCC-BS/dcc-mcp-facade.

Architecture ​

Request flow:

client ──HTTPS──> nginx (TLS termination)
                       │ proxy_pass (preserves Host header)
                       ▼
                   Traefik (in-cluster reverse proxy)
                       │ routes by Host header
          ┌────────────┼──────────────────────────┐
          ▼            ▼                          ▼
   mcp.data.bs.ch  mcp-grosserrat.statistik.bs.ch  mcp.statistik.bs.ch
   ogd container   grossrat container              statistik container
   (mcp-data-bs)   (grossrat-bs-mcp)               (mcp-statistikportal)
  • nginx is external to the compose project: it terminates TLS for each domain and forwards traffic to the Traefik container over the shared Docker bridge network (mcp).
  • Traefik (pinned to v3.6.1+ for Docker v29+ API compatibility) routes incoming requests strictly by their Host header to the respective service container. Any unrouted host receives a 404.

Services ​

ServiceContainerImageRole
ogdmcp-ogdghcr.io/dcc-bs/mcp-data-bs:latestMCP server for data.bs.ch
grossratmcp-grossratghcr.io/dcc-bs/grossrat-bs-mcp:latestMCP server for the Grosser Rat Basel-Stadt
statistikmcp-statistikghcr.io/dcc-bs/mcp-statistikportal:latestMCP server for statistik.bs.ch
reverse-proxymcp-reverse-proxytraefik:v3.6.1Hostname-based reverse proxy

Configuration & Environment ​

Configuration is controlled via .env:

bash
cp .env.example .env
VariableDescriptionExample
DOCKER_SOCKETDocker daemon socket for Traefik's Docker provider/var/run/docker.sock (rootful) or /run/user/<UID>/docker.sock (rootless)
TRAEFIK_PORTPublished host port for the Traefik proxy8001 (default)

Allowed Hosts (MCP_ALLOWED_HOSTS) ​

The MCP HTTP servers include DNS-rebinding protection by default. Because nginx forwards a bare Host header (e.g., Host: mcp.data.bs.ch without a port), MCP_ALLOWED_HOSTS in the container configuration must be an exact match:

yaml
MCP_ALLOWED_HOSTS: "mcp.data.bs.ch"

Do not use wildcards behind nginx

Do not configure mcp.data.bs.ch:*. That pattern matches only Host headers containing a port number; a bare host from nginx will fail with 421 Invalid Host header.

Operations ​

Start the stack:

bash
docker compose up -d

Verify status and healthchecks:

bash
docker compose ps
docker compose logs reverse-proxy

To update the grossrat server with a newer data release:

bash
docker compose pull grossrat && docker compose up -d grossrat

Local Testing ​

Test endpoints locally by providing the target Host header directly to Traefik's port:

bash
# Health checks
curl -H "Host: mcp.data.bs.ch" http://localhost:8001/healthz
curl -H "Host: mcp-grosserrat.statistik.bs.ch" http://localhost:8001/healthz
curl -H "Host: mcp.statistik.bs.ch" http://localhost:8001/healthz

# Test MCP initialize
curl -N \
  -H "Host: mcp.data.bs.ch" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}}' \
  http://localhost:8001/mcp

Developed with ❤️ by the DCC. Documentation released under the MIT License.