CLI commands and flags

mcpgw is a single binary. The CLI plus YAML config is the only administrative surface — there is no web UI. The commands are grouped into:

GroupPurpose
Process lifecycleserve / no args, stdio, version, healthcheck, reload
Diagnosticsdoctor
Configconfig validate, config show, config init
Keyskey generate, key list, key revoke, key rotate
Licenselicense info, license verify
Policypolicy lint, policy test, policy explain, policy replay
Upstreamupstream list, upstream ping
Tool indextools list, tools search
Telemetrytrace probe
Auditaudit tail, audit follow, audit stats
Shellcompletion bash, completion zsh

Every command supports --help / -h. Every read-only command supports --json for machine-parseable output where it makes sense (the JSON form is documented per-command below).

Exit codes

Stable contract — scripts can branch on these.

CodeMeaning
0Success
1Generic failure (network error, denied policy decision, etc.)
64Usage error (bad flags) — EX_USAGE
70Internal error — EX_SOFTWARE
74I/O error — EX_IOERR
78Config invalid or check failed — EX_CONFIG

Process lifecycle

serve

Run the gateway as a long-running HTTP server. This is the default action — mcpgw with no arguments is equivalent to mcpgw serve.

mcpgw [serve] [--config=PATH] [--pidfile=PATH]
FlagDefaultNotes
--config/etc/mcpgw/mcpgw.yamlPath to the YAML config. Validation errors are fatal at startup.
--pidfileOptional path to write the server PID. Required if you want to use mcpgw reload. Refuses to clobber an existing pidfile that names a live process.

Signals

SignalAction
SIGHUPRe-read config; atomically swap policy, auth store, OAuth verifier, CIMD fetcher, tool index, TLS reloaders.
SIGTERM, SIGINTGraceful shutdown. Flush audit + OTLP.

Endpoints

PathMethodPurpose
/mcpPOSTThe MCP JSON-RPC endpoint.
/healthzGETLiveness.
/readyzGETReadiness (license validity).
/.well-known/oauth-protected-resourceGETRFC 9728 metadata (when OAuth enabled).

stdio

Bridge a stdio-based MCP client (Claude Desktop, Cursor) to a remote gateway.

mcpgw stdio --upstream=URL
FlagDefaultNotes
--upstreamRequired. Base URL of the gateway. /mcp is appended automatically.

version

mcpgw version [--json]

Without --json: prints mcpgw <version>.

With --json: emits a JSON object with version, go_version, goos, goarch, vcs_commit, vcs_time, vcs_dirty, and license_pubkey_fingerprint (first 8 hex chars of the embedded Ed25519 pubkey). Use it from scripts that need to assert the running binary is what you think it is.

healthcheck

Probe a running gateway’s /healthz and exit non-zero on failure. Used by docker HEALTHCHECK.

mcpgw healthcheck [--url=URL] [--timeout=DUR]
FlagDefaultNotes
--urlhttp://127.0.0.1:7332/healthzEndpoint to probe.
--timeout2sRequest deadline.

reload

Send SIGHUP to a running gateway so it re-reads config and atomically swaps policy, auth store, OAuth verifier, CIMD fetcher, tool index, and TLS reloaders. The server must have been started with --pidfile=PATH.

mcpgw reload --pidfile=PATH
mcpgw reload --pid=N
FlagDefaultNotes
--pidfilePath to pidfile written by mcpgw serve --pidfile=PATH.
--pidExplicit PID (override). Mutually exclusive with --pidfile.

Exit 1 if the target PID is unsignalable (dead process, foreign uid).


Diagnostics

doctor

Comprehensive preflight check. Exercises every external dependency the gateway will touch at startup, plus the local prerequisites. Never starts the listener — safe to run repeatedly during config iteration.

mcpgw doctor [--config=PATH] [--timeout=DUR] [--offline] [--json]
FlagDefaultNotes
--config/etc/mcpgw/mcpgw.yamlPath to config.
--timeout10sPer-check network timeout.
--offlinefalseSkip checks that need network (upstreams, JWKS, OTLP, sinks).
--jsonfalseEmit a JSON list of check results.

Checks performed

NameVerifies
config.loadYAML parses and passes config.Validate.
licenseLicense JWT exists, signature matches embedded pubkey, days-to-expiry. Warning at < 14 days, error past grace.
tls.keypairCert + key load; leaf certificate’s not_after. Warning at < 30 days, error if expired.
tls.client_ca(if mTLS) Bundle contains parseable PEM certificates.
auth.keysArgon2id hashes parse; auth.NewStore succeeds.
audit.pathAudit directory exists and is writable (probed with a temp file).
upstream.<name>One tools/list POST per upstream; reports tool count + latency.
oauth.verifieroauth.NewVerifier constructs successfully (resolves JWKS).
telemetry.customer / telemetry.operatorTCP/TLS handshake against OTLP endpoint (no spans sent).
audit.sinks[].<type>Webhook/Kafka TCP reachability; S3/GCS metadata sanity.

Exit codes

  • 0 — all checks ok or warning
  • 78 — one or more checks errored

config validate

Parse + validate a config file without starting the server. Identical to the config.load step doctor runs.

mcpgw config validate [--config=PATH]

config show

Print the effective config with defaults applied, secrets masked. Useful for confirming env-var substitution and unset fields.

mcpgw config show [--config=PATH] [--unmask]
FlagDefaultNotes
--unmaskfalseInclude argon2id hashes and webhook header values verbatim. Off by default so the output is safe to share.

config init

Write a starter mcpgw.yaml to stdout (or a path). Refuses to overwrite an existing file.

mcpgw config init [--out=PATH]
FlagDefaultNotes
--out-Output path; - for stdout. Refuses to overwrite existing files.

Keys

key generate

Mint a new API key and emit a YAML stanza for auth.keys[]. The raw key is printed once.

mcpgw key generate [--label=ID] [--expires=DUR]
FlagDefaultNotes
--labelkey-1Human-readable key id stored in audit + spans.
--expires2160h (90d)Key lifetime. 0 for no expiry.

key list

List configured key ids, scopes, created_at, expires_at. Hashes are never printed.

mcpgw key list [--config=PATH]

Status is EXPIRED when expires_at < now. If auth.enabled: false, prints a notice and exits 0.

key revoke

Emit an updated YAML stanza with one key removed. Does not mutate the config file — paste the output back yourself. This keeps the CLI safe to run on production configs.

mcpgw key revoke --id=KEY_ID [--config=PATH]

Exit non-zero if the id is not found.

key rotate

The canonical rotation flow: mint a fresh key, set the old key’s expires_at to now + grace so clients have a window to switch over, and emit the combined auth.keys stanza. As with revoke, the config file is not mutated.

mcpgw key rotate --id=ID [--new-id=ID] [--grace=DUR] [--new-expires=DUR] [--config=PATH]
FlagDefaultNotes
--idRequired. Id of the key to rotate.
--new-id<old>-nextId for the new key.
--grace24hHow long the old key remains valid after rotation.
--new-expires2160h (90d)Lifetime of the new key; 0 = no expiry.

The raw new key is printed once. After clients have switched, run mcpgw key revoke --id=<old> to drop the old key entirely.


License

license info

Print claims, days-to-expiry, grace-period status. Verifies the JWT signature against the embedded pubkey.

mcpgw license info --path=PATH [--json]

JSON shape:

{
  "subject": "acme",
  "tier": "enterprise",
  "seat_count": 50,
  "issued_at": "2026-01-01T00:00:00Z",
  "expires_at": "2027-01-01T00:00:00Z",
  "grace_days": 30,
  "grace_until": "2027-01-31T00:00:00Z",
  "days_to_expiry": 220,
  "expired": false,
  "grace_period": false
}

license verify

Exit 0 if the signature matches; exit 1 if it does not. Quieter than info — for scripting.

mcpgw license verify --path=PATH

Policy

The policy CLI mirrors the same engine the proxy uses. There is no shadow engine — policy test and the live gateway always agree.

policy lint

Structural sanity over rules. Reports:

  • Errors: missing rule id, duplicate rule id.
  • Warnings: unreachable rules (an earlier universal matcher would short-circuit), conflicting actions for the same matcher.
mcpgw policy lint [--config=PATH]

Exit 0 if no errors (warnings allowed); 78 if any error.

policy test

Simulate one request through the engine and print the decision.

mcpgw policy test [--config=PATH] [--method=METHOD] [--tool=NAME] \
                  [--direction=client_to_server|server_to_client] \
                  [--session=ID] [--body=FILE] [--json]
FlagDefaultNotes
--methodtools/callJSON-RPC method.
--toolTool name. Required when --method=tools/call.
--directionclient_to_serverStream direction (matters for SSE-frame rules).
--sessiontest-sessionSynthetic session id used by rate_limit rules.
--bodyOptional path to a raw JSON request body. Use this to test redact patterns against captured traffic.

Exit codes

  • 0 on allow, redact, strip_app
  • 1 on deny, rate_limit_blocked

policy explain

Show every rule that would match a given request, mark the winner and the shadowed ones.

mcpgw policy explain [--config=PATH] --tool=NAME [--method=METHOD] [--direction=...]

Useful for “why is my rule not firing?” — anything tagged SHADOW is matched but bypassed because an earlier rule already decided the request.

policy replay

Re-evaluate captured audit records against the current policy. Use this to answer “what would change if I deployed this policy candidate against last hour of traffic?” before you ship the change.

mcpgw policy replay [--config=PATH] (--audit=PATH | --stdin) [--limit=N] [--diff-only] [--json]
FlagDefaultNotes
--auditPath to JSONL audit log to replay. Mutually exclusive with --stdin.
--stdinfalseRead records from stdin (pair with tail, grep, etc.).
--limit0 (no limit)Stop after N records.
--diff-onlyfalseSkip the recorded-vs-replayed breakdown; print only divergences.
--jsonfalseEmit a structured summary + full divergence list.

Output

  • Action histograms — how the records break down by recorded action vs by replayed action under the current policy.
  • Rule firing counts — how many records each rule in the current policy would fire on.
  • Divergences — every record whose replayed action differs from its recorded action, with timestamp, method/tool, the previous rule_id, and the new rule_id.

Caveats

  • The synthetic envelope is reconstructed from method + tool_name + session_id. The original request body is not in the audit log (by design), so redact rules are evaluated at the matcher level only — body-content matches won’t fire.
  • rate_limit rules run against a fresh in-memory token bucket per replay, so rate_limit hits reflect matcher matches rather than wire-accurate throttling.

Exit codes

  • 0 if no divergences (current policy reproduces every recorded decision).
  • 1 if any divergences are found. Useful as a CI gate: mcpgw policy replay --audit=last-hour.jsonl || refuse-deploy.

Upstream

upstream list

Print configured upstreams, their URLs, and which one is default_upstream.

mcpgw upstream list [--config=PATH]

upstream ping

POST a tools/list to one upstream. Confirms reachability and MCP compliance without booting the full gateway.

mcpgw upstream ping --name=NAME [--config=PATH] [--timeout=DUR] [--json]

Prints latency, tool count, and up to five sample tool names. Exit 1 on any transport failure or non-200 response.


Tool index

Both subcommands refresh the index synchronously (one tools/list per upstream) then answer locally.

tools list

Print every tool the gateway would see, with its upstream and (truncated) description.

mcpgw tools list [--config=PATH] [--timeout=DUR] [--json]

Run the mcp_search ranker offline. Use this to predict what mcp_search would return for a query without enabling tool-search in config.

mcpgw tools search [--config=PATH] [--limit=N] [--timeout=DUR] [--json] <query>

Telemetry

trace probe

Emit one synthetic span (mcpgw.cli.trace_probe) via every enabled OTLP exporter, then flush. Verifies the exporter is reachable end-to-end without running the gateway.

mcpgw trace probe [--config=PATH] [--timeout=DUR]

Exit 1 if either exporter’s flush returns an error.


Audit

The audit log is line-delimited JSON. These commands operate on the file directly — no live gateway needed.

audit tail

Pretty-print records with optional filters. Bad lines (torn writes) are skipped with a stderr warning rather than aborting.

mcpgw audit tail [--path=PATH] [--tool=NAME] [--method=METHOD] \
                 [--action=allow|deny|redact|...] [--since=DUR] [--json]

--json emits the original JSONL lines verbatim (post-filter).

audit follow

tail -f equivalent: stream new audit records as they’re appended. Seeks to end of file at start, polls every 200ms for new bytes, follows log rotation (inode swap or truncate). SIGINT/SIGTERM stops cleanly.

mcpgw audit follow [--path=PATH] [--tool=NAME] [--method=METHOD] [--action=...] [--json]

Filters match audit tail. Polls rather than using fsnotify to stay reliable on Docker bind-mounts (where filesystem-watch events are dropped).

audit stats

Summarize calls/minute, top tools, top denials over a time window.

mcpgw audit stats [--path=PATH] [--since=DUR] [--json]
FlagDefaultNotes
--since1hAggregate records newer than now - since.

Shell completion

Generated directly from the live command tree, so new subcommands and flags appear in completion as soon as you upgrade the binary.

completion bash

# System-wide:
mcpgw completion bash | sudo tee /etc/bash_completion.d/mcpgw

# Per-user:
mcpgw completion bash > ~/.local/share/bash-completion/completions/mcpgw

completion zsh

mcpgw completion zsh > "${fpath[1]}/_mcpgw"
# Then in ~/.zshrc:
autoload -U compinit && compinit