Skip to content

Latest commit

 

History

History
995 lines (768 loc) · 33.2 KB

File metadata and controls

995 lines (768 loc) · 33.2 KB

CLI Management Commands

This document describes the CLI commands for managing MCPProxy upstream servers and monitoring system health.

Overview

MCPProxy provides two command groups:

  1. mcpproxy upstream - Server management (add, remove, list, logs, enable, disable, restart)
  2. mcpproxy doctor - Health checks and diagnostics

All commands support both daemon mode (fast, via socket) and standalone mode (direct connection).

Global flags

-c/--config and -d/--data-dir are accepted before or after the command name and apply to every management command. An empty value (-c "", --config=) is an error, never "use the default". A command's own --config wins over the global one. With only -d DIR, management commands read DIR/mcp_config.json when it exists; they never create a default configuration under ~/.mcpproxy when a path was given.

Command Reference

mcpproxy upstream list

List all configured upstream servers with connection status.

Usage:

mcpproxy upstream list [flags]

Flags:

  • --output, -o - Output format (table, json) [default: table]
  • --log-level, -l - Log level (trace, debug, info, warn, error) [default: warn]
  • --config, -c - Path to config file
  • --profile - Show only the servers of this profile's effective server set; the tool count of each row becomes the number of tools visible under the profile (needs the running daemon)
  • --status - Filter by health status (repeatable; a comma-separated value is equivalent to repeating the flag — several values select the union of their statuses): ready, connecting, sign_in_required, needs_review, needs_secret, needs_config, error, disabled

Examples:

# List servers with table output
mcpproxy upstream list

# JSON output for scripting
mcpproxy upstream list --output=json

# With debug logging
mcpproxy upstream list --log-level=debug

# Only servers that need a sign-in or a review
mcpproxy upstream list --status sign_in_required --status needs_review
mcpproxy upstream list --status sign_in_required,needs_review

Output Fields:

  • NAME - Server name
  • PROTOCOL - Transport protocol (stdio, http, sse, streamable-http)
  • TOOLS - Number of available tools
  • STATUS - The one status label (Spec 109), e.g. "Online", "Sign-in required", "Needs review" — never the free-text summary, which stays available in -o json as health.summary
  • ACTION - Suggested remediation command, keyed on health.actions[0] (if applicable)

Status Indicators (emoji): the emoji is keyed on admin_state first, then level (a severity signal) — independent of the status label shown in the STATUS column, so the same status value can render with different emoji depending on severity:

  • ⏸️ disabled admin state — server manually disabled by user, regardless of level
  • 🔒 quarantined admin state — server pending security approval, regardless of level; this is the only emoji a needs_review status ever renders as
  • ✅ enabled, not quarantined, level: healthy — covers both ready and the transient connecting status
  • ⚠️ enabled, not quarantined, level: degraded — e.g. a first-time sign_in_required sign-in, or an OAuth token refresh still retrying
  • ❌ enabled, not quarantined, level: unhealthy — covers error, needs_secret, needs_config, and a sign_in_required re-auth/expired-token case

Tool-hold overlay (GH #938): independent of the emoji rules above, a server with tools pending approval, changed (rug-pull), or blocked never renders a bare ✅ — even when it is otherwise ready/healthy. STATUS gets a · N held suffix (e.g. Online · 2 pending held) and the emoji downgrades one step to ⚠️, so a hold is never hidden behind an all-clear green. ACTION also fills in with tools list --server=<name> when no other action applies. See mcpproxy tools list --server=<name> or upstream logs for the hold detail.

-o json's health object always carries status, usable (true only when status == "ready") and actions (every applicable next step, in priority order) alongside the existing level/admin_state/summary/detail/action fields.

Example Output:

NAME                      PROTOCOL   TOOLS      STATUS                         ACTION
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅    github-server           http       15         Online                         -
❌    oauth-server            http       0          Sign-in required               auth login --server=oauth-server
⏸️    disabled-server         stdio      0          Disabled                       upstream enable disabled-server

mcpproxy upstream add

Add a new upstream MCP server to the configuration.

Usage:

# HTTP server
mcpproxy upstream add <name> <url> [flags]

# Stdio server (use -- to separate command)
mcpproxy upstream add <name> -- <command> [args...] [flags]

Flags:

  • --header - HTTP header in 'Name: value' format (repeatable)
  • --env - Environment variable in KEY=value format (repeatable)
  • --working-dir - Working directory for stdio commands
  • --transport - Transport type: http or stdio (auto-detected if not specified)
  • --if-not-exists - Don't error if server already exists
  • --no-quarantine - Don't quarantine the new server (use with caution)

Examples:

# Add HTTP-based server
mcpproxy upstream add notion https://mcp.notion.com/sse

# Add with authentication header
mcpproxy upstream add weather https://api.weather.com/mcp \
  --header "Authorization: Bearer my-token"

# Add stdio-based server using -- separator
mcpproxy upstream add fs -- npx -y @anthropic/mcp-server-filesystem /path/to/dir

# Add with environment variables and working directory
mcpproxy upstream add sqlite -- uvx mcp-server-sqlite --db mydb.db \
  --env "DEBUG=true" \
  --working-dir /home/user/projects

# Idempotent add (for scripts)
mcpproxy upstream add notion https://mcp.notion.com/sse --if-not-exists

Behavior:

  • New servers are quarantined by default for security
  • Works in both daemon mode (via API) and standalone mode (direct config file)
  • Transport is auto-detected: URL → http, -- separator → stdio

Output:

✅ Added server 'notion'
   ⚠️  New servers are quarantined by default. Approve in the web UI.

mcpproxy upstream remove

Remove an upstream MCP server from the configuration.

Usage:

mcpproxy upstream remove <name> [flags]

Flags:

  • --yes, -y - Skip confirmation prompt
  • --if-exists - Don't error if server doesn't exist

Examples:

# Remove with confirmation prompt
mcpproxy upstream remove notion

# Skip confirmation (for scripts)
mcpproxy upstream remove notion --yes
mcpproxy upstream remove notion -y

# Idempotent remove (for scripts)
mcpproxy upstream remove notion --yes --if-exists

Interactive Confirmation:

$ mcpproxy upstream remove notion
Remove server 'notion'? [y/N]: y
✅ Removed server 'notion'

Behavior:

  • Works in both daemon mode (via API) and standalone mode (direct config file)
  • Removes server from running daemon and config file
  • Clears server's tool index entries

mcpproxy upstream add-json

Add an upstream server using a JSON configuration object.

Usage:

mcpproxy upstream add-json <name> '<json>'

JSON Fields:

  • url - Server URL (for HTTP transport)
  • command - Command to run (for stdio transport)
  • args - Command arguments (array)
  • headers - HTTP headers (object)
  • env - Environment variables (object)
  • working_dir - Working directory (for stdio)
  • protocol - Transport type (auto-detected if not specified)

Examples:

# Add HTTP server with headers
mcpproxy upstream add-json weather '{"url":"https://api.weather.com/mcp","headers":{"Authorization":"Bearer token"}}'

# Add stdio server with environment
mcpproxy upstream add-json sqlite '{"command":"uvx","args":["mcp-server-sqlite","--db","mydb.db"],"env":{"DEBUG":"1"}}'

# Complex configuration
mcpproxy upstream add-json complex-server '{
  "command": "node",
  "args": ["./server.js"],
  "env": {"NODE_ENV": "production", "PORT": "3000"},
  "working_dir": "/opt/mcp-server"
}'

Behavior:

  • Useful for complex configurations or when copying from JSON config files
  • Protocol is auto-detected from url (→ http) or command (→ stdio)
  • New servers are quarantined by default

mcpproxy upstream logs <name>

Display recent log entries from a specific server.

Usage:

mcpproxy upstream logs <server-name> [flags]
mcpproxy upstream logs --server <server-name> [flags]

Flags:

  • --server, -s - Server name (alternative to positional argument)
  • --tail, -n - Number of lines to show [default: 50]
  • --follow, -f - Follow log output (requires daemon)
  • --log-level, -l - Log level [default: warn]
  • --config, -c - Path to config file

Examples:

# Show last 50 lines (either form works)
mcpproxy upstream logs github-server
mcpproxy upstream logs --server github-server
mcpproxy upstream logs -s github-server

# Show last 200 lines
mcpproxy upstream logs github-server --tail=200

# Follow logs (Ctrl+C to stop)
mcpproxy upstream logs github-server --follow

Behavior:

  • Daemon mode: Fetches logs via API from running server
  • Standalone mode: Reads directly from log file (read-only)
  • Follow mode: Requires running daemon, polls for new lines

mcpproxy upstream enable <name|--all>

Enable a disabled server or all disabled servers.

Usage:

mcpproxy upstream enable <server-name>
mcpproxy upstream enable --server <server-name>
mcpproxy upstream enable --all [--force]

Flags:

  • --server, -s - Server name (alternative to positional argument)
  • --all - Enable all disabled servers
  • --force - Skip confirmation prompt (for automation)

Requirements:

  • Daemon must be running

Examples:

# Enable single server (either form works)
mcpproxy upstream enable github-server
mcpproxy upstream enable --server github-server
mcpproxy upstream enable -s github-server

# Enable all servers (interactive confirmation)
mcpproxy upstream enable --all

# Enable all servers (skip prompt)
mcpproxy upstream enable --all --force

Interactive Confirmation:

$ mcpproxy upstream enable --all
⚠️  This will enable 5 server(s). Continue? [y/N]: _

Non-interactive mode requires --force:

$ echo "y" | mcpproxy upstream enable --all
Error: --all requires --force flag in non-interactive mode

mcpproxy upstream disable <name|--all>

Disable a server or all enabled servers.

Usage:

mcpproxy upstream disable <server-name>
mcpproxy upstream disable --server <server-name>
mcpproxy upstream disable --all [--force]

Flags:

  • --server, -s - Server name (alternative to positional argument)
  • --all - Disable all enabled servers
  • --force - Skip confirmation prompt

Requirements:

  • Daemon must be running

Examples:

# Disable single server (either form works)
mcpproxy upstream disable github-server
mcpproxy upstream disable --server github-server
mcpproxy upstream disable -s github-server

# Disable all servers (interactive confirmation)
mcpproxy upstream disable --all

# Disable all in script
mcpproxy upstream disable --all --force

mcpproxy upstream restart <name|--all>

Restart a server or all enabled servers.

Usage:

mcpproxy upstream restart <server-name>
mcpproxy upstream restart --server <server-name>
mcpproxy upstream restart --all

Flags:

  • --server, -s - Server name (alternative to positional argument)
  • --all - Restart all enabled servers

Requirements:

  • Daemon must be running

Examples:

# Restart single server (either form works)
mcpproxy upstream restart github-server
mcpproxy upstream restart --server github-server
mcpproxy upstream restart -s github-server

# Restart all servers (no confirmation needed)
mcpproxy upstream restart --all

Note: Restart does not require confirmation as it's non-destructive.

Locally-launched HTTP/SSE upstreams: when a server is configured with both command and an HTTP/SSE url (see docs/configuration.md), restart stops the spawned child (SIGTERM → grace → SIGKILL) before re-running Connect. The grace timeout is fixed at 5s today; the next start won't begin until the previous child is fully reaped, so you can rely on the port being free after the command returns. Stop ordering is: close MCP client → stop launched child → release per-server state.


mcpproxy doctor

Run comprehensive health checks to identify issues.

Usage:

mcpproxy doctor [flags]

Flags:

  • --output, -o - Output format (pretty, json, yaml) [default: pretty]; the global --json is shorthand for -o json
  • --log-level, -l - Log level [default: warn]
  • --config, -c - Path to config file
  • --server - Limit health checks to a single upstream server by name (Spec 044)

Requirements:

  • Daemon must be running

Examples:

# Pretty output with issue categorization
mcpproxy doctor

# JSON output for scripting
mcpproxy doctor --output=json

# Only show issues for a single server
mcpproxy doctor --server=github

The report is made to be shared, so credentials are always redacted in every format: query parameters such as ?apikey= or ?token= in any URL (including web_ui_url) print as REDACTED, and so does the admin API key wherever it would appear. Use mcpproxy status --show-key or mcpproxy status --web-url when you need the key itself.

Health Checks:

  • Upstream server connection errors
  • OAuth authentication requirements
  • Missing secrets (unresolved references)
  • Runtime warnings
  • Docker isolation status
  • Profiles & clients (Spec 108): profiles.binding_bypass (a client bound to a profile could escape it by omitting its credential while require_mcp_auth is off, so anonymous callers are denied) and connect.admin_key_in_client_config (a client config still holds the admin API key). Both are read from the warnings of GET /api/v1/clients, so they always agree with the REST warning and with the refusals of connect, profile and client; doctor never reads a client config. A server edition or an older daemon without /clients reports them as skipped. -o json adds a top-level profile_checks list (id, status of ok|warn|info|skipped, message, and bindings/fixes when present)

Output:

  • Total issue count
  • Categorized issues with actionable remediation steps
  • Exit code 0 even if issues found (for scripting)

mcpproxy doctor fix <CODE> (Spec 044)

Run a registered diagnostics fixer for a specific (server, code) pair. By default the fix runs as a non-destructive dry_run; pass --execute to apply mutating changes.

Usage:

mcpproxy doctor fix <CODE> --server <name> [--execute] [--fixer-key <key>] [flags]

Flags:

  • --server - Upstream server name (required)
  • --fixer-key - Override the auto-resolved fixer_key (rare; needed only when a code defines multiple button-type fix steps)
  • --execute - Apply the fix (default behaviour is dry_run)
  • --output, -o - Output format (pretty, json) [default: pretty]

Requirements:

  • Daemon must be running

Examples:

# Dry-run an OAuth re-login for the github server
mcpproxy doctor fix MCPX_OAUTH_REFRESH_EXPIRED --server github

# Actually re-run the OAuth flow (destructive)
mcpproxy doctor fix MCPX_OAUTH_REFRESH_EXPIRED --server github --execute

# JSON output for scripting / CI
mcpproxy doctor fix MCPX_STDIO_SPAWN_ENOENT --server my-stdio -o json

Error code reference: run mcpproxy doctor list-codes for the full catalog with associated fix steps and fixer keys.


mcpproxy connect <client>

Register MCPProxy in an AI client's MCP configuration (Claude Code, Cursor, Windsurf, VS Code, Codex, Gemini, OpenCode, ZCode). Connect never writes the instance admin API key: every write carries a per-client credential (mcp_cli_...) that identifies the client and binds it to a profile. The credential is valid on MCP endpoints only, so it cannot open the REST API.

Flags:

  • --profile <name|all> - the profile the client's credential binds to; all is the built-in All servers scope. A new credential defaults to all (switchable); a reconnect keeps the client's existing binding unless --profile is given
  • --lock / --switchable - lock the client to its profile so it can never switch (the default for a named profile), or let it switch within the profile's switchable_to (the default for all); mutually exclusive
  • --keyless - write an entry with no credential; only possible while require_mcp_auth is off, and the client is then unidentified. It cannot be combined with --profile, --lock or --switchable
  • --force - overwrite an existing entry; --name - server name in the client config; --all - connect every supported client with the same options (a refusal for one client is reported and never aborts the others)

Output:

MCPProxy registered in Cursor as "mcpproxy"
Backup: ~/.cursor/mcp.json.bak.20260930-101500
Credential: mcp_cli_•••• (token client-cursor, profile ro, locked)
Config: ~/.cursor/mcp.json
Next: Restart Cursor to load MCPProxy

The credential is shown masked; the secret is written only into the client's config file. Reconnecting over an active credential is a staged rotation: the old secret keeps working until the new config has been written.

With require_mcp_auth off, a named binding is refused (exit 1, nothing written) when an anonymous caller could reach more than the client, because the client could then escape its profile by omitting its credential. The refusal lists the fixes: turn require_mcp_auth on, or set anonymous_profile to a profile that is not wider than the binding.

With a running daemon the write goes through the daemon (it mints, records and notifies); with none, the command runs locally over the data directory with the strictest guard (any named binding is refused while require_mcp_auth is off). If a running mcpproxy holds the database but its socket was not reachable, the command reports that instead of guessing.

Examples:

mcpproxy connect cursor --profile ro           # Bind Cursor to "ro" (locked)
mcpproxy connect cursor --profile work --switchable
mcpproxy connect --all --profile all           # Every supported client, all servers

Profiles, clients, access explanations and token profiles (Spec 108)

The profile, client (bindings), access explain and token --profile commands are documented on the published site: https://docs.mcpproxy.app/cli/profile-commands (source: docs/cli/profile-commands.md).

Common Workflows

Debugging Server Connection Issues

# 1. Run health check to see all issues
mcpproxy doctor

# 2. Check specific server status
mcpproxy upstream list

# 3. View logs for failing server
mcpproxy upstream logs failing-server --tail=100

# 4. Restart server
mcpproxy upstream restart failing-server

# 5. Verify fix
mcpproxy upstream list

Monitoring Server Health

# Follow logs in terminal 1
mcpproxy upstream logs github-server --follow

# In terminal 2, trigger operations
mcpproxy call tool --tool-name=github:get_user --json_args='{"username":"octocat"}'

# Watch logs update in real-time

Bulk Server Management

# List all servers first
mcpproxy upstream list

# Disable all for maintenance
mcpproxy upstream disable --all --force

# Perform maintenance...

# Re-enable all
mcpproxy upstream enable --all --force

# Verify
mcpproxy upstream list

Daemon Mode vs Standalone Mode

Daemon Mode (Preferred)

  • Detection: Socket file exists at ~/.mcpproxy/mcpproxy.sock
  • Behavior: Uses HTTP API via Unix socket
  • Speed: Fast (reuses existing connections)
  • Requirements: mcpproxy serve running

Standalone Mode (Fallback)

  • Detection: No socket file found
  • Behavior: Direct connection to servers or file reading
  • Speed: Slower (establishes new connections)
  • Limitations: Some commands unavailable (enable, disable, restart, follow, doctor)

Command Availability

Command Daemon Mode Standalone Mode Notes
upstream add ✅ Via API ✅ Config file Both modes supported
upstream remove ✅ Via API ✅ Config file Both modes supported
upstream add-json ✅ Via API ✅ Config file Both modes supported
upstream list ✅ Full status ✅ Config only Standalone shows "unknown"
upstream logs ✅ Via API ✅ File read Follow requires daemon
upstream enable ✅ ❌ Requires daemon
upstream disable ✅ ❌ Requires daemon
upstream restart ✅ ❌ Requires daemon
doctor ✅ ❌ Requires daemon

Safety Considerations

Security Quarantine for New Servers

All servers added via upstream add or upstream add-json are quarantined by default:

  • Quarantined servers are visible but not connected
  • No tool calls are proxied to quarantined servers
  • Approve servers via the web UI to unquarantine
  • This protects against Tool Poisoning Attacks (TPA)
# Add server (automatically quarantined)
mcpproxy upstream add notion https://mcp.notion.com/sse

# View quarantine status
mcpproxy upstream list
# 🔒 notion  http  0  Needs review  Approve in Web UI

# Approve in web UI or via API:
curl -X POST "http://localhost:8080/api/v1/servers/notion/unquarantine" \
  -H "X-API-Key: your-key"

Bulk Operations Warning

⚠️ Safety Warning: Bulk Operations

The --all flag affects all servers simultaneously:

  • disable --all - Stops all upstream connections (reversible with enable --all)
  • enable --all - Activates all servers (may trigger API calls, OAuth flows)
  • restart --all - Reconnects all servers (may cause brief service disruption)

Best practices:

  • Use upstream list first to see affected servers
  • Test with single server before using --all
  • Use --force in automation only when appropriate

Interactive Confirmation

Confirmation prompt shows count:

⚠️  This will disable 12 server(s). Continue? [y/N]:

Shows what will be affected before proceeding.


Tool Management (mcpproxy tools)

Global view and per-tool enable/disable across all configured servers.

mcpproxy tools list

List tools from upstream servers. Without --server, lists every tool across all servers from the consolidated endpoint (requires daemon). With --server, lists tools from that specific server only (daemon or standalone).

Usage:

mcpproxy tools list [flags]

Flags:

  • --server, -s - Server name (optional; omit for global list)
  • --status - Filter by state: enabled, disabled, config-denied
  • --risk - Filter by risk level: read, write, destructive
  • --approval - Filter by approval: approved, pending, changed
  • --client - View as a client (administrator only, global list): adds ACCESS (callable, visible, hidden) and REASON columns, the verdict of each tool for that client's connection. With a scoped token this exits 1 with operation requires admin access
  • --profile - View as a profile (global list): adds the same columns. A non-administrator sees only the visible tools and a count of the hidden ones
  • --output, -o - Output format: table, json, yaml
  • --log-level, -l - Log level [default: info]
  • --config, -c - Path to config file
  • --timeout, -t - Connection timeout [default: 30s]
  • --trace-transport - Enable HTTP/SSE frame-by-frame tracing

Examples:

# Global list (all servers) — requires daemon
mcpproxy tools list
mcpproxy tools list -o json | jq '.[0]'

# Filtered list
mcpproxy tools list --status disabled
mcpproxy tools list --risk read
mcpproxy tools list --approval pending

# Server-scoped debug listing (daemon or standalone)
mcpproxy tools list --server=github-server
mcpproxy tools list --server=github-server --log-level=trace

Output Columns (global view):

  • NAME, SERVER, STATE (enabled/disabled/config-denied), APPROVAL, USAGE, LAST USED, DESCRIPTION

mcpproxy tools enable <server:tool> [...]

Enable one or more tools. Requires daemon.

Usage:

mcpproxy tools enable <server:tool> [<server:tool>...]

Examples:

mcpproxy tools enable github:create_issue
mcpproxy tools enable github:create_issue github:list_repos memory:create_entities

Behavior:

  • Each server:tool is parsed by splitting on the first : (tool names may contain :)
  • All targets are attempted independently; invalid targets are reported without aborting others
  • Prints OK <server:tool>: enabled or FAILED <server:tool>: <reason> per target
  • Exits non-zero if any target failed (enabling partial failure detection in scripts)

mcpproxy tools disable <server:tool> [...]

Disable one or more tools. Requires daemon.

Usage:

mcpproxy tools disable <server:tool> [<server:tool>...]

Examples:

mcpproxy tools disable github:create_issue
mcpproxy tools disable everything:echo memory:foo
echo "exit: $?"   # non-zero if any failed

Behavior:

  • Same per-target processing and exit-code semantics as tools enable
  • Config-denied tools fail individually (server rejects) without aborting other targets

mcpproxy tools approve [<server:tool>...]

Approve tools pending tool-level quarantine (Spec 032), clearing pending / changed tools for use without the Web UI or MCP. Requires daemon.

Usage:

mcpproxy tools approve <server:tool> [<server:tool>...]
mcpproxy tools approve --server <name> <tool> [<tool>...]
mcpproxy tools approve --server <name> --all

Flags:

  • -s, --server <name> - Scope bare tool names to this server (required with --all)
  • --all - Approve every pending/changed tool for --server

Examples:

mcpproxy tools approve github:create_issue
mcpproxy tools approve github:create_issue github:list_repos
mcpproxy tools approve --server github create_issue list_repos
mcpproxy tools approve --server github --all
mcpproxy tools approve --server github --all -o json

Behavior:

  • Targets are either <server>:<tool> pairs (the colon form wins) or bare tool names scoped via --server; bare names without --server are rejected
  • Targets are grouped per server; each server group is processed independently
  • --all requires --server and cannot be combined with explicit targets
  • Prints OK <server>: approved N tool(s) per server group; exits non-zero if any server group failed
  • -o json|yaml (or MCPPROXY_OUTPUT) emits a structured per-server result array

mcpproxy tools reject [<server:tool>...]

Reject (block) tools pending tool-level quarantine (Spec 032). Reject maps to the block action: the tool is atomically approved and disabled (hidden), so it is never left in the approved+enabled state — mirroring the Web UI "Block" button. Requires daemon.

Usage:

mcpproxy tools reject <server:tool> [<server:tool>...]
mcpproxy tools reject --server <name> <tool> [<tool>...]
mcpproxy tools reject --server <name> --all

Flags:

  • -s, --server <name> - Scope bare tool names to this server (required with --all)
  • --all - Reject every pending/changed tool for --server

Examples:

mcpproxy tools reject github:delete_repo
mcpproxy tools reject --server github delete_repo force_push
mcpproxy tools reject --server github --all

Behavior:

  • Same target parsing, per-server grouping, exit-code, and output-format semantics as tools approve
  • Prints OK <server>: blocked N tool(s) per server group

mcpproxy tools preflight <server:tool> [...]

Check that required tools are ready — deterministically, side-effect-free, and without calling any upstream server — before a cron/CI job spends model tokens finding out the hard way (Spec 098). Wraps POST /api/v1/preflight; the check reads the tool index, approval records, connection state and config policy only, and changes nothing. Requires daemon. The exit code is the product: a wrapper can branch retry-vs-page-vs-fix on it without parsing any JSON. See Required-Tools Preflight.

Usage:

mcpproxy tools preflight <server:tool> [<server:tool>...] [flags]

Flags:

  • --profile <name> - Evaluate under a named profile's server scope (unknown profile fails with exit 1)
  • --pin <server:tool>=sha256/v<N>:<hex> - Pin a tool to a schema hash; a divergence reports hash_mismatch (repeatable; each pinned id must be in the requested list). Current pins come from mcpproxy tools list -o json (hash field, operator tier)
  • --read-only-only - Require tools to be annotated read-only
  • --exclude-destructive - Require tools to be annotated non-destructive
  • --exclude-open-world - Require tools to be annotated closed-world
  • --wait <duration> - Poll local state for up to this long while every failure is retryable (max 10s; larger values are rejected by the daemon)
  • --output, -o - Output format: table, json, yaml (or MCPPROXY_OUTPUT); --help-json for machine-readable command metadata

Exit codes (worst class present wins):

Exit Verdict Meaning Wrapper action
0 ready Every tool is ready Proceed
10 degraded_retryable A server is starting up or unhealthy Back off and retry
11 blocked Operator action needed (approve, enable, log in, re-pin) Page the operator
12 unknown_ids At least one requested id is unknown in your view (typo, removed server) Fix the job's tool list
1 — The command itself failed (daemon unreachable, invalid arguments, rejected request) Investigate

Examples:

mcpproxy tools preflight gh-ops:sync_issues slack:post_message
mcpproxy tools preflight ctl:echo -o json
mcpproxy tools preflight ctl:echo --pin ctl:echo=sha256/v1:9f86d081884c7d65...
mcpproxy tools preflight ctl:echo --profile work --wait 5s
mcpproxy tools preflight ctl:echo --read-only-only

Output:

  • table (default): a VERDICT: <verdict> (exit <code>) / CHECKED: <time> header (plus WAITED: <n>ms when --wait was given), then one row per tool: ID, STATUS, REASON, RETRYABLE, ACTION, DETAIL
  • -o json|yaml: the wire response body (verdict, checked_at, waited_ms, tools[] with per-tool reason/retryable/action/detail/remediation); identical key names in both formats

Behavior:

  • Zero upstream calls, zero side effects — never triggers connects, reconnects or re-indexing, even when it finds a dead server
  • One result per unique id; a ready tool carries no failure fields
  • A non-ready verdict is reported once (as the exit code + a one-line summary on stderr), not as a duplicated error dump
  • Every executed preflight writes an activity record before the response — auditable via mcpproxy activity list (type preflight, source cli)

Cron recipe — gate a nightly agent job on its required tools; the exit code alone decides:

#!/bin/sh
# Fail deterministically BEFORE the agent session spends tokens on discovery.
mcpproxy tools preflight gh-ops:sync_issues slack:post_message --wait 10s || exit $?
run-nightly-agent-job

Or branch on the class:

mcpproxy tools preflight gh-ops:sync_issues slack:post_message --wait 10s
case $? in
  0)  run-nightly-agent-job ;;
  10) echo "degraded, retrying next cycle" ;;          # transient — back off
  11) notify-oncall "mcpproxy: operator action required" ;;
  12) notify-oncall "mcpproxy: unknown tool id — config drift?" ;;
  *)  notify-oncall "mcpproxy: preflight itself failed" ;;
esac

GitHub Actions step — a failing preflight fails the step and skips the rest of the job:

jobs:
  nightly-agent:
    runs-on: [self-hosted, mcpproxy]
    steps:
      - name: Preflight required tools
        run: |
          mcpproxy tools preflight gh-ops:sync_issues slack:post_message \
            --wait 10s -o json
      - name: Run agent job
        run: ./run-agent-job.sh

Exit Codes

  • 0 - Success
  • 1 - Execution failure (API error, connection failure, user declined confirmation)
  • 2 - Invalid arguments or configuration (non-interactive without --force)

mcpproxy tools preflight additionally uses verdict exit codes 10 (degraded, retryable), 11 (blocked, operator action) and 12 (unknown id) — see its section above.


Implementation Notes

Architecture

  • Commands in cmd/mcpproxy/*_cmd.go
  • Client library in internal/cliclient/client.go
  • API endpoints in internal/httpapi/server.go

Adding New Commands

  1. Create command file in cmd/mcpproxy/
  2. Add methods to internal/cliclient/client.go
  3. Register in cmd/mcpproxy/main.go
  4. Follow existing patterns for daemon detection
  5. Support both daemon and standalone modes where possible

Testing

All commands support manual testing:

go build -o mcpproxy ./cmd/mcpproxy
./mcpproxy <command> [args]