Skip to content
 
 

Repository files navigation

DuckTap

Turn any OpenAPI spec into a working MCP server in one command — deterministically.

No LLM. No API key. No Claude Code. ducktap press ./openapi.yaml reads 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

CI Release PyPI License: MIT Python 3.11+

DuckTap: OpenAPI spec → MCP server in one command

$ 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.

Why a CLI factory?

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-run flag, an agent-context JSON 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 generic tools.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.

Install

pip install ducktap
ducktap --version

The 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]"      # everything

Requires 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

Quick start

# 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:8765

Running 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

Correctness

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.json manifest 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 ships 2022-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 $ref schemas 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.mdc and tools.json are 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> query runs on a read-only SQLite connection.
  • ducktap verify mechanically proves the generated CLI matches the spec — no hallucinated paths, every operation reachable, auth headers correct, no write-only mirror tables.

How DuckTap improves on Printing Press

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.

Commands

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

Plugins

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.

Architecture

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.

Roadmap

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 query command for SQL over the mirror
  • CLI publish to PyPI + GitHub in one command

License

MIT -- see LICENSE.

Acknowledgements

Inspired by Printing Press by Matt Van Horn and the agent-CLI playbook proved out by discrawl and gogcli.

About

Tape any API to your agent in one command. CLI factory for AI agents — prints Python CLIs, MCP servers, and skills from any OpenAPI spec, HAR file, or website.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages