Turn any OpenAPI specification into a Model Context Protocol (MCP) server with a single command.
Project description
OpenAPI MCP Gateway
Turn any OpenAPI (Swagger) spec into a Model Context Protocol (MCP) server with one command, or expose your existing FastAPI app the same way by decorating routes with @mcp_tool. Run several APIs from one process, each on its own mount path, with Bearer, API Key, and OAuth2 (authorization_code for per-user delegation, client_credentials for service tokens) auth built in. Works with Claude Desktop, Cursor, Cline, and any other MCP client.
openapi-mcp-gateway --spec https://petstore3.swagger.io/api/v3/openapi.json
# Server live at http://127.0.0.1:8000/api/mcp
- Zero glue code. Every operation in your spec becomes an MCP tool automatically.
- Multi-API. Expose GitHub, Slack, and internal services from one process, each on its own mount path.
- Auth built in. Bearer, API Key, and OAuth2, including per-user delegation (
authorization_code) and service tokens (client_credentials). - FastAPI native. Decorate routes with
@mcp_toolto expose them as MCP tools in-process, with no extra network hop and no separate spec to maintain. - Flexible transport. Streamable HTTP, SSE, or stdio for desktop clients.
Installation
pip install openapi-mcp-gateway
Or with uv:
uv add openapi-mcp-gateway
Optional extras:
pip install "openapi-mcp-gateway[redis]" # Redis token store, used for auth memoization
Requires Python 3.11+.
Quick Start
1. Public API, No Auth
openapi-mcp-gateway --spec https://petstore3.swagger.io/api/v3/openapi.json --name petstore
Connect an MCP client to http://127.0.0.1:8000/petstore/mcp.
2. Bearer Token
export GITHUB_TOKEN="ghp_..."
openapi-mcp-gateway \
--spec https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.json \
--name github \
--auth-type bearer \
--auth-token '${GITHUB_TOKEN}'
3. OAuth2, Per-User Delegation (authorization_code)
The gateway runs its own OAuth server so each MCP client authenticates as its own end-user; tokens are minted per session.
export ASANA_CLIENT_ID="..." ASANA_CLIENT_SECRET="..."
openapi-mcp-gateway \
--spec https://raw.githubusercontent.com/Asana/openapi/master/defs/asana_oas.yaml \
--name asana \
--auth-type oauth2 \
--auth-client-id '${ASANA_CLIENT_ID}' \
--auth-client-secret '${ASANA_CLIENT_SECRET}' \
--auth-scopes "openid,email,profile,users:read,workspaces:read"
4. OAuth2, Service Token (client_credentials)
When the gateway holds its own credentials and shares one upstream token across every MCP client. No per-user OAuth dance:
export SVC_CLIENT_ID="..." SVC_CLIENT_SECRET="..."
openapi-mcp-gateway \
--spec ./service-api.json \
--name svc \
--auth-type oauth2 \
--auth-flow client_credentials \
--auth-client-id '${SVC_CLIENT_ID}' \
--auth-client-secret '${SVC_CLIENT_SECRET}'
5. Multiple APIs at Once
Mix public, bearer, and OAuth2 services in a single config. Each server is mounted at /{name}/mcp:
# servers.yml
host: "0.0.0.0"
port: 8000
url: http://localhost:8000 # public base URL for OAuth callbacks
servers:
- name: petstore
spec: https://petstore3.swagger.io/api/v3/openapi.json
- name: github
spec: https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.json
auth:
type: bearer
token: ${GITHUB_TOKEN}
policy:
allow: ["GET /repos/*", "GET /users/*"]
deny: ["GET /repos/*/actions/secrets*"]
- name: asana
spec: https://raw.githubusercontent.com/Asana/openapi/master/defs/asana_oas.yaml
auth:
type: oauth2
client_id: ${ASANA_CLIENT_ID}
client_secret: ${ASANA_CLIENT_SECRET}
scopes: [openid, email, profile, users:read, workspaces:read]
export GITHUB_TOKEN="ghp_..."
export ASANA_CLIENT_ID="..." ASANA_CLIENT_SECRET="..."
openapi-mcp-gateway --config servers.yml
Runnable variants of this multi-server setup live in examples/: multi-server.yml, asana.yml, github.yml, petstore.yml. Each YAML lists its prerequisites at the top.
${ENV_VAR} and ${ENV_VAR:-default} work in any string field, resolved at request time. For OAuth2, authorizationUrl / tokenUrl / scopes are auto-detected from the spec's securitySchemes. Override auth.authorization_url, auth.token_url, or auth.scopes when the spec is incomplete.
6. Local Desktop Client (stdio)
For Claude Desktop, IDE integrations, or any MCP client that prefers stdio:
{
"mcpServers": {
"petstore": {
"command": "openapi-mcp-gateway",
"args": ["--spec", "/abs/path/to/openapi.json", "--transport", "stdio"]
}
}
}
Configuration
Run openapi-mcp-gateway --help for the CLI reference. The Quick Start examples cover most setups; the full field reference is below.
When values appear in more than one place, the rule is defaults < YAML (--config) < CLI flags < Gateway.run(...) kwargs, and a layer only overrides what it actually sets. Sub-trees (logging, per-server auth) merge field-by-field; the servers list is replaced wholesale.
Top-Level Fields
| Field | Type | Default | Description |
|---|---|---|---|
host |
string | 0.0.0.0 |
Bind address (0.0.0.0 = all interfaces). Clients on the same machine usually open http://localhost:{port} or http://127.0.0.1:{port}. |
port |
int | 8000 |
Bind port |
url |
string | (empty) | Public base URL for OAuth redirects and discovery. When unset: http://localhost:{port} if host is 0.0.0.0, otherwise http://{host}:{port}. Override when your registered redirect URI uses another host (tunnel, reverse proxy, etc.). |
transport |
string | streamable-http |
sse, streamable-http, or stdio |
store.type |
string | memory |
memory or redis |
store.redis_url |
string | redis://localhost:6379 |
Redis URL when store.type: redis |
logging.level |
string | INFO |
DEBUG, INFO, WARNING, ERROR, CRITICAL |
logging.format |
string | text |
text or json |
logging.file |
string | Mirror logs to this file | |
servers |
list | required | List of per-server config entries |
Per-Server Fields
| Field | Type | Default | Description |
|---|---|---|---|
name |
string | required | Unique identifier; mount path defaults to /{name} |
spec |
string | required | Path or URL to OpenAPI document (JSON or YAML) |
base_url |
string | from spec | Override the upstream base URL |
auth.type |
string | none |
none, bearer, api_key, or oauth2 |
auth.token |
string | Required for bearer / api_key |
|
auth.api_key_header |
string | X-API-Key |
Header name for api_key |
auth.client_id, auth.client_secret |
string | Required for oauth2 |
|
auth.scopes, auth.authorization_url, auth.token_url |
from spec | OAuth2 overrides when securitySchemes is incomplete |
|
policy.allow |
list | Only expose matching operations | |
policy.deny |
list | Exclude matching operations | |
timeout |
float | 90 |
HTTP timeout in seconds |
Filtering Operations
Use policy.allow and policy.deny with fnmatch syntax against operation IDs (getUsers, create*) or method + path (GET /users/*):
policy:
allow: ["GET /repos/*"]
deny: ["GET /repos/*/actions/secrets*"]
Or mark operations directly in the spec and enable marked_only:
# openapi.yml
paths:
/users:
get:
operationId: listUsers
x-mcp-integration:
expose:
tool: {}
# servers.yml
policy:
marked_only: true
Filters apply in order: marked_only, then allow, then deny.
Logging
Configure via the logging.* YAML keys or via CLI flags (--log-level, --log-format, --log-file); -v and -q are shortcuts for DEBUG and WARNING. CLI flags override YAML field-by-field, following the precedence rule above.
Python API
Use the gateway as a library inside your own Python application:
from openapi_mcp_gateway import Gateway
gateway = Gateway()
gateway.add_server(
name="petstore",
spec="https://petstore3.swagger.io/api/v3/openapi.json",
)
gateway.add_server(
name="github",
spec="./github-openapi.json",
auth={"type": "bearer", "token": "${GITHUB_TOKEN}"},
policy={"allow": ["GET /repos/*"]},
)
gateway.run(port=8000)
Expose Your FastAPI App as MCP Tools
Already running FastAPI? Decorate the routes you want to expose with @mcp_tool and the gateway picks them up. No second spec, no separate process. Routes run in-process via httpx.ASGITransport, so there is no extra network hop:
from fastapi import FastAPI
from openapi_mcp_gateway import Gateway, mcp_tool
app = FastAPI()
@app.get("/items/{item_id}")
@mcp_tool()
def read_item(item_id: int):
return {"id": item_id}
@app.get("/internal/health") # not decorated → not exposed
def health():
return {"ok": True}
Gateway.from_fastapi(app, name="myapp").run()
Auth is auto-detected from the app's securitySchemes. If the gateway and the app share an OAuth realm, the MCP client's Authorization header passes through verbatim; for client_credentials schemes the gateway mints upstream tokens from its own credentials. Mix and match by passing an explicit auth=AuthConfig(...) to Gateway.from_fastapi.
Got routes you can't decorate at definition (third-party app, include_router from another package)? Use mark_tool(func) to attach the same metadata imperatively.
To mount the MCP routes onto an existing FastAPI app instead of running standalone, use Gateway.mount(app).
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file openapi_mcp_gateway-0.2.0.tar.gz.
File metadata
- Download URL: openapi_mcp_gateway-0.2.0.tar.gz
- Upload date:
- Size: 142.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c82ff556a49d45db62a4e64e1e861f8538b3aac50ea054d4b89d694abb7f04d0
|
|
| MD5 |
29010bc714a39dea51b528fd4ec64143
|
|
| BLAKE2b-256 |
d25e74b59e2da0f62af8c71bbacb17c7af286d7817f713be327cd3df664a879e
|
Provenance
The following attestation bundles were made for openapi_mcp_gateway-0.2.0.tar.gz:
Publisher:
release.yml on mroops0111/openapi-mcp-gateway
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
openapi_mcp_gateway-0.2.0.tar.gz -
Subject digest:
c82ff556a49d45db62a4e64e1e861f8538b3aac50ea054d4b89d694abb7f04d0 - Sigstore transparency entry: 1495791376
- Sigstore integration time:
-
Permalink:
mroops0111/openapi-mcp-gateway@577ae18c54b27f233085692f09560bf97935dbe2 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/mroops0111
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@577ae18c54b27f233085692f09560bf97935dbe2 -
Trigger Event:
push
-
Statement type:
File details
Details for the file openapi_mcp_gateway-0.2.0-py3-none-any.whl.
File metadata
- Download URL: openapi_mcp_gateway-0.2.0-py3-none-any.whl
- Upload date:
- Size: 48.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
65578b59a1fb17d8028b4c873f2eb55007aa53fc9215b71940330830eeff58f9
|
|
| MD5 |
83aaa3c806a95de36ed2456cd437f8c2
|
|
| BLAKE2b-256 |
28ac09730ee983f515f52a84064a4ab4b129b0d566f1abdfeb60ba17730a8025
|
Provenance
The following attestation bundles were made for openapi_mcp_gateway-0.2.0-py3-none-any.whl:
Publisher:
release.yml on mroops0111/openapi-mcp-gateway
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
openapi_mcp_gateway-0.2.0-py3-none-any.whl -
Subject digest:
65578b59a1fb17d8028b4c873f2eb55007aa53fc9215b71940330830eeff58f9 - Sigstore transparency entry: 1495791462
- Sigstore integration time:
-
Permalink:
mroops0111/openapi-mcp-gateway@577ae18c54b27f233085692f09560bf97935dbe2 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/mroops0111
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@577ae18c54b27f233085692f09560bf97935dbe2 -
Trigger Event:
push
-
Statement type: