mcp2cli
CLI bridge that wraps MCP servers as bash-invokable commands, recovering ~11K tokens of context window per session
Documentation
mcp2cli
CLI bridge that wraps MCP (Model Context Protocol) servers as bash-invokable commands. Instead of loading all MCP tool definitions into an LLM's system prompt (~13K+ tokens permanently), agents invoke tools via bash at zero context cost.
Inspired by Google Workspace CLI, which wrapped Google's complex APIs into simple CLI commands -- all the functionality, none of the hassle. mcp2cli does the same for MCP servers.
Quick Start
# Install
git clone
cd mcp2cli
bun install
bun run build # produces dist/mcp2cli
# Bootstrap from existing Claude config
mcp2cli bootstrap # reads ~/.claude.json mcpServers -> ~/.config/mcp2cli/services.json
# Use it
mcp2cli services # list available services
mcp2cli n8n --help # list tools for a service
mcp2cli n8n n8n_list_workflows --params '{}' # invoke a tool
mcp2cli schema n8n.n8n_list_workflows # inspect tool schemaFor development without building:
bun run dev -- services
bun run dev -- n8n n8n_list_workflows --params '{}'Installation
Prerequisites: Bun v1.0+
git clone
cd mcp2cli
bun install
bun run buildThe compiled binary lands at `dist/mcp2cli`. Add it to your PATH or symlink it.
macOS Binary Upgrades
On macOS, do not overwrite an existing compiled binary in place with `cp new dist/mcp2cli`. Replacing the contents of the same inode can invalidate the ad-hoc code signature and cause the next exec to be killed with `SIGKILL` / exit code 137.
Use a fresh inode instead:
rm dist/mcp2cli
cp /path/to/new/mcp2cli dist/mcp2cliIf the local UI daemon is managed by launchd, restart it after replacing the binary:
launchctl kickstart -k gui/501/com.mcp2cli.local-uiThe already-running daemon keeps the old inode open until restart, so this is safe to do while the daemon is live.
Configuration
Service Registry
mcp2cli discovers MCP servers from `~/.config/mcp2cli/services.json`:
{
"services": {
"n8n": {
"description": "n8n workflow automation",
"backend": "stdio",
"command": "npx",
"args": ["-y", "@anthropic-ai/n8n-mcp"],
"env": {
"N8N_BASE_URL": "https://n8n.example.com",
"N8N_API_KEY": "your-api-key"
}
},
"homekit": {
"description": "HomeKit smart home control",
"backend": "stdio",
"command": "node",
"args": ["/path/to/homekit-mcp/dist/index.js"],
"env": {}
}
}
}Each service entry mirrors the Claude Desktop `mcpServers` format -- same `command`, `args`, and `env` fields.
Bootstrap from Claude Config
If you already have MCP servers configured in `~/.claude.json`:
mcp2cli bootstrapThis reads your `mcpServers` entries and generates `services.json` automatically.
Commands
List Services
mcp2cli servicesList Tools for a Service
mcp2cli --helpInvoke a Tool
mcp2cli --params ''The `--params` value must be valid JSON matching the tool's input schema.
Inspect Tool Schema
mcp2cli schema .Returns the JSON Schema for the tool's input parameters -- useful for discovering required fields.
Dry Run
mcp2cli --params '{"query": "test"}' --dry-runValidates input and shows what would be sent without executing the tool call.
Field Filtering
mcp2cli --params '{}' --fields "id,name,status"Extracts only the specified fields from the response -- reduces output noise for scripting.
Generate Skill Files
mcp2cli generate-skillsGenerates PAI skill files from MCP tool schemas, making tools discoverable by AI agents.
Daemon Management
mcp2cli daemon status # check if daemon is running, connection pool stats
mcp2cli daemon stop # graceful shutdownOutput Format
All responses are structured JSON on stdout. Logs go to stderr.
// Success
{ "success": true, "result": { "workflows": [...] } }
// Error
{ "error": true, "code": "TOOL_ERROR", "message": "Workflow not found", "reason": "..." }This makes mcp2cli composable with `jq`, pipes, and scripting:
# Get workflow names
mcp2cli n8n n8n_list_workflows --params '{}' | jq '.result.workflows[].name'
# Check for errors
mcp2cli n8n n8n_get_workflow --params '{"id": "123"}' | jq 'if .error then .message else .result end'Exit Codes
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Validation error (bad input, schema mismatch) |
| 2 | Auth error (missing credentials, permission denied) |
| 3 | Tool error (MCP tool returned an error) |
| 4 | Connection error (daemon unreachable, transport failure) |
| 5 | Internal error |
Use exit codes for scripting:
mcp2cli n8n n8n_get_workflow --params '{"id": "123"}' 2>/dev/null
if [ $? -eq 4 ]; then
echo "Connection failed -- is the MCP server configured?"
fiEnvironment Variables
| Variable | Default | Description |
|---|---|---|
| `MCP2CLI_LOG_LEVEL` | `silent` | Log verbosity: `silent`, `error`, `warn`, `info`, `debug` |
| `MCP2CLI_IDLE_TIMEOUT` | `60` | Daemon idle timeout in seconds |
| `MCP2CLI_STARTUP_TIMEOUT` | `10000` | CLI wait time for daemon startup readiness in milliseconds |
| `MCP2CLI_TOOL_TIMEOUT` | `60000` | Tool call timeout in milliseconds. A per-service `timeout` in `services.json` overrides it, and the resolved value is passed to the MCP SDK, so it bounds the call for real |
| `MCP2CLI_REQUEST_TIMEOUT_MS` | `60000` | CLI request timeout for local Unix-socket daemon calls in milliseconds. Raise it alongside a long per-service `timeout`, otherwise the CLI gives up before the verb finishes |
| `MCP2CLI_REMOTE_REQUEST_TIMEOUT_MS` | `60000` | CLI HTTP request timeout for explicit remote daemon calls in milliseconds |
| `MCP2CLI_REMOTE_RETRIES` | `3` | Remote request attempts for explicit remote daemon calls |
| `MCP2CLI_REMOTE_FALLBACK_TIMEOUT_MS` | `10000` | CLI HTTP timeout for each `remote-local` probe before falling back to the local daemon |
| `MCP2CLI_REMOTE_FALLBACK_RETRIES` | `1` | Remote probe attempts before `remote-local` calls fall back to the local daemon |
| `MCP2CLI_POOL_MAX` | `50` | Max concurrent MCP connections in the pool |
| `MCP2CLI_LOG_DIR` | `~/.cache/mcp2cli/logs` | Directory for stderr capture logs |
| `MCP2CLI_NO_DAEMON` | (unset) | If set, bypass the daemon and connect directly |
| `MCP2CLI_DEBUG` | (unset) | If `1`, print discarded stdout lines from MCP servers |
Example:
MCP2CLI_LOG_LEVEL=debug mcp2cli n8n n8n_list_workflows --params '{}'
MCP2CLI_NO_DAEMON=1 mcp2cli n8n n8n_list_workflows --params '{}'Tool calls longer than 60 seconds
A slow verb passes through two independent deadlines, and raising only one
changes nothing — the other still fires first:
1. Daemon → MCP server. The per-service `timeout` in `services.json`
(falling back to `MCP2CLI_TOOL_TIMEOUT`, default 60s) is handed to the MCP
SDK on every tool call. Without it the SDK applies its own
`DEFAULT_REQUEST_TIMEOUT_MSEC` of 60s and fails with
`MCP error -32001: Request timed out`.
2. CLI → daemon. `MCP2CLI_REQUEST_TIMEOUT_MS` (default 60s) bounds the
local Unix-socket request. When it fires the CLI reports
`CONNECTION_ERROR: The operation timed out.` even though the daemon and the
MCP server are still working.
So a verb that can run for 20 minutes needs both:
// services.json
{ "services": { "runner-boxes": { "timeout": 1200000 /* ...*/ } } }MCP2CLI_REQUEST_TIMEOUT_MS=1200000 mcp2cli runner-boxes runner_done --params '{}'A timed-out call is not a failed verb: the server-side work may well have
completed after the client stopped waiting. Do not blind-retry a
non-idempotent verb on a timeout.
Architecture
CLI Entry (src/cli/index.ts)
|-- Command Dispatch (services, schema, bootstrap, generate-skills, daemon)
|-- Tool Call Handler -> Daemon Client (Unix socket)
| \-- Daemon Server (src/daemon/server.ts)
| |-- Connection Pool (src/daemon/pool.ts)
| | \-- MCP Transport (src/connection/transport.ts)
| |-- Idle Timer (src/daemon/idle.ts)
| \-- Health Endpoint (/health with memory stats)
|-- Input Validation (src/validation/) -- 48 adversarial patterns
|-- Schema Introspection (src/schema/)
|-- Skill Generation (src/generation/)
\-- Structured Logger (src/logger/) -- JSON on stderrKey Design Decisions
Persistent daemon. MCP servers have a 2-5 second startup cost per connection. The daemon keeps connections alive in a pool, so subsequent calls return in milliseconds instead of seconds. The daemon auto-exits after the idle timeout (default 60s).
Connection pool with health checks. Connections are validated before use and recycled on failure. The pool enforces a max size to prevent resource exhaustion.
Structured JSON everywhere. stdout is always parseable JSON -- no mixed text output. Logs (when enabled) go to stderr as structured JSON lines. This makes mcp2cli reliable for scripting and piping.
Semantic exit codes. Different failure modes get different exit codes so callers can branch on the type of error without parsing output.
Input validation. All tool parameters are validated against the MCP schema before the call is dispatched. The validation layer handles 48 adversarial patterns (injection attempts, type coercion, overflow) to fail fast with clear errors.
Agent Integration
mcp2cli is designed to be called from AI agents via bash tool use. A typical agent workflow:
# Agent discovers available tools
mcp2cli n8n --help
# Agent reads the schema to understand parameters
mcp2cli schema n8n.n8n_get_workflow
# Agent invokes the tool
mcp2cli n8n n8n_get_workflow --params '{"id": "abc123"}'This pattern keeps MCP tool definitions out of the agent's system prompt entirely. The agent only pays context cost when it actually needs to call a tool, and even then only for the specific tool's schema -- not all tools from all servers.
Multi-User Authentication
mcp2cli supports multi-user RBAC via `~/.config/mcp2cli/tokens.json`. Each user or agent gets a bearer token with a role.
tokens.json
{
"tokens": [
{
"id": "rico",
"token": "your-admin-token-here",
"role": "admin",
"description": "Full admin access",
"username": "rico",
"password": "your-web-ui-password",
"expiresAt": "2026-07-01T00:00:00.000Z"
},
{
"id": "skippy",
"token": "your-agent-token-here",
"role": "agent",
"description": "AI agent - tools + read, no config mutations",
"expiresAt": "2026-07-01T00:00:00.000Z"
},
{
"id": "viewer01",
"token": "your-viewer-token-here",
"role": "viewer",
"description": "Read-only access"
}
]
}Generate secure tokens: `openssl rand -base64 32`
RBAC Roles
| Permission | viewer | agent | admin |
|---|---|---|---|
| List services, status | yes | yes | yes |
| Call tools, list tools, schema | no | yes | yes |
| Read credentials | no | yes | yes |
| Add/update/remove services | no | no | yes |
| Write credentials, manage groups | no | no | yes |
| Reload, import, shutdown | no | no | yes |
The `username`/`password` fields enable web UI login at the daemon's root URL. Token-based auth (Bearer header) works for all API and CLI access.
The optional `expiresAt` field enables token expiry and refresh. Expired tokens are rejected. Near-expiry tokens from `tokens.json` can be rotated through `POST /api/auth/refresh`; the daemon writes the new token back to `tokens.json` and hot-reloads token file edits. Local CLI clients proactively refresh near-expiry admin tokens before daemon API calls.
Fallback Behavior
- No tokens.json, no env token: Auth disabled, all requests treated as admin (backward compatible)
- `MCP2CLI_AUTH_TOKEN` env var only: Legacy single-token mode, treated as admin
- tokens.json exists: Full multi-user RBAC
Per-Identity Credential Management
Different users and agents can have their own API keys for backend services. When rico calls open-brain, he uses his key. When skippy calls it, the agents' shared key is used.
credentials.json
Create `~/.config/mcp2cli/credentials.json`:
{
"groups": {
"ai_agents": ["skippy", "bilby", "nagatha", "claude"]
},
"credentials": {
"rico": {
"open-brain": { "headers": { "Authorization": "Bearer ricos-ob-key" } }
},
"ai_agents": {
"open-brain": { "headers": { "Authorization": "Bearer agents-shared-ob-key" } },
"n8n": { "env": { "N8N_API_KEY": "agents-n8n-key" } }
}
},
"defaults": {
"proxmox": { "headers": { "Authorization": "PVEAPIToken=shared-token" } }
}
}Resolution Chain
When a tool call comes in, credentials are resolved in priority order:
1. User-specific -- `credentials[userId][service]`
2. Group -- first matching group the user belongs to
3. Defaults -- `defaults[service]`
4. services.json -- whatever's baked into the service config (backward compatible)
For http/websocket services, credential headers are merged into the connection. For stdio services, credential env vars are merged into the process environment.
For identity-sensitive services, set `requiresCredentials: true` in `services.json`. If no user, group, or explicit default credential exists, the daemon rejects the call instead of using base service headers.
Credential CLI
# Set credentials for an identity on a service
mcp2cli credentials set rico open-brain --header "Authorization: Bearer my-key"
# Set env-based credentials (for stdio services)
mcp2cli credentials set rico n8n --env "N8N_API_KEY=my-n8n-key"
# Set a default credential (used when no user/group match)
mcp2cli credentials set-default proxmox --header "Authorization: PVEAPIToken=shared"
# List all credentials (values are redacted)
mcp2cli credentials list
# Show effective credential source for a user
mcp2cli credentials resolve skippy open-brain
# → {"exists": true, "source": "group"}
# Group management
mcp2cli credentials group add ai_agents skippy bilby nagatha
mcp2cli credentials group add-members ai_agents claude
mcp2cli credentials group remove-members ai_agents bilby
mcp2cli credentials group list
# Remove credentials
mcp2cli credentials remove rico open-brain
mcp2cli credentials remove-default proxmox
mcp2cli credentials group remove ai_agents
# Reload from disk after manual edits
mcp2cli credentials reload
# Populate Open Brain credentials from a Vaultwarden item
mcp2cli credentials bootstrap-open-brain --item "Open Brain - Per-User Tokens"Open Brain Example
Open Brain (OBv2) is an HTTP MCP service where the bearer token controls namespace identity. Do not put an Open Brain `Authorization` header in `services.json`; store it only as a per-identity credential.
services.json -- base config for a hosted daemon (no credentials, endpoint only):
{
"services": {
"open-brain": {
"backend": "http",
"url": "http://open-brain.example.internal:3100/mcp",
"source": "remote",
"requiresCredentials": true,
"preconnect": false
}
}
}Use `source: "remote"` when the CLI is routing through a hosted mcp2cli daemon. `requiresCredentials: true` makes missing per-identity credentials fail closed, and `preconnect: false` prevents daemon startup from opening an unauthenticated base Open Brain connection.
credentials.json -- per-identity keys:
{
"groups": {
"ai_agents": ["skippy", "bilby", "claude"]
},
"credentials": {
"rico": {
"open-brain": { "headers": { "Authorization": "Bearer ricos-ob-api-key" } }
},
"ai_agents": {
"open-brain": { "headers": { "Authorization": "Bearer agents-shared-ob-key" } }
}
}
}Now when rico calls `mcp2cli open-brain search_all --params '{"query": "kubernetes"}'`, his personal key is injected. When skippy calls the same tool, the agents' shared key is used. Each gets their own connection in the pool.
Vaultwarden bootstrap -- if the item `Open Brain - Per-User Tokens` has custom fields such as `AUTH_TOKEN_USER_RICO`, `AUTH_TOKEN_USER_SKIPPY`, and `AUTH_TOKEN_USER_BILBY`, run:
mcp2cli credentials bootstrap-open-brainThe field suffix is lowercased and used as the identity (`AUTH_TOKEN_USER_RICO` -> `rico`). Existing credentials are skipped unless `--force` is passed. The command prints counts and identity names only; it does not print bearer tokens.
Caller Identity Headers
Header and env values support `${caller.id}` and `${caller.role}` template variables. These are replaced with the authenticated caller's identity at call time.
In services.json -- inject identity headers for services whose backend trusts caller metadata instead of per-user bearer tokens:
{
"services": {
"example-service": {
"backend": "http",
"url": "https://example.internal/mcp",
"headers": {
"X-Agent-Id": "${caller.id}",
"X-Role": "${caller.role}"
}
}
}
}When bilby calls the service, the request headers become `X-Agent-Id: bilby` and `X-Role: agent`.
In credentials.json -- combine per-identity keys with identity headers:
{
"credentials": {
"rico": {
"open-brain": {
"headers": {
"Authorization": "Bearer ricos-ob-key",
"X-Namespace": "${caller.id}"
}
}
}
}
}Templates work in both headers and env values. Unknown variables (e.g., `${caller.email}`) are left unexpanded.
Security
- Redacted list output -- `GET /api/credentials` returns `Bear***` not full values
- IDOR protection -- agents can only resolve their own credentials, admin required for others
- File permissions -- `credentials.json` is written with `0600` (owner read/write only)
- Input validation -- header values reject CRLF injection, dangerous headers (Host, Transfer-Encoding) and env vars (PATH, LD_PRELOAD, NODE_OPTIONS) are blocked
- Atomic writes -- temp file + rename prevents partial writes on crash
- Pool invalidation -- changing credentials evicts stale connections automatically
Advanced Features
Schema Caching
Schemas are cached locally to avoid re-fetching on every invocation. Cached schemas live at `~/.cache/mcp2cli/schemas/` with a 24-hour TTL. Cache drift is detected via SHA-256 hashing -- if the upstream schema changes, the cache is automatically invalidated.
# Check cache status (age, TTL, drift)
mcp2cli cache status
# Clear all cached schemas
mcp2cli cache clear
# Clear cache for a specific service
mcp2cli cache clear n8n
# Bypass cache for a single schema lookup
mcp2cli schema n8n.n8n_list_workflows --freshOverride the cache directory with `MCP2CLI_CACHE_DIR`.
Access Control
Restrict which tools are exposed per service using `allowTools` and `blockTools` in `services.json`. Both accept glob patterns.
{
"services": {
"n8n": {
"description": "n8n workflow automation",
"backend": "stdio",
"command": "npx",
"args": ["-y", "@anthropic/n8n-mcp"],
"allowTools": ["n8n_list_*", "n8n_get_*"],
"blockTools": ["n8n_delete_*"]
}
}
}When both are present, `allowTools` is evaluated first (whitelist), then `blockTools` removes matches from the allowed set.
Cross-Service Tool Search
Search for tools across all services using cached schemas:
# Find all tools matching a pattern
mcp2cli grep "workflow"
# Regex patterns work
mcp2cli grep "delete|remove"This searches cached schemas only -- no MCP connections are made.
WebSocket Transport
Connect to MCP servers over WebSocket. Supports optional stdio fallback and access control, same as HTTP.
{
"services": {
"remote-mcp": {
"description": "Remote MCP server via WebSocket",
"backend": "websocket",
"url": "ws://mcp-gateway.local:3000/mcp",
"fallback": {
"command": "npx",
"args": ["-y", "@anthropic/n8n-mcp"]
}
}
}
}WebSocket services benefit from the same circuit breaker and fallback behavior as HTTP services.
Batch Tool Calls
Execute multiple tool calls in a single invocation by piping NDJSON to `mcp2cli batch`. Each line is a JSON object with `service`, `tool`, and `params` fields:
# Sequential execution (default)
cat
mcp2cli n8n n8n_list_workflows --params '{}'When `MCP2CLI_REMOTE_URL` is set, the CLI skips local daemon startup entirely and sends requests directly over HTTP.
Network Environment Variables
In addition to the base environment variables, network mode adds:
| Variable | Default | Description |
|---|---|---|
| `MCP2CLI_LISTEN_HOST` | (unset) | Bind address for TCP mode. Setting this enables TCP instead of Unix socket. Use `0.0.0.0` to listen on all interfaces |
| `MCP2CLI_LISTEN_PORT` | `9500` | TCP port when `MCP2CLI_LISTEN_HOST` is set |
| `MCP2CLI_AUTH_TOKEN` | (unset) | Bearer token for TCP authentication. Required for production deployments. Alias: `MCP_TOKEN` |
| `MCP2CLI_REMOTE_URL` | (unset) | URL of remote mcp2cli daemon (e.g. `https://mcp2cli.rodaddy.live`). Enables remote client mode. Alias: `MCP_HOST` |
| `MCP2CLI_CONFIG` | `~/.config/mcp2cli/services.json` | Path to service definitions (useful for server-side config in `/etc/mcp2cli/`) |
| `MCP2CLI_TOKENS_FILE` | `~/.config/mcp2cli/tokens.json` | Path to multi-user token/RBAC config |
| `MCP2CLI_CREDENTIALS_FILE` | `~/.config/mcp2cli/credentials.json` | Path to per-identity credential mappings |
Authentication
The daemon supports two auth modes (see Multi-User Authentication above):
1. Multi-user RBAC via `tokens.json` -- each user/agent gets their own token and role
2. Legacy single-token via `MCP2CLI_AUTH_TOKEN` env var -- treated as admin
All token comparisons use timing-safe equality to prevent timing attacks.
Auth-exempt paths -- these skip authentication so load balancers and monitoring can probe without credentials:
- `GET /health` -- health check with uptime, memory, and active connection count
- `GET /metrics` -- Prometheus metrics endpoint with aggregate service/tool metrics
Prometheus Metrics
The daemon exposes metrics at `GET /metrics` in Prometheus text exposition format. Key metrics:
| Metric | Type | Description |
|---|---|---|
| `mcp2cli_requests_total` | counter | Total requests by `{service, tool}` |
| `mcp2cli_requests_errors_total` | counter | Failed requests by `{service, tool}` |
| `mcp2cli_request_duration_ms` | histogram | Request latency with buckets (10ms - 30s) |
| `mcp2cli_requests_active` | gauge | Currently in-flight requests |
| `mcp2cli_pool_connections_active` | gauge | Current connection pool size |
| `mcp2cli_pool_services` | gauge | Connected services (`{service}` label) |
| `mcp2cli_connection_events_total` | counter | Connect/disconnect/health-check-failure by `{service}` |
| `mcp2cli_auth_failures_total` | counter | Total authentication failures |
| `mcp2cli_process_uptime_seconds` | gauge | Daemon uptime |
| `mcp2cli_process_memory_rss_bytes` | gauge | Resident set size |
Raw caller labels on public `/metrics` are disabled by default to avoid exposing user IDs to unauthenticated scrapers. Set `MCP2CLI_METRICS_INCLUDE_CALLER=1` only in trusted monitoring environments to add `{service, tool, caller}` series.
For a quick JSON breakdown by identity, call `GET /api/metrics/user/:userId` with a bearer token that has `status` permission. Non-admin callers can only read their own user metrics; admins can read any user.
Remote clients discover the daemon's service inventory through authenticated `GET /api/services/discovery`, not the public `/health` probe.
Add to your Prometheus config:
scrape_configs:
- job_name: mcp2cli
static_configs:
- targets: ['mcp-server.local:9500']Bash Wrapper (curl-only clients)
For machines that only have `curl` and `jq` (no Bun runtime), use the bash wrapper:
# Install the wrapper
cp scripts/mcp2cli-remote /usr/local/bin/
chmod +x /usr/local/bin/mcp2cli-remote
# Configure
export MCP2CLI_REMOTE_URL=http://mcp-server.local:9500
export MCP2CLI_AUTH_TOKEN=
# Use it like the full CLI
mcp2cli-remote n8n n8n_list_workflows '{}'LXC Deployment
The `deploy/` directory contains everything needed to run mcp2cli as a systemd service in an LXC container (or any Linux host):
| File | Purpose |
|---|---|
| `deploy/mcp2cli.service` | systemd unit file (hardened with `NoNewPrivileges`, `ProtectSystem=strict`) |
| `deploy/env.example` | Environment file template -- copy to `/etc/mcp2cli/env` |
| `deploy/services-server.json` | Example server-side service config |
Setup:
# Copy files into place
cp deploy/mcp2cli.service /etc/systemd/system/
mkdir -p /etc/mcp2cli
cp deploy/env.example /etc/mcp2cli/env
cp deploy/services-server.json /etc/mcp2cli/services.json
# Edit config
vim /etc/mcp2cli/env # set MCP2CLI_AUTH_TOKEN
vim /etc/mcp2cli/services.json # configure your MCP backends
# Enable and start
useradd --system --no-create-home mcp2cli
systemctl daemon-reload
systemctl enable --now mcp2clicurl Examples
SERVER=http://mcp-server.local:9500
TOKEN=your-token-here
# Health check (no auth required)
curl -s $SERVER/health | jq .
# Prometheus metrics (no auth required)
curl -s $SERVER/metrics
# List tools for a service
curl -s -X POST $SERVER/list-tools \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"service": "n8n"}' | jq .
# Invoke a tool
curl -s -X POST $SERVER/call \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"service": "n8n", "tool": "n8n_list_workflows", "params": {}}' | jq .
# Get a tool schema
curl -s -X POST $SERVER/schema \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"service": "n8n", "tool": "n8n_list_workflows"}' | jq .Development
bun run dev -- # run without building
bun test # run test suite
bun run build # compile to dist/mcp2cliLicense
MIT
Frequently asked questions
What is mcp2cli?
mcp2cli is CLI bridge that wraps MCP servers as bash-invokable commands, recovering ~11K tokens of context window per session
How do I install mcp2cli?
Open the GitHub repository and follow its README. Most MCP servers are added to your client's MCP config, then called by your agent.
Is mcp2cli open source?
Yes — it is hosted on GitHub at https://github.com/rodaddy/mcp2cli and has 9 stars.
Related MCP tools
The go-to web for your AI coding agent — local-first search, fetch, crawl & research over MCP. No API keys, no cloud, $0/query. Public beta.
Cut AI token costs 95%+ on code exploration. The leading MCP server for precise, symbol-level GitHub code retrieval via tree-sitter AST. Works with Claude Code, Cursor & any MCP client. 313B+ tokens saved.
Open-source coding agent memory. Records issues, attempts, fixes and decisions, then warns your agent before it repeats an approach that already failed. Native MCP server for Claude Code, Cursor, Antigravity and Codex. 100% local, no cloud, no telemetry. MIT.
MCP server that enables AI assistants to interact with Google Gemini CLI, leveraging Gemini's massive token window for large file analysis and codebase understanding
Universal AI context generator. Saves thousands of tokens per conversation in Claude Code, Cursor, Copilot, Codex, and more.
AI Skills, MCP Tools, and CLI for Unity Engine. Full AI develop and test loop. Use cli for quick setup. Efficient token usage, advanced tools. Any C# method may be turned into a tool by a single line. Works with Claude Code, Gemini, Copilot, Cursor and any other absolutely for free.
Run your own MCP server? See who uses it and what to fix.
Measure it with TrackMCP