Self-hosting
Self-hostingbeta10 min

Self-hosting the tunnel relay

Run the mcpg-cloud-relay broker inside your own trust boundary — listeners, authentication (static tokens or control-plane entitlements), TLS and DNS, rate limits, and how tunnelling gateways dial in.

The relay (mcpg-cloud-relay) is the broker that answers a reverse tunnel: self-hosted gateways dial out to it, and it host-routes inbound MCP requests back down the owning tunnel. The managed relay on mcpg.cloud is the zero-ops option; run your own when you want the relay inside your own trust boundary — the honest way to keep the relay from seeing anything you don't want it to (see the e2ee note in the tunnels guide).

This guide is operator-facing: the listeners, how to authenticate dial-ins, TLS and DNS, and how gateways point at your relay.

What it runs

The relay is a single binary with four listeners:

text
   self-hosted gateway ──dials out (wss)──▶  :7845  agent-accept
                                                    │  registers the tunnel
   MCP client ──────────https──────────────▶  :8080  public MCP (host-routed)
   same-org gateway ──/federate/<name>──────▶  :8081  federation ingress
   ops / probes ───────────────────────────▶  :9090  /healthz /readyz /metrics
  • Agent-accept (:7845) — where gateways dial in. Authenticated; TLS when configured.
  • Public MCP (:8080) — public tunnels are reachable here, host-routed by the first DNS label (<id>.<zone>).
  • Federation ingress (:8081) — same-org gateways reach a private tunnel by name for reverse federation.
  • Ops (:9090) — health, readiness, and Prometheus metrics.

Authentication (fail-closed)

The relay refuses to boot without an authenticator — a dial-in is only registered after it's authorized. Pick one:

Static token grants (simple self-host)

Map bearer tokens to orgs. Each dialing gateway presents its token in MCPG_TUNNEL_TOKEN.

bash
MCPG_RELAY_AUTH_TOKENS="tok_acme:acme,tok_globex:globex" \
mcpg-cloud-relay

Control-plane entitlements (managed posture)

Point the relay at a control plane and it authorizes every dial-in against the org's plan — resolution, suspension, allowed exposure/trust modes, and the concurrent-tunnel quota — and meters relayed bytes. An org on the free plan is throttled past its plan's monthly tunnel-byte allowance, never stopped: the control plane tells the relay to slow its tunnels, and the relay holds them to one shared byte rate (MCPG_RELAY_THROTTLE_BYTES_PER_SEC, 64 KiB a second by default) until the next calendar month or an upgrade. A paid org is stopped when its bytes past the allowance reach what its monthly spend cap allows (tunnel_spend_cap_reached): the control plane refuses its new tunnels and closes the open ones. This is what the managed service runs.

bash
MCPG_RELAY_CP_URL=https://cp.internal.example.com \
MCPG_RELAY_CP_TOKEN=$SHARED_RELAY_CP_TOKEN \
mcpg-cloud-relay

MCPG_RELAY_CP_TOKEN must equal the control plane's MCPG_CP_TUNNEL_API_TOKEN. When MCPG_RELAY_CP_URL is set, the CP is authoritative and takes precedence over a static token map.

Insecure (local dev only)

bash
MCPG_RELAY_AUTH=insecure-allow-all MCPG_RELAY_INSECURE_ORG=dev mcpg-cloud-relay

Accepts every dial-in and attributes it to one org. Never in production.

TLS and DNS

  • Agent-accept TLS. Provide a cert + key so the tunnel backbone is encrypted end to end into the relay. Absent, the agent-accept listener is plaintext (valid only behind a TLS-terminating load balancer).

    bash
    MCPG_RELAY_TLS_CERT=/etc/relay/tls.crt \
    MCPG_RELAY_TLS_KEY=/etc/relay/tls.key \
    mcpg-cloud-relay
    
  • Public tunnel zone. Public tunnels are minted at https://<id>.<zone>/mcp, where the zone defaults to tunnels.mcpg.cloud. Set your own and put a wildcard TLS cert for *.<zone> on the public listener (typically at your ingress/load balancer):

    bash
    MCPG_RELAY_ZONE=tunnels.example.com mcpg-cloud-relay
    

    Point a wildcard DNS record *.tunnels.example.com at the public listener. Private tunnels mint no hostname, so they need no DNS.

Rate limits and timeouts

Edge protection is on by default; tune per deployment:

Env varDefaultPurpose
MCPG_RELAY_IP_RATE / MCPG_RELAY_IP_BURST50 / 100Per-IP token bucket on the public edge
MCPG_RELAY_TUNNEL_RATE / MCPG_RELAY_TUNNEL_BURST200 / 400Per-tunnel token bucket
MCPG_RELAY_FORWARD_TIMEOUT_SECS30Wait for a response head before 504 (the streamed SSE body is not bounded by this, and a throttled request body's wait for the byte rate does not count)
MCPG_RELAY_THROTTLE_BYTES_PER_SEC65536Bytes a second one throttled org's tunnels share; the control plane throttles a free-plan org past its monthly byte allowance. At least 1

Pointing gateways at your relay

On each tunnelling gateway, override the relay endpoint and, if your relay uses a private CA, trust it:

bash
export MCPG_TUNNEL_TOKEN=tok_acme                 # matches your relay's grant
export MCPG_TUNNEL_CA=/etc/mcpg/relay-ca.pem      # only if the relay uses a private CA
mcpg --config gw.yaml --tunnel wss://relay.example.com:7845

(--tunnel <wss-url> is shorthand for --tunnel --tunnel-relay <wss-url>.)

For a gateway that federates a private tunnel, point its gateway.server.tunnel_federation.relay_ingress_url at your relay's federation ingress (https://relay.example.com:8081). See the reverse-federation walkthrough.

Full environment reference

Env varFlagDefaultPurpose
MCPG_RELAY_AGENT_ADDR--agent-addr0.0.0.0:7845Agent-accept listener
MCPG_RELAY_PUBLIC_ADDR--public-addr0.0.0.0:8080Public MCP listener
MCPG_RELAY_FEDERATION_ADDR--federation-addr0.0.0.0:8081Federation ingress
MCPG_RELAY_OPS_ADDR--ops-addr0.0.0.0:9090Health / readiness / metrics
MCPG_RELAY_ZONE--zonetunnels.mcpg.cloudPublic URL zone (<id>.<zone>)
MCPG_RELAY_JSON_LOGS--json-logsoffJSON log output
MCPG_RELAY_AUTH_TOKENS--auth-tokens—Static token:org[,…] grants
MCPG_RELAY_AUTH--auth-mode—insecure-allow-all (dev only)
MCPG_RELAY_INSECURE_ORG--insecure-orgdevOrg under insecure-allow-all
MCPG_RELAY_CP_URL--cp-url—Control-plane URL (entitlement-aware auth)
MCPG_RELAY_CP_TOKEN--cp-token—Bearer to the CP tunnel API (required with cp-url)
MCPG_RELAY_TLS_CERT--tls-cert—Agent-accept TLS cert chain
MCPG_RELAY_TLS_KEY--tls-key—Agent-accept TLS key
MCPG_RELAY_IP_RATE / _IP_BURST--ip-rate / --ip-burst50 / 100Per-IP edge limit
MCPG_RELAY_TUNNEL_RATE / _TUNNEL_BURST--tunnel-rate / --tunnel-burst200 / 400Per-tunnel limit
MCPG_RELAY_FORWARD_TIMEOUT_SECS--forward-timeout-secs30Response-head timeout
MCPG_RELAY_THROTTLE_BYTES_PER_SEC--throttle-bytes-per-sec65536Throttled org's shared byte rate

High availability

The relay's tunnel registry is in memory, per replica: a tunnel is registered on the instance it dialed into, and the public/federation route resolves on that same instance. Running multiple replicas therefore needs sticky routing — a dialing gateway and the clients reaching its tunnel must land on the same replica (session affinity at the load balancer, or one relay per zone shard). Until shared-registry routing lands, size a single relay for your tunnel fan-out and scale by sharding zones, not by round-robin replicas.

Security checklist

  • An authenticator is configured (static tokens or CP) — the relay won't boot otherwise.
  • Agent-accept TLS is enabled, or a trusted proxy terminates TLS in front of it.
  • A wildcard cert covers *.<zone> on the public listener (if you allow public tunnels).
  • The ops listener (:9090) is not exposed to the internet.
  • Rate limits are sized for your edge.
  • Gateways trust the relay's CA (MCPG_TUNNEL_CA) if it isn't publicly rooted.

See also