Metadata-Version: 2.4
Name: prompt-to-mcp
Version: 0.1.1
Summary: Describe the MCP server you want, point at an OpenAPI spec, and get a small curated FastMCP server (a few workflow-level tools, not hundreds of endpoint clones) plus a built-in eval harness that measures it.
Author: Oussama Knani
License: MIT
License-File: LICENSE
Keywords: agents,codegen,eval,fastmcp,llm,mcp,model-context-protocol,openapi,tools
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Code Generators
Requires-Python: >=3.12
Requires-Dist: anthropic>=0.117.0
Requires-Dist: fastmcp
Requires-Dist: httpx
Requires-Dist: jinja2>=3.1.6
Requires-Dist: jsonref
Requires-Dist: openai>=2.46.0
Requires-Dist: pydantic
Requires-Dist: python-dotenv>=1.2.2
Requires-Dist: pyyaml
Requires-Dist: rich
Requires-Dist: typer
Description-Content-Type: text/markdown

# prompt-to-mcp

[![tests](https://github.com/KnaniOussama/prompt-to-mcp/actions/workflows/test.yml/badge.svg)](https://github.com/KnaniOussama/prompt-to-mcp/actions/workflows/test.yml)
[![PyPI](https://img.shields.io/pypi/v/prompt-to-mcp)](https://pypi.org/project/prompt-to-mcp/)
[![Python](https://img.shields.io/pypi/pyversions/prompt-to-mcp)](https://pypi.org/project/prompt-to-mcp/)
[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)

Describe the MCP server you want. Point at an OpenAPI spec. Get a small, curated
FastMCP server as editable code.

```bash
pip install prompt-to-mcp        # or run it without installing: uvx prompt-to-mcp
```

Big APIs make bad MCP servers when converted one tool per endpoint: Stripe's spec
becomes 587 tools and ~463,000 tokens of definitions, more than 14 times a 32k context
window. prompt-to-mcp compiles the server you describe instead, with just the tools
that serve your use case, and ships an eval harness that measures the result.

## Contents

- [Quick start](#quick-start)
- [Example: 587 Stripe operations, 6 tools](#example-587-stripe-operations-6-tools)
- [How it works](#how-it-works)
- [Use the generated server](#use-the-generated-server)
- [Models and providers](#models-and-providers)
- [Evidence](#evidence)
- [Coverage tradeoff](#coverage-tradeoff)
- [CLI reference](#cli-reference)
- [Development](#development)
- [License](#license)

## Quick start

You need Python 3.12+ and one LLM. A local Ollama model works; so does any key that
speaks the Anthropic or OpenAI protocol. Nothing to clone.

```bash
# grab a small example spec
curl -o petstore.yaml https://raw.githubusercontent.com/KnaniOussama/prompt-to-mcp/master/tests/fixtures/petstore.yaml

# 1. see what a naive 1:1 conversion would cost (free, no LLM involved)
uvx prompt-to-mcp inspect petstore.yaml

# 2. describe the server you want (the one step that calls an LLM)
uvx prompt-to-mcp plan petstore.yaml "a server for looking up pets and their orders" \
    -o pets.toolplan.yaml --provider ollama --model qwen2.5:7b

# 3. compile it into a runnable server (free, deterministic)
uvx prompt-to-mcp build pets.toolplan.yaml --spec petstore.yaml -o pets-server/
```

Step 2 writes `pets.toolplan.yaml`, a plain YAML plan of the tools. Open it, edit
anything you disagree with, and rerun step 3. Rebuilding is free; only `plan` and
`eval` call a model. `p2m` is a short alias for the same CLI.

Prefer automatic curation of the whole API instead of a description? Pass `--auto` in
step 2.

## Example: 587 Stripe operations, 6 tools

One sentence turned the full Stripe spec into a server scoped to a single job:

```bash
prompt-to-mcp plan stripe-spec.json \
    "a server for support agents to manage customers, invoices and refunds" \
    -o stripe-support.toolplan.yaml --max-tools 8
```

The description drove the curation. The model kept the customer, invoice, and refund
surface and left the rest of the API (subscriptions, products, connect, webhooks,
reporting) out:

| Tool | Read-only | What it does |
|---|---|---|
| `customer_lookup` | yes | Find or retrieve customer information |
| `customer_manage` | no | Create, update, or delete a customer |
| `invoice_lookup` | yes | Find or retrieve invoice information |
| `invoice_manage` | no | Create, update, finalize, pay, void, or send an invoice |
| `refund_lookup` | yes | List or retrieve refunds |
| `refund_manage` | no | Create, update, or cancel a refund |

Six tools, ~956 tokens of definitions, from an API whose naive conversion needs
~463,000. The committed artifacts: [`stripe-support.toolplan.yaml`](stripe-support.toolplan.yaml)
and [`generated/stripe-support/`](generated/stripe-support/).

The same spec with `--auto` and no description produces a broader 11-tool general
server ([`stripe.toolplan.yaml`](stripe.toolplan.yaml)). Same machinery, two shapes:
one fits your task, one fits the API.

## How it works

```mermaid
flowchart LR
    S["OpenAPI spec"] --> I["inspect<br/><i>damage report</i>"]
    I --> P["plan<br/><i>1 LLM pass</i>"]
    P --> Y[["ToolPlan YAML<br/>editable, diff-friendly"]]
    Y --> B["build<br/><i>deterministic codegen</i>"]
    B --> M["FastMCP server"]
    M --> E["eval<br/><i>measure it</i>"]
    style Y fill:#fde68a,stroke:#b45309,color:#000
    style P fill:#bfdbfe,stroke:#1e40af,color:#000
```

| Command | Cost | What it does |
|---|---|---|
| `inspect` | free | Parse the spec, report what a naive conversion costs. |
| `plan` | 1 LLM pass | Curate tools for your description (or `--auto`) into a ToolPlan YAML. |
| `build` | free | Compile the ToolPlan into a runnable FastMCP package. |
| `eval` | LLM per trial | Run task scenarios against the server, emit a report. |

The ToolPlan YAML is the load-bearing design decision. The model proposes; you review
and edit plain YAML; `build` compiles it with no model in the loop. Nothing the LLM
produces is a black box. A tool in the plan looks like this:

```yaml
- name: refund_lookup
  intent: List or retrieve refunds
  read_only: true
  endpoints: [GET /v1/refunds, GET /v1/refunds/{refund}]
  dispatch: { mode: select, selector: mode }   # "list" or "get" picks the endpoint
  response_filter:
    fields: [id, amount, currency, status, reason, created, charge]
```

Generated servers filter API responses to the fields the agent needs, inject auth from
an environment variable at runtime (never baked into code), and turn API errors into
plain messages an agent can act on.

## Use the generated server

Each generated package is self-contained (needs only `fastmcp` and `httpx`) and comes
with its own README naming its auth variable.

```bash
cd pets-server
PETSTORE_API_KEY=... fastmcp run server.py
```

Claude Code:

```bash
claude mcp add pets -e PETSTORE_API_KEY=... -- uv run --with fastmcp fastmcp run /path/to/pets-server/server.py
```

Claude Desktop (`claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "pets": {
      "command": "uv",
      "args": ["run", "--with", "fastmcp", "fastmcp", "run", "/path/to/pets-server/server.py"],
      "env": { "PETSTORE_API_KEY": "..." }
    }
  }
}
```

## Models and providers

The LLM layer speaks both wire protocols, so any model works: hosted or local. This
repo's own artifacts were made with a hosted model for curation and a local 7B for the
evals, with zero code changes between them.

| Preset (`--provider`) | Protocol | Base URL |
|---|---|---|
| `anthropic` | anthropic | SDK default |
| `glm` | anthropic | `api.z.ai/api/anthropic` |
| `kimi` | anthropic | `api.moonshot.ai/anthropic` |
| `openrouter` | openai | `openrouter.ai/api/v1` |
| `ollama` | openai | `localhost:11434/v1` |

Anything else: `--base-url <url> --api-protocol openai|anthropic`. Configuration
resolves flags first, then environment variables (`P2M_API_KEY`, `P2M_MODEL`,
`P2M_BASE_URL`, `P2M_API_PROTOCOL`, `P2M_MAX_OUTPUT_TOKENS`; bare `ANTHROPIC_API_KEY`
or `OPENAI_API_KEY` work as key fallbacks), then a `.env` in the working directory,
then preset defaults. `prompt-to-mcp providers` prints the full picture.

Two provider quirks are handled for you: Gemini 3 requires its per-call
`thought_signature` echoed back on multi-turn tool use, and some gateways count hidden
reasoning tokens against `max_tokens` (the output budget is configurable, with a
truncation-aware retry).

## Evidence

Every request an agent makes carries the full tool list. Drawn to scale against a
32k-token window, for GitHub's API:

```
naive      1,206 tools │ ████████████████████████████████████████  414,263 tok   12.6× over ✗
32k window   (ceiling) │ ███                                         32,768 tok
distilled      9 tools │ ▏                                            1,758 tok   18.6× under ✓
```

A naive tool list this size cannot fit before the agent reads a word of the task.
Strict providers reject it; lenient ones (Ollama) silently truncate it. The eval
harness records the former as `provider_error` and refuses the latter up front with a
pre-flight `--model-context` guard, so a mangled run is never reported as a real one.

The benchmarks below used `--auto` (no description), which makes them a fair,
description-free comparison: naive one-tool-per-endpoint versus automatic curation,
same agent (`qwen2.5:7b`, local), same scenarios, live APIs.

| API | Operations | Naive tool defs | Curated (`--auto`) | Ratio | Eval result |
|---|---|---|---|---|---|
| GitHub | 1,206 | ~414,263 tok | 9 tools / ~1,758 tok | 236× | 13/15 correct (87%) |
| Stripe (test mode) | 587 | ~462,960 tok | 11 tools / ~1,793 tok | 258× | 12/15 correct (80%) |

The naive side was refused by the context guard in every trial; it does not fit a 32k
model. Full artifacts: [`eval_runs/headline/`](eval_runs/headline/) and
[`eval_runs/stripe/`](eval_runs/stripe/).

```mermaid
xychart-beta
    title "Curated GitHub server: trials passed per scenario (3 trials each)"
    x-axis ["repo-overview", "repo-metadata", "list-branches", "repo-language", "search-repo"]
    y-axis "trials passed" 0 --> 3
    bar [3, 3, 3, 3, 1]
```

The weak scenario is also the expensive one: `search-repo` passed 1 of 3 and consumed
about 2.7 times the input tokens of any other task. A small model that cannot decide
between similar options flails, and flailing costs tokens:

```mermaid
xychart-beta
    title "Mean input tokens per task, by scenario"
    x-axis ["repo-overview", "repo-metadata", "list-branches", "repo-language", "search-repo"]
    y-axis "input tokens" 0 --> 10000
    bar [3582, 4062, 3448, 3401, 9569]
```

What this shows: the context problem is decisive, and the pipeline works end to end on
live APIs with a small local agent. What it does not show yet: a comparison against a
naive server that *fits* (cap it with `--naive-max-tools` on a large-context model),
or a no-tools control. Both are supported by the harness and are the next experiments
worth running.

Known limits:

| Limitation | Effect |
|---|---|
| `chars/4` token estimator | context figures are order-of-magnitude, not a tokenizer |
| Small N, one local model | success rates are noisy and model-specific |
| No no-tools control | some scenarios are partly answerable from model memory |
| Naive never run fitted | the win leans on the context ceiling, not proven tool quality |
| One write scenario, missed by the 7B | mutating paths are generated but not yet passed by this agent |
| LLM-as-judge fallback | can misjudge open-ended answers |

## Coverage tradeoff

Curation drops long-tail endpoints on purpose. The Stripe support server above cannot
touch subscriptions or payouts, because the description did not ask for them. That is
the point: you decide what is covered, and the tool surface stays small enough to fit
and cheap enough to reason over.

Nothing is locked in. A missing capability is one YAML edit and a free rebuild away:
add the endpoint to a tool (or add a tool) in the ToolPlan, run `build`, done.

## CLI reference

| Command | Purpose |
|---|---|
| `prompt-to-mcp inspect SPEC` | Damage report for a naive conversion of SPEC (file or URL). |
| `prompt-to-mcp plan SPEC "DESCRIPTION"` | Curate tools for the description. Key flags: `--auto`, `--max-tools`, `--include-clusters`, `--intent-file`, `-o`. |
| `prompt-to-mcp build PLAN --spec SPEC -o DIR` | Compile a ToolPlan into a server package. `--base-url` overrides the API host. |
| `prompt-to-mcp eval SPEC --server-dir DIR` | Run scenarios against the server (and the naive baseline). Key flags: `--scenarios`, `--trials`, `--only`, `--model-context`, `--pace`. |
| `prompt-to-mcp providers` | List provider presets and configuration resolution. |

Provider selection flags (`--provider`, `--model`, `--base-url`, `--api-protocol`,
`--max-output-tokens`) work on `plan` and `eval` alike. On Windows, set `PYTHONUTF8=1`
for live runs.

## Development

```bash
git clone https://github.com/KnaniOussama/prompt-to-mcp && cd prompt-to-mcp
uv sync
uv run pytest    # offline, no keys needed
```

See [CONTRIBUTING.md](CONTRIBUTING.md) for ground rules (determinism of `build` is
enforced by tests) and [SECURITY.md](SECURITY.md) for what to know before running
against real accounts. The design document is [DESIGN.md](DESIGN.md).

## License

MIT. See [`LICENSE`](LICENSE).
