Menu

AI

How to Turn an OpenAPI Spec Into an MCP Server (FastMCP and Other Options)

Generate an MCP server from an OpenAPI spec with FastMCP 4, filter routes to a safe read-only set, handle spec errors, and know when to hand-write tools instead.

By: Irina ShvayaNovember 2, 2026

How to Turn an OpenAPI Spec Into an MCP Server (FastMCP and Other Options)

This tutorial is part of Build an MCP Server, within The MCP Guide. Want it built for you? See MCP development.

You can turn an OpenAPI spec into an MCP server in about 15 lines of Python with FastMCP's from_openapi method. It reads the spec, makes one MCP tool per endpoint, and calls your API through an HTTP client you supply. Add route maps to keep only the endpoints you want, and you have a working prototype in minutes.

The catch: a generated server mirrors your API, and APIs are designed for programmers, not for AI models. FastMCP's own docs say generated servers are a starting point. This guide shows the generator, a real failure it exposed, and how to move from generated to curated tools.

Checked against: FastMCP's OpenAPI integration docs on 2 November 2026, with fastmcp 4.1.5 (the latest PyPI release, which installed mcp 2.3.0 and httpx2 2.16.0). The code ran locally on 2 November 2026 against the public Swagger Petstore demo API.

Key Takeaways

  • FastMCP.from_openapi(openapi_spec=..., client=...) builds a server where, by default, every endpoint becomes a tool.
  • Use route maps with a final catch-all MCPType.EXCLUDE rule to allow-list only the endpoints you trust.
  • Put API credentials on the HTTP client you pass in. FastMCP does not forward the Authorization header from incoming MCP requests.
  • If the API's real responses do not match its spec, calls can fail validation. Fix the spec before you rely on the server.
  • FastMCP's docs say models perform better with curated servers. Use generation to learn which tools you need, then hand-write the important ones.

Your options in November 2026

ToolLanguageStatus checked 2 Nov 2026
FastMCP from_openapiPythonDocumented, open source
Speakeasy (speakeasy quickstart --mcp)Generates TypeScriptDocumented in Speakeasy's MCP guide
StainlessWas TypeScriptWinding down hosted products after joining Anthropic, per its May 2026 announcement
Hand-written wrapperAnySee building an MCP server for a REST API

Speakeasy's guide says its generator creates "a TypeScript-based MCP server." It lets you tag tools with scopes through an x-speakeasy-mcp extension, and start the server with --scope read to mount only read tools. That is a useful pattern whichever tool you pick.

This tutorial uses FastMCP because it runs from a spec with no build step. Our MCP vs OpenAPI page explains why the two formats are complements, not rivals.

Prerequisites

  • Python 3.10 or newer (PyPI lists fastmcp 4.1.5 as requiring 3.10+).
  • An OpenAPI 3 document for your API, as a URL or a file.
  • API credentials with the narrowest rights you can get.
  • Node.js 22.19+ if you want to test with the MCP Inspector.

Install

uv init openapi-mcp --python 3.12
cd openapi-mcp
uv add fastmcp

Note the HTTP library. FastMCP's OpenAPI page now uses httpx2, a separate package from httpx, and installing fastmcp 4.1.5 pulled in httpx2 2.16.0. Older tutorials import httpx. Follow the current docs.

FastMCP is a separate project from the official MCP Python SDK. Its upgrade guide says "FastMCP 4 builds on the MCP Python SDK v2." Our FastMCP vs the official Python SDK comparison covers the differences.

Minimal working server

Create server.py:

import httpx2
from fastmcp import FastMCP
from fastmcp.server.providers.openapi import MCPType, RouteMap

BASE_URL = "https://petstore3.swagger.io/api/v3"

client = httpx2.AsyncClient(base_url=BASE_URL, timeout=20)
spec = httpx2.get(f"{BASE_URL}/openapi.json").json()

mcp = FastMCP.from_openapi(
    openapi_spec=spec,
    client=client,
    name="Petstore (read-only)",
    route_maps=[
        RouteMap(methods=["GET"], pattern=r"^/(pet|store)/.*", mcp_type=MCPType.TOOL),
        RouteMap(mcp_type=MCPType.EXCLUDE),  # everything else is dropped
    ],
)

if __name__ == "__main__":
    mcp.run()

What each part does, per FastMCP's docs:

  • from_openapi reads the spec and creates components. "By default, every endpoint becomes an MCP Tool."
  • client is the HTTP client that makes the real calls. Its base_url decides where requests go.
  • route_maps are checked in order, and the first match wins. Each can match on methods, a path pattern or tags, and set mcp_type to TOOL, RESOURCE, RESOURCE_TEMPLATE or EXCLUDE.
  • The final catch-all EXCLUDE turns the list into an allow-list. FastMCP's docs describe this pattern to stop default mappings from applying to the remaining routes. Here, only GET endpoints under /pet and /store survive. Creating, updating and deleting pets are gone.

Check what the generator made

Create check.py using FastMCP's client:

import asyncio
from fastmcp import Client
from server import mcp


async def main() -> None:
    async with Client(mcp) as c:
        for t in await c.list_tools():
            print(t.name, "|", (t.description or "").splitlines()[0][:70])
        r = await c.call_tool("getPetById", {"petId": 999999999}, raise_on_error=False)
        print(r.is_error, r.content[0].text)

asyncio.run(main())

On 2 November 2026 the five generated tools were:

findPetsByStatus | Multiple status values can be provided with comma separated strings.
findPetsByTags | Multiple tags can be provided with comma separated strings. Use tag1,
getPetById | Returns a single pet.
getInventory | Returns a map of status codes to quantities.
getOrderById | For valid response try integer IDs with value <= 5 or > 10. Other valu

And the missing pet came back as a tool error:

True Error calling tool 'getPetById': HTTP error 404: Not Found - Pet not found

Look at the tool names and descriptions. They come straight from the spec's operationId and description fields. "For valid response try integer IDs with value <= 5 or > 10" is a note for human testers, and the model will read it too. Your spec's wording is now your prompt.

A real failure: the spec and the API disagree

Calling findPetsByStatus with {"status": "available"} failed on 2 November 2026. The API returned pets without a photoUrls field, which the spec marks as required. FastMCP had built an output schema from the spec, and the client rejected the result:

RuntimeError: Invalid structured content returned by tool findPetsByStatus: 'photoUrls' is a required property

The MCP tools specification says that when a tool declares an output schema, servers must provide structured results that conform to it. A spec that drifts from the real API therefore produces tool calls that fail, even though the HTTP call worked. A call to getInventory in the same run failed differently, with HTTP error 500 from the demo server.

The lesson for your own API: validate the spec against real responses before generating a server. Contract tests that compare responses to the spec pay for themselves here.

Add credentials

Put them on the client. FastMCP's docs show:

import os

client = httpx2.AsyncClient(
    base_url="https://api.example.com",
    headers={"Authorization": f"Bearer {os.environ['API_TOKEN']}"},
)

The docs also note that when the MCP server runs over HTTP, FastMCP does not forward credential headers such as Authorization from incoming MCP requests. That is the right default. The MCP security best practices say servers "MUST NOT accept any tokens that were not explicitly issued for the MCP server." See our page on token passthrough.

Test with the MCP Inspector

npx @modelcontextprotocol/inspector --cli \
  uv --directory /ABSOLUTE/PATH/openapi-mcp run server.py \
  --method tools/list --format json

Review every generated input schema. Look for vague parameter names, missing descriptions and enums that should exist but do not. Our Inspector lesson covers the flags.

From generated to curated

Use the generated server as a map, then improve it:

  1. Cut the list. Keep the 5 to 15 operations people ask for. Exclude admin, bulk and internal routes with RouteMap patterns or tags.
  2. Fix names and descriptions in the spec. Clear operationId values like getOrderStatus beat orders_get_v2. Write descriptions for the model, not for testers.
  3. Hand-write the important tools. Combine two calls into one task, trim responses, and add limits. FastMCP lets you add normal @mcp.tool functions to the same server.
  4. Separate reads and writes. Expose writes only where a person approves them. Speakeasy's scope tags show one way to do this.
  5. Fix spec drift so output schemas match reality.

Connect it to a host

claude mcp add -e API_TOKEN=your-token --transport stdio petstore \
  -- uv run --directory /ABSOLUTE/PATH/openapi-mcp server.py

For other clients, generate the JSON with our MCP config generator. Use absolute paths, and restart the host after config changes.

Deploy notes

The spec is fetched at startup in this example. In production, load it from a pinned file in your repo, so an API change cannot silently add new tools. Run the server remotely over Streamable HTTP only behind sign-in, and keep the API token in a secret store.

Pitfalls

  1. Exposing every endpoint. Without an allow-list, DELETE and admin routes become tools.
  2. Test-only notes in descriptions. The model reads them as instructions.
  3. Huge responses. Generated tools return whatever the API returns. Add a curated tool for list endpoints.
  4. Outdated imports. Older FastMCP examples import httpx and fastmcp.server.openapi. The current page imports httpx2 and fastmcp.server.providers.openapi.
  5. Mixing up FastMCP and the SDK. mcp.server.fastmcp was the v1 SDK's built-in copy, and it is gone in SDK v2. The standalone fastmcp package is different.

Next step

Learn tool design and error handling in the free Learn MCP course, especially designing tools an AI uses well. If you want an MCP server for your product's API that customers can connect to, eSEOspace MCP development can help you curate the tool set and set up sign-in.

More in the MCP Guide

Free tools: server.json Validator · MCP Error Code Lookup

Latest research: State of MCP: October 2026

Frequently asked questions

Does this work with Swagger 2.0 specs?

FastMCP's page refers to OpenAPI documents and notes that its provider requires FastMCP v3.0.0 or later. If you only have a Swagger 2.0 file, convert it to OpenAPI 3 first with a standard converter, then check the result.

Can I generate a TypeScript server instead?

Yes. Speakeasy's guide generates a TypeScript MCP server from an OpenAPI document with speakeasy quickstart --mcp. Check its current pricing and terms on Speakeasy's site before you commit.

Why are my tool names ugly?

They come from operationId. Rename operations in the spec, or replace the most-used ones with hand-written tools. Clear names help the model choose correctly.

Should GET endpoints be resources instead of tools?

FastMCP lets you map them to resources or resource templates with route maps. Tools are simpler for most hosts, because the model can call them on its own.

Is a generated server safe for production?

Treat it as a prototype. Review every tool, allow-list routes, use a scoped API key, and add sign-in before anyone else can reach it.

You Might Also like to Read

Get a FREE Website Audit

Enter your website and get a 20–30 page SEO, GEO, AEO & CRO audit within 24 hours. Completely free.

Don't have a site yet? Get in touch →

Ready to speak with a marketing expert?
Give us a call! (916) 866-7893

Websites Launched
1407
Websites Launched
Marketing Experts
29
Marketing Experts
Hours of Dedicated Work
204533
Hours of Dedicated Work
Irina S.Benjamin G.Jeanette M.David G.