Skip to main content
Glama
README.md
# Universal MCP Gateway

A self-hosted **Model Context Protocol (MCP)** gateway that aggregates all your MCP servers behind a single Streamable HTTP endpoint. Includes automatic registry discovery, on-demand Docker provisioning, and multi-device support.

## Features

- **Single Endpoint** — All MCP servers exposed via one URL (`/all` for universal, `/mcp` for legacy)
- **Registry Auto-Broker** — Mirrors the official MCP Registry (19,000+ servers), provisions on demand in isolated Docker containers
- **Multi-Device** — Control server, laptop, and Windows PC from one gateway via SSH
- **SSE Keepalive** — Prevents Cloudflare/proxy idle timeouts on streaming connections
- **OAuth2 PKCE** — Optional OAuth2 authorization code flow with PKCE support
- **Workflow Engine** — Save and replay multi-step tool sequences
- **Bearer Auth** — Simple token-based authentication

## Architecture

```
                    mcp.yourdomain.com (:8798)
                           │
                    ┌──────▼──────┐
                    │ MCP Router  │  Path-based routing
                    │  router.mjs │  /all → Universal, /mcp → Legacy
                    └──────┬──────┘
                           │
              ┌────────────┼────────────┐
              │            │            │
    ┌─────────▼──┐  ┌─────▼────┐  ┌──▼──────────┐
    │ Universal  │  │ Registry │  │  Child MCPs │
    │ Gateway    │  │ Autobroker│  │ (remote +   │
    │ gateway.mjs│  │ registry- │  │  stdio)     │
    └────────────┘  │ manager   │  └─────────────┘
                    └───────────┘
```

## Quick Start

```bash
# Clone
git clone https://github.com/Samuel-Mencke/mcp-gateway.git
cd mcp-gateway

# Install dependencies
npm install

# Configure
cp .env.example .env
# Edit .env — set MCP_PUBLIC_URL and generate a token

# Generate auth token
echo -n "$(openssl rand -hex 32)" > ~/.mcp-gateway/token

# Start the gateway
node gateway.mjs    # Port 8799
# In another terminal:
node router.mjs     # Port 8798 (public-facing)
```

## Configuration

All configuration is via environment variables. See [`.env.example`](.env.example) for all options.

### Key Variables

| Variable | Default | Description |
|----------|---------|-------------|
| `MCP_PORT` | `8799` | Universal Gateway listen port |
| `MCP_ROUTER_PORT` | `8798` | Router listen port |
| `MCP_PUBLIC_URL` | `http://localhost:8798` | Your public URL (domain/tunnel) |
| `MCP_STATE_DIR` | `~/.mcp-gateway` | State directory (registry, tokens, etc.) |
| `MCP_BEARER_TOKEN` | (from `$MCP_STATE_DIR/token`) | Auth token |

### Multi-Device (Optional)

Set these to enable SSH-based remote control:

```bash
# Windows PC
MCP_PC_HOST=windows-host
MCP_PC_USER=username

# Linux laptop
MCP_LAPTOP_HOST=laptop-host
MCP_LAPTOP_USER=username
```

## Default MCP Servers

The gateway ships with these servers enabled by default:

- **Context7** — Current library documentation
- **Exa** — Web search and fetch
- **MCP Docs** — Official MCP documentation
- **GitHub** — Official GitHub MCP server (requires `gh auth`)
- **Playwright** — Browser automation
- **Chrome DevTools** — Debugging and performance
- **Filesystem** — Sandboxed file access
- **Memory** — Persistent knowledge graph
- **Sequential Thinking** — Structured planning

Additional servers can be provisioned on demand from the official MCP Registry via `ensure_capability`.

## Registry Auto-Broker

The gateway mirrors the complete [official MCP Registry](https://registry.modelcontextprotocol.io) and can provision any supported server on demand:

```
Agent: "I need PostgreSQL schema inspection"
Gateway: Searches registry → provisions postgres-mcp → probes handshake → ready
```

Provisioned servers run in restricted Docker containers:
- Read-only root filesystem
- No host mounts
- Dropped capabilities
- CPU/RAM/PID limits
- Blocked private/Tailscale egress

## Deployment

### systemd

```ini
# ~/.config/systemd/user/mcp-router.service
[Unit]
Description=MCP Router
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
WorkingDirectory=%h/mcp-gateway
Environment=MCP_ROUTER_PORT=8798
ExecStart=/usr/bin/node %h/mcp-gateway/router.mjs
Restart=always
RestartSec=5

[Install]
WantedBy=default.target
```

```ini
# ~/.config/systemd/user/mcp-universal.service
[Unit]
Description=MCP Universal Gateway
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
WorkingDirectory=%h/mcp-gateway
EnvironmentFile=%h/mcp-gateway/.env
ExecStart=/usr/bin/node %h/mcp-gateway/gateway.mjs
Restart=always
RestartSec=5
TimeoutStopSec=20

[Install]
WantedBy=default.target
```

### Cloudflare Tunnel

No port-forwarding needed — use a Cloudflare Tunnel:

```yaml
# ~/.cloudflared/config.yml
ingress:
  - hostname: mcp.yourdomain.com
    service: http://127.0.0.1:8798
    originRequest:
      noTLSVerify: true
      connectTimeout: 30s
      keepAliveConnections: 100
      keepAliveTimeout: 600s
```

## Client Configuration

The gateway works with any MCP-compatible client:

| Client | Config |
|--------|--------|
| Claude Code | `type: "url", url: "https://mcp.yourdomain.com/all"` |
| OpenAI Codex | `type: "streamable-http", url: "https://mcp.yourdomain.com/all"` |
| Cursor | `url: "https://mcp.yourdomain.com/all", transport: "streamable-http"` |
| Hermes Agent | `type: "streamable-http", url: "https://mcp.yourdomain.com/all"` |
| OpenCode | `type: "streamable-http", url: "https://mcp.yourdomain.com/all"` |

All clients need: `headers: { Authorization: "Bearer <your-token>" }`

## Files

| File | Description |
|------|-------------|
| `router.mjs` | Path-based router, OAuth2, SSE keepalive |
| `gateway.mjs` | Universal MCP gateway server |
| `registry-manager.mjs` | Registry sync, auto-provisioning, AGENTS.md generation |
| `workflow-store.mjs` | Durable workflow persistence |
| `server.mjs` | Legacy ChatGPT connector (admin/SSH tools) |
| `windows-runner.py` | Windows desktop automation runner |

## Requirements

- Node.js >= 18
- Docker (for registry auto-provisioning)
- SSH access (for multi-device support)

## License

MIT