MM

matthewhand/mcp-openapi-proxy

API development
158 stars 40 forks Quality 85 Trend 36MIT

is a Python package that implements a Model Context Protocol (MCP) server, designed to dynamically expose REST APIs—defined by OpenAPI specifications—as MCP tools.

Overview

is a Python package that implements a Model Context Protocol (MCP) server, designed to dynamically expose REST APIs—defined by OpenAPI specifications—as MCP tools.

README

mcp-openapi-proxy

mcp-openapi-proxy is a Python package that implements a Model Context Protocol (MCP) server, designed to dynamically expose REST APIs—defined by OpenAPI specifications—as MCP tools. This facilitates seamless integration of OpenAPI-described APIs into MCP-based workflows.

v0.4.0 — tagged September 6, 2026; GitHub Release published October 15, 2026 (AEDT): MCP SDK 2.x / protocol 2026-07-28, with native stateless Streamable HTTP and legacy stdio support.

What’s New in 0.4.0

MCP 2026-07-28 (SDK 2.x). The proxy is dual-stack: modern clients send self-contained requests (no initialize, no Mcp-Session-Id) and list results carry ttlMs / cacheScope. Legacy stdio clients that still handshake keep working. Native Streamable HTTP is available with MCP_TRANSPORT=streamable-http and is stateless — round-robin load balancing needs no sticky sessions.

See docs/MIGRATION-0.4.md for breaking changes, new env vars, and gateway cutover notes. 0.3.4 remains the last release on mcp 1.x (mcp>=1.2.0,<2).

What’s New in 0.2.0

Works with every modern MCP-enabled client we tested. Strict MCP clients can now discover and call tools — the low-level server advertises correct capabilities and no longer crashes during resource/prompt discovery, and a slow spec download no longer crash-loops short-timeout clients. Verified live against the full list of mainstream agent CLIs:

  • ✅ Codex, Gemini, Qwen, Kilocode, opencode — native tool calls over stdio
  • ✅ Vibe — native discovery and read calls (writes were CLI-flaky, not a proxy issue)
  • ✅ Letta — Cloud (via a remote streamable-HTTP MCP URL) and self-hosted (via stdio)

See the client matrix for attach mechanisms, models, and exact results.

📄 Full write-up: Verification case study — what the proxy is, the API + client matrices, and every defect found & fixed.

Prompts and resources are real now — including custom resources. Both MCP surfaces are functional and tested: the summarize_spec / whimsical_blog prompts and the spec_file resource, plus a new ADDITIONAL_RESOURCES env var that serves your own use-case documents (e.g. a NetBox naming policy or an Asana project-layout guide) as MCP resources — see examples/resources/.

Bug fixes (every one live-verified):

  • MCP client discovery: empty capability set + a crash in resource discovery left strict clients seeing zero tools (#23) — fixed, with a full stdio-handshake test harness.
  • IGNORE_SSL_TOOLS was ignored by the low-level dispatcher (#14) — fixed (original patch by @robbycochran, #15).
  • Server crash-loop when a slow spec fetch outran a client’s connect timeout (#28) — handshake now answers immediately, spec loads lazily, closed streams exit cleanly.
  • API_AUTH_TYPE custom schemes (e.g. NetBox Token) sent no auth header at all (#24) — fixed.
  • TOOL_WHITELIST never matched Slack-style dot paths like /users.list (#27) — fixed.
  • TOOL_NAME_MAX_LENGTH was not respected, and name-truncation collisions silently dropped tools (#11) — fixed.
  • Array parameters were emitted without items, which the OpenAI API rejects (#16) — fixed.
  • EXTRA_HEADERS now accepts a JSON array and literal \n separators, not just real newlines (#17).
  • Dead Render spec URL (#26) and incomplete ElevenLabs example (#29) — fixed; the GetZep example is documented for self-hosted Zep CE since the hosted endpoint now 401s (#38).
  • Added a Dockerfile + glama.json for the Glama listing (#13); collapsible README examples + a verified-client/API matrix (#35).

Full environment-variable reference is in Environment Variables.

Table of Contents

Overview

The package offers two operational modes:

  • Low-Level Mode (Default): Dynamically registers tools corresponding to all valid API endpoints specified in an OpenAPI document (e.g. /chat/completions becomes chat_completions()).
  • FastMCP Mode (Simple Mode): Provides a streamlined approach by exposing a predefined set of tools (e.g. list_functions() and call_function()) based on static configurations.

Features

  • Dynamic Tool Generation: Automatically creates MCP tools from OpenAPI endpoint definitions.
  • Tool Annotations: Every OpenAPI-derived tool includes MCP annotations (title, plus readOnlyHint / idempotentHint for GET and clearly read-ish POST, or destructiveHint for other methods) so clients such as Gemini Spark see the same fingerprint as Notion/Linear.
  • Simple Mode Option: Offers a static configuration alternative via FastMCP mode.
  • OpenAPI Specification Support: Compatible with OpenAPI v3 with potential support for v2.
  • Flexible Filtering: Allows endpoint filtering through whitelisting by paths or other criteria.
  • Payload Authentication: Supports custom authentication via JMESPath expressions (e.g. for APIs like Slack that expect tokens in the payload not the HTTP header).
  • Header Authentication: Uses Bearer by default for API_KEY in the Authorization header, customizable for APIs like Fly.io requiring Api-Key.
  • MCP Integration: Seamlessly integrates with MCP ecosystems for invoking REST APIs as tools.

Installation

Install the package directly from PyPI using the following command:

uvx mcp-openapi-proxy

MCP Ecosystem Integration

To incorporate mcp-openapi-proxy into your MCP ecosystem configure it within your mcpServers settings. Below is a generic example:

{
    "mcpServers": {
        "mcp-openapi-proxy": {
            "command": "uvx",
            "args": ["mcp-openapi-proxy"],
            "env": {
                "OPENAPI_SPEC_URL": "${OPENAPI_SPEC_URL}",
                "API_KEY": "${API_OPENAPI_KEY}"
            }
        }
    }
}

Refer to the Examples section below for practical configurations tailored to specific APIs.

Modes of Operation

FastMCP Mode (Simple Mode)

  • Enabled by: Setting the environment variable OPENAPI_SIMPLE_MODE=true.
  • Description: Exposes a fixed set of tools derived from specific OpenAPI endpoints as defined in the code.
  • Configuration: Relies on environment variables to specify tool behavior.

Low-Level Mode (Default)

  • Description: Automatically registers all valid API endpoints from the provided OpenAPI specification as individual tools.
  • Tool Naming: Derives tool names from normalized OpenAPI paths and methods.
  • Behavior: Generates tool descriptions from OpenAPI operation summaries and descriptions.

Environment Variables

  • OPENAPI_SPEC_URL: (Required) The URL to the OpenAPI specification JSON file (e.g. https://example.com/spec.json or file:///path/to/local/spec.json).

  • OPENAPI_SIMPLE_MODE: (Optional) Set to true to enable FastMCP mode.

  • TOOL_WHITELIST: (Optional) A comma-separated list of endpoint paths to expose as tools.

  • ADDITIONAL_RESOURCES: (Optional) Comma-separated name=/path/to/file entries served as extra MCP resources (use-case docs such as naming policies or layout conventions; see examples/resources/).

  • TOOL_NAME_PREFIX: (Optional) A prefix to prepend to all tool names.

  • API_KEY: (Optional) Authentication token for the API sent as Bearer in the Authorization header by default.

  • API_AUTH_TYPE: (Optional) Overrides the default Bearer Authorization scheme. api-key sends the key in the header named by API_AUTH_HEADER; any other value is used as a custom scheme prefix (e.g. Token for NetBox → Authorization: Token ).

  • STRIP_PARAM: (Optional) JMESPath expression to strip unwanted parameters (e.g. token for Slack).

  • DEBUG: (Optional) Enables verbose debug logging when set to “true”, “1”, or “yes”. Native Streamable HTTP also emits one INFO line per inbound JSON-RPC method (mcp rpc method=tools/list, mcp rpc method=tools/call name=…) without arguments or Authorization.

  • EXTRA_HEADERS: (Optional) One or more outgoing HTTP headers. Accepts a JSON array (["X-A: 1","X-B: 2"]), one Header: Value per line, or literal \n-separated entries (for configs that cannot embed newlines).

  • SERVER_URL_OVERRIDE: (Optional) Overrides the base URL from the OpenAPI specification when set, useful for custom deployments.

  • TOOL_NAME_MAX_LENGTH: (Optional) Truncates tool names to a max length.

  • Additional Variable: OPENAPI_SPEC_URL_ – a variant for unique per-test configurations (falls back to OPENAPI_SPEC_URL).

  • IGNORE_SSL_SPEC: (Optional) Set to true to disable SSL certificate verification when fetching the OpenAPI spec.

  • IGNORE_SSL_TOOLS: (Optional) Set to true to disable SSL certificate verification for API requests made by tools.

  • API_AUTH_HEADER: (Optional) Header name used when API_AUTH_TYPE=api-key (e.g. x-apikey for VirusTotal, xi-api-key for ElevenLabs). Defaults to Authorization.

  • OPENAPI_SPEC_FORMAT: (Optional) Set to yaml to parse file:// specs as YAML (remote specs auto-detect). Default json.

  • OPENAPI_SPEC_CACHE_TTL_SECONDS: (Optional) Live-first disk cache for remote specs: the cached copy is served only when the live fetch fails or stalls (and respawned servers fail fast to it). Default 86400; set 0 to disable.

  • ENABLE_TOOLS / ENABLE_PROMPTS / ENABLE_RESOURCES: (Optional) Feature gates for the three MCP surfaces in low-level mode; each defaults to enabled. Disabling a feature removes its handlers and its capability advertisement.

  • PROMPT: (Optional) JSON object that registers one extra MCP prompt next to summarize_spec and whimsical_blog, in both low-level and simple mode. Example: {"name":"asana_prompt","description":"Asana helper","messages":[{"user":"What is Asana?","assistant":"Asana is a work management platform."}]}. Each message may include user, assistant, or both. Invalid JSON is logged and ignored. summarize_spec and whimsical_blog are reserved names. Restart the server after changing it. Low-level mode also requires prompts to stay enabled (ENABLE_PROMPTS, default on). Custom prompt content is returned to connected MCP clients: do not put credentials or other private data in PROMPT, and protect public HTTP ingress with authentication and host/origin controls.

  • CAPABILITIES_TOOLS / CAPABILITIES_PROMPTS / CAPABILITIES_RESOURCES: (Optional) Advertise listChanged on the corresponding capability (for clients that key on it). Default false.

  • MCP_SERVER_NAME: (Optional) Identity advertised in serverInfo / server/discover. Set this to the proxied API (e.g. gpt-terminal-plus, flyio) so multiple instances are not all called openapi-proxy. Falls back to the OpenAPI file stem or spec-URL host, then openapi-proxy.

  • MCP_SERVER_TITLE / MCP_SERVER_DESCRIPTION: (Optional) Display title and description alongside the name.

  • MCP_TRANSPORT: (Optional) stdio (default) or streamable-http for native stateless Streamable HTTP (MCP 2026-07-28; no Mcp-Session-Id).

  • MCP_HOST / MCP_PORT / MCP_PATH: (Optional) HTTP bind address (127.0.0.1), port (8000), and path (/mcp) when MCP_TRANSPORT=streamable-http. Native Streamable HTTP also serves GET /healthz → {"ok":true,"name":,"port":} without taking the MCP request lock (no spec fetch, no initialize).

  • MCP_JSON_RESPONSE: (Optional) true (default) returns JSON instead of SSE on Streamable HTTP.

  • MCP_LIST_TTL_MS / MCP_READ_TTL_MS: (Optional) ttlMs for list results (default 60000) and resources/read (default 5000).

  • MCP_ALLOWED_HOSTS: (Optional) Comma-separated Host allow-list for DNS-rebinding protection. Default * (protection off — typical behind nginx).

  • MCP_REQUEST_STATE_KEY: (Optional) Shared HMAC key so MRTR requestState verifies across instances. Unset = process-local. Unreleased on main (not in tagged/PyPI v0.4.0): The next two raw-command logging settings are available only in newer source.

  • MCP_LOG_RAW_COMMANDS: (Optional, default off) 1/true/yes adds one extra INFO record per shell-execution tool call, emitted before the command runs:

    mcp raw-command tool=post_command_execute_shell
    cd /srv/app && npm run build   # <- the verbatim command, unmodified
    

    The command is logged byte-for-byte — whitespace, blank lines, tabs and quoting are preserved; nothing is normalised, reconstructed, or redacted. The existing mcp rpc method=… name=… line is unchanged and still the only record when this is off. ⚠️ This can write secrets into your logs: shell commands routinely carry credentials (export TOKEN=…, curl -H 'Authorization: Bearer …', ssh/psql invocations). It is opt-in for that reason — enable it only where the log sink is trusted, and scrub or rotate the log afterwards. Only the command body is dumped, never Authorization headers or unrelated tool arguments. Scoped to the allow-list below; not a general tool-argument dump.

  • MCP_LOG_RAW_COMMAND_TOOLS: (Optional) Comma-separated tool names eligible for raw-command logging, overriding the default post_command_execute_shell,executeShell. Entries that are not valid tool names are ignored. Use this when a new terminal-style upstream lands.

Verified Clients & Live Results (2026-06-12)

The example configurations below were exercised against the live APIs, and the proxy was attached to a range of agent CLIs over stdio MCP. Results from that verification run:

API example results

API example Tools Sample call proven Extra env needed
glama 6 get_v1_attributes none
apis.guru 7 get_metrics_json none
wolframalpha 2 get_v1_llm_api API_KEY
virustotal 4 IP report API_KEY + API_AUTH_TYPE=api-key + API_AUTH_HEADER=x-apikey
asana 73 (whitelist /workspaces,/projects,/tasks) created project + 11 tasks SERVER_URL_OVERRIDE + API_KEY
render 52 get_services API_KEY (NEW spec URL render-public-api-1.json)
notion 4–5 (whitelist /v1/users,/v1/search or /v1/pages) page create + title read-back SERVER_URL_OVERRIDE + EXTRA_HEADERS (Notion-Version) + API_KEY
elevenlabs 19 TTS mp3 generated SERVER_URL_OVERRIDE + API_AUTH_TYPE=api-key + API_AUTH_HEADER=xi-api-key
flyio 34–35 apps + machine health API_KEY
slack 7 (exact dot-path whitelist until #27 fix) auth.test + postMessage API_KEY
netbox 9 (whitelist /ipam/ip-addresses) IPAM write + read API_KEY + API_AUTH_TYPE=Token
homeassistant 21 get_config (200) + call_service (light.turn_on, 200) SERVER_URL_OVERRIDE + API_KEY (${HA_TOKEN}, sent as Bearer)

Client matrix

Agent CLI Model used (live test) MCP attach mechanism Tool calls Prompts/Resources surfaced to model?
opencode (CLI default) ~/.config/opencode/opencode.json mcp ✅ native prompts: ✅ (slash) · resources: ✅ — most complete ‖
Codex gpt-5-codex (OpenAI API) codex exec -c mcp_servers.* ✅ native prompts: ❌ low-level · ✅ fastmcp (via call_function→get_prompt) · resources: ✅ (read_mcp_resource) ‖
Kilocode kilo-auto/free global settings/mcp_settings.json ✅ native prompts: ❌ (no prompt mechanism) · resources: ✅ (access_mcp_resource) ‖
Qwen agent group via local LiteLLM gateway project .qwen/settings.json ✅ native (live invoke auth-blocked) prompts: ✅ (slash /summarize_spec) · resources: ❌ (no client support) ‖
Gemini Google OAuth free tier (CLI default model) project .gemini/settings.json mcpServers ✅ native prompts: interactive slash only · resources: interactive @ only (neither reaches the model headless) ‖
Vibe mistral-medium-3.5 ~/.vibe/config.toml [[mcp_servers]] ✅ discovery + reads prompts: ❌ · resources: ❌ (tools-only client) ‖
agy — — ❌ headless cannot enable MCP n/a
letta (cloud / self-hosted) Letta Cloud / PUT /v1/tools/mcp/servers streamable-HTTP / stdio ✅ (per prior sweep) unknown — needs a running Letta server to test ‡

How to read the prompts/resources column. MCP has three surfaces — tools, prompts, resources. mcp-openapi-proxy serves all three, advertised by default since 0.3.0. Whether they reach the model is up to the client, and that varies:

  • ‖ re-verified 2026-06-15 against the published 0.3.3 release with no flags set (validating the default-on advertising), driving each real client binary, in both server modes (low-level and FastMCP simple). Per-client results were the same across modes except where noted.
  • Tools work on every client tested. Prompts→model: opencode & Qwen (slash commands); Gemini interactive-only. Resources→model: opencode, Codex, Kilocode. opencode is the only client that surfaces all three in any mode.
  • FastMCP simple mode exception: a client with no native prompt surface can still reach prompts through the static call_function→get_prompt indirection. Observed with Codex (prompts ❌ in low-level, ✅ in FastMCP simple mode). Vibe stays tools-only regardless of mode.
  • ‡ unknown — Letta can’t be exercised without standing up a Letta server + model; left untested rather than guessed.
  • Every cell on the proxy side was confirmed via a raw stdio handshake (initialize advertises tools+prompts+resources; prompts/get and resources/read return content). The remaining ❌ are client-side gaps, not proxy limitations.

(All pre-0.3.0 prompt/resource findings were voided — they were measured while advertising defaulted off, so they reflected the server default, not the clients.)

Minimal sanitized configs per client (the no-auth Glama spec is used as the smallest working example; substitute your own spec URL and $YOUR_KEY as needed):

agy (headless) and letta cloud could not attach a stdio MCP server at all, so no config snippet applies: agy provides no way to enable MCP in headless mode, and letta cloud only accepts remote MCP servers.

Tips

  • Big specs need TOOL_WHITELIST. Large APIs (Asana, Render, Fly.io, Slack, NetBox) expose dozens of endpoints; whitelist the paths you need to keep the tool count manageable for clients.
  • Dot-style paths need exact whitelist entries for now. Slack-style paths like /chat.postMessage are not matched by prefix whitelisting — list each path exactly (issue #27 / PR #32).
  • Slow remote specs can crash-loop short-timeout clients. If a client kills the server before a large spec finishes downloading, fetch the spec once and point OPENAPI_SPEC_URL at a local file:// copy (issue #28).
  • Custom auth schemes are handled via API_AUTH_TYPE (PR #25), e.g. Token for NetBox, or api-key with API_AUTH_HEADER for Virustotal/ElevenLabs-style header keys.

Examples

For testing you can run the uvx command as demonstrated in the examples then interact with the MCP server via JSON-RPC messages to list tools and resources. See the “JSON-RPC Testing” section below.

Each example below is collapsed — click to expand.

Troubleshooting

JSON-RPC Testing

For legacy stdio testing, you can interact with the MCP server via JSON-RPC. After starting the server, send the following initialization message. Native Streamable HTTP in v0.4.0 uses self-contained requests and does not require an initialize handshake; see the migration guide:

{"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"claude-ai","version":"0.1.0"}},"jsonrpc":"2.0","id":0}

The response should contain a JSON-RPC result with a negotiated protocolVersion, advertised capabilities, and serverInfo identifying this proxy (not sqlite). Exact names, versions and capabilities depend on the active SDK, configuration and client; the old fixed example did not describe this project’s response.

Then paste these follow-up messages:

{"method":"notifications/initialized","jsonrpc":"2.0"}
{"method":"resources/list","params":{},"jsonrpc":"2.0","id":1}
{"method":"tools/list","params":{},"jsonrpc":"2.0","id":2}
  • Missing OPENAPI_SPEC_URL: Ensure it’s set to a valid OpenAPI JSON URL or local file path.
  • Invalid Specification: Verify the OpenAPI document is standard-compliant.
  • Tool Filtering Issues: Check TOOL_WHITELIST matches desired endpoints.
  • Authentication Errors: Confirm API_KEY and API_AUTH_TYPE are correct.
  • Logging: Set DEBUG=true for detailed output to stderr. On native Streamable HTTP (MCP_TRANSPORT=streamable-http), INFO always includes mcp rpc method=… (and name= for tools/call) so POST /mcp 200 can be attributed without enabling DEBUG.
  • Test Server: Run directly:
uvx mcp-openapi-proxy

License

MIT License

View the full README on GitHub

Install

uvx mcp-openapi-proxy
New to MCP? Start with the mcpskill.org guide →

Configuration

{ "mcpServers": { "mcp-openapi-proxy": { "command": "uvx", "args": ["mcp-openapi-proxy"], "env": { "OPENAPI_SPEC_URL": "${OPENAPI_SPEC_URL}", "API_KEY": "${API_OPENAPI_KEY}" } } } }