# cli2mcp [![npm version](https://img.shields.io/npm/v/cli2mcp?color=crimson&label=npm)](https://www.npmjs.com/package/cli2mcp) [![npm downloads](https://img.shields.io/npm/dm/cli2mcp?color=blue&label=downloads)](https://www.npmjs.com/package/cli2mcp) [![CI](https://img.shields.io/github/actions/workflow/status/RonieNeubauer/cli2mcp/ci.yml?branch=main&label=CI)](https://github.com/RonieNeubauer/cli2mcp/actions) [![node](https://img.shields.io/node/v/cli2mcp?color=green)](https://nodejs.org) [![license](https://img.shields.io/npm/l/cli2mcp?color=gray)](LICENSE) > **Status:** v0.1 — early release. Stdio transport only. APIs may change before 1.0. Expose any command-line binary as a [Model Context Protocol](https://modelcontextprotocol.io) tool by parsing its `--help` output and synthesizing a JSON Schema at startup. One command, no boilerplate. Works with **any MCP-compatible client** — Claude Desktop, ChatGPT (via OpenAI Agents SDK), Cursor, Gemini CLI, Cline, Windsurf, Continue, Zed, and anything else that speaks the [MCP stdio transport](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#stdio). ```sh npx cli2mcp ``` ![cli2mcp demo](https://raw.githubusercontent.com/RonieNeubauer/cli2mcp/main/docs/demo.svg) --- ## Why Writing an MCP server for a CLI you already have is mechanical work: instantiate the SDK, register a tool, hand-write the input schema, marshal arguments, spawn the subprocess, format the output. Roughly 80–150 lines of TypeScript per binary, repeated forever as new tools come out. `cli2mcp` does it in one command. The CLI's own `--help` is the source of truth for the schema — if `rg` adds a flag tomorrow, the AI sees it tomorrow without code changes. --- ## Install ```sh npm install -g cli2mcp # or invoke without installing npx cli2mcp ``` Requires Node.js 22+. --- ## Configure your MCP client `cli2mcp` is launched by your client as a stdio subprocess. Add an entry per CLI you want to expose. ### Claude Desktop Config file location: | OS | Path | |---|---| | macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` | | Windows | `%APPDATA%\Claude\claude_desktop_config.json` | | Linux | `~/.config/Claude/claude_desktop_config.json` | ```json { "mcpServers": { "ripgrep": { "command": "npx", "args": ["-y", "cli2mcp", "rg", "--name", "ripgrep"] }, "jq": { "command": "npx", "args": ["-y", "cli2mcp", "jq"] } } } ``` Restart Claude Desktop after editing. ### Other clients | Client | Config file | Format | |---|---|---| | ChatGPT (OpenAI Agents SDK) | `MCPServerStdio` parameter — see [OpenAI Agents docs](https://openai.github.io/openai-agents-python/mcp/) | `command: "npx"`, `args: ["-y", "cli2mcp", ""]` | | Cursor | `.cursor/mcp.json` (project) or `~/.cursor/mcp.json` (global) | Same `mcpServers` block as above | | Cline | VS Code → Cline → MCP Settings → `cline_mcp_settings.json` | Same `mcpServers` block | | Windsurf | `~/.codeium/windsurf/mcp_config.json` | Same `mcpServers` block | | Gemini CLI | `~/.gemini/settings.json` | Same `mcpServers` block | | Continue | `~/.continue/config.json` → `experimental.modelContextProtocolServers` | Same launcher | | Zed | `~/.config/zed/settings.json` → `context_servers` | Same launcher | | Any stdio-capable MCP client | per the client's docs | Same launcher: `npx -y cli2mcp ` | Refer to each client's documentation for the exact config path on your platform — they evolve and are not guaranteed to match the table above. --- ## Quick wins — copy-paste configs Drop any of these into your client's `mcpServers` block (paths shown above per client). Each one wraps a popular CLI as an MCP tool an AI can call directly. ```json { "mcpServers": { "ripgrep": { "command": "npx", "args": ["-y", "cli2mcp", "rg", "--name", "ripgrep", "--description", "Recursively search files with regex"] }, "jq": { "command": "npx", "args": ["-y", "cli2mcp", "jq", "--description", "Query and transform JSON via stdin"] }, "pandoc": { "command": "npx", "args": ["-y", "cli2mcp", "pandoc", "--description", "Convert documents between markup formats"] }, "sqlite3": { "command": "npx", "args": ["-y", "cli2mcp", "sqlite3", "--description", "Run SQL against a SQLite database file", "--cwd", "/path/to/safe/dir"] }, "yt-dlp": { "command": "npx", "args": ["-y", "cli2mcp", "yt-dlp", "--description", "Download media from URLs", "--cwd", "/path/to/downloads", "--timeout", "300000"] } } } ``` > Each CLI must already be installed and on `PATH`. `cli2mcp` does not install them for you. --- ## How it compares | Approach | LOC per CLI | New flag handling | Maintenance | |---|---|---|---| | Hand-written MCP server (TypeScript SDK) | ~80–150 | manual schema edit | per-CLI release cycle | | OpenAPI → MCP generators | n/a | requires an OpenAPI spec | does not cover arbitrary CLIs | | Wrapping `bash` / `sh` as a tool | ~10 | n/a — gives the AI a shell | unsafe, no schema, no sandbox | | **`cli2mcp `** | **0** | **automatic at next start** | **none — re-reads `--help`** | The closest neighbor is FastMCP's `from_openapi` — it does not cover arbitrary CLI binaries. As of April 2026 there is no other published tool that turns an arbitrary `--help` output into a typed MCP tool in one command. --- ## Verified targets These CLIs are covered by the test suite or have been manually exercised end-to-end: | CLI | Status | Notes | |---|---|---| | `jq` | ✅ tested | help-on-stderr correctly captured; `stdin` piping works | | `ripgrep` (`rg`) | ✅ tested | 90+ flags inferred; `args` positional handled | | `curl` | ✅ fixture | shape extraction validated against bundled fixture | | `node` | ✅ integration test | end-to-end MCP handshake + `tools/call` | Other POSIX-style CLIs (e.g. `ffmpeg`, `yt-dlp`, `pandoc`, `sqlite3`, `imagemagick`) are *expected* to work but are not yet covered by tests. Report bugs in [issues](https://github.com/RonieNeubauer/cli2mcp/issues). --- ## How --help becomes a JSON Schema | Help fragment | MCP property | |---|---| | `--flag` | `boolean` | | `--flag ` / `` / `` | `string` | | `--flag ` / `` / `` | `number` | | `--flag ` | `string` enum with choices | | Repeatable flag | `array` | | Positional args | `args: array` | | Reserved input `stdin` | `string` piped to subprocess stdin | When parsing fails on an unconventional `--help`, `cli2mcp` falls back to a single variadic `args` positional so the tool is still usable — the model just gets a free-form argument list instead of typed flags. --- ## Options ``` cli2mcp [options] --name Tool name shown to the AI (default: ) --description Tool description shown to the AI (default: first --help line) --timeout Subprocess timeout per call (default: 60000) --cwd Working directory for subprocess (default: process.cwd()) --env Extra environment variables (repeatable) --stderr stderr handling: include → appended to tool output (default) drop → discarded error → any stderr → isError: true -h, --help Show help ``` ### Piping stdin Reserved input property `stdin` is piped to the subprocess: ```json { "args": [".name"], "stdin": "{\"name\": \"cli2mcp\"}" } ``` --- ## How it works ``` cli2mcp rg │ ├─ 1. spawn: rg --help → capture stdout + stderr ├─ 2. parse help text → CliShape { flags, positionals, description } ├─ 3. synthesize JSON Schema → inputSchema ├─ 4. register one MCP tool → name: "rg", schema: └─ 5. start stdio MCP server → await client connection On tools/call: { args, flags, stdin? } → argv builder → execa(rg, argv, { stdin }) │ stdout (+ stderr) → content[text] ``` Non-zero exit → `{ isError: true, content: [{ type: "text", text: }] }` (unless `--stderr drop`). --- ## Security `cli2mcp` lets an AI agent invoke the CLIs you expose, with the arguments the agent chooses. **You are responsible for what those CLIs can do on your machine.** Practical guidance: - **Only expose CLIs whose blast radius you accept.** `jq`, `rg`, `pandoc` are mostly safe (read-only, deterministic). `curl`, `ffmpeg --output`, `sqlite3`, `rm`, `kubectl`, `aws` are not. - **The AI is not sandboxed.** A prompt injection attack could cause an exposed `curl` to fetch `evil.example.com`, an exposed `rm` to delete files, etc. - **Use `--cwd` to constrain filesystem scope** when wrapping CLIs that touch files. - **Use `--env` deliberately.** Do not pass through credentials the model shouldn't reach. - **Never expose `sh`, `bash`, `zsh`, `python -c`, or anything with eval semantics** — that bypasses every safeguard `cli2mcp` provides. The schema-from-help design *reduces* the risk of malformed argv but does **not** eliminate the risk of misuse. Treat each exposed CLI as a delegated capability, not a sandbox. --- ## Troubleshooting **The CLI has no `--help` flag.** `cli2mcp` will still start with a single `args` positional. The AI can pass arguments freely; you lose typed flag inference. **The schema came out empty / wrong.** Run `cli2mcp ` manually and inspect the `tools/list` response (use `npx @modelcontextprotocol/inspector`). The most common cause is non-standard help formatting (no `--long-form` flags, columns misaligned). Open an issue with the ` --help` output attached. **The subprocess hangs.** The default 60s timeout will kill it. Raise via `--timeout`. If your CLI is interactive (waits for a TTY), `cli2mcp` cannot help — pipe input via `stdin` instead. **Flag not being passed.** Set `--stderr include` (the default) and inspect the `content[].text`. If the flag isn't appearing in argv, the help parser failed to extract it — file an issue. --- ## Contributing Bug reports and patches welcome. Fixtures for new CLIs (`test/fixtures/help/.txt` + a shape test) are the highest-leverage contributions. ```sh pnpm install pnpm test # vitest pnpm typecheck # tsc --noEmit pnpm lint # biome check ``` --- ## Star history [![Star History Chart](https://api.star-history.com/svg?repos=RonieNeubauer/cli2mcp&type=Date)](https://star-history.com/#RonieNeubauer/cli2mcp&Date) If `cli2mcp` saved you an afternoon of writing MCP boilerplate, a star helps other people find it. --- ## Author Built by **Ronie Neubauer** — Principal Engineer, 22+ years shipping production systems. - GitHub: [@RonieNeubauer](https://github.com/RonieNeubauer) - Blog: [ronieneubauer.com](https://ronieneubauer.com) - Issues & ideas: [github.com/RonieNeubauer/cli2mcp/discussions](https://github.com/RonieNeubauer/cli2mcp/discussions) --- ## License MIT © 2026 Ronie Neubauer.