From 2e2917f9767b97aef461d19c4663f4021722b7c2 Mon Sep 17 00:00:00 2001 From: Jonathan Haas Date: Mon, 3 Aug 2026 19:49:42 -0700 Subject: [PATCH] Rewrite README: quickstart, security model, full CLI reference - npx/Claude Code/Claude Desktop quickstart snippets - Warning that the npm package named mcp-openapi is an unrelated project - Security model section: default 127.0.0.1 binding, Origin validation, MCP_OPENAPI_HTTP_AUTH_TOKEN, outbound allowlists, policy webhook, log redaction, size/concurrency caps - Complete flag table including previously undocumented flags (--descriptions, --auth-scope, --policy-webhook, --tool-name-separator, --host, --allow-origins, {service} placeholder) - Operation-to-tool mapping, pagination, auth env var table, metrics list Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01LqBsGC7xLvihtBxhCdWKz5 --- README.md | 270 +++++++++++++++++++++++++++--------------------------- 1 file changed, 133 insertions(+), 137 deletions(-) diff --git a/README.md b/README.md index bae4397..64a0ad1 100644 --- a/README.md +++ b/README.md @@ -1,158 +1,142 @@ # mcp-openapi -OpenAPI 3.x to MCP server bridge in TypeScript. - -`mcp-openapi` takes an OpenAPI spec and turns it into an MCP server where each OpenAPI operation is an MCP tool. Tool calls are proxied to the original REST API with runtime validation and auth handling. - -## Capabilities - -- OpenAPI 3.0+ support (YAML/JSON, `$ref` dereference, operation compilation) -- Proxy behavior to upstream REST API -- Authentication via env vars: - - API keys (`in: header|query|cookie`) - - HTTP Bearer - - HTTP Basic - - OAuth2 / OpenID Connect (static token or client credentials token fetch) -- Runtime validation: - - Zod validation generated from OpenAPI-derived JSON Schema - - AJV JSON Schema validation - - Response schema validation by HTTP status -- Typed TypeScript implementation -- Strict lint mode for OpenAPI quality gates (`--strict`) -- Configurable tool naming template (`--tool-name-template`) -- Policy engine: - - allow/deny tool patterns - - allow methods/path prefixes - - allow hosts -- Optional response transform hook (`--response-transform `) -- Multiple transports: - - `stdio` - - `streamable-http` (Hono) - - `sse` (legacy compatibility transport) -- Transport hardening: - - graceful shutdown - - SSE session caps/TTL -- Observability: - - Prometheus metrics - - status counters - - latency histogram buckets -- Built-in browser test clients: - - `/test/streamable` - - `/test/sse` -- Project scaffold (`init`) that generates: - - `package.json` - - `tsconfig.json` - - `src/server.ts` - - `.env.example` - - `README.md` - - `Dockerfile` - -## Install +[![CI](https://github.com/evalops/mcp-openapi/actions/workflows/ci.yml/badge.svg)](https://github.com/evalops/mcp-openapi/actions/workflows/ci.yml) +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) + +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. + +> **Install from GitHub.** The `mcp-openapi` package on the npm registry is an unrelated third-party project. This project is installed as `github:evalops/mcp-openapi`. + +## Quickstart + +Run against any OpenAPI file, no install step: ```bash -npm install +npx -y github:evalops/mcp-openapi --spec ./openapi.yaml ``` -Consume as a library from GitHub: +Add to Claude Code: ```bash -npm install github:evalops/mcp-openapi +claude mcp add my-api -- npx -y github:evalops/mcp-openapi --spec /abs/path/openapi.yaml ``` -## Library Usage +Claude Desktop (`claude_desktop_config.json`): -```ts -import { parseSpec, generateToolsWithTags } from "mcp-openapi"; +```json +{ + "mcpServers": { + "my-api": { + "command": "npx", + "args": ["-y", "github:evalops/mcp-openapi", "--spec", "/abs/path/openapi.yaml"] + } + } +} +``` -const normalized = await parseSpec("./openapi.yaml"); -const generated = generateToolsWithTags(normalized, { prefix: "github" }); +HTTP transport instead of stdio: -console.log(generated.tools[0]?.name); +```bash +npx -y github:evalops/mcp-openapi --spec ./openapi.yaml --transport streamable-http --port 3000 +# MCP endpoint: http://127.0.0.1:3000/mcp ``` -The library entrypoint exports: +## How operations map to tools -- `parseSpec` -- `generateTools` -- `generateToolsWithTags` -- `NormalizedSpec` +- 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`. -## Run +## Transports -### stdio +| 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` | -```bash -npm run dev -- --spec ./openapi.yaml -``` +## Security model -### StreamableHTTP +- 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 ` 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 ` 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). -```bash -npm run dev -- --spec ./openapi.yaml --transport streamable-http --port 3000 -``` +## Upstream authentication -Endpoints: +Auth is injected from environment variables based on the spec's `securitySchemes`: -- `http://localhost:3000/health` -- `http://localhost:3000/metrics` -- `http://localhost:3000/mcp` -- `http://localhost:3000/test/streamable` +| Scheme | Env vars | +|---|---| +| Any scheme, by name | `MCP_OPENAPI__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) | -### SSE (legacy) +`--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`. -```bash -npm run dev -- --spec ./openapi.yaml --transport sse --port 3000 -``` +## Pagination -Endpoints: +Every tool whose operation has query parameters accepts a `pagination` argument: -- `http://localhost:3000/health` -- `http://localhost:3000/metrics` -- `http://localhost:3000/sse` -- `http://localhost:3000/messages?sessionId=...` -- `http://localhost:3000/test/sse` +```json +{ "pagination": { "enabled": true, "mode": "autoCursor", "maxPages": 5, "cursorParam": "cursor", "nextCursorPath": "next_cursor" } } +``` -## CLI +`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. -```bash +## CLI reference + +``` mcp-openapi --spec [options] mcp-openapi init [dir] mcp-openapi generate --spec [--out-dir ./generated] ``` -Options: - -- `--server-url ` -- `--cache-path ` -- `--out-dir ` -- `--strict` -- `--tool-name-template