{
  "markdown": "# cli2mcp\n\n[![npm version](https://img.shields.io/npm/v/cli2mcp?color=crimson&label=npm)](https://www.npmjs.com/package/cli2mcp)\n[![npm downloads](https://img.shields.io/npm/dm/cli2mcp?color=blue&label=downloads)](https://www.npmjs.com/package/cli2mcp)\n[![CI](https://img.shields.io/github/actions/workflow/status/RonieNeubauer/cli2mcp/ci.yml?branch=main&label=CI)](https://github.com/RonieNeubauer/cli2mcp/actions)\n[![node](https://img.shields.io/node/v/cli2mcp?color=green)](https://nodejs.org)\n[![license](https://img.shields.io/npm/l/cli2mcp?color=gray)](LICENSE)\n\n> **Status:** v0.1 — early release. Stdio transport only. APIs may change before 1.0.\n\nExpose 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.\n\nWorks 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).\n\n```sh\nnpx cli2mcp <command>\n```\n\n![cli2mcp demo](https://raw.githubusercontent.com/RonieNeubauer/cli2mcp/main/docs/demo.svg)\n\n---\n\n## Why\n\nWriting 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.\n\n`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.\n\n---\n\n## Install\n\n```sh\nnpm install -g cli2mcp\n# or invoke without installing\nnpx cli2mcp <command>\n```\n\nRequires Node.js 22+.\n\n---\n\n## Configure your MCP client\n\n`cli2mcp` is launched by your client as a stdio subprocess. Add an entry per CLI you want to expose.\n\n### Claude Desktop\n\nConfig file location:\n\n| OS | Path |\n|---|---|\n| macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` |\n| Windows | `%APPDATA%\\Claude\\claude_desktop_config.json` |\n| Linux | `~/.config/Claude/claude_desktop_config.json` |\n\n```json\n{\n  \"mcpServers\": {\n    \"ripgrep\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"cli2mcp\", \"rg\", \"--name\", \"ripgrep\"]\n    },\n    \"jq\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"cli2mcp\", \"jq\"]\n    }\n  }\n}\n```\n\nRestart Claude Desktop after editing.\n\n### Other clients\n\n| Client | Config file | Format |\n|---|---|---|\n| ChatGPT (OpenAI Agents SDK) | `MCPServerStdio` parameter — see [OpenAI Agents docs](https://openai.github.io/openai-agents-python/mcp/) | `command: \"npx\"`, `args: [\"-y\", \"cli2mcp\", \"<cli>\"]` |\n| Cursor | `.cursor/mcp.json` (project) or `~/.cursor/mcp.json` (global) | Same `mcpServers` block as above |\n| Cline | VS Code → Cline → MCP Settings → `cline_mcp_settings.json` | Same `mcpServers` block |\n| Windsurf | `~/.codeium/windsurf/mcp_config.json` | Same `mcpServers` block |\n| Gemini CLI | `~/.gemini/settings.json` | Same `mcpServers` block |\n| Continue | `~/.continue/config.json` → `experimental.modelContextProtocolServers` | Same launcher |\n| Zed | `~/.config/zed/settings.json` → `context_servers` | Same launcher |\n| Any stdio-capable MCP client | per the client's docs | Same launcher: `npx -y cli2mcp <command>` |\n\nRefer to each client's documentation for the exact config path on your platform — they evolve and are not guaranteed to match the table above.\n\n---\n\n## Quick wins — copy-paste configs\n\nDrop 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.\n\n```json\n{\n  \"mcpServers\": {\n    \"ripgrep\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"cli2mcp\", \"rg\", \"--name\", \"ripgrep\",\n               \"--description\", \"Recursively search files with regex\"]\n    },\n    \"jq\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"cli2mcp\", \"jq\",\n               \"--description\", \"Query and transform JSON via stdin\"]\n    },\n    \"pandoc\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"cli2mcp\", \"pandoc\",\n               \"--description\", \"Convert documents between markup formats\"]\n    },\n    \"sqlite3\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"cli2mcp\", \"sqlite3\",\n               \"--description\", \"Run SQL against a SQLite database file\",\n               \"--cwd\", \"/path/to/safe/dir\"]\n    },\n    \"yt-dlp\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"cli2mcp\", \"yt-dlp\",\n               \"--description\", \"Download media from URLs\",\n               \"--cwd\", \"/path/to/downloads\",\n               \"--timeout\", \"300000\"]\n    }\n  }\n}\n```\n\n> Each CLI must already be installed and on `PATH`. `cli2mcp` does not install them for you.\n\n---\n\n## How it compares\n\n| Approach | LOC per CLI | New flag handling | Maintenance |\n|---|---|---|---|\n| Hand-written MCP server (TypeScript SDK) | ~80–150 | manual schema edit | per-CLI release cycle |\n| OpenAPI → MCP generators | n/a | requires an OpenAPI spec | does not cover arbitrary CLIs |\n| Wrapping `bash` / `sh` as a tool | ~10 | n/a — gives the AI a shell | unsafe, no schema, no sandbox |\n| **`cli2mcp <command>`** | **0** | **automatic at next start** | **none — re-reads `--help`** |\n\nThe 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.\n\n---\n\n## Verified targets\n\nThese CLIs are covered by the test suite or have been manually exercised end-to-end:\n\n| CLI | Status | Notes |\n|---|---|---|\n| `jq` | ✅ tested | help-on-stderr correctly captured; `stdin` piping works |\n| `ripgrep` (`rg`) | ✅ tested | 90+ flags inferred; `args` positional handled |\n| `curl` | ✅ fixture | shape extraction validated against bundled fixture |\n| `node` | ✅ integration test | end-to-end MCP handshake + `tools/call` |\n\nOther 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).\n\n---\n\n## How --help becomes a JSON Schema\n\n| Help fragment | MCP property |\n|---|---|\n| `--flag` | `boolean` |\n| `--flag <value>` / `<file>` / `<path>` | `string` |\n| `--flag <n>` / `<ms>` / `<size>` | `number` |\n| `--flag <a\\|b\\|c>` | `string` enum with choices |\n| Repeatable flag | `array<string>` |\n| Positional args | `args: array<string>` |\n| Reserved input `stdin` | `string` piped to subprocess stdin |\n\nWhen 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.\n\n---\n\n## Options\n\n```\ncli2mcp <command> [options]\n\n  --name <name>         Tool name shown to the AI           (default: <command>)\n  --description <text>  Tool description shown to the AI    (default: first --help line)\n  --timeout <ms>        Subprocess timeout per call         (default: 60000)\n  --cwd <path>          Working directory for subprocess    (default: process.cwd())\n  --env <KEY=VALUE>     Extra environment variables         (repeatable)\n  --stderr <mode>       stderr handling:\n                          include  →  appended to tool output (default)\n                          drop     →  discarded\n                          error    →  any stderr → isError: true\n  -h, --help            Show help\n```\n\n### Piping stdin\n\nReserved input property `stdin` is piped to the subprocess:\n\n```json\n{ \"args\": [\".name\"], \"stdin\": \"{\\\"name\\\": \\\"cli2mcp\\\"}\" }\n```\n\n---\n\n## How it works\n\n```\ncli2mcp rg\n   │\n   ├─ 1. spawn: rg --help          →  capture stdout + stderr\n   ├─ 2. parse help text           →  CliShape { flags, positionals, description }\n   ├─ 3. synthesize JSON Schema    →  inputSchema\n   ├─ 4. register one MCP tool     →  name: \"rg\", schema: <above>\n   └─ 5. start stdio MCP server    →  await client connection\n\nOn tools/call:\n   { args, flags, stdin? }  →  argv builder  →  execa(rg, argv, { stdin })\n                                                           │\n                                          stdout (+ stderr) → content[text]\n```\n\nNon-zero exit → `{ isError: true, content: [{ type: \"text\", text: <stderr> }] }` (unless `--stderr drop`).\n\n---\n\n## Security\n\n`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.**\n\nPractical guidance:\n\n- **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.\n- **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.\n- **Use `--cwd` to constrain filesystem scope** when wrapping CLIs that touch files.\n- **Use `--env` deliberately.** Do not pass through credentials the model shouldn't reach.\n- **Never expose `sh`, `bash`, `zsh`, `python -c`, or anything with eval semantics** — that bypasses every safeguard `cli2mcp` provides.\n\nThe 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.\n\n---\n\n## Troubleshooting\n\n**The CLI has no `--help` flag.**\n`cli2mcp` will still start with a single `args` positional. The AI can pass arguments freely; you lose typed flag inference.\n\n**The schema came out empty / wrong.**\nRun `cli2mcp <command>` 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 `<command> --help` output attached.\n\n**The subprocess hangs.**\nThe 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.\n\n**Flag not being passed.**\nSet `--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.\n\n---\n\n## Contributing\n\nBug reports and patches welcome. Fixtures for new CLIs (`test/fixtures/help/<cli>.txt` + a shape test) are the highest-leverage contributions.\n\n```sh\npnpm install\npnpm test         # vitest\npnpm typecheck    # tsc --noEmit\npnpm lint         # biome check\n```\n\n---\n\n## Star history\n\n[![Star History Chart](https://api.star-history.com/svg?repos=RonieNeubauer/cli2mcp&type=Date)](https://star-history.com/#RonieNeubauer/cli2mcp&Date)\n\nIf `cli2mcp` saved you an afternoon of writing MCP boilerplate, a star helps other people find it.\n\n---\n\n## Author\n\nBuilt by **Ronie Neubauer** — Principal Engineer, 22+ years shipping production systems.\n\n- GitHub: [@RonieNeubauer](https://github.com/RonieNeubauer)\n- Blog: [ronieneubauer.com](https://ronieneubauer.com)\n- Issues & ideas: [github.com/RonieNeubauer/cli2mcp/discussions](https://github.com/RonieNeubauer/cli2mcp/discussions)\n\n---\n\n## License\n\nMIT © 2026 Ronie Neubauer.\n",
  "bytes": 11295,
  "sha": "2ea1b17fbcc7c40de40d13e37d8f1c4e95c4513f317875b66cb9a3a7b8daa634",
  "repo_slug": "ronieneubauer/cli2mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_ronieneubauer_cli2mcp_f6bc7813/readme"
}