Focused, production-ready CLI: turn any OpenAPI REST API into an MCP server.
English | Türkçe
Turn any OpenAPI REST API into an MCP server — so Claude Code, Cursor, and every other MCP client can call your API directly. One command, zero runtime dependencies:
mcpify serve https://your-company.com/openapi.json
# try it right now, nothing installed (uvx pulls from PyPI on demand)
uvx --from mcpify-openapi mcpify list examples/petstore.json --cost
Focused, production-ready, CLI-first: one job (OpenAPI → MCP). Everything else — governance, credentials, token economics, operations — is opt-in and stays out of the way until you need it:
--lazy), secret masking (--redact), blast-radius limits (--read-only, per-token RBAC)From spec to server
parameters + requestBody, internal $refs are resolvedmcpify diff old.yaml new.yaml reports added/removed/changed operations with per-change breaking verdicts and a migration guide; --fail-on-breaking is a CI gatemcpify doctor — tells you if your spec is agent-friendly before you ship: missing operationIds, missing summaries, instruction-like tool text, overlong descriptions; --probe dials the API once — with your real credential (--auth-env) when you want auth proven end-to-end, and --fail-on-http-error for a strict CI gatemcpify try / mcpify mock / mcpify output-server — call the tools without an agent client, serve a schema-shaped fake API for CI, or bake a serve command into a shareable scriptCredentials & policy
--write-oauth2-* gives non-GET calls a second client identity so reads and writes authenticate as different clients--write-auth-env splits the static credential (reads on your read key, writes on a dedicated key), --read-only filters the surface, --deny/--allow hides mutating GETs, per-token RBAC gives each bearer token its own allow/deny scopes--redact password,token — values whose key names a secret are masked with *** at every level of every response (error bodies included, case-insensitive); the model never sees them--plugin loads your Python module for auth/request/result hooksToken economics
mcpify list --cost prices the surface (~4 chars/token): what every agent pays in EVERY tools/list; multi-API configs get per-API and total prices in one run--fields id,event — response projection that selects at every level: selected keys keep their value, non-selected containers stay transparent, emptied containers drop. Live weather.gov: 350 alerts in full inside the budget that previously truncated at ~189"truncated": true marker, never mid-document--lazy search-then-call — cut api.weather.gov's listing by 95.5%; search results now show what pulling each full schema would cost, so the agent pulls only what it needs[tool-text] overrides — doctor flags model-facing instruction-like descriptions; you replace them per tool in configOperations
serve --http 8080 speaks MCP Streamable HTTP (SSE responses for clients that ask, JSON otherwise) so a whole team shares one server, with optional bearer tokens[apis.NAME] sections in .mcpify.toml: per-API auth, caching, retries, filters and rate limits; collision-safe renames; aggregated health; mcpify status probes every API in parallel--wait-on-429 honors Retry-After, --rate-limit RPS caps requests/second (per upstream in multi-API, retries included)--metrics (call counters, latencies, cache, health — plus projection/redaction counters when those run), --otel spans, --reload hot swap, mcpify ui local dashboardpip install 'mcpify[yaml]'# install (installs the `mcpify` command)
pipx install mcpify-openapi
# run without installing (uvx — pulls from PyPI on demand)
uvx --from mcpify-openapi mcpify list ./openapi.json --read-only
# first time? the wizard writes a config for you
uvx --from mcpify-openapi mcpify init
# ...as a container (GHCR, published on every release)
docker run -i ghcr.io/furkan708/mcpify:latest serve ./openapi.json --read-only
# ...or from source
git clone https://github.com/furkan708/mcpify.git
cd mcpify && pip install .
# 1. preview the tools that will be generated (add --cost for the context price)
mcpify list examples/petstore.json
# 2. validate the spec is agent-friendly (+ --probe for a live pre-flight)
mcpify doctor examples/petstore.json
# 3. serve it over MCP
mcpify serve examples/petstore.json --base-url https://petstore.example.com/v1
# 4. no agent client at hand? try the tools in your terminal
mcpify try examples/petstore.json --base-url https://petstore.example.com/v1
# 5. or share it over HTTP with the whole team
mcpify serve examples/petstore.json --http 8080 --http-token $SHARED_TOKEN
# Bearer token read from the environment (never hardcoded)
export PETSTORE_KEY="sk-..."
mcpify serve petstore.json \
--base-url https://petstore.example.com/v1 \
--auth-env PETSTORE_KEY \
--auth-style bearer \ # optional: auto-detected from the spec
--read-only
No explicit style needed in the common case — the spec's security
declarations pick bearer/basic/header/query (with the right name) for
you. For HTTP Basic, the env variable holds username:password:
--auth-style basic --auth-env CREDS.
| Flag | Meaning |
|---|---|
--auth-env VAR | environment variable holding the credential |
--auth-style bearer|basic|header|query | how it is sent (default: auto-detected from the spec) |
--auth-name NAME | header / query name for non-bearer styles (e.g. X-API-Key) |
For APIs behind an OAuth2 identity provider (RFC 6749 §4.4). Credentials
live in the environment; the access token is fetched, cached until its
expires_in, refreshed transparently, and re-fetched automatically once
if the API answers 401 mid-flight:
export OAUTH2_CLIENT_ID="..."
export OAUTH2_CLIENT_SECRET="..."
mcpify serve api.json \
--oauth2-token-url https://idp.example.com/oauth2/token \
--oauth2-client-id-env OAUTH2_CLIENT_ID \
--oauth2-client-secret-env OAUTH2_CLIENT_SECRET \
--oauth2-scope "read write" # optional; --oauth2-client-auth body for token endpoints that reject Basic
Split write identities too: --write-oauth2-token-url (+ client/scope
flags) runs a second client-credentials flow for non-GET calls — reads
authenticate as the read client, writes as the write client, each with
its own token cache and the same 401 self-heal. Mutually exclusive with
--write-auth-env (pick one credential kind for writes).
Put several OpenAPI documents in one config and serve them as a single tool surface — no gateway, no per-API process:
# .mcpify.toml
[apis.catalog]
spec = "https://shop.example.com/openapi.json"
auth-env = "CATALOG_TOKEN" # per-API credential
cache-ttl = 60
rate-limit = 5 # per-API courtesy throttle (req/s)
redact = "password,client_secret" # per-API response masking
[apis.crm]
spec = "./crm.yaml"
read-only = true # per-API policy
base-url = "https://crm.internal/v2"
fields = "id,name" # per-API response projection
[apis.weather]
spec = "https://api.weather.gov/openapi.json"
timeout = 10
Surface switches (--lazy, --enable-preview, --http, --format) are
server-wide flags; credentials, policies, caching and retries are per-API.
mcpify list --cost # preview every API and price each surface
mcpify serve # stdio, all three APIs, prefixed on collisions
mcpify serve --http 8080
mcpify try # REPL across every API
mcpify status # probes each API concurrently
mcpify status reports per API — [catalog] reachable (status 200, 0.03s) — https://shop.example.com — 31 tools — and exits non-zero if any API is
unreachable. When two APIs expose the same tool name (list_pets), both
get renamed with their label (catalog_list_pets, crm_list_pets) so
nothing silently wins; non-conflicting names stay untouched. The
mcpify_health tool returns one report covering every API. Precedence
per key: CLI flags > [apis.NAME] > [serve]. Pass a positional spec
or [apis.*] sections — never both.
Claude Code:
claude mcp add my-api -- mcpify serve openapi.json --read-only
Claude Desktop / Cursor / any MCP client (claude_desktop_config.json):
{
"mcpServers": {
"petstore": {
"command": "mcpify",
"args": ["serve", "~/specs/petstore.json", "--auth-env", "PETSTORE_KEY"]
}
}
}
HTTP transport (team-shared server) — run mcpify serve api.json --http 0.0.0.0:8080 --http-token $TOKEN once, then point HTTP-capable clients at it:
{
"mcpServers": {
"petstore": {
"type": "http",
"url": "http://your-host:8080",
"headers": { "Authorization": "Bearer <token>" }
}
}
}
Now ask your agent: "list the pets, then create one named Milo" — it discovers list_pets and create_pet, fills the arguments, and performs real HTTP calls.
| OpenAPI | mcpify |
|---|---|
operationId | tool name (sanitized; falls back to method_path) |
summary / description | tool description the agent reads |
deprecated: true | shown by mcpify list before you expose old endpoints |
parameters (path/query/header) | individual typed arguments with enums |
requestBody (JSON) | a body object argument |
$ref pointers | resolved inline (components → real schemas) |
servers[0].url | default base URL (override: --base-url) |
The agent only ever sees the tool list and your API's JSON responses — mcpify adds no middleware, caches nothing you did not ask for, and sends credentials nowhere except your API.
$ mcpify doctor my-api.json
openapi: 3.0.3
title: Acme API
paths: 23
tools: 41 operations
servers: https://api.acme.com
warning: 12/41 operations have no operationId (names fall back to method_path)
warning: 30/41 operations have no summary (agents see no description)
Add --probe for a live pre-flight — after the static report, mcpify
dials one argument-free GET (or the base URL) and reports reachability;
a connection failure exits non-zero so CI and shell scripts stop before
serving:
$ mcpify doctor https://api.weather.gov/openapi.json --probe
...
probe: GET /alerts → 200 reachable (2.80s)
mcpify list <spec> [--tag T] [--include P] [--exclude P] [--read-only] [--json]
mcpify list --cost # price the surface (~4 chars/token)
mcpify list --cost --lazy # ...and the 3-meta-tool lazy surface
mcpify list --config .mcpify.toml --cost # multi-API: every surface priced
mcpify serve <spec> [--base-url URL] [--server INDEX|NAME] [--name N] [--auth-env VAR]
[--auth-style bearer|header|query] [--auth-name NAME]
[--oauth2-token-url URL --oauth2-client-id-env VAR
--oauth2-client-secret-env VAR] [--timeout S]
[--read-only] [--tag T] [--include P] [--exclude P]
[--http [HOST:]PORT] [--http-token TOKEN] [--wait-on-429 SEC]
mcpify try <spec> [same serve flags] # interactive REPL, no agent needed
mcpify output-server <spec> -o FILE [-- <any serve flags>]
mcpify ui <spec> [same serve flags] # local dashboard (tool explorer, health, config)
mcpify mock <spec> [--http 8000] [--delay-ms N]
mcpify diff OLD NEW [--json] [--fail-on-breaking] # upgrade report + CI gate
mcpify diff OLD NEW --probe [--auth-env V] # ...+ live check of the NEW API + cost delta
mcpify config-schema # JSON Schema for .mcpify.toml (editor wiring)
mcpify doctor <spec> [--probe --auth-env V --fail-on-http-error] # static audit + live pre-flight / CI gate
# token economics: --fields id,name (projection), --redact password,token (masking),
# --rate-limit RPS (upstream courtesy, retries included)
# credential split: --write-auth-env WRITE_KEY_ENV or --write-oauth2-token-url (reads keep --auth-env)
# tool-text overrides: [tool-text.TOOL] description = "..." in .mcpify.toml
# multi-API: define [apis.NAME] sections in .mcpify.toml, then run
# mcpify list|serve|try|status|ui (no positional spec) — one process, every API
# ops add-ons for serve/ui: --metrics [HOST:]PORT --reload --cache-warm
# --audit-log FILE --http-token-file FILE --plugin FILE (repeatable) --otel [ENDPOINT]
pip install 'mcpify[yaml]'$ref targets (files or URLs) are bundled automatically at load; circular
cross-file refs are left in place rather than unwound (surface skips what it cannot resolve)--fields selects at every level by documented rule (selected keys verbatim,
non-selected containers transparent); it is a projection, not a security boundary —
use --redact when a field must never reach the modelbody object argument — predictable over cleverAccept: text/event-stream get it framed as a
single SSE message event. Server-initiated streams (a GET stream with sessions)
stay deliberately out of scope for a stateless servermcpify is audited on every release against a 10-category checklist of MCP best practices and published production failure modes — not just our own examples:
$refs, multipart uploads,
allOf schemas, server URL variables, relative base URLs, oversized
responses — every scenario derived from a documented real-world failure,
fixed, and locked in by a regression test. Sources include the arXiv
study of REST→MCP generation across 18 real APIs.initialize handshake.isError tool result with remediation — never a dead
stdio connection (the failure mode reported against other OpenAPI-to-MCP
servers, replayed here and locked in by regression tests).--timeout, credentials never logged.Full checklist with per-item status: docs/AUDIT-CHECKLIST.md
520 passing, plus one live-integration test that loads the real
api.weather.gov document (auto-skipped when offline) and an OTel positive
test that runs wherever the optional tracing extra is installed. Every
suite runs on Python 3.10–3.12 across Linux and Windows; ruff, strict
mypy and CodeQL gate every push.
| Suite | Tests | What it pins down |
|---|---|---|
| Spec parsing & resolution | 13 | OpenAPI 3.x + YAML loading, $ref chains, allOf merge, server variables, malformed input |
| Tool translation | 19 | operationId naming with collision suffixing, input schemas, enums, body handling, annotation & output-schema derivation |
| Agent surface | 32 | HTTP-derived annotations, structured output contract, remediation errors, --lazy search, dry-run previews |
| CLI | 15 | list / doctor / serve flags, --json output, deprecated badges |
| Hostile corpus | 11 | circular $refs, multipart bodies, relative base URLs, 300 KB truncation, 500-op performance — each traced to a documented real-world failure |
| Lifecycle & hygiene | 8 | initialize handshake (-32002), byte-pure stdio, credentials never logged |
| Protocol end-to-end | 9 | real JSON-RPC over stdio against a live local HTTP API, wire-level assertions |
| Policy layer | 7 | --read-only, --allow / --deny precedence, mutating-GET protection |
$ref parameters | 4 | parameter schemas resolved against the full spec — the weather.gov bug class (one test hits the live document) |
| Ops & configuration | 47 | config files + env precedence, init wizard, cache TTL & bounds, retry safety, XML conversion, discovery, batching, status/health |
| Protocol version compat | 5 | 2026-07-28 stateless _meta requests and the legacy 2025-06-18 handshake, on the same wire |
| HTTP transport | 19 | Streamable HTTP: lifecycle over POST, 405/411/413/415 error ladder, parse/batch rejections, bearer enforcement, bind-string parser |
| OAuth2 client-credentials | 18 | token fetch/cache/refresh with a fake clock, Basic vs body client auth, public clients, every failure mode, 401 self-heal end-to-end |
try REPL | 26 | piped-stdin sessions: selection by number/name, typed prompts, re-prompt on bad input, :raw/:info, clean EOF/Ctrl+C exits, read-only surface |
output-server | 11 | embedded spec integrity, guard rails (existing file, bad spec, unknown flags), secret warnings, and a real subprocess E2E handshake |
| Server selection | 17 | `--server INDEX |
| Auth auto-detection & Basic | 22 | securitySchemes → style/name resolution (OpenAPI + Swagger 2.0), requirement-order precedence, operation-level security, exact hint text, HTTP Basic header encoding, CLI/try/doctor wiring, explicit-style override |
Rate-limit courtesy (--wait-on-429) | 9 | Retry-After honored once within cap, cap exceeded returns 429 untouched, missing header falls back to retry delay, HTTP-date form never waits, POST never auto-waited, CLI wiring |
| Multi-API aggregation | 26 | [apis.*] merge with two-sided collision prefixes and _2 suffixes, per-API routing/auth/cache isolation, concurrent aggregated health (dead-API named in hint), lazy search across APIs incl. label match, preview routing, status exit codes, --env inheritance, both-rejected flag combos |
| Ops: dashboard, metrics, mock, reload | 25 | /metrics text format (counters/histograms/cache hit-miss/health gauges), token'd UI routes, masked preview API, config-form writer (+unknown-key 400), schema-shaped mock responses with template routing, hot-reload rebuild incl. broken-spec survival |
| CLI connectivity glue | 10 | --http wiring, MCPIFY_HTTP_TOKEN fallback, OAuth2 flag rules, config-file keys, wizard option 5, try smoke test |
Spec diff (mcpify diff) | 14 | added/removed/changed ops, breaking verdicts (required param added/became, body became required, op removal), deprecation & operationId warnings, migration guide, document-level diff, CLI exit contract 0/1/2, --json |
| v1.11 serving: audit, cache, RBAC, plugins | 17 | JSONL audit trail with argument fingerprints + fail-safe on unwritable files, ETag 304 revalidation on stale entries, mcpify_cache_invalidate (scoped + full), --cache-warm pre-calls argument-free GETs only, token-file scoping end-to-end (401 / filtered lists / refused calls, deny wins, duplicate-token rejection), plugin hooks on real requests, mcpify ui dispatch (dead-command regression), config-schema matches the config module, OTel guard |
External $ref bundling | 6 | file + URL-base targets inlined, component-only target files, nested refs resolved relative to their own file, missing targets skipped, circular refs survive, same-document refs untouched |
| Governance: split keys, tool text, valid truncation | 21 | read-key/write-key per method over a live upstream (shared-identity default unchanged), style/name inheritance + explicit override, config write-auth-* keys in serve/envs/apis, [tool-text] override through list --json, unknown-tool warnings, validator errors, schema/keys parity, doctor instruction-like + overlong-description counts, oversized array → valid JSON with marker, object key-keeping, non-JSON fallback, error-prefix survival |
| v1.13: cost, projection, SSE, OAuth2 write | 20 | surface pricing in JSON + human output, recursive projection with transparent envelopes (both rules pinned: the top-level-only first rule failed live), selected keys keep their arrays, SSE framing vs JSON clients, write-flow resolution + mutual exclusion with --write-auth-env |
| v1.16: status policy, REPL session controls, diff probe + cost delta | 12 | policy (fields/redact/rate-limit) in multi-API JSON+human status and single-spec policy: line, :redact/:fields session set/show/clear over a live upstream (masking verified on the wire), diff surface-cost delta in JSON+human, --probe reachable/unreachable exit contract (2 on probe failure), form retry-delay float |
| v1.15: auth-probe, strict gate, metrics, lazy pricing | 16 | probe with a real credential (401-without vs 200-with over a live local upstream), strict-mode verdicts, doctor CLI exit contract, projection/redaction Prometheus counters (values counted, fresh-session enable), count_redact_targets, lazy-surface pricing lines, init --probe reachable/unreachable, dashboard-form token keys (float coercion, unknown-key rejection) |
| v1.14: redact, rate-limit, probe, multi list | 29 | masking at every level incl. error bodies and selected-key overlap, arrays masked in place, limiter slots with a fake clock, retry throttling, probe target selection + reachability exit contract, config redact/rate-limit in serve/apis/envs, per-upstream limiters, multi-API list + pricing |
Policy on failures: every bug found in the wild becomes a pinned regression test before the fix ships — the suite only grows.
Run it locally:
pip install pytest pyyaml
pytest -v
The v1.6–1.16 roadmap is fully shipped. Possible future work (not promised): server-initiated SSE (a GET stream with sessions — deliberately out for a stateless transport).
status policy visibility, REPL :redact/:fields, diff --probe + cost delta, form examplesdoctor --probe + --fail-on-http-error CI gate, init --probe, projection/redaction metrics, lazy-surface pricing--redact, --rate-limit, doctor --probe, lazy-search costs, multi-API list--write-oauth2-*), list --cost, --fields projection, SSE POST responses$ref bundling + OTel extraserve process fronting several OpenAPI documentsmcpify try REPL--output-serverMIT — see the LICENSE file for details.
Source-derived launch command. Check the maintainer’s required arguments and credentials before running:
uvx mcpify-openapiMerge this template into ~/Library/Application Support/Claude/claude_desktop_config.json. Keep existing servers. Add any arguments, credentials, and permissions required by the maintainer; this template has not been install-tested.
{
"mcpServers": {
"io-github-furkan708-mcpify": {
"command": "uvx",
"args": [
"mcpify-openapi"
]
}
}
}Restart Claude Desktop completely for changes to take effect. Confirm the server appears connected in the client’s tool list, then try a read-only example from its documentation.
Claude Desktop setup referencemcpify-openapipypimcpify works with any MCP-compatible client. Copy the config snippet from the Configuration section above and add it to the file shown for your client, then restart the application.
~/Library/Application Support/Claude/claude_desktop_config.jsonRestart Claude Desktop completely for changes to take effect.~/.cursor/mcp.jsonRestart Cursor for changes to take effect..vscode/mcp.jsonReload VS Code window for changes to take effect.~/.codeium/windsurf/mcp_config.jsonRestart Windsurf for changes to take effect..mcp.jsonSave at the project root, then start Claude Code in that project and review the MCP server approval prompt. Keep real credentials out of shared files.