π openapi-to-mcp
Turn any OpenAPI 3.x spec into a live MCP server.Point it at a spec (file or URL) and every endpoint becomes a validated tool yourAI agent can call β no glue code, no per-API server to write.
Quick start Β· How it maps Β· Config Β· Secure it Β· Limits Β· EspaΓ±ol
OpenAPI 3.x spec your AI client
(JSON / YAML, file or URL) (Claude, Cursor)
β β
βΌ β MCP / JSON-RPC (stdio)
[ openapi-to-mcp ] βββββββββββββββββββββββββββββ
β GET /pets/{id} β tool "get_pet_by_id"
βΌ HTTP + your auth header
the underlying REST API
Why
MCP lets an agent call tools, but someone has to write those tools. If thecapability you want already exists as a REST API with an OpenAPI spec, writing abespoke MCP server for it is busywork.
openapi-to-mcp reads the spec and does the mapping for you: each METHOD /pathbecomes a tool, each parameter and request body becomes a Zod-validated input,and every call is turned into an HTTP request with your auth header attached. Theresponse comes back trimmed for an LLM context window.
Quick start
No install needed β run it straight from npm:
npx @karlangas12/openapi-to-mcp --spec https://api.example.com/openapi.json \
--auth-header "Authorization: Bearer YOUR_TOKEN"
See what tools a spec produces without starting a server:
npx @karlangas12/openapi-to-mcp --spec ./openapi.yaml --list
Claude Desktop β claude_desktop_config.json
{
"mcpServers": {
"petstore": {
"command": "npx",
"args": [
"-y", "@karlangas12/openapi-to-mcp",
"--spec", "https://petstore3.swagger.io/api/v3/openapi.json",
"--auth-header", "Authorization: Bearer YOUR_TOKEN"
]
}
}
}
Cursor β .cursor/mcp.json
{
"mcpServers": {
"my-api": {
"command": "npx",
"args": [
"-y", "@karlangas12/openapi-to-mcp",
"--spec", "./openapi.yaml",
"--base-url", "https://api.internal.company.com"
]
}
}
}
That's the whole integration. Restart the client and the API's endpoints show upas tools.
How it maps
| OpenAPI | Becomes |
|---|---|
operationId, or METHOD /path if absent |
Tool name (get_pets_by_pet_id) |
summary / description |
Tool description |
| Path, query and header parameters | Tool input properties (path params are required) |
requestBody (application/json) |
A body input property |
| Every input schema | A Zod validator + a JSON Schema for tools/list |
Local $ref (#/components/...) |
Resolved inline |
enum, minimum/maximum, required, nullable, oneOf/anyOf/allOf |
Enforced on input |
When the agent calls a tool, arguments are validated before any HTTP requestgoes out. Path params are substituted into the URL, query params are appended,header params and your static --auth-header values are sent, and a JSON body isserialized. A 4xx/5xx response comes back as an error result the model canread and react to β not a silent failure.
Configuration
openapi-to-mcp --spec <path|url> [options]
-s, --spec <path|url> OpenAPI 3.x spec (JSON or YAML, local or remote)
--base-url <url> API base URL (when the spec declares no "servers")
-H, --auth-header <h> Header to forward, "Name: value" (repeatable)
--format <mode> Response format: markdown (default) or json
-n, --name <name> MCP server name
--list Print the generated tools and exit
-h, --help / -v, --version
Multiple headers:
npx @karlangas12/openapi-to-mcp --spec ./api.yaml \
--auth-header "Authorization: Bearer XYZ" \
--auth-header "X-Api-Version: 2024-01"
Export a standalone server (codegen)
npx @karlangas12/openapi-to-mcp codegen --spec ./api.yaml -o server.ts
Writes a single self-contained TypeScript file that embeds the spec and boots theserver via this package β handy for committing a pinned server to a repo.
Pairing with mcp-shield-proxy
openapi-to-mcp talks to a third-party API on your behalf, with yourcredentials. If you want a policy, credential masking and an audit trail aroundthat, wrap it with mcp-shield-proxyβ they compose with one extra line:
{
"mcpServers": {
"petstore": {
"command": "npx",
"args": [
"-y", "mcp-shield-proxy", "--", // β inspect, mask, audit
"npx", "-y", "@karlangas12/openapi-to-mcp",
"--spec", "https://api.example.com/openapi.json",
"--auth-header", "Authorization: Bearer YOUR_TOKEN"
]
}
}
}
Now every generated tool call is policy-checked, credentials in the traffic aremasked, and the whole session lands in a verifiable audit log.
Use as a library
import { loadSpec, buildTools, createMcpServer, startStdioServer } from '@karlangas12/openapi-to-mcp';
const doc = await loadSpec('./openapi.yaml');
const { tools, baseUrl } = buildTools(doc);
const server = createMcpServer({
tools,
baseUrl: baseUrl ?? 'https://api.example.com',
headers: { Authorization: 'Bearer YOUR_TOKEN' },
fetchFn: (url, init) => fetch(url, init as RequestInit),
});
startStdioServer(server);
The parser, the schemaβZod converter and the tool builder are all exportedindependently.
Honest limitations
- stdio transport only. The MCP server speaks stdio, which covers ClaudeDesktop, Cursor and Claude Code. Native HTTP/SSE serving isn't implemented yet.
- Local
$refonly. References inside the document (#/components/...) areresolved. External refs (other files or URLs) are not β they raise a clearerror rather than failing quietly. application/jsonbodies. Request bodies are mapped for JSON content.multipart/form-dataandapplication/x-www-form-urlencodedaren't mapped yet.- It's a mapper, not a gateway. It doesn't add retries, caching or ratelimiting. Pair it with a proxy if you need those.
Performance
The spec is parsed once at startup; after that, each tool call is a schemavalidation plus one HTTP request. Parsing and tool generation for a typical specis sub-millisecond β the latency you feel is the underlying API's, not this.
Testing
npm test # 32 tests
npm run typecheck # strict TypeScript
Coverage includes JSON and YAML parsing, endpointβtool conversion with Zodschemas, and a full tools/call round trip over JSON-RPC against a mocked HTTPbackend (asserting the URL, method, headers and body that reach it).
License
MIT