openapi-mcp-hub
Turn any OpenAPI spec — or a live PostgreSQL / MySQL database — into production-ready MCP endpoints. One process. One port. Zero code.
Install:
pip install openapi-mcp-huboruvx openapi-mcp-hub· Python 3.10+ · MIT
POST an openapi.json to the gateway and you instantly get an MCP endpoint any agent can call:
POST /services → http://your-gateway:20033/mcp/{service-name}
No wrapper code. No per-spec deployment. No port sprawl. Register as many services as you like — REST APIs or read-only databases — all behind a single port, hot-loaded the moment they register.
Why
- OpenAPI/Swagger is the de-facto standard for describing REST APIs — most internal services already have a spec.
- MCP (Model Context Protocol) is how agents consume tools — Claude Code and friends speak it natively over Streamable HTTP.
- Hand-writing MCP wrappers for every endpoint is repetitive and drifts out of sync. Running one MCP server per spec means port sprawl and linear ops cost.
openapi-mcp-hub is a gateway: one resident process, one port, path-level multi-tenancy (/mcp/{service}). Registration takes effect immediately — no restarts.
Public 2026 MCP ecosystem reports keep finding the same pain: only ~8.5% of MCP servers use OAuth, and tool-poisoning / rug-pull changes go unnoticed until an agent misbehaves. That is why this gateway treats data-plane auth and tool-change visibility as first-class features, not enterprise add-ons.
┌──────────── admin plane (admin token) ─────────────┐
│ POST /services register (REST or DB) │
│ GET /services list / inspect │
│ POST /services/preview dry-run registration │
└────────────────────┬───────────────────────────────┘
▼
Agents ── MCP (streamable HTTP / SSE) ──▶ openapi-mcp-hub gateway ── HTTP ──▶ your APIs
Claude Code, Cursor, custom clients single port :20033 ── SQL ──▶ PostgreSQL / MySQL 8+
per-service api_key
Quickstart — 60 seconds
git clone https://github.com/zhangjk1207/openapi-mcp-hub && cd openapi-mcp-hub
docker compose up -d # gateway + PostgreSQL + demo API, auto-registers a petstore service
Open http://localhost:20033 and paste the demo admin token: demo-admin-token — the console shows a ready-to-use petstore service (fixed demo key, listed below).
Try the agent-callable endpoint right away:
curl -X POST http://localhost:20033/mcp/petstore \
-H "X-Api-Key: psk-demo-petstore-key" -H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_pets","arguments":{"limit":3}}}'
And in Claude Code:
claude mcp add --transport http petstore http://localhost:20033/mcp/petstore \
--header "X-Api-Key: psk-demo-petstore-key"
The demo mode (
OPENAPI_MCP_HUB_DEMO=1indocker-compose.yml) uses a fixed, publicly-documented key for a friction-free tour. Turn it off (and changeOPENAPI_MCP_HUB_ADMIN_TOKEN) before any real use — real registrations always mint a fresh one-time key.
Register your own API
ADMIN_TOKEN=demo-admin-token
# spec_text receives the raw file content (JSON or YAML)
jq -n --rawfile s your-openapi.json '{name:"my-api", base_url:"https://api.example.com", spec_text:$s}' | \
curl -X POST http://localhost:20033/services \
-H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" -d @-
Connect it to Claude Code (the response includes a ready-to-run command):
claude mcp add --transport http my-api http://localhost:20033/mcp/my-api \
--header 'X-Api-Key: psk-xxxx'
That's it — your API's operations are now agent-callable tools.
Register a database instead of an API
No REST spec needed. Point the gateway at a database (via an environment variable name — the DSN itself never touches disk):
export WORKFLOW_DB_URL='postgresql://ro_user:pass@db.internal.example:5432/workflow'
curl -X POST http://localhost:20033/services \
-H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" \
-d '{"name":"workflow-db","db":{"url_env":"WORKFLOW_DB_URL"}}'
You get three tools the agent can use blind: list_tables, describe_table (schema + comments embedded), and query (SELECT-only).
Read-only is enforced in six layers
| Layer | What it does |
|---|---|
| Lexical whitelist | First token must be SELECT/WITH/EXPLAIN; comments blocked; string literals respected |
READ ONLY transactions |
Server-enforced — writes fail even if they slip past the lexer (PG / MySQL 8+) |
Outer LIMIT wrap |
SELECT * FROM (your SQL) sub LIMIT max_rows+1 — result size capped regardless |
| Statement timeout | statement_timeout (PG) / max_execution_time (MySQL) per pool |
| Isolated pool | Separate connection pool per service, minsize=1, maxsize=5 |
| Table filtering | include_tables / exclude_tables at introspection time |
Credentials are referenced by env var name only — nothing sensitive is persisted or logged. SQL digests land in the in-memory call log for auditing.
Features
| Sources | OpenAPI 3.0/3.1, Swagger 2.0 (auto-normalized), spec URL with SSRF allowlist, JSON/YAML upload, PostgreSQL, MySQL 8+ |
| Transport | Streamable HTTP + legacy SSE (/mcp/{name}/sse) on one port |
| Multi-tenant security | Per-service api_key (shown once, stored hashed, rotatable), admin token for the management plane |
| MCP data-plane auth | Static key or JWT as an OAuth 2.1 resource server: external IdP (RS256/ES256 + JWKS + issuer/audience checks, RFC 9728 discovery, WWW-Authenticate on 401) or built-in issuance (HS256, short-TTL, aud bound to the service) |
| Tool-poisoning visibility | Every update/refresh returns a tool-set diff (added / removed / description_changed / input_schema_changed) plus a human-review warning when descriptions change — descriptions are the LLM's instruction surface |
| Target API auth | Bearer / API key / Basic / OAuth2 client_credentials — token fetched, cached & auto-refreshed by the gateway (one 401 retry); credentials referenced by env var name only, token_url allowlist-checked at registration |
| Hot registration | Register / update / delete without restarts; failures roll back atomically; spec_url sources re-pull via refresh (key preserved) |
| Filtering | Path include/exclude globs, tool whitelist, max_tools cap, response templates |
| Persistence | JSON file or PostgreSQL backend, automatic one-way migration |
| Console | Single-file admin UI, zero build chain, zero external assets — works in air-gapped intranets |
| Observability | Health probes, in-memory ring call log (last 20/service), call stats (ok-rate / p50 / p95 / top tools), structured error codes (-32000/-32602/-32002) |
Comparison
| openapi-mcp-hub | one-MCP-server-per-spec | general API gateways + MCP plugin | |
|---|---|---|---|
| New service | POST a spec, ~1s | Write & deploy a wrapper | Plugin config + reload |
| Ports | 1 | N | 1 (heavyweight) |
| Database → MCP | Built-in, read-only enforced | Separate project | Rarely |
| Air-gapped console | Yes, single HTML file | — | Usually needs internet |
| MCP data-plane auth (OAuth 2.1 resource server) | Built-in, free | — | Kong AI Gateway: RBAC/OIDC requires the paid Enterprise tier; Zuplo: SaaS |
| Tool-poisoning visibility | Tool-set diff + review warning on every update/refresh | — | Rarely |
| Runtime deps | FastAPI + mcp SDK (~10 packages) | varies | Full gateway stack |
Security posture
Everything below ships in the free, self-hosted, single-binary build:
| Layer | Mechanism |
|---|---|
| Management plane | OPENAPI_MCP_HUB_ADMIN_TOKEN (env only); all admin routes require it |
| Data plane (static mode) | Per-service X-Api-Key, shown once, stored as SHA-256 hash, rotatable via rotate-key |
| Data plane (JWT mode) | Authorization: Bearer — external IdP: RS256/ES256 via JWKS (cached 300s, kid rotation aware), iss/aud enforced, algorithms whitelisted (no alg-confusion); built-in: HS256 with OPENAPI_MCP_HUB_JWT_SECRET (env only, never persisted), TTL ≤ 24h, aud bound to the service name. RFC 9728 metadata at /.well-known/oauth-protected-resource/mcp/{name} so spec-native clients (e.g. Claude Code) can discover your IdP |
| SSRF | Three layers: registration-time host allowlist, per-hop re-check on redirects, response timeouts |
| Secrets at rest | Target-API credentials referenced by env var names only — nothing sensitive is persisted |
| Database tools | Six-layer read-only (see above) |
| Tool poisoning | tool_changes diff + human-review warning whenever tool descriptions change on update/refresh |
JWT mode in three commands (built-in issuance):
export OPENAPI_MCP_HUB_JWT_SECRET="$(openssl rand -hex 32)"
# register with authn={"mode":"jwt"} (see USAGE.md for external-IdP mode)
curl -X POST $GW/services -H "Authorization: Bearer $ADMIN" -d '{"name":"crm","spec_url":"...","base_url":"...","authn":{"mode":"jwt"}}'
curl -X POST $GW/services/crm/token -H "Authorization: Bearer $ADMIN" -d '{"ttl_s":3600}'
# → use the returned token as: Authorization: Bearer <token>
Console screenshots
Real console, not mockups — REST and database sources in one table, generated tool sets, health probes and key rotation, all from a single static page: no build chain, no external assets, works air-gapped.
Services — REST and database sources side by side, /mcp/{name} per row:
Service detail — endpoint, generated tools with descriptions, health probe, key rotation:
Register a REST API — spec upload / paste, path filters, target auth by env var name:
Register a database — DSN referenced by env var name only, read-only tool trio generated:
Roadmap
- v0.4.0 — JWT data-plane auth (OAuth 2.1 resource server, RFC 9728) · tool-poisoning drift diff on update/refresh
- v0.3.0 — OAuth2 client_credentials target auth · spec refresh endpoint · call stats · console auth fix
- M5-1 — Database → MCP (PostgreSQL + MySQL 8, six-layer read-only)
- M5-2 — per-table tool mode, sensitive-column masking
- M5-3 — write operations (opt-in, statement allowlist)
- Metrics endpoint (Prometheus) & key expiry
- Per-tenant quotas / rate limiting
- Per-service circuit breaking
Not implemented today — do not plan around these: circuit breaking, per-tenant rate limiting or quotas, HA / multi-replica coordination, Prometheus metrics, database write operations, per-table database tools, API-key expiry. Safety isolation today is per-service httpx client + independent timeout + connection-pool cap + the SSRF allowlist, all inside a single process.
Development
pip install -e ".[dev]"
pytest tests/ -q # 178 pass; 22 opt-in integration tests skip without PG/MySQL DSNs
License
Metadata
Release files for openapi-mcp-hub 0.4.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| openapi_mcp_hub-0.4.1.tar.gz | 115.7 kB | With provenance | Details for openapi_mcp_hub-0.4.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| openapi_mcp_hub-0.4.1-py3-none-any.whl | Python 3 | none | any | Details for openapi_mcp_hub-0.4.1-py3-none-any.whl |
Total release size: 200.0 kB
Release files / openapi_mcp_hub-0.4.1.tar.gz
| Download URL | openapi_mcp_hub-0.4.1.tar.gz |
|---|---|
| Size | 115.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
85260ba200c9f61ec4c7a30610c4b309b105a7eb6b18b67c1f4794d82942e8e4
|
|
BLAKE2b-256 checksum How to use checksums |
119595e975f39994996845f89100633118fe8cb99332905d1f13cd997e1181fb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.
Transparency logRelease files / openapi_mcp_hub-0.4.1-py3-none-any.whl
| Download URL | openapi_mcp_hub-0.4.1-py3-none-any.whl |
|---|---|
| Size | 84.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0b68fc1e2c1ab92ded1698a26eed0f02d90cd101f99af806850dd8d931d3a751
|
|
BLAKE2b-256 checksum How to use checksums |
d698f2bd6349e642ec68e0b1523f2b5166ba41f5b36ac2047cc02b2055885a69
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.
Transparency logSecurity for openapi-mcp-hub 0.4.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for openapi_mcp_hub-0.4.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.