Skip to main content
Glama
cociweb

MCP Gateway

by cociweb

MCP Gateway

A FastMCP 2.x-based gateway that aggregates multiple upstream Model Context Protocol servers (stdio, streamable-http, sse, websocket) behind a single streamable-HTTP endpoint. Upstream servers are configured declaratively in config/mcpServers.json, with per-server namespacing, tool/resource/prompt filtering, read-only enforcement, environment-variable interpolation, and automatic reload on config changes.

Architecture

gateway/
├── main.py             # entrypoint: builds & serves the gateway, wires reload
├── config.py            # GatewayConfig, loaded from environment variables
├── loader.py             # mcpServers.json parsing, env interpolation, validation
├── proxy.py              # builds the aggregated FastMCP server (mount per upstream)
├── namespace.py          # per-server middleware: toolFilter/resourceFilter/promptFilter, readOnly
├── reconnect.py           # retry-with-backoff helper used for upstream connectivity probes
├── reload.py              # watchdog-based watcher for mcpServers.json
├── health.py              # /health, /healthz, /metrics routes
├── logging.py             # logging setup
└── transports/
    ├── stdio.py            # spawns local processes (npx, uvx, ...)
    ├── streamable_http.py  # streamable-HTTP upstream client transport
    ├── sse.py              # SSE upstream client transport
    └── websocket.py        # WebSocket upstream client transport (custom, mcp SDK based)

Each enabled upstream server becomes a FastMCP proxy sub-server (FastMCPProxy with an explicit client_factory that builds a fresh transport per call, for concurrent-request isolation), gets a ServerFilterMiddleware attached (enforcing readOnly / toolFilter / resourceFilter / promptFilter), and is mounted onto the main gateway server with namespace=<namespace> when prefixTools is enabled. Tool/resource/prompt names are then automatically namespaced by FastMCP as {namespace}_{name}.

Configuration

config/mcpServers.json

{
  "mcpServers": {
    "filesystem": {
      "type": "stdio",
      "enabled": true,
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "${FS_ROOT:-/data}"],
      "env": {},
      "namespace": "fs",
      "readOnly": false,
      "prefixTools": true,
      "toolFilter": ["*"],
      "resourceFilter": ["*"],
      "promptFilter": ["*"],
      "timeout": 30
    }
  }
}

Field reference per server entry:

Field

Type

Description

type

stdio | streamable-http | sse | websocket

Upstream transport kind

enabled

bool

If false, the server is skipped entirely

url

string

Required for streamable-http / sse / websocket

command, args, env

-

Required for stdio (e.g. npx, uvx)

headers

object

Extra HTTP headers for streamable-http / sse

namespace

string

Prefix used for this server's tools/resources/prompts; defaults to the server's key

readOnly

bool

If true, no tools from this server are registered

prefixTools

bool

If true, tools become {namespace}_{tool}

toolFilter / resourceFilter / promptFilter

list of glob patterns

fnmatch-style; ["*"] = allow all

timeout

number

Per-call timeout (seconds) for the upstream connection probe

Any string value in the file supports ${VAR} / ${VAR:-default} interpolation, resolved against the process environment (.env + docker-compose.yml environment:). An unresolved variable without a default raises a configuration error for enabled servers; disabled server entries with unresolved variables are tolerated (logged as a warning).

Environment variables (.env / docker-compose.yml)

Variable

Default

Description

GATEWAY_LISTEN

0.0.0.0

Bind address (0.0.0.0, 127.0.0.1, localhost supported)

GATEWAY_PORT

5555

Bind port

GATEWAY_PATH

/mcp

HTTP path for the MCP streamable-HTTP endpoint

GATEWAY_RELOAD

true

Watch mcpServers.json and restart the process on change

GATEWAY_HEALTH_ENDPOINT

true

Enable /health and /healthz

GATEWAY_METRICS

true

Enable /metrics (Prometheus text format)

GATEWAY_LOG_LEVEL

info

debug | info | warning | error | critical

GATEWAY_PREFIX_TOOLS

true

Default for prefixTools when a server entry omits it

MCP_SERVERS_CONFIG

/app/config/mcpServers.json

Path to the servers config file

Running locally

python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
cp .env .env.local   # adjust as needed
export $(grep -v '^#' .env.local | xargs)
python -m gateway.main

The gateway will be reachable at http://localhost:5555/mcp, with http://localhost:5555/health and http://localhost:5555/metrics alongside it.

stdio upstream servers

stdio servers (npx, uvx, ...) are spawned as subprocesses inside the gateway process/container — no separate container or network hop is needed. Make sure node/npx and/or uv/uvx are available in the runtime environment (the provided Dockerfile installs both).

Docker Compose

docker compose up -d --build

This starts only the gateway service, on the mcp Docker network, mounting ./config read-only and loading ./.env. To use the published image instead of building locally:

docker pull ghcr.io/cociweb/mcp-gateway:latest
docker compose up -d

Reload behavior

When GATEWAY_RELOAD=true, a watchdog observer watches the directory containing mcpServers.json. On any change to that file, the gateway gracefully restarts itself in-place (os.execv) to rebuild the full mounted server tree (reconnect upstreams, reapply filters/prefixes). Streamable-HTTP clients are expected to reconnect automatically on disconnect.

Health & metrics

  • GET /health — JSON status, per-server connectivity and config summary.

  • GET /healthz — plain-text ok liveness probe.

  • GET /metrics — Prometheus-style text exposition (uptime, enabled/connected server counts, per-server connectivity gauge).

Development / testing

pip install -e ".[dev]"
pytest -q

tests/test_proxy_integration.py spins up a real npx-based MCP filesystem server and is skipped automatically if npx is not available.

CI / GHCR

.github/workflows/ghcr.yml builds a multi-platform (linux/amd64, linux/arm64) image and pushes it to ghcr.io/cociweb/mcp-gateway on pushes to main and on version tags.

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/cociweb/mcp-gateway'

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