If you want agents to actually use your CLI, this is the missing piece.
Project page: https://projects.mioli.dev/cli-bridge/ · Blog: https://blog.mioli.dev · Author: Stefano Mioli
You wrote the CLI, you mentioned it in CLAUDE.md, the agent used it twice and then went back to Bash. cli-bridge solves that by registering your tool's commands as real MCP tools that live in the agent's tool registry — outside the context window, where they don't decay under conversation pressure.
You built a CLI that does something useful. Maybe it queries a database, lints code, or manages infrastructure. You tell the agent about it in CLAUDE.md or your system prompt. It works for a while, then the agent quietly goes back to grep and Bash.
This happens because text instructions sit in the context window, and context is a lossy channel. When the conversation gets long or the task gets complex, the agent forgets your tool exists. It falls back to what it knows: shell commands.
MCP tools don't have this problem. They live outside the context window, in the tool registry. The agent sees them every time it decides which tool to call, regardless of how long the conversation is or how much pressure the context is under.
cli-bridge lets you describe your CLI's interface in a JSON spec file. On startup, it reads the spec and registers each subcommand as a real MCP tool. The agent sees tool_foo and tool_bar in its tool list, not buried in a CLAUDE.md paragraph it might skip.
When the agent calls one of these tools, cli-bridge runs your binary via execFile (no shell), parses the output according to the spec, and returns structured content. Your CLI stays a CLI. It just also happens to be an MCP tool now.
This works with Claude Code, Codex, and anything else that speaks MCP.
There are two paths. Pick one — they're not sequential.
One command, gets you the binary + MCP server registration + the /cli-bridge:register slash command:
/plugin marketplace add walkindude/cli-bridge
/plugin install cli-bridge@cli-bridge
Skip the rest of this section.
If you're not on Claude Code, or you want the binary without the plugin scaffolding:
1. Install the binary. Pick whichever fits:
npm install -g cli-bridge # or pnpm / yarn / bun
mise use -g npm:cli-bridge@latest # via mise
nix profile install github:walkindude/cli-bridge # via nixFor source builds: git clone … && pnpm install && pnpm run build && npm link.
2. Register as an MCP server. For Claude Code, the canonical command is one line:
claude mcp add cli-bridge -s user -- cli-bridgeOr by hand — add to ~/.claude.json (user scope) or your project's .mcp.json:
{
"mcpServers": {
"cli-bridge": {
"type": "stdio",
"command": "cli-bridge"
}
}
}For Codex — add to ~/.codex/config.toml:
[mcp_servers.cli-bridge]
command = "cli-bridge"3. Restart your MCP client. Tools register at startup; new MCP servers don't appear in an already-running session.
A registered tool means cli-bridge knows where its spec is and what binary version it targets.
Use the slash command from inside Claude Code:
/cli-bridge:register <binary>
It tries the canonical path first, falls back to scraping --help if needed. Both paths write to ~/.config/cli-bridge/specs/<tool>/<version>.json.
You don't have the slash command. Two options:
-
Canonical path (preferred for CLI authors who follow the convention). If your tool exposes
<binary> cli-bridge-manifest(this is the convention — gosymdb is the reference), write the spec directly:mkdir -p ~/.config/cli-bridge/specs/<tool> <tool> cli-bridge-manifest > ~/.config/cli-bridge/specs/<tool>/$(<tool> --version | awk '{print $NF}').json
After this, cli-bridge auto-refreshes the spec whenever the binary version changes. You'll see a
[cli-bridge] auto-refreshed spec for <tool> to v<new>log line on the next startup. No manual re-registration needed for canonical-convention tools. -
Heuristic fallback. If the tool doesn't have
cli-bridge-manifest, install the plugin (Path A) once just to get the slash command's--helpscraping logic, then run/cli-bridge:register <binary>. Hand-tune the triggers afterwards.
Specs land at ~/.config/cli-bridge/specs/<tool>/<version>.json. See Spec Format below.
The spec schema (specVersion: "1") is additive-only. New optional fields can appear in v1; existing fields will not be renamed, removed, or have their semantics changed within v1. A breaking schema change would mint specVersion: "2" alongside, and cli-bridge would continue to load v1 specs indefinitely.
In practice this means a spec written today against gosymdb v0.1.2 (or cairn, or any other tool that ships <binary> cli-bridge-manifest) will keep working as cli-bridge evolves. The contract is the JSON shape, not the package version.
-
On startup, the MCP server discovers specs from three locations (highest priority first):
.cli-bridge/specs/in the current project (team-shared)~/.config/cli-bridge/specs/(user-local,XDG_CONFIG_HOMErespected)specs/in the plugin directory (bundled, empty by default)
-
For each spec, it resolves the binary via
which, detects the installed version, and picks the best-matching spec file. -
Each command in the spec becomes an MCP tool named
{tool}_{command}(e.g.tool_foo,tool_bar). -
When Claude calls a tool, cli-bridge runs the binary via
execFile(no shell), parses stdout according to the spec's output format, and returns structured MCP content.
Specs live at ~/.config/cli-bridge/specs/<tool>/<version>.json.
{
"name": "jq",
"specVersion": "1",
"binary": "jq",
"binaryVersion": "1.7.1",
"description": "Command-line JSON processor",
"versionDetection": {
"command": "--version",
"pattern": "jq-(\\d+\\.\\d+[.\\d+]*)"
},
"triggers": {
"positive": ["JSON parsing or filter pipelines", "applying a transformation to a JSON document"],
"negative": ["non-JSON input formats (XML, CSV, raw text)"]
},
"commands": [
{
"name": "run",
"description": "Apply a jq filter to JSON input",
"usage": "jq <filter> [file]",
"args": [
{ "name": "filter", "description": "jq filter expression", "required": true, "type": "string" }
],
"flags": [
{ "name": "raw-output", "short": "r", "description": "Output raw strings", "required": false, "type": "boolean" }
],
"output": { "format": "json" }
}
]
}Key fields:
| Field | Purpose |
|---|---|
triggers.positive |
Situations where this tool is the right answer |
triggers.negative |
Situations where it doesn't fit |
output.format |
How to parse stdout: json, jsonl, text, csv, tsv |
versionDetection |
How to detect the installed binary version on startup |
globalFlags |
Flags available on every command |
Trigger phrases land in the agent's MCP tool catalog at server startup, so they're prompt-adjacent by construction. Descriptive register ("JSON parsing tasks", "non-Go codebases") works the same as imperatives ("BEFORE you parse JSON", "Do NOT use for non-Go") for tool selection but doesn't carry directive weight into runtime contexts. The convention here is descriptive.
Teams can share specs by checking them into .cli-bridge/specs/ in their repo. Project-level specs take priority over user-level specs.
cli-bridge executes real binaries on your machine. A spec is effectively a shortcut to running a CLI command. Loading a spec grants the same permissions as running the binary directly.
Spec sources and trust:
| Source | Trust level | Who controls it |
|---|---|---|
.cli-bridge/specs/ in repo |
Same as cloning the repo | Repo maintainers |
~/.config/cli-bridge/specs/ |
User-controlled | You |
specs/ in plugin dir |
Bundled with cli-bridge | cli-bridge maintainers |
Project-local specs (.cli-bridge/specs/) load automatically when you open a project, the same trust model as Makefile, .vscode/tasks.json, or package.json scripts. Review specs from untrusted repos before use.
Execution safety:
- All commands run via
execFile(no shell), no metacharacter expansion - Output is capped at 10 MB per invocation
- Timeouts enforced (default 30s, max 5 min)
- Trigger phrases are length-limited to prevent prompt injection via tool descriptions
pnpm run build # compile + bundle
pnpm test # vitest
pnpm run test:coverage # coverage report
pnpm run lint # eslint
pnpm run typecheck # tsc --noEmit
pnpm run format # prettier --write
pnpm run format:check # prettier --check| Module | Purpose |
|---|---|
src/server.ts |
MCP stdio server. Loads specs, registers tools, handles calls |
src/registry.ts |
Spec discovery, version resolution, MCP tool generation |
src/executor.ts |
Runs CLI via execFile. No shell. Timeout enforcement |
src/schema.ts |
CliToolSpec types + JSON Schema + validateSpec() |
src/parser.ts |
Parses stdout (json/text/csv/tsv/jsonl) into MCP content |
src/resolver.ts |
Binary resolution (which), version detection, semver matching |
src/paths.ts |
Spec directory resolution with XDG support |
src/types.ts |
Result<T,E>, error types |
See AGENTS.md for the convention, when to use cli-bridge tools versus Bash, and which sibling projects in this family already follow the manifest convention.
Releases are cut via git tags (v0.1.0, v0.2.0, etc.). Pushing a tag triggers a GitHub Release with auto-generated release notes.
Plugin updates: bump the version field in .claude-plugin/plugin.json and package.json. Users on the marketplace will pick up the new version on next session start (if auto-update is enabled) or via /plugin update.
Apache-2.0