Skip to main content
Glama

mcp-gw

An aggregating MCP (Model Context Protocol) gateway that runs as a Docker container. Claude Code connects to one endpoint and gets tools from all registered backend MCP servers combined.

Architecture

Claude Code
    │  HTTP MCP (localhost:3000/mcp)
    ▼
┌─────────────────────────────┐
│       mcp-gateway           │  ← Alpine Docker container
│  ┌─────────┐ ┌──────────┐  │
│  │ MCP srv │ │Dashboard │  │
│  └────┬────┘ └──────────┘  │
│       │  mcp-gw-net         │
└───────┼─────────────────────┘
        │
        ├── backend-a:8080          ← Docker container on mcp-gw-net
        ├── backend-b:8080          ← Docker container on mcp-gw-net
        └── 10.10.7.22:8123        ← External host (e.g. Home Assistant)

Tools from all backends are aggregated and namespaced as backend__tool_name so Claude Code sees them in a single tool list.

Related MCP server: Multi MCP

Quick Start

git clone https://github.com/gpimthong/mcp-gw
cd mcp-gw
docker compose up -d

The gateway starts on port 3000.

Connect Claude Code

Add to ~/.mcp.json:

{
  "mcpServers": {
    "gateway": {
      "type": "http",
      "url": "http://localhost:3000/mcp"
    }
  }
}

Restart Claude Code to pick up the change.

Homelab Deployment

First-time setup on a new machine

config/backends.json is gitignored (to prevent credentials from leaking). You must create it manually on every fresh clone:

mkdir -p config
cat > config/backends.json << 'EOF'
{
  "backends": []
}
EOF

Then start the gateway and register your backends via the dashboard or API.

Persistent config across restarts

The config/ directory is bind-mounted into the container (see docker-compose.yml). Any backends you register via the dashboard or API are written to config/backends.json on the host and survive container restarts automatically.

Network topology

The gateway runs on a dedicated bridge network mcp-gw-net (172.22.0.0/16). Backend containers must be on this network. External hosts (like Home Assistant on your LAN) are reachable directly by IP — no network joining needed for those.

Adding Backend MCP Servers

Docker container backends

The container must be on the mcp-gw-net network:

# Attach an existing container
docker network connect mcp-gw-net <container-name>

# Or start a new backend directly on the network
docker run -d --name my-mcp --network mcp-gw-net my-mcp-image

Then register it:

curl -X POST http://localhost:3000/api/backends \
  -H 'Content-Type: application/json' \
  -d '{"name":"my-mcp","url":"http://my-mcp:8080/mcp","transport":"http","enabled":true}'

External host backends

Backends that run outside Docker (on a LAN IP, VM, or another machine) are registered by IP. No network setup needed — the gateway container reaches them over the host network.

curl -X POST http://localhost:3000/api/backends \
  -H 'Content-Type: application/json' \
  -d '{"name":"my-service","url":"http://192.168.1.50:8080/mcp","transport":"http","enabled":true}'

Authenticated backends (Bearer token / custom headers)

Pass a headers object to send arbitrary HTTP headers on every request to a backend. This is how you authenticate against services like Home Assistant:

curl -X POST http://localhost:3000/api/backends \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "my-service",
    "url": "http://192.168.1.50:8080/mcp",
    "transport": "sse",
    "enabled": true,
    "headers": {
      "Authorization": "Bearer <your-token-here>"
    }
  }'

The headers field is optional. Without it, requests are sent unauthenticated.

Home Assistant Setup

Home Assistant's built-in MCP server is exposed at /mcp_server/sse and uses SSE transport. It requires a long-lived access token.

Get a long-lived access token

  1. Open Home Assistant → Profile (bottom-left avatar)

  2. Scroll to Long-Lived Access TokensCreate Token

  3. Give it a name (e.g. mcp-gateway) and copy the token

Register Home Assistant as a backend

curl -X POST http://localhost:3000/api/backends \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "homeassistant",
    "url": "http://<HA_IP>:8123/mcp_server/sse",
    "transport": "sse",
    "enabled": true,
    "headers": {
      "Authorization": "Bearer <your-long-lived-token>"
    }
  }'

Replace <HA_IP> with your Home Assistant host IP (e.g. 10.10.7.22) and <your-long-lived-token> with the token from the previous step.

Resulting tools in Claude Code

Once connected, Home Assistant tools appear namespaced as homeassistant__<tool>, for example:

  • homeassistant__HassTurnOn

  • homeassistant__HassTurnOff

  • homeassistant__HassSetVolume

Dashboard

The dashboard at http://localhost:3000 shows:

  • Backend cards — connection status, tool count, latency, last ping

  • Live request log — real-time stream of every tool call with duration and status

  • Tool catalog — all tools from all backends, expandable by backend

Use the + Add Backend button to register new backends, or click Remove / Reconnect on each card.

REST API

Method

Path

Description

GET

/api/health

Status, version, uptime, tool count

GET

/api/backends

List all backends with state

POST

/api/backends

Add / reconnect a backend

DELETE

/api/backends/:name

Remove a backend

POST

/api/backends/:name/reconnect

Force reconnect

GET

/api/logs

Last 200 log entries

GET

/api/logs/stream

SSE live log stream

Tool Namespacing

Backend memory with tool search_notes → exposed to Claude Code as memory__search_notes.

The double-underscore separator avoids collisions between backends. The gateway parses the prefix at call time to route to the right backend client.

Transport Support

Backend transport

Config value

MCP Streamable HTTP

"transport": "http"

MCP SSE (legacy)

"transport": "sse"

Home Assistant uses SSE. Most modern MCP servers use Streamable HTTP.

config/backends.json

This file is gitignored because it typically contains auth tokens. It is created and maintained at runtime by the gateway — every dashboard or API action writes through to this file.

Template for a fresh deployment:

{
  "backends": []
}

Start from the empty template and re-register your backends after cloning. The config/ directory is bind-mounted so the file persists across docker compose down / up cycles.

Development

Requires Node.js 22+.

npm install
npm run dev        # tsx hot-reload
npm run build      # compile TypeScript → dist/

Config is read from ./config/backends.json in dev mode (set CONFIG_PATH env var to override).

Versioning

Version is set in package.json. The gateway advertises it in the MCP initialize handshake and at /api/health. Git tags follow v<semver> (e.g. v0.1.0).

Project Structure

src/
  index.ts            — entry point
  version.ts          — reads version from package.json
  types.ts            — shared interfaces
  logger.ts           — circular log buffer + SSE emitter
  config.ts           — load/save backends.json
  backend-registry.ts — MCP client connections + heartbeat
  tool-aggregator.ts  — namespace + route tool calls
  mcp-server.ts       — MCP HTTP server (session management)
  dashboard.ts        — Express app (REST API + SSE)
public/
  index.html          — dashboard SPA (no build step)
config/
  backends.json       — persisted backend definitions (gitignored)
A
license - permissive license
-
quality - not tested
A
maintenance

Maintenance

Maintainers
Response time
Release cycle
1Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/gpimthong/mcp-gw'

If you have feedback or need assistance with the MCP directory API, please join our Discord server