Standalone Gateway Guide¶
Standalone Gateway Packaging¶
This document describes how to run the MCP-Fabric gateway as an independent, production-oriented process — separate from the demos, dashboard, and validation harnesses. It also records what shared-state backends are production-ready today versus future work.
What “standalone” means here¶
The gateway is a single Node process that:
accepts MCP client traffic over HTTP/SSE,
makes session placement and routing decisions, and
routes each request to a backend MCP server (remote over HTTP) or to an in-process demo application (self-contained evaluation mode).
It is packaged from the packages/ tree only and does not bundle backend MCP
servers. The entrypoint is
packages/gateway/bin/standalone-gateway.js,
exposed as:
npm run start:gateway
The entrypoint wires together already-tested modules (operatorConfigFromEnv,
createHttpSseGatewayServer, createRemoteHttpApplication). It introduces no
new routing, runtime-mode, or adaptive-placement behavior.
Topologies¶
The same entrypoint supports two topologies, selected by environment:
Topology |
When |
Backend |
|---|---|---|
Self-contained (evaluation) |
|
In-process demo application servers. Good for local smoke tests and single-host evaluation without standing up MCP servers. |
Fronting remote MCP servers (production-like) |
|
Routes to remote MCP servers over HTTP. Requires |
When REMOTE_BASE_URLS_JSON is set, SERVER_INSTANCES_JSON is required; the
entrypoint fails fast otherwise so a misconfigured gateway never silently falls
back to demo applications.
Quick start¶
Self-contained (binds the loopback interface; safe by default):
PORT=3000 HOST=127.0.0.1 npm run start:gateway
# -> {"type":"ready","kind":"standalone-gateway","host":"127.0.0.1","port":3000,"topology":"self-contained-demo"}
curl -s http://127.0.0.1:3000/health | jq .
Fronting two remote MCP servers with a shared Redis registry:
PORT=4400 HOST=0.0.0.0 \
MCP_GATEWAY_ALLOW_PUBLIC_BIND=true \
MCP_GATEWAY_ENFORCE_STARTUP_SECURITY_AUDIT=false \
MCP_GATEWAY_SESSION_REGISTRY_BACKEND=redis \
REDIS_URL=redis://redis:6379 \
SERVER_INSTANCES_JSON='[{"serverInstanceId":"fs-a","load":0.12,"healthy":true,"acceptingNewSessions":true},{"serverInstanceId":"fs-b","load":0.26,"healthy":true,"acceptingNewSessions":true}]' \
REMOTE_BASE_URLS_JSON='{"fs-a":"http://mcp-server-a:4101","fs-b":"http://mcp-server-b:4102"}' \
npm run start:gateway
Ports¶
The gateway publishes a single HTTP port (PORT, default 3000). All endpoints
below are served from that one port. There are no other listeners. Backend MCP
servers run on their own ports and are reached by the URLs in
REMOTE_BASE_URLS_JSON; the gateway does not open ports on their behalf.
Configuration (environment variables)¶
All operator configuration is read by operatorConfigFromEnv. Values are
validated at startup; invalid values fail fast.
Variable |
Purpose |
Default |
|---|---|---|
|
Gateway HTTP listen port (must be a positive integer) |
|
|
Bind address |
|
|
Demo instance count when no instances are supplied |
|
|
Per-instance load ceiling for routing (0–1) |
|
|
Cluster pressure threshold for the autoscaler hook (0–1) |
|
|
Session record TTL |
|
|
Reconnect grace window |
|
|
Disconnect policy: |
|
|
Allow binding |
|
|
Run the self-hijack probe on public bind |
|
|
|
|
|
File path when backend is |
— |
|
Redis hash key for session records |
|
|
Redis connection URL when backend is |
— |
|
Enable Phase 3 adaptive placement gate |
|
|
Comma-separated canary client IDs |
empty |
|
JSON array of routable backend instances |
— |
|
JSON object mapping |
— |
Each MCP_GATEWAY_* knob also accepts its MCP_OPERATOR_* alias (see
operatorConfigFromEnv).
Health and observability endpoints¶
All on the gateway’s single HTTP port:
Method + path |
Returns |
|---|---|
|
|
|
|
|
Operator config snapshot, observer summary counters (including adaptive-placement mismatch/fallback counters), and recent audit events. |
|
Upsert a backend instance (health, load, accepting-new-sessions). |
|
SSE event stream for a session. |
|
MCP message ingress ( |
|
Browser inspector page for manual session/SSE testing. |
There is no Prometheus exporter today; GET /observability is the structured
counter/event surface. Scraping or forwarding those counters is left to the
operator’s monitoring stack.
Security posture¶
The gateway has no built-in authentication. The startup security audit
(runStartupSecurityAudit) treats a 0.0.0.0/:: bind as public and:
fails closed unless
MCP_GATEWAY_ALLOW_PUBLIC_BIND=true, andwhen enforcement is on, runs a self-hijack probe and refuses to start if an unauthenticated request succeeds.
Therefore a publicly reachable gateway must sit behind an authenticating,
TLS-terminating reverse proxy. The container image keeps the secure default so a
naive docker run cannot expose an unauthenticated gateway by accident. To run
behind a trusted proxy/private network, set both
MCP_GATEWAY_ALLOW_PUBLIC_BIND=true and (because there is no first-party auth
for the probe to detect) MCP_GATEWAY_ENFORCE_STARTUP_SECURITY_AUDIT=false.