cli-mcp
Swagger for Command Line Tools
cli-mcp exposes command-line applications through a
Model Context Protocol (MCP) server — without you
writing a single line of MCP code.
Like Swagger/OpenAPI describes an HTTP API, cli-mcp describes CLI commands, their arguments,
and their results as structured tools. It also executes those tools with argument validation
and configured execution controls, and can export OpenAPI documentation.
Automatic discovery depends on the CLI's help format or supported framework; use a manifest
when explicit metadata is needed. Windows .bat/.cmd targets are not supported.
Today: CLI Tool -> custom MCP wrapper -> MCP client
With cli-mcp: CLI Tool -> cli-mcp -> MCP client
cli-mcp expose git
cli-mcp expose kubectl
cli-mcp expose docker
cli-mcp expose opcua-nodeset-linker
✓ Binary detected
✓ Commands discovered
✓ Schemas generated
✓ MCP tools published
✓ MCP server started
Works with Claude, Copilot, Cursor, VS Code, Cline, Roo Code, Open WebUI and any other MCP-compatible client.
Install
Requires Python 3.13 or newer. CI covers Windows and Linux with Python 3.13 and 3.14.
pip install cli-mcp
or, for development:
git clone https://github.com/AndreasHeine/cli-mcp
cd cli-mcp
uv sync --extra dev
uv run cli-mcp --help
Quick start
Only expose trusted programs. These quick-start examples do not enable read-only mode or sandboxing; discovery itself runs help commands on the host.
# Discover commands and serve them over stdio (the default transport)
cli-mcp expose git
# Look at the generated tools without starting a server
cli-mcp inspect kubectl
# Serve over Streamable HTTP
cli-mcp serve docker --transport streamable-http --port 8801
# Export machine-readable artefacts
cli-mcp export git -o tools.md
cli-mcp export-schema git -o schemas.json
cli-mcp export-openapi git -o openapi.json
# Validate discovery quality
cli-mcp doctor git
# Generate a configuration template
cli-mcp init opcua-nodeset-linker -o cli-mcp.yaml
Client configuration (.mcp.json, Claude Desktop, Cursor, VS Code, …):
The following is a common client configuration shape; VS Code and other clients may use different keys. See clients.md for client-specific recipes.
{
"mcpServers": {
"git": {
"command": "cli-mcp",
"args": ["serve", "git"]
},
"docker": {
"url": "http://127.0.0.1:8801/mcp"
}
}
}
Working directory and Windows shells
Some CLIs resolve paths inside their input configuration relative to the process working directory, not relative to that configuration file. Configure an absolute existing directory for both discovery and execution:
binary: my-cli
execution:
cwd: 'C:\projects\my-cli'
Without execution.cwd, the launch directory is used. Use an absolute manifest path when
changing the discovery directory. Configuration and tool definitions are loaded at startup:
restart the server and reconnect the client after changes.
PowerShell:
$env:PYTHONDONTWRITEBYTECODE = '1'
& 'C:\projects\cli-mcp\.venv\Scripts\python.exe' -B -m cli_mcp.cli.main expose --config 'C:\projects\cli-mcp\cli-mcp.yaml'
cmd.exe (run these as two separate commands):
set PYTHONDONTWRITEBYTECODE=1
"C:\projects\cli-mcp\.venv\Scripts\python.exe" -B -m cli_mcp.cli.main expose --config "C:\projects\cli-mcp\cli-mcp.yaml"
These examples assume an existing development environment at the shown location.
How discovery works
cli-mcp tries discovery strategies in order. The first strategy that yields usable commands
wins; manifest entries then add tools or replace discovered tools with the same name.
The final include/exclude filters select which tools are published, not which help commands
may execute.
Import reflection is disabled by default and requires --allow-reflection or
discovery.allow_import_reflection: true. It may execute target code.
discovery.max_depth limits the recursive walk; discovery.max_commands limits both published
tools and uncached help invocations, including root queries, failures and fallback flags.
The help budget is checked before spawning processes; cache hits do not consume it.
| Strategy | How it works | Frameworks |
|---|---|---|
| Native reflection | Inspects the real command objects in the target process | Python: argparse, click, typer · Node.js: commander.js |
| Framework help grammars | Parses framework-specific help output with type/default/required semantics | Go: cobra · Rust: clap · .NET: System.CommandLine · Node.js: yargs · click · argparse |
| Generic help parsing | Recursive tool --help / tool <command> --help parser |
anything with --help |
| Manifest file | User-provided YAML metadata, authoritative for matching tool names | supported executables and scripts |
Every discovered command becomes an MCP tool:
{
"name": "reduce",
"description": "Reduce an OPC UA nodeset",
"inputSchema": {
"type": "object",
"properties": {
"input": { "type": "string", "description": "Input nodeset file" },
"output": { "type": "string", "description": "Reduced nodeset file" }
},
"required": ["input", "output"]
}
}
Calling the tool executes the original CLI:
linker reduce --input model.xml --output reduced.xml
and returns:
{
"stdout": "...",
"stderr": "",
"exitCode": 0,
"executionTimeMs": 125,
"argv": ["linker", "reduce", "--input", "model.xml", "--output", "reduced.xml"],
"sandboxed": false,
"truncated": false
}
The actual argv contains the resolved launch command, including interpreter prefixes where
needed. A connected client or completed MCP call does not prove CLI success: inspect exitCode
and stderr. Exitcode 0 does not establish side-effect freedom; sandboxed: false means the
target ran on the host.
Security
Security is a first-class concern, not an afterthought.
binary: opcua-nodeset-linker
security:
allowlist: [git, kubectl, docker, opcua-nodeset-linker]
readonly: true # deny unless the complete argv is explicitly listed
readonly_argv: [] # configure verified exact invocations before use
approval: true # human confirmation required
timeout_seconds: 30
max_output_bytes: 200000
max_argv_tokens: 256
max_argv_bytes: 32768
sandbox:
backend: docker
image: my-cli-image:latest
network: false
workdir_mount: false
- Allow list — a non-empty list restricts configured binaries by basename; an empty list permits the configured binary. This is not an OS permission boundary.
- Read-only mode — only explicitly listed complete argv vectors may execute;
known risky commands and flags are additionally blocked. An empty
readonly_argvblocks every execution and triggers a startup warning; publishing tools does not authorize them. - Approval mode —
auto/promptrequire a local human at a terminal; without a TTY approval-required execution is refused. Clients cannot approve their own calls. Legacyexplicitrefuses approval-required calls. Normal stdio clients do not provide a local TTY. - Sandboxing — run the CLI inside Docker or Podman; the working directory is not mounted by default. Discovery still runs on the host. Firecracker support planned.
- Execution limits — each invocation is limited to 256 argv tokens and 32 KiB, including the executable and separators, before approval or process creation.
- Argument validation — arguments are validated against the generated JSON schema before the requested tool process is spawned. Arguments are not interpolated into a shell string.
Read-only, approval and sandboxing are opt-in. Exact argv permissions do not prove that a program, its environment or its input files are harmless. Discovery is outside tool execution guards. Output limits bound retained stdout/stderr, not the target's own CPU or memory use. HTTP/SSE defaults to loopback and does not configure authentication or TLS; do not expose it publicly without appropriate protection.
Windows batch targets (.bat/.cmd) are rejected. Output is retained within a byte budget,
including timeouts. See security.md for limitations, configuration migration,
and non-invasive Windows/Linux tests using a single loopback ping.
Architecture
Clean architecture, one responsibility per package:
src/cli_mcp/
├── cli/ # Typer entry point (expose/inspect/serve/export/doctor/init)
├── discovery/ # provider protocol + strategies
├── schema/ # JSON Schema, Markdown docs, OpenAPI
├── executor/ # argv building, subprocess execution, approval, sandbox
├── mcp/ # MCPServer assembly, tool adapters
├── security/ # allow list, read-only rules, access policy
├── config/ # pydantic settings + YAML loading
├── transport/ # stdio, HTTP, Streamable HTTP, SSE
└── plugins/ # entry-point based provider discovery
Dependencies point inward: the CLI depends on discovery/schema/executor/security; the MCP layer depends on nothing framework-specific. See docs/architecture.md.
Plugin system
Third parties add discovery providers without touching core code:
from cli_mcp.discovery.base import DiscoveryContext, DiscoveryResult, TargetInfo
class MyProvider:
name = "my-framework"
strategy = "help"
frameworks = ("my-framework",)
def supports(self, target: TargetInfo) -> bool:
return target.framework == "my-framework"
def discover(self, target: TargetInfo, ctx: DiscoveryContext) -> DiscoveryResult | None:
... # return CommandSpecs
Register it:
[project.entry-points."cli_mcp.discovery"]
my-framework = "my_pkg.provider:MyProvider"
cli-mcp providers lists every registered provider.
Development
uv run ruff check .
uv run mypy src
uv run pytest # unit + integration + e2e, coverage gate at 90%
pre-commit run --all-files
The full test suite creates temporary files and runs Git integration commands. For non-invasive validation without test caches, coverage files or generated target scripts, use the focused Windows/Linux commands in security.md.
GitHub Actions run lint, type checking, Windows/Linux tests (Python 3.13/3.14), and
distribution builds on pushes to master/main and pull requests. CI can also be started
manually from the Actions tab. Dependencies are installed from the checked-in lockfile.
Published GitHub releases (or a manual Release workflow run) validate and build distributions, then upload wheels and source distributions as workflow artifacts. PyPI publishing is disabled until a publishing account and trusted publisher are explicitly configured.
Examples
examples/cli-mcp.yaml— full configurationexamples/sample-clis/— small argparse/click/typer/commander apps used by the tests
Documentation
- configuration.md — every config key and CLI flag
- discovery.md — reflection, help parsing and manifests
- security.md — allow list, read-only, approval, sandboxing
- transports.md — stdio, HTTP, Streamable HTTP, SSE
- clients.md — configuration recipes for Claude, VS Code, Cursor, Cline, Open WebUI
- plugins.md — adding a discovery provider for a new framework
- architecture.md — how the pipeline fits together
License
Apache License 2.0 — see LICENSE.
Metadata
Release files for universal-cli-mcp-server 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| universal_cli_mcp_server-0.2.0.tar.gz | 104.6 kB | With provenance | Details for universal_cli_mcp_server-0.2.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| universal_cli_mcp_server-0.2.0-py3-none-any.whl | Python 3 | none | any | Details for universal_cli_mcp_server-0.2.0-py3-none-any.whl |
Total release size: 181.0 kB
Release files / universal_cli_mcp_server-0.2.0.tar.gz
| Download URL | universal_cli_mcp_server-0.2.0.tar.gz |
|---|---|
| Size | 104.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b2fe7cf7af09ac806b0981a42b996ec5c3b0052d55997362934735b1f2187012
|
|
BLAKE2b-256 checksum How to use checksums |
a8c4b56fd299ed6e2f17115c4ab0842a264b857ab7e60343967ec0fed0274bec
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.
Transparency logRelease files / universal_cli_mcp_server-0.2.0-py3-none-any.whl
| Download URL | universal_cli_mcp_server-0.2.0-py3-none-any.whl |
|---|---|
| Size | 76.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6196984e7daa82a422d643ce5d8ef47899183234fc46d597f0adc1f36032776b
|
|
BLAKE2b-256 checksum How to use checksums |
79525b22e8959612a1c294edb5231d95556aba2344941da8632be78bc20b69c1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.
Transparency logSecurity for universal-cli-mcp-server 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for universal_cli_mcp_server-0.2.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.