Gateway
Gateway8 min

Configuration sources

How the gateway assembles its config from files, remote URLs, and inline base64 — the MCPG_CONFIG env var and the repeatable --config flag, how layers merge, how secrets resolve, and what hot-reloads.

The gateway builds its running configuration by layering sources — each a YAML document — in a defined order, then overlaying environment variables. This page is the complete model: where config comes from, how layers merge, how secrets resolve, and what changes on a hot-reload. For the field-by-field config surface, see the configuration reference.

The two inputs

Config comes from two ordered inputs. Both are later-wins overlays, and the second is applied after the first:

  1. MCPG_CONFIG — an environment variable holding a single file path, or a path-separator-joined list of files (: on Unix, ; on Windows).
  2. --config <source> — a repeatable CLI flag. Each value is one source, applied after the MCPG_CONFIG files. Because a --config value is not path-split, it can be more than a file: a URL or inline content.
bash
# Env files only
MCPG_CONFIG=base.yaml:prod.yaml mcpg

# Flag only
mcpg --config base.yaml --config prod.yaml

# Both: env files are the base, --config layers on top
MCPG_CONFIG=base.yaml mcpg --config prod.yaml

Running with no config at all is valid — the gateway boots on built-in defaults plus any MCPG_* environment overrides.

Source forms

Every MCPG_CONFIG entry is a file. A --config source is classified by its scheme:

SourceExampleResolved as
Local pathgw.yaml, /etc/mcpg/gw.yamla file, read at load time
file:// pathfile:///etc/mcpg/gw.yamla file
https:// URLhttps://cfg.example.com/gw.yamlfetched at boot over TLS
http:// URLhttp://cfg.internal/gw.yamlrefused unless opted in (below)
base64:base64:Z2F0ZXdheToge30Kinline base64-decoded YAML
data: URIdata:application/yaml;base64,Z2F0…inline (RFC 2397, base64 form only)

Files are re-read from disk on every hot-reload. Remote and inline sources (https / base64 / data) are resolved once at boot, held in memory — never written to disk — and reused as-is on reload.

Merge order

Layers merge deterministically, later winning:

text
built-in defaults
  → each MCPG_CONFIG file, in order
    → each --config source, in order
      → MCPG_* environment variables   (always last, override everything)
  • Objects deep-merge — a later layer that sets gateway.server.request_timeout_ms keeps every other field the base layer defined under gateway.server.
  • Scalars and arrays replace wholesale — a later layer's list replaces the earlier list; it does not append.

This is what makes a base.yaml + prod.yaml overlay ergonomic: the override only names the handful of fields it changes.

Environment-variable overrides

Any MCPG_* variable (double-underscore __ as the nesting separator) overrides the matching config path, and it wins over every file/URL/inline layer:

bash
# Override gateway.server.bind_address without touching the file
MCPG_CONFIG=gw.yaml MCPG_GATEWAY__SERVER__BIND_ADDRESS=0.0.0.0:8080 mcpg

The env var name mirrors the YAML path: gateway.server.bind_addressMCPG_GATEWAY__SERVER__BIND_ADDRESS.

Secrets never live in a source

A config source — file, URL, or base64 — should never contain a literal secret. Reference secrets instead, and they resolve at runtime, after the config is loaded and hashed:

  • ${env.VAR} — pulled from the process environment at startup.
  • cred://<plugin_id>/<provider> — resolved by a credential-issuer plugin.
yaml
mcp:
  capabilities:
    tools:
      - backend:
          kind: http
          headers:
            Authorization: "Bearer ${env.UPSTREAM_TOKEN}"

Because references (not values) are what live in the source, the config digest in the mcpg.config.loaded audit event stays reproducible from the source alone and never leaks a secret. This holds identically whether the layer came from a file, an https:// URL, or a base64: blob.

What hot-reloads

The gateway re-reads config without a restart on three triggers: SIGHUP, the POST /admin/v1/config:reload admin endpoint, and — when enabled — the gateway.config_watch file poller. On reload:

  • File layers are re-read from disk, so edits to a file take effect.
  • Remote and inline layers reuse their boot snapshot — a reload does not re-fetch a URL or re-decode a blob. To pick up a changed remote config, restart the gateway.

The file poller only watches the file layers; a gateway whose config is entirely remote/inline has nothing on disk to watch.

Security posture for remote config

Fetching config over the network at boot is powerful, so it's fenced:

  • HTTPS only. A plaintext http:// source is refused — a network attacker who can rewrite your config owns your gateway. Override only for a trusted network (e.g. a link-local metadata service) with MCPG_CONFIG_ALLOW_INSECURE_HTTP=1.
  • Bounded. A remote fetch has a 10-second timeout and a 5 MiB cap; a non-2xx response fails the boot.
  • In memory. Remote and inline config is never written to disk, so a secret-bearing config body doesn't linger in a temp file.

Worked examples

Base + production overlay (files):

bash
MCPG_CONFIG=base.yaml:production.yaml mcpg
# production.yaml only needs the fields it changes

Remote base, local override (GitOps / config service):

bash
mcpg --config https://config.internal.example.com/mcpg/base.yaml \
     --config /etc/mcpg/node-override.yaml
# fleet-wide base served centrally; per-node file wins on top

Inline config in a constrained environment (CI, a container arg, a serverless slot):

bash
# No file to mount — ship the whole config as one flag value
mcpg --config "base64:$(base64 -w0 gw.yaml)"

Env-only, no files:

bash
MCPG_GATEWAY__SERVER__BIND_ADDRESS=0.0.0.0:8787 \
MCPG_OBSERVABILITY__LOGS__LEVEL=info \
mcpg

Troubleshooting

SymptomCause
config file not found: …a file layer path is wrong or unmounted
refusing to fetch config over plaintext http://use https://, or set MCPG_CONFIG_ALLOW_INSECURE_HTTP=1 for a trusted network
--config base64: payload is not valid standard base64the blob isn't standard base64 (check base64 -w0, no line wraps)
--config data: URI must be base64-encodedonly the data:...;base64,<payload> form is accepted
fetch config from …: HTTP 404the remote config URL is wrong or unauthorized
A remote config change didn't take effect on reloadremote/inline layers are boot snapshots — restart to re-fetch
An unknown-field error at boota typo or stale key; deny_unknown_fields is on at every level. Run mcpg config check.

Validate any source set before shipping with mcpg config check.

See also