Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

41 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mcp-openapi

CI License: 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:

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

Transports

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

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:

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.

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]
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 };
}

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";

const spec = await parseSpec("./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

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages