By the Octri team · Updated
How to generate an MCP server from OpenAPI: mapping, a working generator and pitfalls
A sizeable OpenAPI document already contains most of the information needed to expose an API to an agent: operations, names, arguments, response shapes, and authentication requirements. It is tempting to turn every operation into a tool and call the job finished. You can do that mechanically. Whether the resulting tool list is useful is a more interesting question.
In this guide, generating an MCP server from OpenAPI means building a reliable translation layer, then curating the interface it exposes. The translation can be automated; decisions about context cost, permissions, and a model's likelihood of choosing the right action need deliberate work.
How do you generate an MCP server from OpenAPI?
Load the specification, resolve references, map operations into tool definitions, and dispatch each call to the correct HTTP request. The operation's operationId is a natural candidate for a tool name. Its parameters and request body become JSON Schema inputs. A response schema can describe structured results, while security schemes tell the wrapper which credential belongs on the upstream call.
Before publishing, filter the catalog. There is no benefit in exposing an operation that the agent cannot safely use or that duplicates a clearer tool. Read-only tools, potentially destructive tools and administrative operations should not all inherit the same default permissions.
How operations map to tools
Consider a stock reservation API. A GET /items/{itemId} operation maps comfortably to getItem, with one required string. A POST /reservations needs a validated body with item ID and quantity. The methods are different; their descriptions should communicate that distinction to the model.
| OpenAPI property | MCP result |
|---|---|
operationId |
Tool name, after validation and collision checking |
summary / description |
Tool description, ideally edited for the agent's decision |
parameters |
Path, header and query inputs |
requestBody |
Structured payload input |
responses |
Possible structured output schemas and error handling |
security |
How the wrapper obtains an upstream credential |

A GET is usually read-only, but the HTTP method is only a starting signal. Some APIs have unusual semantics. Review the operation's actual behavior before setting read-only or destructive annotations.
A working generator in 85 lines
The most revealing part of a generator is how little code it takes to produce a plausible interface. For this rewrite I made a local OpenAPI-like document with getOrder and createOrder, then extracted the names and required inputs. The output was getOrder: [id] and createOrder: [body]. That test used Python's standard library and did not start an MCP server; it checked the schema-mapping decision before transport was involved.
Here is a compact version of the extraction logic. It handles direct path parameters and JSON bodies; it deliberately omits $ref, authentication and dispatch, which a production generator must add.
def tools_from_spec(spec):
for path, paths in spec["paths"].items():
for method, operation in paths.items():
properties = {}
required = []
for param in operation.get("parameters", []):
properties[param["name"]] = param["schema"]
if param.get("required"):
required.append(param["name"])
body = operation.get("requestBody")
if body:
properties["body"] = body["content"]["application/json"]["schema"]
if body.get("required"):
required.append("body")
yield {
"name": operation["operationId"],
"description": operation.get("description", operation.get("summary", "")),
"inputSchema": {"type": "object", "properties": properties,
"required": required},
"readOnly": method.lower() == "get",
"upstream": (method.upper(), path),
}An implementer still needs to validate tool names, resolve nested schema references without recursion loops, preserve path-versus-query placement, encode URL segments safely, handle content types besides JSON, and check the result against the declared response. Call this a starting scaffold, not a complete generator.
What a real spec produces
At small scale, a one-operation-one-tool mapping is easy to understand. At hundreds of operations, the tool list itself becomes part of your token budget. Every name, description and schema is information the model may need to read just to decide what action exists.
This is why the interesting measurement is not only the number of OpenAPI operations. Count the serialized tool definitions and tokenize them using the model family you intend to support. Include long enums, nested object descriptions, and redundant paths. The biggest schema is often a better optimization target than twenty tiny tools.
The source material for this topic describes a 110-operation measurement in which one operation had a disproportionately large schema. Rather than carrying those numbers forward as a current benchmark, repeat the measurement against the exact spec and model you're deploying. A model context window is a cost and a selection constraint, not simply a storage limit.

Pitfalls to handle in a generator
An operation with no operationId needs a stable fallback, but a generated name such as postV2CustomersCustomerIdSubscriptions tells a model little about intent. Duplicate operation IDs are worse: a later tool can silently overwrite the first. Validate the catalog and fail with a useful report before serving it.
Schema flattening is another trap. If both a URL parameter and a body field are named id, the generator must preserve them as distinct inputs. Preserve where each argument belongs. Also distinguish required request body from required fields inside the body, or an agent may be allowed to send {} to an operation that requires a payload.
Finally, credentials should be resolved on the server. An MCP client should not have to supply your upstream API key as a tool argument. A remote server needs its own authorization layer and should validate tokens intended for that server before calling the API. The MCP OAuth guide explains that boundary.
Curate what the spec generated
The generated list is inventory, not an editorial decision. Start with the workflows an agent should complete. If the job is “find an order and check delivery,” perhaps find_orders, get_order and track_shipment are enough. A hundred administrative operations may be valid API methods while being irrelevant to that job, and some might be unsafe to expose.
Write descriptions in the language of the user's goal: “Use this to find an order when you have the customer's email,” for example, is more actionable than “Returns order collection.” Pin an account or a region in the server configuration when the agent should never choose it. Hide sensitive arguments and place writes behind approval.

Different clients may benefit from different exposure strategies. Some prefer a short list of domain tools; others can search a larger catalog or execute controlled SDK code. Evaluate with realistic tasks instead of assuming the most complete tool list is the best one.
Checklist for generating an MCP server from OpenAPI
Check operation name uniqueness, $ref resolution, parameter locations, request-body requiredness, response handling, method annotations, upstream authentication and version compatibility. Then measure the tool-list size, remove unnecessary actions, test missing permissions and invalid input, and keep a stable audit trail of changes to tool definitions.
A generator passes its first test when the tool calls the right endpoint. It passes the more important test when an agent can reliably choose it and the server refuses what should not run.
OpenAPI to MCP with Octri
Octri MCP Studio starts with the API description you already maintain and lets you decide which resulting tools an agent should receive. Its tool-group and input-configuration controls are especially relevant to the curation step: you can build a narrow agent-facing surface without removing endpoints from the underlying API or SDKs.
Treat the generated server as another interface that requires review. Operation names, examples, risk boundaries and observability matter as much here as they do in an API reference.