Turn an OpenAPI 3.x spec into an MCP server. Each operation becomes an MCP tool; tool calls are validated, proxied to the upstream REST API, and the response is validated against the spec's response schemas.
Package name:
@evalops/mcp-openapi. The unscopedmcp-openapipackage on the npm registry is an unrelated third-party project. Install this project from GitHub (npm install github:evalops/mcp-openapi) or from npm under the scoped name once a tagged release has been published.
Run against any OpenAPI file, no install step:
npx -y github:evalops/mcp-openapi --spec ./openapi.yamlAdd to Claude Code:
claude mcp add my-api -- npx -y github:evalops/mcp-openapi --spec /abs/path/openapi.yamlClaude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"my-api": {
"command": "npx",
"args": ["-y", "github:evalops/mcp-openapi", "--spec", "/abs/path/openapi.yaml"]
}
}
}HTTP transport instead of stdio:
npx -y github:evalops/mcp-openapi --spec ./openapi.yaml --transport streamable-http --port 3000
# MCP endpoint: http://127.0.0.1:3000/mcp--spec is repeatable. With more than one spec, every tool name is prefixed with the spec's name — given explicitly as --spec name=path or derived from the file's basename — so names stay deterministic and --allow-tools/--deny-tools patterns keep working. Remaining collisions get a numeric suffix. With a single spec, tool names are the bare operationIds, unchanged.
mcp-openapi --spec github=./github.yaml --spec linear=./linear.yaml
# tools: github_listIssues, linear_createIssue, ...--server-url is only valid with a single spec; with multiple specs each upstream URL comes from that spec's servers[].
- One MCP tool per OpenAPI operation. Tool name defaults to
operationId; missing IDs fall back tomethod_path. Collisions get a numeric suffix. - Tool input is grouped by parameter location:
{ path, query, header, cookie, body, pagination }. - Tool annotations are derived from the HTTP method:
GET/HEAD/OPTIONSare markedreadOnlyHint,PUT/DELETEidempotent + destructive,POST/PATCHdestructive. - Inputs are validated twice (Zod and AJV) before any network call. Responses are validated against the per-status response schemas; validation failures set
isErrorand include the issue list. - Successful responses are returned as
structuredContentplus a JSON text block with status, headers (allowlisted subset), attempt count, and validation results. tools/listis cursor-paginated at 50 tools per page and emitslistChangedwhen--watch-specreloads the spec.x-mcp-hidden: trueon an operation removes it.x-mcp-descriptionoverrides the tool description, then--descriptionsfile entries, thensummary/description.
The server exposes two read-only resources per loaded spec, so clients can introspect the API without extra tooling:
| URI | Content |
|---|---|
openapi://<spec-name>/spec |
The full dereferenced OpenAPI document, JSON |
openapi://<spec-name>/tools |
[{ name, method, path, description, tags }] for every compiled tool, filtered by the active tool policy |
<spec-name> is the --spec name= value or the spec file's basename (openapi://sample-openapi/spec for sample-openapi.yaml).
| Transport | Flag | Endpoints |
|---|---|---|
| stdio (default) | --transport stdio |
— |
| Streamable HTTP | --transport streamable-http |
/mcp, /health, /metrics, /test/streamable |
| SSE (legacy) | --transport sse |
/sse, /messages?sessionId=…, /health, /metrics, /test/sse |
- Web transports bind
127.0.0.1by default. Set--host 0.0.0.0to expose beyond the local machine. - The
Originheader is validated on/mcp,/sse, and/messagesto block DNS-rebinding from browsers. Localhost origins are always accepted; add others with--allow-origins. Clients that send noOriginheader (normal MCP clients) are unaffected. - Set
MCP_OPENAPI_HTTP_AUTH_TOKENto requireAuthorization: Bearer <token>on/mcp,/sse, and/messages. Comparison is timing-safe./healthand/metricsstay open. - Outbound calls can be restricted with
--allow-hosts,--allow-methods,--allow-path-prefixes, and tool name patterns (--allow-tools,--deny-tools,*wildcard). --policy-webhook <url>POSTs{tool, method, path, input, tags}before each call and blocks unless the webhook answers{"allow": true}. Webhook errors block the call. Decisions are cached for 30 s per tool.- Values under keys containing
authorization,token,password, orsecretare replaced with[REDACTED]in MCP logging notifications. - Responses larger than
--max-response-bytes(default 2 MB) are rejected. Concurrent tool calls are capped by--max-concurrency(default 8).
Auth is injected from environment variables based on the spec's securitySchemes:
| Scheme | Env vars |
|---|---|
| Any scheme, by name | MCP_OPENAPI_<SCHEME_NAME>_TOKEN |
API key (in: header|query|cookie) |
MCP_OPENAPI_API_KEY |
| HTTP Bearer | MCP_OPENAPI_BEARER_TOKEN |
| HTTP Basic | MCP_OPENAPI_BASIC_USERNAME, MCP_OPENAPI_BASIC_PASSWORD |
| OAuth2 / OIDC, static token | MCP_OPENAPI_OAUTH2_ACCESS_TOKEN |
| OAuth2 client credentials | MCP_OPENAPI_OAUTH2_CLIENT_ID, MCP_OPENAPI_OAUTH2_CLIENT_SECRET (token fetched from the scheme's tokenUrl and cached until expiry) |
--auth-scope tag=PREFIX maps operations with a given OpenAPI tag to a different env prefix, e.g. --auth-scope governance=GOV makes governance-tagged operations read GOV_BEARER_TOKEN.
Every tool whose operation has query parameters accepts a pagination argument:
{ "pagination": { "enabled": true, "mode": "autoCursor", "maxPages": 5, "cursorParam": "cursor", "nextCursorPath": "next_cursor" } }autoCursor follows a cursor field in the response body; incrementPage increments a page number until an empty page. Page bodies are merged (arrays concatenated, items arrays merged) and the result reports pagesFetched and why fetching stopped.
mcp-openapi --spec <openapi-file> [options]
mcp-openapi init [dir]
mcp-openapi generate --spec <openapi-file> [--out-dir ./generated]
| Flag | Default | Purpose |
|---|---|---|
--spec [name=]<file> |
required, repeatable | OpenAPI 3.x file, YAML or JSON; multiple specs prefix tool names |
--server-url <url> |
spec servers[0] |
Override upstream base URL (single spec only) |
--transport <t> |
stdio |
stdio, streamable-http, or sse |
--port <n> |
3000 |
Web transport port |
--host <addr> |
127.0.0.1 |
Web transport bind address |
--allow-origins o1,o2 |
localhost only | Extra allowed Origin values |
--strict |
off | Fail on lint errors (missing operationIds, etc.) |
--validate-spec |
off | Compile, report tool count, exit |
--print-tools |
off | List tool names, exit |
--watch-spec |
off | Recompile on spec file change |
--tool-name-template <t> |
{operationId} |
Placeholders: {operationId}, {method}, {path}, {tag}, {service} |
--tool-name-separator <c> |
_ |
Separator used in generated names |
--descriptions <file> |
— | JSON/YAML map of operationId → description |
--auth-scope tag=PREFIX,… |
— | Per-tag env prefix for upstream auth |
--policy-webhook <url> |
— | Pre-call policy check, fail-closed |
--allow-hosts h1,h2 |
all | Upstream host allowlist |
--allow-tools p1,p2 / --deny-tools p1,p2 |
— | Tool name patterns, * wildcard |
--allow-methods GET,POST |
all | HTTP method allowlist |
--allow-path-prefixes /v1 |
all | Path prefix allowlist |
--timeout-ms <ms> |
20000 |
Per-request timeout |
--retries <n> |
2 |
Retries on 408/429/5xx and network errors, honors Retry-After |
--retry-delay-ms <ms> |
500 |
Base retry delay (multiplied by attempt) |
--max-response-bytes <n> |
2000000 |
Response size cap |
--max-concurrency <n> |
8 |
Concurrent tool call cap |
--response-transform <module> |
— | JS module transforming response bodies |
--cache-path <file> |
.cache/mcp-openapi-cache.json |
Compiled-operation cache |
--sse-max-sessions <n> |
100 |
SSE session cap |
--sse-session-ttl-ms <ms> |
300000 |
SSE session TTL |
--version |
— | Print version, exit |
Unknown flags are an error.
Response transform module:
export default function transform({ operation, response }) {
return { ...response.body, transformedBy: operation.operationId };
}/metrics serves Prometheus text format: mcp_openapi_build_info{version}, mcp_openapi_uptime_seconds, mcp_openapi_tool_calls_total, _failed_total, _cancelled_total, _in_flight, _by_status_total{status}, mcp_openapi_retries_total, mcp_openapi_tool_call_latency_avg_ms, and a latency histogram mcp_openapi_tool_call_latency_ms_bucket. Tool call start/completion and retry events are also emitted as MCP logging notifications.
import { parseSpec, generateToolsWithTags } from "@evalops/mcp-openapi";
const spec = await parseSpec("./openapi.yaml");
const { tools } = generateToolsWithTags(spec, { prefix: "github" });Exports: parseSpec, generateTools, generateToolsWithTags, and the NormalizedSpec types.
mcp-openapi init [dir]writes a starter project:package.json,tsconfig.json,src/server.ts,.env.example,README.md,Dockerfile, and agate/directory with a Gate connector config.mcp-openapi generate --spec … --out-dir …writes the same skeleton pinned to your spec, plus a Gate Rego policy allowlisting the compiled tool names.
npm ci
npm run check # tsc --noEmit
npm test # node:test suite
npm run smoke # build + end-to-end stdio smoke
npm run mcp:inspect # MCP Inspector tools/list against the sample specMIT