Turn any OpenAPI spec into a working MCP server in one command — deterministically.
No LLM. No API key. No Claude Code.
ducktap press ./openapi.yamlreads the spec and prints an MCP server — plus a Python CLI and an agent skill — that any agent can use. Same spec → same output, every time. Runs in CI.
Website: duck-tap.vercel.app
$ ducktap press ./openapi.yaml --name petstore
Pressed petstore (19 operations) -> out
archetype: unknown
insight: Swagger Petstore - OpenAPI 3.0 isn't just a set of endpoints. It's a
queryable view of a live system. Every record is a signal about
state, freshness, and change over time.
python-cli: 11 files
mcp-server: 5 files
skill: 3 files
Scorecard: 87/100 (B)
- coverage: 95 -- 19 operations exposed
- documentation: 100 -- 19/19 operations have docs
- auth: 100 -- 2 auth scheme(s)
- typed_params: 54 -- 29/53 params typed/enum
- artifacts: 100 -- 3/3 expected artifact dirs present
- naming: 100 -- 19/19 unique operation ids
- domain_correctness: 72 -- restful=66, responses=100, base_url=50
Why deterministic? DuckTap parses the spec and emits code directly — no model in the
loop. That means no API key, no per-run token cost, reproducible output you can diff and
review, and generation that runs in CI. (An LLM can optionally sharpen the generated
insight — ducktap press --llm, needing the [llm] extra — but it is off by default so
press stays reproducible.)
Inspired by Printing Press by @mvanhorn — same north star (muscle memory for agents). Printing Press is the prompt-driven, Claude-Code-native take; DuckTap is the spec-first, deterministic one. Here's an honest DuckTap vs Printing Press on when to use which.
In a world of AI agents, a well-designed CLI is muscle memory. No hunting through docs, no wrong turns, no wasted tokens. DuckTap reads the spec, sniffs the traffic when no spec exists, and prints:
-
A Python CLI (
<api>-dt-cli) -- Click-based, auth from env vars, JSON by default, pretty mode for humans, local SQLite mirror with FTS5 full-text search, compound query commands (stale,health,bottleneck), retries on transient errors. -
TypeScript CLI (
<api>-dt-ts) -- oclif-based for Node.js teams. -
Go CLI (
<api>-dt-go) -- cobra-based, single binary. -
Rust CLI (
<api>-dt-rs) -- clap-based, single binary distribution.Generate any of these with
ducktap press <spec> -t go-cli,rust-cli,typescript-cli. Each language's generated project is compiled in CI (go build,cargo build,tsc) so the templates can't silently drift. As of v0.7.0 all four languages share an agent-parity bundle: a--dry-runflag, anagent-contextJSON self-introspection command, and typed exit codes (3/4/5/7). -
An MCP server (
<api>-dt-mcp) -- every operation exposed as an MCP tool, stdio transport, drop into Claude Desktop or Cursor in 60 seconds. -
A skill for Claude Code, Cursor (
.mdc), and a generictools.json-- so any agent harness can pick up where the others left off. -
A scorecard grading coverage, docs, auth clarity, typed params, artifacts, and naming.
pip install ducktap
ducktap --versionThe core install is deterministic-only and stays small. Optional extras:
pip install "ducktap[sniff]" # browser + proxy sniffing (playwright, mitmproxy)
playwright install chromium
pip install "ducktap[llm]" # optional LLM steps: polish, rename, insight, vision
pip install "ducktap[search]" # crowd-sniff web search
pip install "ducktap[all]" # everythingRequires Python 3.11+.
Developer install (contributing to DuckTap itself)
git clone https://github.com/zanni098/DuckTap
cd DuckTap
pip install -e ".[dev]"
python -m pytest tests/ -q # 145 passed; language compile tests skipped by default
# Opt into the heavy compile tests (needs go, cargo, and node installed).
# These press the fixture into Go/Rust/TS and actually build each project.
DUCKTAP_COMPILE_TESTS=1 pytest -q tests/test_generated_multilang.py# 1. Press a built-in catalog entry (no URL needed)
ducktap catalog list # browse 30 built-in APIs
ducktap catalog print stripe # press Stripe CLI + MCP + skill
# 2. From any OpenAPI spec URL
ducktap press https://petstore3.swagger.io/api/v3/openapi.yaml
# 3. From a local spec or HAR file
ducktap press ./openapi.yaml --name myapi
ducktap press ./traffic.har --name myapi
# 4. From a website with no public spec (needs [sniff] extra)
ducktap sniff https://example.com
# 5. Open the dashboard
ducktap ui # http://127.0.0.1:8765Running ducktap press prints something like:
Pressed petstore (19 operations) -> ./out
archetype: unknown
insight: Swagger Petstore - OpenAPI 3.0 isn't just a set of endpoints. It's a
queryable view of a live system. Every record is a signal about
state, freshness, and change over time.
python-cli: 11 files
mcp-server: 5 files
skill: 3 files
Scorecard: 87/100 (B)
- coverage: 95 -- 19 operations exposed
- documentation: 100 -- 19/19 operations have docs
- auth: 100 -- 2 auth scheme(s)
- typed_params: 54 -- 29/53 params typed/enum
- artifacts: 100 -- 3/3 expected artifact dirs present
- naming: 100 -- 19/19 unique operation ids
- domain_correctness: 72 -- restful=66, responses=100, base_url=50
What you get under ./out/:
out/
├── petstore-dt-cli/ # pip install -e . → petstore-dt-cli --help
│ ├── pyproject.toml
│ ├── README.md
│ ├── petstore_dt_cli/
│ │ ├── __main__.py
│ │ ├── main.py
│ │ ├── commands.py # one click subcommand per API operation
│ │ ├── client.py # httpx + env-var auth + retries
│ │ └── mirror.py # local SQLite cache
│ └── tests/test_smoke.py
├── petstore-dt-mcp/ # pip install -e . → add to Claude Desktop config
│ └── petstore_dt_mcp/server.py
└── skills/ducktap-petstore/
├── SKILL.md # Claude Code skill
├── ducktap-petstore.mdc # Cursor rule
└── tools.json # generic agent tool definitions
Generation is only useful if the output is right, so the guarantees are tested rather than asserted:
- Deterministic. Two presses of the same spec produce byte-identical output.
The
.ducktap.jsonmanifest records a checksum of the normalized spec so you can tell "the API changed" from "DuckTap changed". - It parses, installs, and runs. Reserved words (
operationId: class), colliding operation ids, colliding parameter names, and non-PEP 440 versions (Stripe ships2022-11-15) are all handled — each one used to produce code that wouldn't compile, silently dropped a command, or built the wrong request. - Recursive
$refschemas work. Self-referencing models are the norm in real specs; cycles are cut at the discovery boundary. - The skill matches the CLI.
SKILL.md,cursor.mdcandtools.jsonare cross-checked against the generated Click command tree by a test, so an agent reading the skill never sees a command or flag that doesn't exist. - Generated CLIs don't leak credentials. Redirects are followed by hand and
only benign headers cross an origin boundary; path parameters are
percent-encoded;
<cli> queryruns on a read-only SQLite connection. ducktap verifymechanically proves the generated CLI matches the spec — no hallucinated paths, every operation reachable, auth headers correct, no write-only mirror tables.
| Printing Press | DuckTap | |
|---|---|---|
| Generation | Prompt-driven, a fresh model run each time | Deterministic -- same spec, same bytes, runs in CI |
| Needs to run | Go + Node + Claude Code | A Python interpreter |
| Output languages | Go | Python, Go, Rust, TypeScript from one spec |
| Skills | Claude Code | Claude Code + Cursor .mdc + generic tools.json |
| UI | None | Local FastAPI dashboard (ducktap ui) |
| Plugins | Source fork | Entry-point plugin system -- drop-in discoverers & generators |
| Browser sniff | Custom Go browser | Playwright -- full HAR export, scriptable actions |
| LLM (optional) | Claude only | Multi-LLM via LiteLLM (Anthropic, OpenAI, Gemini, Ollama, Groq, Azure) |
Printing Press wins on maturity and on its 250+ CLI community library. The honest comparison covers when to reach for which.
See docs/COMPARISON.md for the full feature matrix.
ducktap press <source> # discover + generate (the default loop)
ducktap research <source> # discover only -- emit normalized APISpec JSON
ducktap sniff <url> # browser-sniff a site (needs [sniff] extra)
ducktap scorecard <source> # quality scorecard
ducktap shipcheck <name> # structural & runtime sanity checks
ducktap verify <name> -s <src> # proof of behavior against the spec
ducktap info # read back the provenance manifest
ducktap catalog list|print # browse the recipe library
ducktap plugins list # show installed discoverers + generators
ducktap library list|add|search # local registry of printed CLIs
ducktap macro list|run|new # compound command macros
ducktap publish <name> # push a generated CLI to GitHub + PyPI
ducktap emboss <name> # brand-stamp a generated CLI
ducktap ui # local web dashboard
# Optional, need the [llm] extra:
ducktap insight <source> # one-sentence Non-Obvious Insight
ducktap polish|rename <source> # LLM cleanup of summaries / operation ids
ducktap absorb <api> # feature gate against the agent-CLI playbook
ducktap crowd-sniff <api> # study community CLIs (needs [search] too)
ducktap vision <url> # read a docs page from a screenshot
Add a discoverer or generator without forking. Register via Python entry points:
# your_plugin/pyproject.toml
[project.entry-points."ducktap.plugins"]
mything = "your_plugin.module" # module just calls plugins.register_discoverer(...)See docs/PLUGINS.md and the sample at
src/ducktap/plugins/builtin/graphql_intro.py.
input (URL | spec | HAR)
│
▼
┌─────────────┐
│ Discovery │ openapi / har / browser-sniff / graphql (plugin) / ...
└──────┬──────┘
▼
APISpec (Pydantic) ──── intermediate normalized representation
│
▼
┌─────────────┐
│ Generator │ python-cli / mcp-server / skill / ...
└──────┬──────┘
▼
artifacts/ (CLI pkg + MCP pkg + SKILL.md + cursor.mdc + tools.json)
│
▼
┌─────────────┐
│ Verify │ scorecard + shipcheck + (optional) live smoke test
└─────────────┘
See docs/ARCHITECTURE.md.
See docs/ROADMAP.md. Highlights for v0.4+:
- Compound command macros -- YAML recipes that chain API calls with step references
- FTS5 full-text search over saved JSON responses in the local data lake
- DuckDB backend as an alternative to SQLite for analytical workloads
- Top-level
querycommand for SQL over the mirror - CLI publish to PyPI + GitHub in one command
MIT -- see LICENSE.
Inspired by Printing Press by Matt Van Horn and the agent-CLI playbook proved out by discrawl and gogcli.
