openapi-to-mcp
by Karlangas12
README.md
<div align="center">
# π 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 your
AI agent can call β no glue code, no per-API server to write.
[](https://github.com/Karlangas12/openapi-to-mcp/actions/workflows/ci.yml)
[](https://www.npmjs.com/package/@karlangas12/openapi-to-mcp)
[](#testing)
[](https://nodejs.org)
[](LICENSE)
[Quick start](#quick-start) Β· [How it maps](#how-it-maps) Β· [Config](#configuration) Β· [Secure it](#pairing-with-mcp-shield-proxy) Β· [Limits](#honest-limitations) Β· [EspaΓ±ol](README.es.md)
</div>
```
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 the
capability you want already exists as a REST API with an OpenAPI spec, writing a
bespoke MCP server for it is busywork.
`openapi-to-mcp` reads the spec and does the mapping for you: each `METHOD /path`
becomes 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. The
response comes back trimmed for an LLM context window.
## Quick start
No install needed β run it straight from npm:
```bash
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:
```bash
npx @karlangas12/openapi-to-mcp --spec ./openapi.yaml --list
```
### Claude Desktop β `claude_desktop_config.json`
```jsonc
{
"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`
```jsonc
{
"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 up
as 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 request
goes 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 is
serialized. A `4xx`/`5xx` response comes back as an error result the model can
read 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:
```bash
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)
```bash
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 the
server 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 your
credentials. If you want a policy, credential masking and an audit trail around
that, wrap it with [mcp-shield-proxy](https://www.npmjs.com/package/mcp-shield-proxy)
β they compose with one extra line:
```jsonc
{
"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 are
masked, and the whole session lands in a verifiable audit log.
## Use as a library
```ts
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 exported
independently.
## Honest limitations
- **stdio transport only.** The MCP server speaks stdio, which covers Claude
Desktop, Cursor and Claude Code. Native HTTP/SSE serving isn't implemented yet.
- **Local `$ref` only.** References inside the document (`#/components/...`) are
resolved. External refs (other files or URLs) are not β they raise a clear
error rather than failing quietly.
- **`application/json` bodies.** Request bodies are mapped for JSON content.
`multipart/form-data` and `application/x-www-form-urlencoded` aren't mapped yet.
- **It's a mapper, not a gateway.** It doesn't add retries, caching or rate
limiting. Pair it with a proxy if you need those.
## Performance
The spec is parsed once at startup; after that, each tool call is a schema
validation plus one HTTP request. Parsing and tool generation for a typical spec
is sub-millisecond β the latency you feel is the underlying API's, not this.
## Testing
```bash
npm test # 32 tests
npm run typecheck # strict TypeScript
```
Coverage includes JSON **and** YAML parsing, endpointβtool conversion with Zod
schemas, and a full `tools/call` round trip over JSON-RPC against a mocked HTTP
backend (asserting the URL, method, headers and body that reach it).
## License
MIT
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues