@arcadeai/mcpx
v1.4.1
Published
A command-line interface for MCP servers. curl for MCP.
Readme
mcpx
A command-line interface for MCP servers. curl for MCP.
The internet is debating CLI vs MCP like they're competitors. They're not.
Three audiences:
- Coding agents (Claude Code, Cursor) that prefer shelling out over maintaining persistent MCP connections — better for token management, progressive tool discovery, and sharing a single pool of MCP servers across multiple agents on one machine
- Non-coding agents that need programmatic access to MCP tools from TypeScript — remote, persistent, or isolated agents that don't have a shell
- MCP developers who need a fast way to discover, debug, and test their servers from the terminal
Install
# Via bun (all platforms)
bun install -g @arcadeai/mcpx
# Via curl (macOS/Linux)
curl -fsSL https://raw.githubusercontent.com/arcadeai-labs/mcpx/main/install.sh | bash# Via PowerShell (Windows)
irm https://raw.githubusercontent.com/arcadeai-labs/mcpx/main/install.ps1 | iexThe curl/PowerShell installers download a pre-built binary — no runtime needed. The bun install method requires Bun. Binaries for all platforms are also available on the GitHub Releases page.
Quick Start
# Add the GitHub MCP server
mcpx add github --url https://mcp.github.com
# List all servers and their tools
mcpx
# List with descriptions
mcpx -d
# Inspect a server
mcpx info github
# Inspect a specific tool
mcpx info github search_repositories
# Execute a tool (JSON args)
mcpx exec github search_repositories '{"query": "mcp server"}'
# Execute a tool with shell-style flags (anything after `--` is parsed against the tool's input schema)
mcpx exec github search_repositories -- --query "mcp server"
# Execute a tool without specifying the server (auto-resolved)
mcpx exec search_repositories '{"query": "mcp server"}'
mcpx exec search_repositories -- --query "mcp server"
# Search tools — combines keyword and semantic matching
mcpx search "post a ticket to linear"
# Search with only keyword/glob matching (fast, no embeddings)
mcpx search -k "*file*"
# Search with only semantic matching
mcpx search -q "manage pull requests"
# Limit the number of results (default: 10)
mcpx search -n 5 "manage pull requests"Commands
| Command | Description |
| -------------------------------------- | ------------------------------------------------------ |
| mcpx | List all configured servers and tools |
| mcpx servers | List configured servers (name, type, detail) |
| mcpx info <server> | Server overview (version, capabilities, tools, counts, session id) |
| mcpx info <server> <tool> | Show tool schema |
| mcpx search <query> | Search tools (keyword + semantic) |
| mcpx search -k <pattern> | Keyword/glob search only |
| mcpx search -q <query> | Semantic search only |
| mcpx search -n <number> <query> | Limit number of results (default: 10) |
| mcpx index | Build/rebuild the search index |
| mcpx index -i | Show index status |
| mcpx exec <server> <tool> [json] | Validate inputs locally, then execute tool |
| mcpx exec <tool> [json] | Execute tool (server auto-resolved if unambiguous) |
| mcpx exec <server> <tool> -- --k=v | Shell-flag args (typed via the tool's input schema) |
| mcpx exec <server> <tool> -f file | Read tool args from a JSON file |
| mcpx exec <server> | List available tools for a server |
| mcpx auth <server> | Authenticate with an HTTP MCP server (OAuth) |
| mcpx auth <server> -s | Check auth status and token TTL |
| mcpx auth <server> -r | Force token refresh |
| mcpx deauth <server> | Remove stored authentication for a server |
| mcpx add <name> --command <cmd> | Add a stdio MCP server to your config |
| mcpx add [name] --url <url> | Add an HTTP MCP server (name derived from URL if omitted) |
| mcpx remove <name> | Remove an MCP server from your config |
| mcpx ping | Check connectivity to all configured servers |
| mcpx ping <server> [server2...] | Check connectivity to specific server(s) |
| mcpx grade [server] | Grade server quality against the 100-point rubric |
| mcpx session | Print Streamable HTTP session ids for all servers |
| mcpx session <server> [server2...] | Print Streamable HTTP session id(s) for specific servers |
| mcpx skill install --claude | Install the mcpx skill for Claude Code |
| mcpx skill install --cursor | Install the mcpx rule for Cursor |
| mcpx resource | List all resources across all servers |
| mcpx resource <server> | List resources for a server |
| mcpx resource <server> <uri> | Read a specific resource |
| mcpx prompt | List all prompts across all servers |
| mcpx prompt <server> | List prompts for a server |
| mcpx prompt <server> <name> [json] | Get a specific prompt |
| mcpx exec [server] <tool> --no-wait | Execute as async task, return task handle immediately |
| mcpx exec [server] <tool> --ttl <ms> | Set task TTL in milliseconds (default: 60000) |
| mcpx task list <server> | List tasks on a server |
| mcpx task get <server> <taskId> | Get task status |
| mcpx task result <server> <taskId> | Retrieve completed task result |
| mcpx task cancel <server> <taskId> | Cancel a running task |
| mcpx allow <server> | Allow an agent to exec all tools on a server |
| mcpx allow <server> <tools...> | Allow specific tools only |
| mcpx allow --all | Allow all mcpx exec calls |
| mcpx allow --all-read | Allow read-only commands (search, info, list, etc.) |
| mcpx allow --list | Show current mcpx-related permissions |
| mcpx allow --cursor <server> | Allow for Cursor instead of Claude Code |
| mcpx deny <server> | Remove permissions for a server |
| mcpx deny --all | Remove all mcpx-related permissions |
| mcpx check-update | Check for a newer version of mcpx |
| mcpx upgrade | Upgrade mcpx to the latest version |
Options
| Flag | Purpose |
| ------------------------- | -------------------------------------------------------- |
| -h, --help | Show help |
| -V, --version | Show version |
| -d, --with-descriptions | Include tool descriptions in list output |
| -c, --config <path> | Specify config file location |
| -v, --verbose | Show HTTP details and JSON-RPC protocol messages |
| -S, --show-secrets | Show full auth tokens in verbose output (unmasked) |
| -j, --json | Force JSON output (default when piped) |
| -F, --format <format> | Output format: json or markdown |
| -N, --no-interactive | Decline server elicitation requests (for scripted usage) |
| --no-color | Disable ANSI colors in output |
| --force-color | Force ANSI colors even when piped |
| -l, --log-level <level> | Minimum server log level to display (default: warning) |
| --mcp-version <version> | MCP protocol era: v1, v2, or auto (default: v1) |
Output & colors
mcpx auto-detects whether stdout/stderr are interactive and adapts:
- TTY → colored, formatted output (tables, headers, badges).
- Non-TTY / piped → JSON.
Color emission honors the standard env vars and matching flags:
NO_COLOR=1or--no-color— disable ANSI colors.FORCE_COLOR=1or--force-color— enable ANSI colors even when piped.--json/-j— JSON output, no colors.CI=true— treated as non-interactive (spinners off).
Server log messages (notifications/message) are displayed on stderr with level-appropriate coloring. Valid levels (in ascending severity): debug, info, notice, warning, error, critical, alert, emergency. When a server declares logging capability, mcpx sends logging/setLevel to request messages at the configured threshold and above.
Managing Servers
Add and remove servers from the CLI — no manual JSON editing required.
# Add a stdio server (anything after `--` is passed to the command verbatim)
mcpx add filesystem --command npx -- -y @modelcontextprotocol/server-filesystem /tmp
# Equivalent forms: repeatable --args, or a single comma-separated --args
mcpx add filesystem --command npx --args -y --args @modelcontextprotocol/server-filesystem --args /tmp
mcpx add filesystem --command npx --args "-y,@modelcontextprotocol/server-filesystem,/tmp"
# Add an HTTP server with headers
mcpx add my-api --url https://api.example.com/mcp --header "Authorization:Bearer tok123"
# When --url is used, the name is optional — derived from the URL's last path
# segment (or hostname if there is none). The example below stores the server
# under the name "evan-coding".
mcpx add --url https://api.arcade.dev/mcp/evan-coding
# Add with tool filtering (repeatable, or comma-separated)
mcpx add github --url https://mcp.github.com --allowed-tools "search_*" --allowed-tools "get_*"
# Add a legacy SSE server (explicit transport)
mcpx add legacy-api --url https://api.example.com/sse --transport sse
# Add with environment variables (repeatable, or comma-separated)
mcpx add my-server --command node --args server.js --env API_KEY=sk-123 --env DEBUG=true
# Overwrite an existing server
mcpx add filesystem --command echo --force
# Remove a server (also cleans up auth.json)
mcpx remove filesystem
# Remove but keep stored auth credentials
mcpx remove my-api --keep-auth
# Preview what would be removed
mcpx remove my-api --dry-runadd options:
| Flag | Purpose |
| -------------------------- | ---------------------------------------------------------------------- |
| --command <cmd> | Command to run (stdio server) |
| --args <arg> | Argument for the command. Repeatable, or comma-separated. Tokens after -- are also appended (stdio only). |
| --env <KEY=VAL> | Environment variable. Repeatable, or comma-separated. |
| --cwd <dir> | Working directory for the command |
| --url <url> | Server URL (HTTP server) |
| --header <Key:Value> | HTTP header. Repeatable. |
| --transport <type> | Transport: sse or streamable-http |
| --allowed-tools <pat> | Allowed tool pattern. Repeatable, or comma-separated. |
| --disabled-tools <pat> | Disabled tool pattern. Repeatable, or comma-separated. |
| --mcp-version <version> | MCP protocol era for this server (v1, v2, or auto) |
| -f, --force | Overwrite if server already exists |
| --no-auth | Skip automatic OAuth after adding |
| --no-index | Skip rebuilding the search index |
remove options:
| Flag | Purpose |
| ------------- | ------------------------------------------------- |
| --keep-auth | Don't remove stored auth credentials |
| --dry-run | Show what would be removed without changing files |
Configuration
Config lives in ~/.mcpx/ (or the current directory). Three files:
servers.json — MCP Server Definitions
Standard MCP server config format. Supports both stdio and HTTP servers.
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "."],
"env": { "API_KEY": "${API_KEY}" },
"allowedTools": ["read_file", "list_directory"],
"disabledTools": ["delete_file"]
},
"github": {
"url": "https://mcp.github.com"
},
"internal-api": {
"url": "https://mcp.internal.example.com",
"headers": { "Authorization": "Bearer ${TOKEN}" }
},
"legacy-sse": {
"url": "https://legacy.example.com/sse",
"transport": "sse"
},
"modern-mcp": {
"url": "https://mcp.example.com",
"mcp": "v2"
}
}
}Stdio servers — command + args, spawned as child processes
HTTP servers — url, with optional static headers for pre-shared tokens. OAuth is auto-discovered at connection time via .well-known/oauth-authorization-server — no config needed. By default, mcpx tries Streamable HTTP first and automatically falls back to legacy SSE if the server doesn't support it. Set "transport": "sse" or "transport": "streamable-http" to skip auto-detection. Set "mcp": "v1", "v2", or "auto" to choose the protocol era (see MCP versions).
Environment variables are interpolated via ${VAR_NAME} syntax. Set MCP_STRICT_ENV=false to warn instead of error on missing variables.
Tool filtering:
allowedTools— glob patterns for tools to expose (whitelist)disabledTools— glob patterns for tools to hide (blacklist, takes precedence)
auth.json — OAuth Token Storage (managed automatically)
Stores OAuth tokens for HTTP MCP servers. You don't edit this directly — managed automatically.
{
"github": {
"access_token": "gho_xxxx",
"refresh_token": "ghr_xxxx",
"expires_at": "2026-03-03T12:00:00Z",
"token_type": "bearer",
"scope": "repo,read:org"
},
"linear": {
"access_token": "lin_xxxx",
"refresh_token": "lin_ref_xxxx",
"expires_at": "2026-03-04T08:30:00Z",
"token_type": "bearer"
}
}Tokens are automatically refreshed when expired (if a refresh token is available). Any command that connects to a server (exec, info, search, listing) will refresh tokens transparently. If refresh is impossible or the server still rejects the token, the command fails immediately with a prompt to run mcpx auth <server> instead of reopening the browser. mcpx auth <server> --status shows current token state and TTL.
search.json — Semantic Search Index (managed automatically)
Contains every discovered tool with metadata for semantic search. Built by mcpx index and kept fresh automatically — mcpx (the default list) and mcpx index --status already fetch every server's live tools, so they detect new/changed/removed tools and re-index the affected servers in the background.
{
"version": 1,
"indexed_at": "2026-03-03T10:00:00Z",
"embedding_model": "Xenova/bge-small-en-v1.5",
"tools": [
{
"server": "linear",
"tool": "createIssue",
"description": "Create a new issue in Linear",
"input_schema": { "...": "..." },
"scenarios": ["Create a new issue in Linear", "create issue"],
"keywords": ["create", "issue"],
"embedding": [0.012, -0.034, "..."]
}
]
}Each tool gets:
- scenarios — the tool description plus a keyword phrase derived from the tool name
- keywords — terms extracted by splitting the tool name on
_,-, and camelCase boundaries - embedding — 384-dim vector for cosine similarity search
Scenarios and keywords are extracted heuristically from tool names and descriptions. Embeddings are generated in-process using Xenova/bge-small-en-v1.5 (~33MB ONNX model, downloaded on first run). No API keys needed.
Config Resolution Order
MCP_CONFIG_PATHenvironment variable-c / --configflag./servers.json(current directory)~/.mcpx/servers.json
Environment Variables
| Variable | Purpose | Default |
| ----------------- | --------------------------- | ---------- |
| MCP_CONFIG_PATH | Config directory path | ~/.mcpx/ |
| MCP_DEBUG | Enable debug output | false |
| MCP_TIMEOUT | Request timeout (seconds) | 1800 |
| MCP_CONCURRENCY | Parallel server connections | 5 |
| MCP_MAX_RETRIES | Retry attempts | 3 |
| MCP_STRICT_ENV | Error on missing ${VAR} | true |
| MCP_VERSION | MCP protocol era (v1, v2, auto) | v1 |
MCP versions
mcpx uses the official MCP TypeScript SDK v2 client (@modelcontextprotocol/client) and can speak both protocol eras:
| Value | Handshake | Use when |
| ------ | ----------------------------------------------------------------- | --------------------------------------------- |
| v1 | 2025 initialize (legacy). Default. | Existing MCP servers (2024–2025 spec) |
| v2 | Pin 2026-07-28 (server/discover). No fallback. | Servers that require the modern protocol |
| auto | Probe for 2026-07-28, fall back to the 2025 handshake | Mixed or unknown servers |
Choose it in any of these places (most specific wins):
# 1. Per-server config (highest priority)
mcpx add github --url https://mcp.github.com --mcp-version v2
# or in servers.json: { "url": "...", "mcp": "v2" }
# 2. CLI flag (applies to servers without an `mcp` field)
mcpx --mcp-version v2 ping github
mcpx --mcp-version auto exec github search_repositories '{"query":"mcp"}'
# 3. Environment variable
export MCP_VERSION=v2
mcpx ping github// 4. TypeScript SDK
const client = new McpxClient({ mcp: "v2" });mcpx info <server> reports the requested mcp value plus the negotiated protocolVersion and protocolEra.
Server quality checks
Grade a configured MCP server across security, protocol readiness, tool quality, and surface size:
mcpx grade github
mcpx grade github --json
mcpx grade github --expect github.com --min-score 80
mcpx grade github --rubric ./my-rubric.json
mcpx grade github --no-probeInteractive output includes a color-coded ASCII letter grade, compact overall and per-category pie charts,
progress bars, detailed rule evidence, and prioritized improvements. Use --json (or --format json) for a stable
machine-readable report with schemaVersion, score, grade, category totals, status summary, and every rule result.
The default rubric is rubric/mcp-quality.json. It is the single source of truth for
rule weights and grade bands; custom rubrics may change weights or disable rules, but enabled weights must total
100. Safe error-quality probes call at most three tools explicitly annotated with readOnlyHint: true; use
--no-probe to skip them.
Tool-quality checks also apply the Arcade tool patterns: verb-led tool names
(read synonyms such as fetch, retrieve, lookup, and describe count alongside get/list/search), behavior
annotations that match the name's verb, permanence warnings on destructive tools, safe boolean defaults, prerequisite
hints for ID parameters, enums instead of prose choices, documented date formats, parameter examples, bounded arrays,
cursor pagination on list tools, shallow schemas, consistent parameter names, deprecation guidance, and distinct tool
descriptions.
For HTTP servers, the report also runs MCP Debugger-style wire diagnostics: malformed JSON-RPC and protocol
headers, Origin rejection, session termination, response envelopes and content types, reserved _meta, error and
notification behavior, OAuth challenge parsing, protected-resource metadata, authorization-server discovery,
PKCE, registration, grants, token authentication methods, and synthetic invalid-token rejection. The invalid-token
probe can create an expected security-log event. --no-probe disables tool calls but not these non-mutating
protocol and authorization probes.
The checks are inspired by the ToolBench methodology, Smithery scoring guidance, MCP Debugger and its example report, the official MCP conformance suite, and the MCP tool specification.
OAuth Flow
For HTTP MCP servers that require OAuth:
# Start the OAuth flow — opens browser for authorization
mcpx auth github
# Check token status
mcpx auth github -s
# => github: authenticated (expires in 47m)
# Force re-authentication
mcpx auth github -r
# Authenticate without rebuilding the search index
mcpx auth github --no-indexThe OAuth flow:
- Discovers the server's OAuth metadata via
/.well-known/oauth-authorization-server - Starts a local callback server on a random port
- Opens the browser for user authorization
- Exchanges the authorization code for tokens
- Stores tokens in
auth.json - Automatically refreshes tokens before they expire on any subsequent command
- If a stored token is rejected and cannot be refreshed, commands fail immediately with a prompt to run
mcpx auth <server>— they do not reopen the browser in a loop
Search
mcpx search is a single command that combines keyword matching and semantic vector search. By default, both strategies run and results are merged.
# Combined search (default) — keyword hits + semantic matches, merged and ranked
mcpx search "send a message to slack"
# => slack/postMessage (0.94) Post a message to a channel
# => slack/sendDirectMessage (0.87) Send a DM to a user
# => teams/sendMessage (0.72) Send a Teams message
# Keyword only — fast glob match against tool names, descriptions, and keywords
mcpx search -k "*pull*request*"
# => github/createPullRequest
# => github/getPullRequest
# => github/mergePullRequest
# Semantic only — vector similarity against intent
mcpx search -q "review someone's code changes"
# => github/submitPullRequestReview (0.91) Submit a PR review
# => github/getPullRequest (0.85) Get PR details
# => github/listPullRequestCommits (0.78) List commits in a PRThe combined search pipeline:
- Keyword match — glob/substring against tool names, descriptions, and indexed keywords
- Semantic match — embed the query, cosine similarity against tool embeddings
- Merge & rank — combine both result sets, deduplicate, sort by score
- Return — top results with similarity scores
The index updates incrementally — only new or changed tools are re-indexed. The first run indexes everything; subsequent runs are fast.
Tasks (Async Tool Execution)
MCP servers can declare support for tasks — long-running operations that return a task handle instead of blocking until completion. When a tool supports tasks (execution.taskSupport: "optional" or "required"), mcpx automatically uses task-augmented execution.
# Default: wait for the task to complete, showing progress updates
mcpx exec my-server long_running_tool '{"input": "data"}'
# Return immediately with a task handle (useful for scripting)
mcpx exec my-server long_running_tool '{"input": "data"}' --no-wait
# => Task created: task-abc123 (status: working)
# Check task status
mcpx task get my-server task-abc123
# Retrieve the result once complete
mcpx task result my-server task-abc123
# List all tasks on a server
mcpx task list my-server
# Cancel a running task
mcpx task cancel my-server task-abc123For tools that don't support tasks, exec works exactly as before — no changes needed.
Elicitation (Server-Requested User Input)
MCP servers can request user input mid-operation via elicitation. mcpx supports both modes:
- Form mode: The server sends a JSON schema describing input fields (strings, numbers, booleans, enums, multi-select). mcpx renders prompts in the terminal and validates input before returning it.
- URL mode: The server sends a URL for the user to visit (e.g., for authentication or payment flows). mcpx opens it in the default browser.
# Interactive — prompts appear in the terminal
mcpx exec my-server deploy_tool '{"target": "staging"}'
# Server requests input: Confirm deployment
# *Confirm [y/n]: y
# Non-interactive — decline all elicitation (for scripts/CI)
mcpx exec my-server deploy_tool '{"target": "staging"}' --no-interactive
# JSON mode — elicitation requests are written to stdout as JSON,
# and responses are read from stdin (for programmatic handling)
echo '{"action":"accept","content":{"confirm":true}}' | \
mcpx exec my-server deploy_tool '{"target": "staging"}' --jsonDebugging with Verbose Mode
-v shows both HTTP request/response details (like curl -v) and JSON-RPC protocol messages exchanged with the server. All debug output goes to stderr so piping to jq still works.
JSON-RPC Protocol Tracing
Verbose mode traces every JSON-RPC message at the transport layer — requests, responses, and notifications — for both stdio and HTTP servers:
mcpx -v exec mock echo '{"message":"hello"}'
# → initialize (id: 0)
# ← initialize (id: 0) [45ms] — mock-server v1.0
# → notifications/initialized
# → tools/call (id: 1)
# ← tools/call (id: 1) [12ms] — okWith --json, trace output is NDJSON on stderr (one JSON object per message):
mcpx -v -j exec mock echo '{"message":"hello"}' 2>trace.jsonlHTTP Traffic
For HTTP/SSE servers, verbose mode also shows raw HTTP headers and timing:
mcpx -v exec arcade Gmail_WhoAmI
# > POST https://api.arcade.dev/mcp/evan-coding
# > authorization: Bearer eyJhbGci...
# > content-type: application/json
# > accept: application/json, text/event-stream
# >
# {
# "method": "tools/call",
# "params": {
# "name": "Gmail_WhoAmI",
# "arguments": {}
# }
# }
# < 200 OK (142ms)
# < content-type: application/json
# < x-request-id: abc123
# <
# { "content": [ ... ] }
# Debug on stderr, clean JSON on stdout
mcpx -v exec arcade Gmail_WhoAmI | jq .
# Show full auth tokens (unmasked)
mcpx -v -S exec arcade Gmail_WhoAmIThe > / < convention matches curl — > for request, < for response. The → / ← arrows show JSON-RPC protocol messages with method names, IDs, round-trip timing, and result summaries.
Session IDs
Streamable HTTP servers may issue an mcp-session-id after initialize. That id lives on the transport mcpx keeps after connect — it is not a config-file field. You can export it from a live connection:
# Print session ids for every configured server
mcpx session
# One or more servers
mcpx session github
mcpx session github linear --json
# Also included on connect metadata
mcpx ping github --json
mcpx info githubStdio and SSE transports (and HTTP servers that omit the header) report no session id.
From TypeScript:
const sessionId = await client.getSessionId("github");
// or: (await client.getServerInfo("github")).sessionIdInput Validation
mcpx exec validates tool arguments locally before sending them to the server. MCP tools advertise a JSON Schema for their inputs — mcpx uses this to catch errors fast, without a round-trip.
# Missing required field — caught locally
mcpx exec github create_issue '{"title": "bug"}'
# => error: missing required field "repo" (github/create_issue)
# Wrong type — caught locally
mcpx exec github create_issue '{"repo": "foo", "title": 123}'
# => error: "title" must be a string, got number (github/create_issue)
# Valid — sent to server
mcpx exec github create_issue '{"repo": "foo", "title": "bug"}'
# => { ... }Validation covers:
- Required fields — errors before sending if any are missing
- Type checking — string, number, boolean, array, object
- Enum values — rejects values not in the allowed set
- Nested objects — validates recursively
If a tool's inputSchema is unavailable (some servers don't provide one), execution proceeds without local validation.
Shell-flag args
Anything after a -- separator is parsed as shell flags using the tool's input schema for type coercion. This is handy for interactive use — you don't need to remember JSON quoting rules.
# JSON form
mcpx exec github create_issue '{"owner":"arcadeai-labs","repo":"mcpx","title":"bug"}'
# Equivalent shell-flag form
mcpx exec github create_issue -- --owner arcadeai-labs --repo mcpx --title bug
# --field=value also works
mcpx exec github create_issue -- --owner=arcadeai-labs --repo=mcpx --title=bug
# Booleans
mcpx exec my-server flagit -- --enabled # true
mcpx exec my-server flagit -- --no-enabled # false
# Arrays — repeatable flag or comma-split
mcpx exec my-server tag -- --label bug --label todo
mcpx exec my-server tag -- --label bug,todoType coercion follows the field's type in the input schema (string, integer, number, boolean, array). Nested objects must use the JSON form. Combining -- shell flags with inline JSON args, --file, or stdin is rejected.
Shell Output & Piping
Output is human-friendly by default, JSON when piped:
# Human-readable
mcpx info github
# JSON (piped)
mcpx info github | jq '.tools[].name'
# Force JSON
mcpx info github --jsonOutput Formats (--format)
Tool results (exec, task result) support three output formats via the global --format / -F flag:
| Format | Description |
| ---------- | ----------------------------------------------------------------------- |
| json | Full MCP protocol response as JSON (default) |
| text | Extract text from content blocks, strip protocol wrapper |
| markdown | Extract text and render with rich terminal formatting (colors, borders) |
# Default JSON output — full MCP response with content array
mcpx exec github search_repositories '{"query":"mcp"}'
# Markdown — rich terminal rendering with colors and formatting
mcpx exec github search_repositories '{"query":"mcp"}' -F markdownThe markdown format extracts text from MCP content blocks and renders it through Bun's built-in markdown parser with ANSI styling — headings, bold/italic, code blocks with borders, colored links, and bullet lists. JSON content is converted to a structured document with headings and bullet lists.
For other commands (list, info, search), --format json forces JSON output and --format markdown uses the existing human-friendly formatting.
Chaining tool results
Tool results are JSON by default, designed for chaining:
# Search repos and read the first result
mcpx exec github search_repositories '{"query":"mcp"}' \
| jq -r '.content[0].text | fromjson | .items[0].full_name' \
| xargs -I {} mcpx exec github get_file_contents '{"owner":"{}","path":"README.md"}'
# Conditional execution
mcpx exec filesystem list_directory '{"path":"."}' \
| jq -e '.content[0].text | contains("package.json")' \
&& mcpx exec filesystem read_file '{"path":"./package.json"}'Stdin and file input work for tool arguments:
# Pipe JSON directly
echo '{"path":"./README.md"}' | mcpx exec filesystem read_file
# Pipe from a file
cat params.json | mcpx exec server tool
# Shell redirect from a file
mcpx exec server tool < params.json
# Read args from a file with --file flag
mcpx exec filesystem read_file -f params.jsonAgent Integration
Claude Code Skill
mcpx ships a Claude Code skill at .claude/skills/mcpx.md that teaches Claude Code how to discover and use MCP tools. Install it:
# Install to the current project (.claude/skills/mcpx.md)
mcpx skill install --claude
# Install globally (~/.claude/skills/mcpx.md)
mcpx skill install --claude --global
# Install to both locations
mcpx skill install --claude --global --project
# Overwrite an existing skill file
mcpx skill install --claude --forceThen in any Claude Code session, the agent can use /mcpx or the skill triggers automatically when the agent needs to interact with external services. The skill instructs the agent to:
- Search first —
mcpx search "<intent>"to find relevant tools - Inspect —
mcpx info <server> <tool>to get the schema before calling - Execute —
mcpx exec <tool> '<json>'to execute (ormcpx exec <server> <tool> '<json>'if the tool name is ambiguous)
This keeps tool schemas out of the system prompt entirely. The agent discovers what it needs on-demand, saving tokens and context window space.
Cursor Rule
mcpx ships a Cursor rule at .cursor/rules/mcpx.mdc that teaches Cursor how to discover and use MCP tools. Install it:
# Install to the current project (.cursor/rules/mcpx.mdc)
mcpx skill install --cursor
# Install globally (~/.cursor/rules/mcpx.mdc)
mcpx skill install --cursor --global
# Install both Claude and Cursor at once
mcpx skill install --claude --cursor
# Overwrite an existing rule file
mcpx skill install --cursor --forceRaw System Prompt (other agents)
For non-Claude-Code agents, add this to the system prompt:
You have access to MCP tools via the `mcpx` CLI.
To discover tools:
mcpx search "<what you want to do>" # combined keyword + semantic
mcpx search -k "<pattern>" # keyword/glob only
mcpx info <server> <tool> # tool schema
To execute tools:
mcpx exec <tool> '<json args>' # server auto-resolved
mcpx exec <server> <tool> '<json args>' # explicit server
mcpx exec <server> <tool> -- --k=v # shell-flag args (typed via schema)
mcpx exec <server> <tool> -f params.json
Always search before executing — don't assume tool names.Programmatic Usage (TypeScript SDK)
For agents that don't have shell access — remote, persistent, or isolated agents running in TypeScript:
import { McpxClient } from "@arcadeai/mcpx";
const client = new McpxClient();
// or: new McpxClient({ mcp: "v2" }) // official SDK v2 client, pin 2026-07-28
// or: new McpxClient({ mcp: "auto" }) // probe v2, fall back to v1
// or: new McpxClient({ configDir: "/path/to/.mcpx" })
// or: new McpxClient({ servers: { mcpServers: { ... } } })
// 1. Search for tools
const results = await client.search("send a message");
// 2. Inspect the tool schema
const tool = await client.info("arcade", "Slack_SendMessage");
// 3. Execute the tool
const result = await client.exec("arcade", "Slack_SendMessage", {
channel: "#general",
message: "hello",
});
// Also available: listTools, listResources, readResource,
// listPrompts, getPrompt, listTasks, getTask, cancelTask,
// getServerInfo, getServerNames, getSessionId, validateToolInput
await client.close();The SDK uses the same config files as the CLI (~/.mcpx/servers.json, auth.json, search.json). Server management (add, remove, auth) is done via the CLI — the SDK is read-only.
You can also pass server config directly, bypassing file loading entirely:
const client = new McpxClient({
servers: {
mcpServers: {
local: { command: "node", args: ["server.js"] },
remote: { url: "https://mcp.example.com", mcp: "v2" },
},
},
mcp: "auto", // default for servers that omit `mcp`
});Tool metadata
info() and listTools() return the raw MCP Tool, so you get everything the server declares: name, title, description, inputSchema, outputSchema, execution.taskSupport, and annotations. The annotations object carries the MCP behavioral hints:
const tool = await client.info("github", "delete_repo");
tool?.annotations; // { title?, readOnlyHint?, destructiveHint?, idempotentHint?, openWorldHint? }⚠️ Annotations are untrusted hints — per the MCP spec, clients should never make tool-use decisions based on annotations from untrusted servers. Treat the approval gate below as a guardrail, not a security boundary.
Human-in-the-loop approval gate
Because the SDK runs non-interactively, mcpx can't prompt a human itself — instead you supply an approval callback and a policy for which tools to gate. This lets you require approval before, say, any open-world writeable tool (openWorldHint: true and not readOnlyHint) runs:
import { McpxClient, ToolApprovalDeniedError } from "@arcadeai/mcpx";
const client = new McpxClient({
servers: { mcpServers: { github: { url: "https://mcp.github.com" } } },
approvalPolicy: "open-world-writeable", // default is "none"
onApprovalRequired: async ({ server, tool, args, annotations, reason }) => {
// Prompt a human, call out to an approval service, check a policy, etc.
return await promptHuman(`Allow ${server}/${tool}? (${reason})`, args);
},
});
// Gated tools wait for the callback; returning false throws ToolApprovalDeniedError.
// If a tool is gated but no onApprovalRequired callback was provided, exec() throws
// ToolApprovalRequiredError (fail-closed).
await client.exec("github", "delete_repo", { repo: "old-thing" });approvalPolicy accepts:
| Value | Gates |
| ----------------------- | ---------------------------------------------------------------------------------- |
| "none" (default) | nothing — existing behavior, zero overhead |
| "open-world-writeable"| tools with openWorldHint: true and not readOnlyHint (unannotated tools pass) |
| "writeable" | any tool not explicitly readOnlyHint: true (also gates unannotated tools) |
| "all" | every exec() call |
| (tool, server) => boolean | a custom predicate |
| Array<…> | any of the above, combined with OR |
Helpers isOpenWorldWriteable(tool) and isWriteable(tool) are exported for building custom predicates, along with the ToolAnnotations type. mcpx info <server> <tool> also surfaces these hints in the CLI.
Gating a specific tool by name. The annotation presets can't tell two similar tools apart — a "create PR" and a "create issue" tool both look like open-world writes. To gate one but not the other, use the custom predicate form and match on tool.name (and server):
const client = new McpxClient({
servers: { mcpServers: { github: { url: "https://mcp.github.com" } } },
// Require approval ONLY for creating PRs — issue creation runs freely.
approvalPolicy: (tool, server) => server === "github" && tool.name === "create_pull_request",
onApprovalRequired: async ({ server, tool, args }) => await promptHuman(`Allow ${server}/${tool}?`, args),
});
await client.exec("github", "create_pull_request", { ... }); // waits for approval
await client.exec("github", "create_issue", { ... }); // runs immediately, no promptConfirm the exact tool.name first — it must match exactly, and servers name tools differently (e.g. create_pull_request vs github_create_pr):
mcpx search "open pull request" # find the tool and its server
mcpx info github create_pull_request # confirm the nameTo match a family of tools, use a regex (/pull_request|merge_pr/.test(tool.name)); to gate the PR tool and everything open-world-writeable, combine them with the array form: ["open-world-writeable", (tool, server) => …]. Matching on tool.name is reliable — unlike annotations, which are untrusted server-supplied hints.
Permissions (Claude Code & Cursor)
AI agents like Claude Code and Cursor prompt users to approve each mcpx exec call. mcpx allow and mcpx deny manage fine-grained permission rules so agents can self-authorize specific tools without broad access.
Key insight: If the user allows the initial permission pattern once (safe — it only writes to local settings files), the agent can then grant itself access to specific tools as needed. This is an opt-in workflow — by default, agents cannot self-authorize and will prompt the user for each mcpx exec call.
# Allow all tools on a server (Claude Code, default)
mcpx allow github
# Allow for Cursor instead
mcpx allow github --cursor
# Allow specific tools only
mcpx allow github search_repositories get_file
# Allow read-only commands (search, info, list, servers, ping, etc.)
mcpx allow --all-read
# Allow all mcpx exec calls
mcpx allow --all
# Show current permissions across all scopes
mcpx allow --list
mcpx allow --list --cursor
# Preview what would be written
mcpx allow github --dry-run
# Revoke a server's permissions
mcpx deny github
# Revoke all mcpx permissions
mcpx deny --allTarget flag — by default, permissions target Claude Code. Use --cursor to target Cursor instead:
| Flag | Pattern prefix | Settings files |
| ----------- | -------------- | ----------------------------------------------- |
| (default) | Bash(…) | .claude/settings.local.json, etc. |
| --cursor | Shell(…) | .cursor/cli.json, ~/.cursor/cli-config.json |
Scope flags control where the permission is written:
| Flag | Claude Code file | Cursor file | Default |
| ----------- | ----------------------------- | --------------------------- | ------- |
| --local | .claude/settings.local.json | .cursor/cli.json | ✓ |
| --project | .claude/settings.json | .cursor/cli.json | |
| --global | ~/.claude/settings.json | ~/.cursor/cli-config.json | |
allow options:
| Flag | Purpose |
| ------------ | --------------------------------------------------- |
| --all | Allow all mcpx exec calls |
| --all-read | Allow read-only commands (search, info, list, etc.) |
| --list | Show current mcpx-related permissions |
| --cursor | Target Cursor settings instead of Claude Code |
| --local | Write to local settings (default) |
| --project | Write to project settings (shared) |
| --global | Write to global settings |
| --dry-run | Show patterns without writing |
deny options:
| Flag | Purpose |
| ------------ | --------------------------------------------- |
| --all | Remove all mcpx-related permissions |
| --all-read | Remove read-only command permissions |
| --cursor | Target Cursor settings instead of Claude Code |
| --local | Write to local settings (default) |
| --project | Write to project settings (shared) |
| --global | Write to global settings |
| --dry-run | Show what would be removed |
Development
# Install dependencies
bun install
# Run in development
bun run dev
# Run tests
bun test
# Build single binary
bun run build
# Lint
bun lintTech Stack
| Layer | Choice |
| ----------- | ----------------------------------------------------- |
| Runtime | Bun |
| Language | TypeScript |
| MCP Client | @modelcontextprotocol/client (official SDK v2) |
| CLI Parsing | commander |
| Validation | ajv (JSON Schema) |
| Embeddings | @huggingface/transformers (Xenova/bge-small-en-v1.5) |
Inspiration
Inspired by mcp-cli by Phil Schmid, which nails the core DX of a shell-friendly MCP client. mcpx extends that foundation with OAuth support for HTTP servers and semantic tool search.
Why mcpx?
mcpx is the client. If you need the server side — auth, governance, and production tools at scale — check out Arcade.
The full story: curl for MCP: Why Coding Agents Are Happier Using the CLI
License
MIT
