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:
| Group | Purpose |
|---|---|
| Process lifecycle | serve / no args, stdio, version, healthcheck, reload |
| Diagnostics | doctor |
| Config | config validate, config show, config init |
| Keys | key generate, key list, key revoke, key rotate |
| License | license info, license verify |
| Policy | policy lint, policy test, policy explain, policy replay |
| Upstream | upstream list, upstream ping |
| Tool index | tools list, tools search |
| Telemetry | trace probe |
| Audit | audit tail, audit follow, audit stats |
| Shell | completion 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.
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Generic failure (network error, denied policy decision, etc.) |
| 64 | Usage error (bad flags) — EX_USAGE |
| 70 | Internal error — EX_SOFTWARE |
| 74 | I/O error — EX_IOERR |
| 78 | Config 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]
| Flag | Default | Notes |
|---|---|---|
--config | /etc/mcpgw/mcpgw.yaml | Path to the YAML config. Validation errors are fatal at startup. |
--pidfile | — | Optional 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
| Signal | Action |
|---|---|
SIGHUP | Re-read config; atomically swap policy, auth store, OAuth verifier, CIMD fetcher, tool index, TLS reloaders. |
SIGTERM, SIGINT | Graceful shutdown. Flush audit + OTLP. |
Endpoints
| Path | Method | Purpose |
|---|---|---|
/mcp | POST | The MCP JSON-RPC endpoint. |
/healthz | GET | Liveness. |
/readyz | GET | Readiness (license validity). |
/.well-known/oauth-protected-resource | GET | RFC 9728 metadata (when OAuth enabled). |
stdio
Bridge a stdio-based MCP client (Claude Desktop, Cursor) to a remote gateway.
mcpgw stdio --upstream=URL
| Flag | Default | Notes |
|---|---|---|
--upstream | — | Required. 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]
| Flag | Default | Notes |
|---|---|---|
--url | http://127.0.0.1:7332/healthz | Endpoint to probe. |
--timeout | 2s | Request 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
| Flag | Default | Notes |
|---|---|---|
--pidfile | — | Path to pidfile written by mcpgw serve --pidfile=PATH. |
--pid | — | Explicit 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]
| Flag | Default | Notes |
|---|---|---|
--config | /etc/mcpgw/mcpgw.yaml | Path to config. |
--timeout | 10s | Per-check network timeout. |
--offline | false | Skip checks that need network (upstreams, JWKS, OTLP, sinks). |
--json | false | Emit a JSON list of check results. |
Checks performed
| Name | Verifies |
|---|---|
config.load | YAML parses and passes config.Validate. |
license | License JWT exists, signature matches embedded pubkey, days-to-expiry. Warning at < 14 days, error past grace. |
tls.keypair | Cert + 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.keys | Argon2id hashes parse; auth.NewStore succeeds. |
audit.path | Audit directory exists and is writable (probed with a temp file). |
upstream.<name> | One tools/list POST per upstream; reports tool count + latency. |
oauth.verifier | oauth.NewVerifier constructs successfully (resolves JWKS). |
telemetry.customer / telemetry.operator | TCP/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 warning78— 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]
| Flag | Default | Notes |
|---|---|---|
--unmask | false | Include 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]
| Flag | Default | Notes |
|---|---|---|
--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]
| Flag | Default | Notes |
|---|---|---|
--label | key-1 | Human-readable key id stored in audit + spans. |
--expires | 2160h (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]
| Flag | Default | Notes |
|---|---|---|
--id | — | Required. Id of the key to rotate. |
--new-id | <old>-next | Id for the new key. |
--grace | 24h | How long the old key remains valid after rotation. |
--new-expires | 2160h (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]
| Flag | Default | Notes |
|---|---|---|
--method | tools/call | JSON-RPC method. |
--tool | — | Tool name. Required when --method=tools/call. |
--direction | client_to_server | Stream direction (matters for SSE-frame rules). |
--session | test-session | Synthetic session id used by rate_limit rules. |
--body | — | Optional path to a raw JSON request body. Use this to test redact patterns against captured traffic. |
Exit codes
0onallow,redact,strip_app1ondeny,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]
| Flag | Default | Notes |
|---|---|---|
--audit | — | Path to JSONL audit log to replay. Mutually exclusive with --stdin. |
--stdin | false | Read records from stdin (pair with tail, grep, etc.). |
--limit | 0 (no limit) | Stop after N records. |
--diff-only | false | Skip the recorded-vs-replayed breakdown; print only divergences. |
--json | false | Emit 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), soredactrules are evaluated at the matcher level only — body-content matches won’t fire. rate_limitrules run against a fresh in-memory token bucket per replay, so rate_limit hits reflect matcher matches rather than wire-accurate throttling.
Exit codes
0if no divergences (current policy reproduces every recorded decision).1if 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]
tools search
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]
| Flag | Default | Notes |
|---|---|---|
--since | 1h | Aggregate 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