mcp-servers/mcp-gateway/README.md

4 KiB

MCP Gateway Stack

Aggregates multiple MCP backend servers behind a single Streamable HTTP endpoint with OAuth 2.1 authentication, exposed via Tailscale Funnel.

Architecture

claude.ai / Claude Mobile / Claude Code
        │
        │  OAuth 2.1 (PKCE + DCR)
        ▼
┌─────────────────────────────┐
│  MCP Gateway Proxy (:4444)  │  ← mcp.wilddragon.net via Tailscale Funnel
│  OAuth Provider + Aggregator│
└────┬──────────┬─────────┬───┘
     │          │         │
     ▼          ▼         ▼
  ERPNext    TrueNAS    Home
  MCP        MCP        Assistant
  (:32802)   (:8100)    MCP (:8200)

OAuth 2.1 Flow

When claude.ai connects to https://mcp.wilddragon.net/mcp:

  1. Gateway returns 401 with WWW-Authenticate header pointing to resource metadata
  2. Claude discovers /.well-known/oauth-protected-resource → finds authorization server
  3. Claude discovers /.well-known/oauth-authorization-server → finds all OAuth endpoints
  4. Claude calls /oauth/register (Dynamic Client Registration) to get a client_id
  5. Claude opens /oauth/authorize in browser → you see a consent page → enter your password
  6. Gateway issues an authorization code, redirects to Claude's callback
  7. Claude exchanges the code at /oauth/token (with PKCE verification) → gets access + refresh tokens
  8. Claude sends MCP requests to /mcp with Authorization: Bearer <token>
  9. Tokens auto-refresh via the refresh token grant

Setup

  1. Copy .env.example to .env and fill in your values
  2. Set a strong OAUTH_PASSWORD — this is what you type in the consent page
  3. Set OAUTH_ISSUER_URL to your public gateway URL (e.g., https://mcp.wilddragon.net)
  4. Build and start: docker compose up -d --build
  5. In claude.ai → Settings → Connectors → Add → paste https://mcp.wilddragon.net/mcp
  6. Complete the OAuth flow when prompted (enter your gateway password)

Environment Variables

Variable Required Default Description
OAUTH_ISSUER_URL Yes https://mcp.wilddragon.net Public URL of the gateway
OAUTH_PASSWORD Yes Password for the consent page
OAUTH_ACCESS_TOKEN_TTL No 3600 Access token lifetime (seconds)
OAUTH_REFRESH_TOKEN_TTL No 2592000 Refresh token lifetime (seconds)
ERPNEXT_URL Yes ERPNext instance URL
ERPNEXT_API_KEY Yes ERPNext API key
ERPNEXT_API_SECRET Yes ERPNext API secret
TRUENAS_URL Yes TrueNAS API URL
TRUENAS_API_KEY Yes TrueNAS API key
HASS_URL Yes Home Assistant URL
HASS_TOKEN Yes Home Assistant long-lived token

Endpoints

Endpoint Auth Purpose
GET /health None Health check
GET /status Bearer Detailed backend status
GET /.well-known/oauth-protected-resource None RFC 9728 resource metadata
GET /.well-known/oauth-authorization-server None RFC 8414 server metadata
POST /oauth/register None RFC 7591 dynamic client registration
GET /oauth/authorize None Authorization page (consent form)
POST /oauth/token None Token exchange / refresh
POST /mcp Bearer MCP JSON-RPC endpoint

Testing

# Health check
curl https://mcp.wilddragon.net/health

# Check OAuth metadata
curl https://mcp.wilddragon.net/.well-known/oauth-authorization-server

# Check resource metadata
curl https://mcp.wilddragon.net/.well-known/oauth-protected-resource

# Verify 401 on unauthenticated MCP request
curl -X POST https://mcp.wilddragon.net/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"initialize","params":{},"id":1}'

Adding New Backends

Add a new MCP_BACKEND_<NAME> env var to the gateway service in docker-compose.yml and rebuild. Tools will be auto-discovered and prefixed with the backend name.