Skip to content

Repository files navigation

mcp-openapi

CILicense: MIT

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 unscoped mcp-openapi package 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.

Quickstart

Run against any OpenAPI file, no install step:

npx -y github:evalops/mcp-openapi --spec ./openapi.yaml

Add to Claude Code:

claude mcp add my-api -- npx -y github:evalops/mcp-openapi --spec /abs/path/openapi.yaml

Claude 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

Multiple specs

--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[].

How operations map to tools

  • One MCP tool per OpenAPI operation. Tool name defaults to operationId; missing IDs fall back to method_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/OPTIONS are marked readOnlyHint, PUT/DELETE idempotent + destructive, POST/PATCH destructive.
  • Inputs are validated twice (Zod and AJV) before any network call. Responses are validated against the per-status response schemas; validation failures set isError and include the issue list.
  • Successful responses are returned as structuredContent plus a JSON text block with status, headers (allowlisted subset), attempt count, and validation results.
  • tools/list is cursor-paginated at 50 tools per page and emits listChanged when --watch-spec reloads the spec.
  • x-mcp-hidden: true on an operation removes it. x-mcp-description overrides the tool description, then --descriptions file entries, then summary/description.

MCP resources

The server exposes two read-only resources per loaded spec, so clients can introspect the API without extra tooling:

URIContent
openapi://<spec-name>/specThe 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).

Transports

TransportFlagEndpoints
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

Security model

  • Web transports bind 127.0.0.1 by default. Set --host 0.0.0.0 to expose beyond the local machine.
  • The Origin header is validated on /mcp, /sse, and /messages to block DNS-rebinding from browsers. Localhost origins are always accepted; add others with --allow-origins. Clients that send no Origin header (normal MCP clients) are unaffected.
  • Set MCP_OPENAPI_HTTP_AUTH_TOKEN to require Authorization: Bearer <token> on /mcp, /sse, and /messages. Comparison is timing-safe. /health and /metrics stay 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, or secret are 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).

Upstream authentication

Auth is injected from environment variables based on the spec's securitySchemes:

SchemeEnv vars
Any scheme, by nameMCP_OPENAPI_<SCHEME_NAME>_TOKEN
API key (in: header|query|cookie)MCP_OPENAPI_API_KEY
HTTP BearerMCP_OPENAPI_BEARER_TOKEN
HTTP BasicMCP_OPENAPI_BASIC_USERNAME, MCP_OPENAPI_BASIC_PASSWORD
OAuth2 / OIDC, static tokenMCP_OPENAPI_OAUTH2_ACCESS_TOKEN
OAuth2 client credentialsMCP_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.

Pagination

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.

CLI reference

mcp-openapi --spec <openapi-file> [options]
mcp-openapi init [dir]
mcp-openapi generate --spec <openapi-file> [--out-dir ./generated]
FlagDefaultPurpose
--spec [name=]<file>required, repeatableOpenAPI 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>stdiostdio, streamable-http, or sse
--port <n>3000Web transport port
--host <addr>127.0.0.1Web transport bind address
--allow-origins o1,o2localhost onlyExtra allowed Origin values
--strictoffFail on lint errors (missing operationIds, etc.)
--validate-specoffCompile, report tool count, exit
--print-toolsoffList tool names, exit
--watch-specoffRecompile 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,h2allUpstream host allowlist
--allow-tools p1,p2 / --deny-tools p1,p2Tool name patterns, * wildcard
--allow-methods GET,POSTallHTTP method allowlist
--allow-path-prefixes /v1allPath prefix allowlist
--timeout-ms <ms>20000Per-request timeout
--retries <n>2Retries on 408/429/5xx and network errors, honors Retry-After
--retry-delay-ms <ms>500Base retry delay (multiplied by attempt)
--max-response-bytes <n>2000000Response size cap
--max-concurrency <n>8Concurrent tool call cap
--response-transform <module>JS module transforming response bodies
--cache-path <file>.cache/mcp-openapi-cache.jsonCompiled-operation cache
--sse-max-sessions <n>100SSE session cap
--sse-session-ttl-ms <ms>300000SSE session TTL
--versionPrint version, exit

Unknown flags are an error.

Response transform module:

exportdefaultfunctiontransform({ operation, response }){return{ ...response.body,transformedBy: operation.operationId};}

Observability

/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.

Library usage

import{parseSpec,generateToolsWithTags}from"@evalops/mcp-openapi";constspec=awaitparseSpec("./openapi.yaml");const{ tools }=generateToolsWithTags(spec,{prefix: "github"});

Exports: parseSpec, generateTools, generateToolsWithTags, and the NormalizedSpec types.

Scaffolding

  • mcp-openapi init [dir] writes a starter project: package.json, tsconfig.json, src/server.ts, .env.example, README.md, Dockerfile, and a gate/ 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.

Development

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 spec

License

MIT

About

OpenAPI 3.x to MCP server bridge in TypeScript with stdio, StreamableHTTP, and SSE transports

Topics

Resources

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages