openapi-to-cli (short ocli) is a TypeScript CLI that turns any HTTP API described by an OpenAPI/Swagger spec into a set of CLI commands — at runtime, without code generation.
npm install -g openapi-to-cli
ocli profiles add github \
--api-base-url https://api.github.com \
--openapi-spec https://api.github.com/openapi.json \
--api-bearer-token "$GITHUB_TOKEN"
ocli commands --query "create pull request" --limit 3
ocli repos_owner_repo_pulls_post --owner octocat --repo hello --title "Fix bug" --head feature --base main
Tools, MCP, skills, and CLI are not competing approaches — they solve different problems at different layers:
| Layer | What | Best for |
|---|---|---|
| Built-in tools | Standard agent toolset | Critical capabilities that must always be in context (file read/write, shell, browser) |
| MCP | Remote tool servers | APIs that need centralized auth, enterprise SSO, shared state, persistent connections, or can't be in standard delivery |
| Skills | On-demand instructions | Context isolation, teaching agents when and how to use a tool — loaded only when needed |
| CLI | Runtime execution | Long action chains, automation, shell pipelines — agent already knows what to do |
ocli lives at the runtime layer. When an agent needs to call a REST API — search for the right endpoint, check its parameters, execute the call — CLI does this with minimal context overhead and zero infrastructure.
MCP is the right choice when you need centralized auth, persistent connections, or shared state. CLI is the right choice when you need a lightweight, portable way to call HTTP APIs from any agent with shell access.
# Install
npm install -g openapi-to-cli
# Add an API profile
ocli profiles add myapi \
--api-base-url https://api.example.com \
--openapi-spec https://api.example.com/openapi.json \
--api-bearer-token "$TOKEN" \
--include-endpoints "get:/messages,post:/messages" \
--command-prefix "myapi_" \
--custom-headers '{"X-Tenant":"acme"}'
# Set as active profile
ocli use myapi
# Discover commands
ocli commands --query "send message" --limit 5
# Check parameters
ocli myapi_messages_post --help
# Execute
ocli myapi_messages_post --text "Hello world"
# Or target a different profile for a single call (no 'use' required)
ocli myapi_messages_post --profile other --text "Hello world"
ocli commands -p other --query "send message"
--profile (short -p) overrides the profile selected by ocli use for this invocation only. It works for both dynamic API commands and ocli commands. Place it anywhere after the command name. When omitted, the profile set via ocli use is used (falling back to default).
Or use npx without global install:
npx openapi-to-cli onboard \
--api-base-url https://api.example.com \
--openapi-spec https://api.example.com/openapi.json
ocli now handles a wider range of real-world OpenAPI and Swagger documents:
requestBody for JSON payloadsbody and formData parameters$ref references for parameters and request bodiesIn practice this improves compatibility with APIs that define inputs outside simple path/query parameters, especially for POST, PUT, and PATCH operations.
ocli now uses more request metadata from the specification when building real HTTP calls:
deepObject, pipeDelimited, and Swagger 2 collection formatsIn practice this improves compatibility with APIs that rely on non-trivial parameter encoding or per-operation server definitions.
ocli now works better with larger, more structured API descriptions:
$ref resolution across multiple local or remote OpenAPI / Swagger documents--help output with schema hints such as enum, default, nullable, and oneOfallOf for shared request object structureIn practice this improves compatibility with modular specs and makes generated commands easier to use without opening the original OpenAPI document.
# BM25 natural language search
ocli commands --query "upload files" --limit 5
# Regex pattern matching
ocli commands --regex "users.*post" --limit 10
# List all commands
ocli commands
The BM25 engine ranks commands by relevance across name, method, path, description, and parameter names. Tested on APIs with 845+ endpoints (GitHub API).
Install the ocli-api skill from ClawHub:
clawhub install ocli-api
Or manually copy skills/ocli-api/SKILL.md to ~/.openclaw/skills/ocli-api/SKILL.md.
Copy the example skill to your project:
cp examples/skill-ocli-api.md .claude/skills/api.md
ocli commands --query "upload file" — discover the right commandocli files_content_post --help — check parametersocli files_content_post --file ./data.csv — executeFour strategies compared on Swagger Petstore (19 endpoints), with scaling projections to GitHub API (845 endpoints). All search strategies use the same BM25 engine.
TOOL DEFINITION OVERHEAD (sent with every API request)
MCP Naive █████████████████████████ 2,945 tok (19 tools)
MCP+Search Full ███ 355 tok (2 tools)
MCP+Search Compact ████ 437 tok (3 tools)
CLI (ocli) █ 158 tok (1 tool)
TOTAL TOKENS PER TASK (realistic multi-turn agent flow)
MCP Naive █████████████████████████ 3,015 tok (1 turn)
MCP+Search Full ██████████████████ 2,185 tok (2 turns)
MCP+Search Compact █████████████████ 2,066 tok (3 turns)
CLI (ocli) ████████ 925 tok (3 turns)
SCALING: OVERHEAD PER TURN vs ENDPOINT COUNT
Endpoints MCP Naive MCP+S Compact CLI (ocli)
19 2,945 tok 437 tok 158 tok ← Petstore
845 130,106 tok 437 tok 158 tok ← GitHub API
Run the benchmark yourself: npx ts-node benchmarks/benchmark.ts
Note: MCP+Search Compact (search → get_schema → call) is the fairest comparison to CLI (search → --help → execute) — same number of turns, same BM25 engine. The difference is tool definition overhead (437 vs 158 tok/turn) and schema format (JSON vs text).
| Feature | ocli | mcp2cli | openapi-cli-generator | CLI-Anything |
|---|---|---|---|---|
| Runtime interpretation (no codegen) | ✅ | ✅ | ❌ | ❌ |
| Works without LLM | ✅ | ✅ | ✅ | ❌ |
Zero-setup install (npx/uvx) |
✅ | ✅ | ❌ | ❌ |
| Multiple API profiles | ✅ | ✅ (bake mode) | ❌ | ❌ |
| BM25 command search | ✅ | ❌ (substring only) | ❌ | ❌ |
| Regex command search | ✅ | ❌ | ❌ | ❌ |
| Per-profile endpoint filtering | ✅ | ✅ | ❌ | ❌ |
| OpenAPI/Swagger (JSON + YAML) | ✅ | ✅ | ✅ | ❌ |
| MCP server support | ❌ | ✅ (HTTP/SSE/stdio) | ❌ | ❌ |
| GraphQL support | ❌ | ✅ (introspection) | ❌ | ❌ |
| Spec caching | ✅ | ✅ (1h TTL) | ❌ | ❌ |
| Custom HTTP headers | ✅ | ✅ | ❌ | ❌ |
| Command name prefix | ✅ | ❌ | ❌ | ❌ |
| Basic / Bearer auth | ✅ | ✅ | ✅ | ❌ |
| OAuth2 | ❌ | ✅ (PKCE) | ✅ | ✅ |
| Response filtering (jq/JMESPath) | ❌ | ✅ (jq) | ✅ (JMESPath) | ❌ |
| Token-optimized output (TOON) | ❌ | ✅ | ❌ | ❌ |
| JSON structured output | ❌ | ✅ | ✅ | ✅ |
| Active project | ✅ | ✅ | ❌ (deprecated) | ✅ |
This project is licensed under the MIT License, see the LICENSE file in the repository root for details.
$ claude mcp add openapi-to-cli \
-- python -m otcore.mcp_server <graph>