CLI Reference

MCP Hangar provides a comprehensive command-line interface for managing MCP servers.

Installation

pip install mcp-hangar
# or
uv pip install mcp-hangar

Synopsis

mcp-hangar [OPTIONS] COMMAND [ARGS]...

Global Options

These options go before the command name (mcp-hangar --json status):

OptionShortTypeDefaultEnv VariableDescription
--config-cPATH-MCP_CONFIGPath to config.yaml file, for every command. A command's own flag wins over it, and it wins over MCP_CONFIG; see Default Locations
--verbose-vFLAGfalse-Show verbose output including debug information
--quiet-qFLAGfalse-Suppress non-essential output
--json-FLAGfalse-Output in JSON format for scripting
--version-VFLAG--Show version and exit
--install-completion-FLAG--Install completion for the current shell
--show-completion-FLAG--Print completion for the current shell
--help-FLAG--Show help message and exit

pin, config check and auth bootstrap-admin do not read the global --config: each takes its own (see those commands). serve takes its own --config as well, and falls back to the global one.

Commands

CommandDescription
initInteractive setup wizard
statusShow MCP server health dashboard
addAdd MCP server from registry
removeRemove MCP server from configuration
serveStart the MCP server
pinCompute, write and check tool digest pins
completionGenerate shell completion scripts
authManage authentication (bootstrap the initial admin)
configValidate config.yaml without starting a gateway

init

Interactive setup wizard for MCP Hangar. Guides you through MCP server selection and configuration in under 5 minutes.

Synopsis

mcp-hangar init [OPTIONS]

Options

OptionShortTypeDefaultDescription
--non-interactive-yFLAGfalseRun without prompts, using defaults
--bundle-bTEXT-MCP Server bundle to install
--servers-TEXT-Comma-separated list of MCP servers (alias: --mcp_servers)
--config-path-PATHthe shared ruleCustom path for config file. Without it, the global --config, then MCP_CONFIG, then ./config.yaml if present, then ~/.config/mcp-hangar/config.yaml
--client-TEXTdetectedClient to point at Hangar; repeatable. claude-code, claude-code-project, cursor, cursor-project, claude-desktop, all
--claude-config-PATH-Write this exact client config file instead of the detected ones
--skip-clients-FLAGfalseDo not modify any MCP client config (alias: --skip-claude)
--skip-test-FLAGfalseSkip the smoke test. No digest pins are written either — they come from that run
--reset-FLAGfalseReset existing configuration

MCP Server Bundles

BundleMCP serversUse Case
starterfilesystem, fetch, memoryGeneral use, getting started
developerfilesystem, fetch, memory, github, gitSoftware development
datafilesystem, fetch, memory, sqlite, postgresData analysis

Examples

# Interactive setup
mcp-hangar init

# Install starter bundle
mcp-hangar init --bundle starter

# Install specific mcp_servers
mcp-hangar init --servers filesystem,github,sqlite

# Non-interactive with developer bundle
mcp-hangar init -y --bundle developer

# Custom config location
mcp-hangar init --config-path ~/my-config.yaml

# Point one client, or every detected one, at Hangar
mcp-hangar init -y --client claude-code
mcp-hangar init -y --client all

# Touch no client config
mcp-hangar init --skip-clients

What It Does

  1. Detects available runtimes (uvx, npx, container engines)
  2. Detects the MCP clients on this machine (Claude Code, Cursor, Claude Desktop)
  3. Presents MCP server categories for selection
  4. Collects required configuration (API keys, paths)
  5. Generates config.yaml — with tool_access.mode: front_door and an auth.stdio.principal block
  6. Starts each MCP server once to verify it, and records a digest pin for every tool it serves while it is up, under that server's tool_projection with digest_enforcement: block
  7. Writes the Hangar entry into the selected clients, merging it into their existing mcpServers rather than replacing them
  8. Shows what was written, what was tested and what was pinned

With --skip-test, step 6 writes no pins — an unverified pin would refuse every call to a tool nobody digested — and the summary says so instead of reporting a pass. Run pin --write afterwards to add them.

With -y, nothing is asked: servers that need a path or a token are written without one, and an existing config file is copied to config.backup.<timestamp>.yaml beside it and overwritten.


status

Display health dashboard of all configured MCP servers with real-time updates.

Synopsis

mcp-hangar status [OPTIONS] [SERVER]

Arguments

ArgumentRequiredDescription
SERVERNoShow detailed status for specific MCP server

Options

OptionShortTypeDefaultDescription
--watch-wFLAGfalseContinuously update the display
--interval-iFLOAT2.0Update interval in seconds (with --watch)
--details-dFLAGfalseShow additional columns (mode, memory, uptime)

MCP Server States

StateIndicatorDescription
READYOK (green)MCP Server is running and healthy
COLD-- (dim)MCP Server not started
INITIALIZING.. (cyan)MCP Server starting up
DEGRADED!! (yellow)MCP Server has issues
DEADXX (red)MCP Server failed/crashed

Examples

# Show all mcp_servers
mcp-hangar status

# Watch mode with live updates
mcp-hangar status --watch

# Faster refresh rate
mcp-hangar status -w -i 0.5

# Show detailed information
mcp-hangar status --details

# Single mcp_server details
mcp-hangar status github

# JSON output for scripting
mcp-hangar --json status

Output Columns

Standard view:

  • MCP Server name
  • State indicator
  • Health
  • Tools count

Detailed view (--details) adds:

  • Mode (subprocess/docker/remote)
  • Memory usage
  • Uptime
  • Last used

add

Add a MCP server from the MCP Registry to your configuration.

Synopsis

mcp-hangar add [OPTIONS] NAME

Arguments

ArgumentRequiredDescription
NAMEYesMCP Server name or search query

Options

OptionShortTypeDefaultDescription
--search-sFLAGfalseSearch registry instead of exact match
--yes-yFLAGfalseSkip confirmation prompts
--no-reload-FLAGfalseDon't hot-reload running server

Available MCP servers

MCP ServerDescriptionRequires Config
filesystemFile system accessYes (allowed paths)
fetchHTTP requestsNo
memoryKey-value storageNo
githubGitHub APIYes (token)
gitGit operationsNo
sqliteSQLite databasesYes (database path)
postgresPostgreSQL databasesYes (connection string)
brave-searchBrave Search APIYes (API key)
puppeteerBrowser automationNo
slackSlack integrationYes (token)
google-mapsGoogle Maps APIYes (API key)

The registry is a fixed list of these eleven. google-drive, sentry, raygun, everart and sequential-thinking were listed here and are not in it -- mcp-hangar add sentry answers Did you mean: sqlite, slack? and installs nothing. For anything outside the list, add the server to config.yaml yourself.

Examples

# Add by exact name
mcp-hangar add github

# Search for mcp_servers
mcp-hangar add --search database

# Skip confirmation
mcp-hangar add filesystem -y

# Add without hot-reload
mcp-hangar add postgres --no-reload

Configuration Prompts

When adding a MCP server that requires configuration, you'll be prompted for:

  • Secrets (API keys, tokens): Hidden input, stored securely
  • Paths (directories, files): Path validation
  • Text (URLs, names): Standard input

Environment variables are detected automatically. If GITHUB_TOKEN is set, you'll be asked whether to use it.

With -y no prompt is shown, so a server that needs configuration is added without it. add rewrites the config file through PyYAML, which keeps values but not comments.


remove

Remove a MCP server from your configuration.

Synopsis

mcp-hangar remove [OPTIONS] NAME

Arguments

ArgumentRequiredDescription
NAMEYesMCP Server name to remove

Options

OptionShortTypeDefaultDescription
--yes-yFLAGfalseSkip confirmation prompt
--keep-running-FLAGfalseDon't stop running MCP server instance

Examples

# Remove with confirmation
mcp-hangar remove github

# Remove without confirmation
mcp-hangar remove filesystem -y

# Remove from config but keep running
mcp-hangar remove postgres --keep-running

Behavior

  1. Validates MCP server exists in configuration
  2. Prompts for confirmation (unless -y)
  3. Stops running instance (unless --keep-running)
  4. Removes from config.yaml

serve

Start the MCP Hangar server. This is the default command when no subcommand is specified.

Synopsis

mcp-hangar serve [OPTIONS]
# or simply:
mcp-hangar [OPTIONS]

Bare mcp-hangar accepts only the global options; pass --http, --port and the rest to mcp-hangar serve.

Options

OptionShortTypeDefaultEnv VariableDescription
--config-cPATHthe shared rule-Path to config.yaml file. Overrides the global --config, which overrides MCP_CONFIG
--http-FLAGfalseMCP_MODE=httpRun in HTTP mode
--host-TEXT0.0.0.0MCP_HTTP_HOSTHTTP server host
--port-pINT8000MCP_HTTP_PORTHTTP server port
--log-file-PATH--Path to log file
--log-level-TEXTINFOMCP_LOG_LEVELLog level
--json-logs-FLAGfalseMCP_JSON_LOGSFormat logs as JSON
--unsafe-no-auth-FLAGfalse-Allow non-loopback HTTP binding without authentication (unsafe)

Transport Modes

stdio (default)

JSON-RPC over stdin/stdout. Used by Claude Desktop and similar clients.

mcp-hangar serve
mcp-hangar --config config.yaml serve

HTTP

HTTP server with Streamable HTTP transport. Used by LM Studio and web clients.

mcp-hangar serve --http
mcp-hangar serve --http --port 9000
mcp-hangar serve --http --host 127.0.0.1 --port 8080

Binding a non-loopback host requires authentication. When --host is anything other than a loopback address (e.g. the default 0.0.0.0), Hangar refuses to start unless authentication is configured (auth) or the --unsafe-no-auth flag is passed to explicitly accept an unauthenticated listener. A loopback bind (--host 127.0.0.1) is exempt. This prevents accidentally exposing an unauthenticated gateway on a routable interface.

HTTP Endpoints

When running in HTTP mode:

EndpointMethodDescription
/mcpPOST/GETMCP protocol endpoint (GET is served with tool_access.mode: front_door; otherwise it answers 405)
/health/liveGETLiveness probe
/health/readyGETReadiness probe
/health/startupGETStartup probe
/metricsGETPrometheus metrics

Log Levels

  • DEBUG - Detailed debugging information
  • INFO - General operational information (default)
  • WARNING - Warning messages
  • ERROR - Error messages only
  • CRITICAL - Critical errors only

Examples

# stdio mode (for Claude Desktop)
mcp-hangar serve

# HTTP mode on default port
mcp-hangar serve --http

# HTTP mode with custom port
mcp-hangar serve --http -p 9000

# With debug logging
mcp-hangar serve --log-level DEBUG

# With log file
mcp-hangar serve --log-file /var/log/mcp-hangar.log

# JSON logs for log aggregation
mcp-hangar serve --json-logs

# Full production setup
mcp-hangar serve --http --host 0.0.0.0 --port 8000 \
  --log-level INFO --json-logs --log-file /var/log/mcp.log

pin

Compute the digest of every tool your MCP servers serve, write those digests into the configuration, or check the configuration against what the servers serve right now.

A pin is the SHA-256 of a tool's name, description, inputSchema and outputSchema, canonicalized per RFC 8785. When digest_enforcement is block (the default), a tool whose digest no longer matches its pin is refused before the upstream is asked:

Tool 'fetch' schema does not match its pinned digest

Because the description is part of the digest, a poisoned description that changes no parameter is caught.

Synopsis

mcp-hangar pin [OPTIONS]

Options

OptionShortTypeDefaultDescription
--config-cPATHthe shared ruleConfiguration to read (and, with --write, to update)
--server-sTEXTevery serverOnly this MCP server. Repeatable
--write-FLAGfalseMerge the observed digests into mcp_servers.<id>.tool_projection.pins
--check-FLAGfalseExit 1 when a configured pin disagrees with what the server serves
--json-FLAGfalseMachine-readable output
--log-level-TEXTINFOLog level; overrides the config file's logging.level. Env: MCP_LOG_LEVEL
--quiet-qFLAGfalseLog errors only, and print only digests, drift and failures

--write and --check ask different questions; pass one or neither. pin reads its own --config, then the global one, so mcp-hangar --config X pin and mcp-hangar pin --config X read the same file. Before 2.25.0 pin ignored the global --config (mcp-hangar#1682).

Exit Codes

CodeMeaning
0The pins and the servers agree, or the write succeeded
1Drift: at least one pinned tool no longer matches (--check only)
2The question could not be answered — no such config, unreadable YAML, unknown --server, or a server that never started

Examples

# What would I pin?
mcp-hangar pin

# Pin everything, in place
mcp-hangar pin --write

# Pin one server only
mcp-hangar pin --server fetch --write

# Has anything changed since? (exit 1 if yes)
mcp-hangar pin --check

# In a script
mcp-hangar pin --check --json

In CI or a pre-commit hook

# .pre-commit-config.yaml
- repo: local
  hooks:
    - id: mcp-hangar-pin-check
      name: MCP tool schemas have not drifted
      entry: mcp-hangar pin --check
      language: system
      pass_filenames: false

Notes

  • pin starts the servers your configuration describes, asks each for its tools, and stops it again. A tool list is what a server answers, not something the configuration knows.
  • --write rewrites the configuration file through PyYAML, which preserves values but not comments or key order. The previous file is kept beside it as <config>.bak. Bare mcp-hangar pin prints the same digests if you would rather paste them into a file you maintain by hand.
  • Only the all-tenants tool_projection.pins block is written and checked. Pins under tenant_overrides are left alone.
  • A tool that is served but not pinned is not drift — pins is a subset by design. A pinned tool that is no longer served is.

completion

Generate shell completion scripts for tab-completion support.

Synopsis

mcp-hangar completion COMMAND

Subcommands

CommandDescription
bashGenerate bash completion script
zshGenerate zsh completion script
fishGenerate fish completion script
installAuto-install completion for detected shell

Installation

Bash

# System-wide
mcp-hangar completion bash | sudo tee /etc/bash_completion.d/mcp-hangar

# User-only
mcp-hangar completion bash >> ~/.bashrc

Zsh

# Add to fpath
mcp-hangar completion zsh > ~/.zfunc/_mcp-hangar

# Add to .zshrc if not already present:
# fpath=(~/.zfunc $fpath)
# autoload -Uz compinit && compinit

Fish

mcp-hangar completion fish > ~/.config/fish/completions/mcp-hangar.fish

Auto-install

# Detect shell and install
mcp-hangar completion install

# Specify shell
mcp-hangar completion install zsh

auth

Authentication management. Currently exposes a one-time administrator bootstrap.

auth bootstrap-admin

Grant the one-time global admin role to an existing principal, using the server's own durable auth backend. Solves the chicken-and-egg problem where a fresh durable auth store with anonymous access disabled cannot create its first administrator through the protected API. Added in 1.5.0.

The claim also mints an API key for that principal. Whether its secret is printed is --show-key's decision, and the claim succeeds exactly once, so it is a decision to make before running the command rather than after.

Synopsis

mcp-hangar auth bootstrap-admin --config PATH --principal PRINCIPAL [OPTIONS]

Options

OptionDescription
--config PATH, -cRequired. Path to the server config.yaml whose durable auth backend to bootstrap.
--principal PRINCIPALRequired. Existing external principal to grant global admin (e.g. user:admin).
--key-name NAMEHuman-readable label recorded for the bootstrap key. Default: initial admin.
--show-keyPrint the minted API key's secret. Required when API keys are the only authenticator. Off by default. Since 2.5.0.

Behavior

  • Uses the same durable backend the server uses (never an in-memory store); the claim is a single atomic operation.
  • Fails closed when auth.enabled is false, allow_anonymous is true, or the storage driver is non-durable (memory / event_sourcing).
  • A second run is refused without mutating storage — exactly one bootstrap succeeds.
  • Grants a global admin role to the principal, and mints an API key for it as part of the same claim. The key is stored hashed; its secret is printed only with --show-key, and is not recoverable afterwards. The grant is auditable.
  • Since 2.5.0: omitting --show-key is refused when no OIDC issuer is configured, before the claim is spent. API keys are then the only way in, so a run that withheld the secret would leave an admin nobody could present and a claim that cannot be made again. The refusal names the flag while re-running still works.
  • Since 2.5.0: a configuration with no authenticator at all — API keys disabled and no trusted issuer — is refused on the same grounds.
  • --show-key with auth.api_key.enabled: false is refused: no authenticator would accept the printed secret.

Example

An OIDC deployment: the principal authenticates on its own identity, so no secret is needed.

mcp-hangar auth bootstrap-admin --config /etc/mcp-hangar/config.yaml --principal user:alice@example.com

A deployment authenticated by API keys. Capture the secret from this run — there is no second one:

mcp-hangar auth bootstrap-admin --config /etc/mcp-hangar/config.yaml \
  --principal service:my-app --show-key

config

Answer "is this configuration valid" without starting a gateway.

config check

mcp-hangar config check [PATH]

Reports every key that Hangar does not read. A key it does not read is kept and ignored, so the setting simply does not apply -- which is why a misspelling surfaces as a server that will not start, or as authentication that is quietly off, rather than as a configuration error.

PATH wins; without it the command reads the global --config, then follows the shared rule. Before 2.25.0 the global --config was not read here.

exit codemeaning
0every key is one Hangar reads
1at least one key is not
2the file is missing, or is not YAML
$ mcp-hangar config check config.yaml
FAIL config.yaml: 2 key(s) nothing reads:

  auth has unknown key(s) ['enabledd']; allowed keys: ['allow_anonymous',
  'api_key', 'enabled', 'oidc', 'opa', 'rate_limit', 'role_assignments', 'stdio',
  'storage']
  mcp_servers.math has unknown key(s) ['commandd']; allowed keys: [...]

The command is always strict. Loading a config only warns about an unknown key today -- see Unknown keys for what changes in 3.0.0 -- but this command exists to be asked the question directly, so it answers it. That makes it the thing to run in CI, and before a rollout.

Checked: top-level section names, the direct keys of each section, and the keys of an mcp_servers.<id> spec. Not checked: anything deeper.


Configuration File

Default Locations

Since 2.25.0 init, status, add, remove, pin, serve, a bare mcp-hangar, config check and python -m mcp_hangar.server find the configuration file by one rule, highest first (mcp-hangar#1657):

  1. a path on the command line -- the command's own flag or argument (serve --config, pin --config, init --config-path, config check PATH), then the global --config;
  2. $MCP_CONFIG;
  3. ./config.yaml, if the working directory has one;
  4. ~/.config/mcp-hangar/config.yaml, the file init writes.

auth bootstrap-admin takes its own required --config.

So a bare mcp-hangar serve after init serves the file init wrote, unless the working directory has a config.yaml of its own -- and then init, add and remove there use that file too: add and remove edit it in place, and init -y backs it up and replaces it. Run them where config.yaml is Hangar's, or name the file (--config ~/.config/mcp-hangar/config.yaml, or init --config-path) to keep the old target. A gateway started without --config reloads the file it booted from. If no rule finds a file, serve refuses to start and tells you to run mcp-hangar init (2.25.0). A path named by a flag or by MCP_CONFIG that does not exist, is a directory or is not readable is refused the same way, with the path and where it came from on stderr, for example:

Error: Configuration file /etc/hangar/confg.yaml (named on the command line) does not exist. Nothing is served without the configuration that was asked for.

Before 2.25.0 every one of those cases booted a built-in demo configuration (math_subprocess and every hangar_* tool, with none of your pins, policies or auth) and logged only config_not_found_using_default at INFO.

Before 2.25.0 each command picked its own default: serve, pin and config check read ./config.yaml, init, add and remove wrote ~/.config/mcp-hangar/config.yaml, MCP_CONFIG beat a global --config on serve, and a gateway started without a path could not reload.

Example Configuration

mcp_servers:
  filesystem:
    mode: subprocess
    command:
      - npx
      - -y
      - "@modelcontextprotocol/server-filesystem"
      - "/home/user/documents"

  github:
    mode: subprocess
    command:
      - npx
      - -y
      - "@modelcontextprotocol/server-github"
    env:
      GITHUB_TOKEN: ${GITHUB_TOKEN}

  my-api:
    mode: remote
    endpoint: https://api.example.com/mcp

logging:
  level: INFO
  json_format: false

event_store:
  enabled: true
  driver: sqlite
  path: data/events.db

Environment Variables

VariableDescriptionDefault
MCP_CONFIGPath to configuration file-
MCP_MODEServer mode (stdio or http)stdio
MCP_HTTP_HOSTHTTP server host0.0.0.0
MCP_HTTP_PORTHTTP server port8000
MCP_LOG_LEVELLog levelINFO
MCP_JSON_LOGSEnable JSON loggingfalse

Exit Codes

CodeMeaning
0Success
1User error (invalid input, missing file, permission denied)
2System error (network failure, MCP server crash), or a usage error (unknown command or option)
130Interrupted by user (Ctrl+C)

See Also