Skip to content
celadosPublic

Repository files navigation

mcpx

mcpx is a command line tool that turns registered MCP servers into an agent-friendly command surface.

It keeps MCP server registrations in a global user registry, discovers tool schemas, handles OAuth where possible, and exposes each MCP server as a root command:

mcpx posthog.projects-get
mcpx sentry.search-issues '{"query":"is:unresolved"}'

mcpx is designed for agents. The command surface is intentionally schema-first, stable, and explicit: commands use dotted paths and receive one structured input token, while mcpx's own control commands live under the @ namespace.

Requirements

  • Bun
  • macOS, Linux, or any environment that can run Bun executables

Install

Install the latest release:

curl -fsSL https://raw.githubusercontent.com/celados/mcpx/main/install.sh | bash

By default, the installer downloads the executable JS bundle from GitHub Releases and installs it to ~/.local/bin/mcpx.

Set MCPX_INSTALL_DIR to choose another install directory.

Add MCP Servers

Register MCP servers globally:

mcpx @add "{ name: 'posthog', url: 'https://mcp.posthog.com/mcp' }"
mcpx @add "{ name: 'sentry', url: 'https://mcp.sentry.dev/mcp' }"
mcpx @add "{ name: 'cf-docs', url: 'https://docs.mcp.cloudflare.com/mcp' }"
mcpx @add "{ name: 'cf-bindings', url: 'https://bindings.mcp.cloudflare.com/mcp' }"
mcpx @add "{ name: 'cf-observability', url: 'https://observability.mcp.cloudflare.com/mcp' }"
mcpx @add "{ name: 'browser', url: 'http://127.0.0.1:9000/mcp' }"

browser-os is an agent-native browser built on Chrome. It exposes a local MCP server that lets agents control the browser directly — navigating pages, extracting content, and interacting with web UIs.

mcpx keeps its state under ~/.agents/mcpx/state-v2/: server declarations in registry.json, credentials (OAuth grants, bearer values, secret headers) in credentials.json, and observed tool schemas in schema-cache.json. The MCP Runtime is the only writer.

Project directories do not need MCP config files. A project should decide which global servers are relevant by using schema selectors or a generated skill.

Call Tools

Call tools through the server command:

mcpx <server>.<tool> '<json-or-json5>'

Examples:

mcpx posthog.projects-get '{}'
mcpx sentry.whoami '{}'

For complex payloads, put the whole object on stdin as YAML — block scalars need no escaping:

mcpx cf-graphql.graphql_query - <<'EOF'
query: |
  query GetWorkerAnalytics($accountTag: String!, $scriptName: String!, $since: Time!, $until: Time!) {
    viewer { accounts(filter: {accountTag: $accountTag}) {
      workersInvocationsAdaptive(limit: 1000, filter: {scriptName: $scriptName, datetime_geq: $since, datetime_leq: $until}, orderBy: [datetimeHour_ASC]) {
        dimensions { datetimeHour scriptName status }
        sum { requests subrequests errors duration }
        quantiles { cpuTimeP50 cpuTimeP99 }
      }
    } }
  }
operationName: GetWorkerAnalytics
variables:
  accountTag: abc123def456
  scriptName: my-worker
  since: "2026-04-20T00:00:00Z"
  until: "2026-04-27T00:00:00Z"
EOF

The command accepts exactly one input object: inline JSON5, or the whole object from @file / - stdin as YAML (JSON5 when it starts with {). Field values are literal; there is no per-field - / @path.

Discover Schemas

Print the command schema for agents:

mcpx @schema

Focus on one server:

mcpx @schema .posthog

Focus on a set of servers:

mcpx @schema '.{posthog,sentry}'

Focus on an internal mcpx command:

mcpx @schema '.["@add"]'

mcpx caches discovered tool schemas so commands and @schema answer without asking the server. The cache is a hint, not the authority, and repairs itself (ADR 0011):

  • calling a tool the cache does not know, or omitting a field the cached schema requires, re-lists that server before the call is rejected;
  • a server's Invalid Params rejection, a new session (for schemas over an hour old) and tools/list_changed re-list it in the background;
  • mcpxd re-lists every schema older than a week when it starts and daily while it runs.

Background listings never open a browser; there is no manual refresh command.

Control Commands

mcpx control commands use the @ namespace so they do not collide with server names:

mcpx @add "{ name: '<server>', url: '<mcp-url>' }"
mcpx @add "{ name: '<server>', url: '<mcp-url>', bearer: 'env:<TOKEN_ENV>' }"
mcpx @add "{ name: '<server>', transport: 'stdio', command: '<command>', args: ['<arg>'] }"
mcpx @remove "{ name: '<server>' }"
mcpx @login "{ name: '<server>' }"
mcpx @daemon.status
mcpx @daemon.stop
mcpx @daemon.server
mcpx @skill
mcpx @skill-install

Server names cannot start with @.

HTTP is the default transport. Pass bearer credentials as a string or array. env:NAME reads a token from an environment variable at runtime; literal values are accepted but are stored in the registry:

mcpx @add "{ name: 'posthog', url: 'https://mcp.posthog.com/mcp', bearer: 'env:POSTHOG_TOKEN' }"
mcpx @add "{ name: 'posthog', url: 'https://mcp.posthog.com/mcp', bearer: ['env:POSTHOG_TOKEN_A', 'env:POSTHOG_TOKEN_B'] }"

Multiple bearer credentials use round-robin selection by default. Stdio servers are local process registrations with structured args and env:

mcpx @add '{
  "name": "open-design",
  "transport": "stdio",
  "command": "node",
  "args": ["/path/to/open-design/apps/daemon/dist/cli.js", "mcp"],
  "env": { "OPEN_DESIGN_TOKEN": "..." }
}'

Stdio servers are called through mcpxd, a user-local daemon that reuses stdio MCP sessions across CLI invocations. mcpxd starts on demand, keeps idle stdio servers warm, and can be inspected or stopped with mcpx @daemon.status and mcpx @daemon.stop. The daemon process is started through the explicit mcpx @daemon.server subcommand. HTTP servers continue to use the direct client path.

Authentication

Bearer auth is configured explicitly in the @add input. Prefer env:NAME references so secrets stay out of the registry. ${NAME} is also accepted when passed through the shell literally. When several bearer credentials are registered, mcpx advances a local round-robin cursor before each HTTP request.

OAuth follows the MCP authorization spec through the official MCP SDK. A server does not need to declare OAuth up front: whenever it answers a request with 401 and a WWW-Authenticate: Bearer resource_metadata=… challenge, mcpx discovers its authorization server from that challenge, authorizes, records the server as OAuth, and retries the request. This covers servers whose tools are partly anonymous — @add lists their tools without credentials, and the first call to a protected tool starts sign-in. A 403 insufficient_scope challenge re-authorizes with the additional scope the same way. When @add itself is challenged, it signs in right away and then lists the tools.

To switch accounts, run mcpx @login "{ name: '<server>' }". It opens a new browser sign-in (with OIDC prompt=login, so the old browser session is not reused silently) and replaces the stored grant only once the new sign-in succeeds.

For OAuth servers that support dynamic client registration, mcpx registers a client automatically for each browser sign-in.

For OAuth servers that do not support dynamic client registration, mcpx prompts for a manual client_id and client_secret. These providers usually require a preconfigured redirect URL. mcpx uses:

http://127.0.0.1:65245/callback

Add and save that exact Redirect URL in the provider app settings before continuing the prompt.

OAuth credentials repair themselves during ordinary tool calls:

  • When a token is close to expiry, mcpx refreshes it silently before the call.
  • When the server rejects a token with 401, mcpx refreshes it and retries the call once. Another call that already replaced the token is not undone.
  • When no refresh is possible (no token yet, or the refresh grant was revoked), the call opens browser authorization, prints the authorization URL to stderr, and waits up to 5 minutes for it to complete before continuing. Concurrent calls for the same server share one authorization.

Providers that need a manual OAuth client can only prompt in an interactive terminal; without one, the call fails immediately with reauth-required and mcpx @login "{ name: '<server>' }" must be run in a terminal.

Tool calls use a 5 minute request timeout by default. Set MCPX_TOOL_CALL_TIMEOUT_MS when a remote MCP server legitimately needs longer.

mcpx buffers MCP subscription notifications and returns them with the next daemon tool-call result by default. Set MCPX_NOTIFICATION_MODE=discard to ignore those notifications for a command; accepted values are buffer and discard.

Output

mcpx optimizes output for humans and agents by default:

  • text MCP content is printed directly
  • JSON text content is rendered as YAML
  • non-text content is saved to a temporary file and printed as file saved <path>
  • supplemental MCP structuredContent is printed as $structured only when it is not already represented by the primary content

Use argc context to preserve raw server output:

mcpx posthog.projects-get --context "{ output: 'raw' }"

Project Skills

Generate a project-local skill that tells agents which global MCP servers to use:

mcpx @skill-install "{ servers: 'posthog,sentry' }"

This writes:

.agents/skills/mcpx/SKILL.md
.agents/skills/mcpx/references/install.md
.agents/skills/mcpx/references/servers.md

The generated skill instructs agents to discover tools with focused schema selectors and call MCP tools through mcpx. A Troubleshooting section at the bottom points collaborators at install and server-setup references so their agent can recover when mcpx is missing or a listed server is not registered.

For temporary agent guidance without writing .agents/skills, print a one-server skill to stdout:

mcpx @skill-install "{ show: 'sentry' }"

Skill

General mcpx skill definition lives at src/index.md (served by mcpx @skill).

License

MIT