openapi-mcp
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@openapi-mcpList all available pets in the petstore"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
openapi-mcp
Turn any OpenAPI spec into a strict, locally running MCP server.
openapi-mcp is a runtime wrapper, not a code generator: there is no codegen step, no
deployment, no per-API project scaffolding. Point it at an OpenAPI 3.0, 3.1 or 3.2 spec (a URL
or a local file) and every allowed operation becomes a validated MCP
tool that hosts like Claude Code and Codex can call directly.
Four pillars:
Strict — hard-fail on invalid specs, strict schema validation at the tool boundary, closed input schemas, deny-wins filtering, read-only by default, fail-closed refresh.
Secure — credentials never appear in argv, tool schemas, tool results, logs, or cache. Secrets are read exclusively from environment variables, with central redaction.
Zero own config — all configuration flows through CLI flags and env vars, i.e. through your MCP host's own config (
.mcp.jsonfor Claude Code,config.tomlfor Codex).~/.kaisers-io/openapi-mcp/holds only caches (the spec cache and Node's compile cache); there is no separate config file to maintain.Context-efficient — filtered tool exposure, concise AI-oriented descriptions, response size caps.
Trust the spec source.
openapi-mcpinterprets the OpenAPI document you give it at runtime — operation names, descriptions, parameters, and examples all flow into the model's context. A hostile or compromised spec is an injection surface: this tool sanitizes and caps what it renders, but it cannot semantically filter hostile prose out of a spec you chose to trust. Only point it at a spec source you trust. As a baseline, the server is read-only by default (onlyGET/HEAD/QUERYoperations are exposed unless you pass--allow), and spec updates fetched at runtime are gated behind an explicitcache promote(or--trust-spec-updatesfor specs you own) rather than applied silently — see Caching & refresh below.
Install
Requirements: Node.js ^22.19.0 || >=24.11.0, macOS, Linux or Windows. Use the latest patch release of your Node line:
older patches miss its security fixes. On Windows the cache directories are private through your user profile's
access control list, not through 0700/0600 modes (see Windows). Other
platforms (e.g. FreeBSD) are refused because they are untested, not because they are known to be unsafe.
npm i -g @kaisers-io/openapi-mcpThen verify the install:
openapi-mcp --version
openapi-mcp doctoropenapi-mcp doctor prints CLI/Node versions and the effective cache root — a good first
check when something looks wrong.
The first run per user is slower: it writes Node's V8 compile cache to
~/.kaisers-io/openapi-mcp/compile-cache/, which later starts reuse. Set
NODE_DISABLE_COMPILE_CACHE=1 to turn it off, e.g. where the home directory does not persist.
Related MCP server: safe-oas2mcp
Quickstart
Preview the tools a spec would generate, without starting a server:
openapi-mcp tools --spec https://api.example.com/openapi.jsonThen run it as an MCP server over stdio. With no --allow, only GET/HEAD/QUERY
operations are exposed:
openapi-mcp serve --spec https://api.example.com/openapi.jsonIf the spec has no absolute servers URL, add --base-url <url>.
Every command accepts --json for a stable, machine-readable envelope and --verbose for
stack traces on error. Run openapi-mcp --help or openapi-mcp <command> --help — the CLI's
own help is the authoritative, always-current reference for every flag and default.
Use as an MCP server
Secrets are supplied through environment variables only, never through argv or a committed
config file. The examples keep the read-only default (no --allow); see
Filtering to expose more. Host examples below always pin an exact version (never latest) — an unpinned
version executing with your credentials on every launch is a supply-chain risk; see
Upgrading.
Claude Code — stdio (.mcp.json)
Claude Code expands ${VAR} / ${VAR:-default} in command, args, and env, so secrets
never appear literally in the committed file:
{
"mcpServers": {
"petstore": {
"command": "npx",
"args": [
"-y",
"@kaisers-io/openapi-mcp@0.4.1",
"serve",
"--spec",
"https://api.example.com/openapi.json",
"--spec-header-env",
"Authorization=SPEC_TOKEN",
"--header-env",
"Authorization=API_TOKEN",
"--cache-key",
"petstore",
"--ttl",
"1h"
],
"env": {
"SPEC_TOKEN": "${SPEC_TOKEN}",
"API_TOKEN": "${API_TOKEN}"
}
}
}
}Codex CLI — stdio (config.toml)
Codex's config.toml does not expand ${VAR}: export the secrets in the shell you
launch Codex from, then whitelist their names with env_vars so Codex forwards them from its
own environment:
# ~/.codex/config.toml (or project-scoped .codex/config.toml)
[mcp_servers.petstore]
command = "npx"
args = [
"-y",
"@kaisers-io/openapi-mcp@0.4.1",
"serve",
"--spec",
"https://api.example.com/openapi.json",
"--spec-header-env",
"Authorization=SPEC_TOKEN",
"--header-env",
"Authorization=API_TOKEN",
"--cache-key",
"petstore",
"--ttl",
"1h",
]
env_vars = ["SPEC_TOKEN", "API_TOKEN"]Windows hosts
Native Windows support starts with 0.4.0; earlier versions refuse to install on Windows.
On Windows, npx (and a globally installed openapi-mcp) is a .cmd script, not an
executable. Hosts that resolve it themselves take the configs above unchanged — Claude
Desktop's own Windows example uses "command": "npx". A host that starts the command directly,
without a shell, cannot run a .cmd file: the server then never starts, and the host log shows
ENOENT, EINVAL (Node refuses to spawn .cmd files without a shell since its April 2024
security release) or just "client closed". Start it through cmd /c instead:
{
"mcpServers": {
"petstore": {
"command": "cmd",
"args": [
"/c",
"npx",
"-y",
"@kaisers-io/openapi-mcp@0.4.1",
"serve",
"--spec",
"C:\\specs\\petstore.json",
"--cache-key",
"petstore"
]
}
}
}Backslashes are doubled in JSON.
--spectakes absolute drive paths (C:\specs\x.json,C:/specs/x.json) and UNC paths (\\server\share\x.json); a drive-relativeC:x.jsonis refused like any other scheme-like value; give the full path.cmdtreats&,|,<,>,^and%in arguments as its own syntax: keep them out ofargs. Secrets belong inenvanyway.Config files: Claude Desktop
%APPDATA%\Claude\claude_desktop_config.json(mcpServers), VS Code.vscode/mcp.json(servers, each with"type": "stdio"), Cursor%USERPROFILE%\.cursor\mcp.json(mcpServers). GUI hosts may not see thePATHof your terminal; ifnpxis not found, give its full path, or install globally and runnodewith the full path of the installedbin\openapi-mcp.mjs(under%APPDATA%\npm\node_modules).The host stops the server by closing its stdin, which
openapi-mcptreats as a clean shutdown on every platform.
HTTP transport (both hosts)
Run the server once with --http, then point either host at it as a remote MCP server. See
HTTP transport below for the flags.
export TOKEN=$(openssl rand -base64 32)
npx -y @kaisers-io/openapi-mcp@0.4.1 serve --spec https://api.example.com/openapi.json \
--http --http-token-env TOKEN --port 3044Claude Code (.mcp.json):
{
"mcpServers": {
"petstore": {
"type": "http",
"url": "http://127.0.0.1:3044/mcp",
"headers": { "Authorization": "Bearer ${TOKEN}" }
}
}
}Codex (config.toml):
[mcp_servers.petstore_http]
url = "http://127.0.0.1:3044/mcp"
bearer_token_env_var = "TOKEN"Run one openapi-mcp server per API per environment — dev/stage/prod are separate host
entries with different spec URLs, credentials, and --cache-keys, not flags on a shared
server.
A refused call (--max-concurrency queue full or wait timed out, or over --rate-limit) is a
tool error whose result _meta carries "openapi-mcp/error" (overloaded | rate_limited)
and "openapi-mcp/retryAfterMs". Keys under openapi-mcp/ in _meta are part of the public
contract; see docs/configuration.md.
Filtering
Tool exposure is controlled with repeatable --allow/--deny patterns, matched against
three forms (case-sensitive, * for a single path segment, ** for a deep match):
readPets,read*— operationId.GET /users/**—METHOD /path(one space between method and path). The method is upper case: a standard one, or one the spec declares under OpenAPI 3.2additionalOperations(such asLINK); any other method is a startup error. So if a spec update removes the last operation with such a method, a pattern naming it stops the next start until you remove it.tag:admin*— OpenAPI tags.
Rules, no exceptions:
--denyalways wins over--allow.Any
--allowgiven replaces the default entirely: only matching, non-denied operations are exposed (including write operations present at startup).No
--allowgiven: read-only default — onlyGET/HEADoperations, and OpenAPI 3.2QUERYoperations, a read with a request body (minus denied ones).
Pass --strict-filters to turn a dead pattern (one that matches nothing) into a startup
error instead of a warning — useful once a filter set is stable and you want drift to fail
loudly. Full flag reference: docs/configuration.md.
Patterns match the spec's raw operationId, path and tags, not the generated tool name. A tool
name is the operationId reduced to [A-Za-z0-9_-] (at most 64 characters; repos.get becomes
repos_get, accented letters become their base letter), or method and path when there is no usable operationId
(GET /users/{id} becomes get_users_by_id, and operation_<hash> when even those leave no
letter or digit). Colliding names get a stable hash suffix.
Large APIs: --tool-mode discover
By default every exposed operation is its own MCP tool, and every tool's schemas are sent to
the model up front — about 30k tokens for DigitalOcean's read-only operations. With
--tool-mode discover the server lists three tools instead — search_operations,
describe_operation, call_operation — and tools/list stays under 5 KB however many
operations are exposed (up to 4096). Filters, validation and redaction apply exactly as for a
direct call. --tool-mode auto picks discover above 64 exposed operations. Details and
trade-offs: Tool modes.
Secrets
Secrets are read exclusively from environment variables named by --spec-header-env
(spec fetch), --header-env and --upstream-oauth-client-id-env/--upstream-oauth-client-secret-env (API
requests; the latter for OAuth2 client credentials), and --http-token-env (HTTP transport) — never
from argv, config files, or the model context:
--spec-header-env Header=ENV/--header-env Header=ENV— equals syntax; the value comes from the named env var.--header Header:value— colon syntax, for static, non-secret headers only.--headerrejects sensitive header names (Authorization,Proxy-Authorization,Cookie,Set-Cookie, and any name containingauth,token,secret,key,session,credential,signature,password,bearerorjwt) — those must use--header-env.
For safe terminal testing, avoid leaving secrets in shell history: prefix the command with a
space if your shell has HIST_IGNORE_SPACE/HISTCONTROL=ignorespace enabled, use read -s
to prompt without echoing, or pull the value from a secret-manager CLI (e.g.
TOKEN=$(op read op://vault/item/token) openapi-mcp serve ...).
Outbound requests follow an egress policy: an address that comes from the spec (its servers
URL) or from a redirect is refused when it is private, loopback or a cloud-metadata endpoint.
--allow-private-network permits private and loopback addresses, never cloud metadata. A
--spec or --base-url you pass yourself may be private or loopback without that flag;
cloud-metadata addresses are refused everywhere.
See docs/security.md for the full threat model.
Caching & refresh
Specs are cached under ~/.kaisers-io/openapi-mcp/cache/<namespace>/ and re-validated at
startup once the TTL has expired (--ttl, default 1h), using conditional requests
(ETag/Last-Modified). A local file is read and hashed on every start; while its bytes match
the accepted baseline, the cached, already validated document is used instead of parsing the
file again. A running server does not refresh its tool list; restart it to pick up a newer
spec.
Refresh is fail-closed: the cache holds an accepted baseline and, separately, at most one
pending candidate. A changed spec fetched at runtime is validated and written as a candidate
only — it never activates automatically. The only automatic change is a removal: while
the candidate waits, an accepted operation that the candidate no longer has (same method and
path) is withheld, on every start, and listed in a warning and in openapi_mcp_status. That
is the fail-safe direction. Operations present in both keep their accepted definition. Paths
are compared with parameter names ignored (/pets/{petId} and /pets/{id} are the same
operation), and servers is not compared: a candidate that only moves the base path (say
/v1 to /v2) withholds nothing, and the move waits for promotion like any other change.
Everything else (new tools, changed descriptions or input schemas, method/path/base-URL
changes, auth-scheme changes) waits for an explicit decision:
openapi-mcp cache list # list entries + opaque ids
openapi-mcp cache refresh --spec <url|file> # force a refetch, write a candidate
openapi-mcp cache promote --spec <url|file> # accept the candidate as baseline
openapi-mcp cache clear --spec <url|file> # drop a cache entry
openapi-mcp cache unlock --spec <url|file> # remove a stale writer lockPass --trust-spec-updates to auto-accept refreshed specs instead (appropriate for an API you
own and control, not a third-party one).
--cache-key <name> is mandatory for any spec fetched with --spec-header-env
(authenticated), unless you pass --trust-spec-updates instead — two host entries hitting
the same URL with different credentials must never share cache state, and a durable baseline
is what makes the gated-refresh policy meaningful across restarts. Omitting both is a startup
usage error.
Full flag reference, including duration and size grammar: docs/configuration.md.
HTTP transport
--http starts a Streamable HTTP server bound to 127.0.0.1, instead of stdio. In the default
static mode, --http-token-env <ENV> is mandatory with --http — the CLI refuses to start
without it, since loopback binding alone does not authenticate local callers:
export TOKEN=$(openssl rand -base64 32)
openapi-mcp serve --spec https://api.example.com/openapi.json \
--http --http-token-env TOKEN --port 3044The token must be exactly 32 bytes from a cryptographically secure random source, encoded as
padded Base64 (what openssl rand -base64 32 prints) or unpadded Base64URL; any other shape,
including hex, is rejected at startup. Exact rules and an openssl-free generator:
docs/security.md.
Clients must send Authorization: Bearer <token>. --port defaults to 3044.
For a shared deployment, --http-auth oauth makes the server an OAuth resource server instead:
it publishes Protected Resource Metadata and validates JWT access tokens from --oauth-issuer
for --oauth-audience. --http-host is accepted only in this mode, and a non-loopback address
also needs an https --oauth-audience. See
docs/security.md.
The server speaks MCP revision 2026-07-28 and is stateless: each request is served on its
own, with no initialize handshake and no session state (no Mcp-Session-Id). Older, 2025-era
clients are served the same stateless way. The endpoint is POST-only — GET and DELETE
return 405. Cancellation is the client closing the request stream.
Other options and behaviour
Each is off unless a default or "on" is stated; details in
docs/configuration.md.
--request-timeout <duration>(default30s): the budget for each upstream call, queue wait included.--timeoutis separate: it bounds the spec fetch and, with--http-auth oauth, the authorization-server metadata and JWKS fetches.--retries <n>(default0, at most3): retry 429/502/503/504 forGET/HEAD/QUERY/PUT/DELETE, and connection errors before any response for all of them butDELETE.POST/PATCHare retried only when the operation declares anIdempotency-Keyheader.Trace propagation is on by default: a valid W3C
traceparent/tracestatefrom a call's_metais forwarded upstream. Pass--no-trace-propagationfor APIs you do not operate.--log json: one JSON line per tool call on stderr, without arguments, header values or bodies.--response-projection: an optional_fieldsargument (JSON Pointers) on JSON tools, so a call returns only the selected fields. Adds about 30% totools/list.--tools-page-size <n>: pagetools/list. Only for hosts that follownextCursor; other hosts would see only the first page.--upstream-oauth-client-id-env/--upstream-oauth-client-secret-env: obtain the upstream API's access token with the OAuth2 client-credentials grant.Request bodies: JSON, form,
multipart/form-dataandapplication/octet-stream(binary as a base64 argument). Query styles:form,deepObject,spaceDelimited,pipeDelimited. Binary responses are not returned; they are a tool error.Results carry selected response headers (
ETag, same-originLocation, rate-limit headers, …) and, for paginatedGETtools, apagination.nexthint with the next call's arguments.
Exit codes: 0 success, 1 unexpected error, 2 usage error, 3 validation error (e.g. an
invalid spec or HTTP token), 4 not found (spec file, cache entry), 5 conflict (cache lock,
candidate mismatch). With --json, tools, doctor and cache print an error as
{"ok": false, "error": {"code", "message"}}. serve always reports errors as text on stderr
(stdout is reserved for MCP), and the platform and Node checks print plain text before any
option is read.
Upgrading
Host config examples above pin an exact version
(npx -y @kaisers-io/openapi-mcp@0.4.1 ...) — never latest. This process runs with your
API credentials on every launch; letting npx silently resolve to whatever was just published
is a supply-chain risk, not a convenience. To upgrade, bump the pinned version number in your
host config deliberately (and re-run npm i -g @kaisers-io/openapi-mcp if you installed it
globally instead of invoking via npx).
Development
Development is supported on Linux and macOS. Requirements, setup, what CI checks and the benchmark gate are in CONTRIBUTING.md. Report security problems privately, as SECURITY.md describes.
Documentation
docs/configuration.md— every flag, grouped by command, with its meaning, default, and an example.docs/overlays.md— correcting one detail of a spec you cannot change with an OpenAPI Overlay, step by step and checked.docs/security.md— the threat model: untrusted spec text, the egress trust boundary, credential hygiene, and HTTP-mode authentication.
License
MIT
This server cannot be deployed
Maintenance
Related MCP Connectors
- TypeshipOAuthdev.typeship
Generate a typed SDK, CLI, and MCP server from any OpenAPI or GraphQL spec, and keep them current.
Point Gecko at an OpenAPI spec; get first-call-correct, auth-hidden agent tools.
Remote MCP for 1,500+ APIs. Vault-managed credentials; OAuth or API key. Search, load, and execute.
Governed MCP gateway: one endpoint for your tools, with credential custody and audit log.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceGenerates safe-by-default MCP servers from OpenAPI specs, exposing only low-risk GET tools and blocking write operations.5MIT
- FlicenseNot gradedqualityDmaintenanceA safe gateway to convert OpenAPI specs into MCP tools with secure defaults, risk inspection, and confirmation gates.-
- FlicenseNot gradedqualityDmaintenanceTurns OpenAPI specs into MCP tools with secure defaults, risk inspection, confirmation gates, response limits, audit logging, and secret redaction.-
- AlicenseNot gradedqualityDmaintenanceConvert any OpenAPI spec into a secure MCP server with scoped auth, per-tool allow/deny policies, rate limiting, and a redacted audit trail.10 npmMIT