PromptisePromptise
Docs
GitHub
Promptise - AI Framework LogoPromptise

The foundation layer for agentic intelligence. Build, secure and operate autonomous AI systems with Promptise Foundry.

pip install promptise

[01] Foundry

  • MCPcast
  • The Promptise Agent
  • Reasoning Engine
  • MCP
  • Agent Runtime
  • Prompt Engineering
  • Execution Engine
  • Agent Identity

[02] Resources

  • Documentation
  • GitHub
  • Guides
  • Learning Paths
  • Questions

[03] Company

  • About
  • Terms of Service
  • Privacy Policy
  • Cookie Policy
  • Subprocessors

© 2026 Promptise by Manser Ventures. All rights reserved.

Open source · Python

← Guides> AI Engineering

OpenAPI to MCP: Turn Any REST API into an MCP Server

Turn an OpenAPI spec into a working MCP server in minutes. Read-only by default, human approval on every write, and tested by a real agent.

Level
Intermediate
Reading time
15 min
Published
Oct 10, 2026
By
Promptise Team
  • MCP
  • OpenAPI
  • REST API
  • MCPcast
  • Human-in-the-Loop
  • Promptise Foundry

You already have an API, and you want AI agents to use it. The quickest honest route is to turn its OpenAPI spec into an MCP server, but a converter that maps every endpoint to a tool tends to produce something agents struggle with: dozens of look-alike tools, and write access nobody signed off on. This guide uses MCPcast, the OpenAPI-to-MCP generator in Promptise Foundry, to do it properly. You'll generate a server from a real API, read what it decided and why, allow writes behind human approval, connect it to Claude, and measure how well an agent actually uses it. Every command was run against Promptise Foundry 1.2.1 and the public Swagger Petstore, and the output is what it printed.

[01]

How do you turn a REST API into an MCP server?

Point MCPcast at your OpenAPI spec:

>_Terminal
pip install promptise
promptise mcpcast https://petstore3.swagger.io/api/v3/openapi.json

That one command reads the spec, sorts every operation by risk, decides which ones become tools, and writes a complete Python project: the MCP server, a test per tool, a Dockerfile, and copy-paste setup for Claude Desktop, Claude Code and Cursor. By default the server can only read. Anything that writes has to be switched on, and then it waits for a person to approve it.


[02]

Why not one tool per endpoint?

Mapping each endpoint to a tool is easy, and it's where most conversions go wrong. A large API turns into a wall of tools whose names come from the URL structure, and the model has to pick the right one from descriptions written for human developers. Worse, every POST, PUT and DELETE becomes something an agent can call on its own.

MCPcast puts a few deliberate steps between the spec and the server:

Rendering diagram…

The plan file in the middle is the point. Every decision lands there in plain YAML, with a reason, before any code exists. You can read it, change it, and regenerate.


[03]

What you need

  • Python 3.10 or newer, and pip install promptise.

  • An OpenAPI spec, as a URL or a file. If your API is built with FastAPI, it already serves one at /openapi.json.

  • Optionally, an API key for a model provider. MCPcast uses a model to design the tools and to test them, but --no-curate runs fully offline.


[04]

Turn the Petstore API into an MCP server, step by step

The Swagger Petstore is a public demo API with 19 operations for pets, orders and users. It's a fair stand-in for a real product API.

Step 01

Generate a read-only server

Start offline, without a model, so every decision comes from MCPcast's own rules:

>_Terminal
promptise mcpcast https://petstore3.swagger.io/api/v3/openapi.json --no-curate -o petstore-readonly
Output
Parsed 19 operations from https://petstore3.swagger.io/api/v3/openapi.json;
profile=read-only auth=passthrough
╭────────────────────────────────── mcpcast ───────────────────────────────────╮
│ petstore → petstore-readonly/                                                │
│   tools: 6  (0 require human approval)                                       │
│   not exposed: 13 operations (with reasons in the plan)                      │
│   files: petstore_mcp/ (10 modules), tests/ (2), mcpcast.plan.yaml,          │
│ server.py, README.md, pyproject.toml, Dockerfile, .env.example, .gitignore   │
╰──────────────────────────────────────────────────────────────────────────────╯

Six tools out of 19 operations. That's the read-only profile at work: searching pets, reading orders and users stays in, and everything that changes data stays out.

Step 02

Read the plan

Open mcpcast.plan.yaml. Each tool lists its risk class and the API routes it calls. At the bottom, every operation that didn't become a tool is listed with the reason:

YAMLmcpcast.plan.yaml
1
2
3
4
5
6
7
8
9
10
dropped:
- operation_id: addPet
  reason: write operation excluded by profile 'read-only'
- operation_id: getPetById
  reason: 'unsupported by mcpcast: requires an API key in header ''api_key'', but this server presents
    its credential in header ''Authorization'''
- operation_id: deletePet
  reason: destructive operation excluded by profile 'read-only'
- operation_id: uploadFile
  reason: 'unsupported by mcpcast: unsupported request body media type application/octet-stream'

There are two kinds of reasons. Some operations are left out on purpose, because of the safety profile. Others MCPcast can't support yet, and it says so instead of generating a tool that would fail later. The full list is longer; this is an excerpt.

The risk class comes from fixed rules, not guesswork. GET is a read. DELETE, and verbs like cancel or revoke, are destructive. Words like charge or refund make an operation financial. Most other POST, PUT and PATCH calls are writes. The profile decides which classes make it in:

Profile

Reads

Writes

Destructive and financial

read-only (default)

Yes

No

No

standard

Yes

Yes, with approval

No

full

Yes

Yes, with approval

Yes, with approval

Step 03

Allow writes, behind approval

To let agents create pets and place orders, switch to the standard profile. --auth env-token makes the server send one API token, from an environment variable, on every call it makes to your API. It's the simplest setup for a desktop client.

>_Terminal
promptise mcpcast https://petstore3.swagger.io/api/v3/openapi.json \
  --no-curate --profile standard --auth env-token -o petstore-mcp
Output
Parsed 19 operations from https://petstore3.swagger.io/api/v3/openapi.json;
profile=standard auth=env-token
╭────────────────────────────────── mcpcast ───────────────────────────────────╮
│ petstore → petstore-mcp/                                                     │
│   tools: 13  (7 require human approval)                                      │
│   not exposed: 6 operations (with reasons in the plan)                       │
│   files: petstore_mcp/ (10 modules), tests/ (2), mcpcast.plan.yaml,          │
│ server.py, README.md, pyproject.toml, Dockerfile, .env.example, .gitignore   │
╰──────────────────────────────────────────────────────────────────────────────╯

Thirteen tools now, and all seven writes need a person to approve them. The three deletes are still out: standard never generates destructive operations.

Step 04

Run the generated tests

The project is an ordinary Python package with its own tests. They run against a fake copy of your API, so they never touch real data:

>_Terminal
cd petstore-mcp
pip install -e ".[dev]"
pytest
Output
.....................                                                    [100%]
21 passed in 0.31s

Each tool is checked three ways: it's listed, it calls the right route, and it's gated when it should be.

Step 05

Connect it to Claude

The generated README.md includes setup for Claude Desktop, Claude Code, Cursor and Promptise agents, filled in for your server. For Claude Desktop, add it to claude_desktop_config.json:

JSONclaude_desktop_config.json
1
2
3
4
5
6
7
8
9
{
  "mcpServers": {
    "petstore": {
      "command": "python",
      "args": ["/absolute/path/to/server.py"],
      "env": { "MCPCAST_UPSTREAM_TOKEN": "Bearer <your API token>" }
    }
  }
}

For Claude Code, it's one command:

>_Terminal
claude mcp add petstore -e MCPCAST_UPSTREAM_TOKEN="Bearer <your API token>" -- python /absolute/path/to/server.py
Note

Put the token in the client's config, as shown. A desktop app started from the dock doesn't see variables you export in a terminal.


[05]

What happens when an agent tries to change something?

Here's a Promptise agent using the generated server. It's the same pattern as in How to Connect MCP Servers to Your AI Agent in Python:

Pythonagent.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import asyncio

from promptise import build_agent
from promptise.config import StdioServerSpec


async def main():
    agent = await build_agent(
        model="openai:gpt-5-mini",
        servers={
            "petstore": StdioServerSpec(
                command="python",
                args=["petstore-mcp/server.py"],
                env={"MCPCAST_UPSTREAM_TOKEN": "Bearer demo"},
            ),
        },
        instructions="You help staff run the pet store. Use your tools.",
        trace_tools=True,
    )
    try:
        for question in [
            "How many pets are available right now?",
            "Add a new pet called Rex with the photo https://example.com/rex.jpg.",
        ]:
            result = await agent.ainvoke({"messages": [{"role": "user", "content": question}]})
            print(">>>", result["messages"][-1].content)
    finally:
        await agent.shutdown()


asyncio.run(main())
Output
→ Invoking tool: find_pets_by_status with {'status': 'available'}
✔ Tool result from find_pets_by_status: [{"id": 918273645, "name": "selfext-probe", … ]
>>> There are 9 pets currently marked as "available." Would you like me to list them or show details for any one?
→ Invoking tool: add_pet with {'name': 'Rex', 'photoUrls': ['https://example.com/rex.jpg'], 'id': None, 'category': {'id': 1, 'name': 'Dogs'}, 'tags': [{'id': 1, 'name': 'rex'}], 'status': 'available'}
✔ Tool result from add_pet: {
  "error": {
    "code": "APPROVAL_DENIED",
    "message": "Approval denied for tool 'add_pet': client declined or returned an invalid elicitation response",
…

The read went straight through and returned live data. The write was stopped. Look closely at what the model sent, too: nobody mentioned a category or tags, yet it invented Dogs and rex. That's exactly the kind of call a person should see before it reaches your API.

By default, approval uses MCP elicitation: the server asks the person behind the client to confirm the exact call. Here is the prompt that person sees, captured from a client that supports elicitation:

Output
Approval required: call tool 'add_pet' with arguments {'name': 'Rex', 'photoUrls': ['https://example.com/rex.jpg'], 'id': 7742, 'category': None, 'tags': None, 'status': 'available'}. Approve?

When they approve, the call goes through and the API's answer comes back. When they decline, or the client can't ask at all, the answer is APPROVAL_DENIED:

Rendering diagram…

That's why the Promptise agent above was refused: Promptise's own MCP client doesn't answer elicitation requests yet, and the server fails closed. For agents that run without a person at the client, generate the server with --auth api-key. Approvals then use the pending mode: a gated call waits until a different person on the same tenant, with the approver role, approves it through the generated approval tools. Human approval in the docs covers both modes.


[06]

Let a model design the tools

So far every tool mirrors one endpoint. Leave out --no-curate and MCPcast asks a model to design the tool set: clearer names, better descriptions, and related endpoints merged into one tool. By default it uses openai:gpt-5-mini, and --model picks another. This run also adds --eval, so set your model provider's key and MCPCAST_UPSTREAM_TOKEN first; the evaluation reads from the live API.

>_Terminal
promptise mcpcast https://petstore3.swagger.io/api/v3/openapi.json \
  --profile standard --auth env-token --eval --eval-tasks 8 -o petstore-curated
Output
Parsed 19 operations from https://petstore3.swagger.io/api/v3/openapi.json;
profile=standard auth=env-token
Curating with openai:gpt-5-mini (budget 25 tools)…
╭────────────────────────────────── mcpcast ───────────────────────────────────╮
│ petstore → petstore-curated/                                                 │
│   tools: 12  (7 require human approval)                                      │
│   not exposed: 6 operations (with reasons in the plan)                       │
│   files: petstore_mcp/ (10 modules), tests/ (2), mcpcast.plan.yaml,          │
│ server.py, README.md, pyproject.toml, Dockerfile, .env.example, .gitignore   │
╰──────────────────────────────────────────────────────────────────────────────╯
Evaluating with openai:gpt-5-mini (8 tasks)…
Agent Readiness: A  (7/8 tasks succeeded)

add_pet became create_pet and get_order_by_id became get_order. The two pet searches, by status and by tags, became a single find_pets that calls whichever route fits the arguments. The model proposes, but MCPcast's code decides: it checks every proposal, and it never lets a tool's risk drop below what the rules assigned.

The model can still get details wrong, which is why the plan stays in your hands. In this run it wrote a description that told agents to use a delete_pet tool, which the standard profile never generates. The fix is to edit the description in mcpcast.plan.yaml and regenerate from the plan:

>_Terminal
promptise mcpcast mcpcast.plan.yaml

Regenerating rewrites the server code from the plan, without calling the model again. It also re-checks the plan's safety rules. Try to remove the approval from a write and it refuses:

Output
Error: invalid plan:
1 validation error for MCPcastPlan
(root)
  Value error, tool 'create_pet' is write: profile 'standard' requires requires_approval=true

[07]

Measure how well agents use it

--eval is the part most converters skip. A model writes realistic tasks for your API, and a real agent tries to complete each one against the generated server. Reads hit your live API. Writes go to mocks built from the spec, so an evaluation never changes real data. The report for the run above:

Measure

Result

Agent Readiness

A, score 0.93

Tasks completed

7 of 8

Right tool chosen first

100%

Parameter errors

0%

The score is 0.6 × the task success rate plus 0.4 × the rate of choosing the right tool first. The one failed task asked for order #1, and the agent picked the right tool for it. The Petstore demo itself answered that request with HTTP 500, as calling the API directly confirms. The report also flagged four tools no task had covered, and suggested raising --eval-tasks to test them too.


[08]

Honest limits

MCPcast is upfront about what it doesn't do yet, and you'll want to know before you rely on it:

  • OpenAPI only. Postman collections and GraphQL aren't supported.

  • No OAuth 2.1 yet. Use a bearer token or pre-shared API keys.

  • Some operations are skipped. Operations that need header or cookie parameters, or take multipart, plain-text or binary uploads, are left out with a reason in the plan. Petstore's uploadFile is one of them.

  • Your spec is trusted input. MCPcast fetches spec URLs without checking for private networks and builds code from what the spec says, so only use specs you'd trust as much as code you run.

  • Claude Desktop needs stdio. Its config can't send headers to an HTTP server, so use the stdio setup shown above.

The full list is in the limits section of the MCPcast reference.


[09]

Frequently asked questions

What is MCPcast?

MCPcast is the part of Promptise Foundry that turns an existing API into an MCP server. It reads an OpenAPI spec and generates a complete, editable Python project. Like the rest of Promptise Foundry, it's open source under the Apache 2.0 license.

Do I need an LLM to use it?

No. With --no-curate, everything runs offline and deterministically. A model is only used to design nicer tools and to run --eval.

Can I edit the generated code?

Edit mcpcast.plan.yaml and regenerate; the server code is rebuilt from it. Your own files and tests in the project are left alone, while generated modules are rewritten. pyproject.toml, the Dockerfile and .env.example are yours after the first run.

Does it work with a FastAPI app?

Yes. FastAPI serves its OpenAPI spec at /openapi.json, so you can point MCPcast at your running app. The guide for Python APIs walks through it.

Is there a guided setup?

Yes. Run promptise mcpcast with no arguments for an interactive setup in your terminal. It walks you through the spec, the model, the safety profile and the auth mode, shows every tool for review, and writes nothing until you confirm. At the end it prints the equivalent one-line command. See Guided setup.


[10]

Where to go next

  • MCPcast reference: every option, the plan format and the approval modes.

  • Recipes for Stripe, GitHub and your own app: real APIs, step by step.

  • Approval gates: how human approval works in any Promptise MCP server.

  • Deployment: running your server in production.

  • How to Connect MCP Servers to Your AI Agent in Python: use your new server from your own agent.

Learning paths

Want more structure? Paths put guides in order, like a short course.

See the paths →

Keep going.

Browse every guide by topic and level, or follow a learning path that puts them in order.

All guidesLearning paths