Skip to main content

mcp-api-gateway

CI

Turn any number of OpenAPI or Swagger services into a single MCP server — and put the MCP servers you already have behind the same /mcp.

Register a service by pointing the gateway at its spec URL, tick the operations worth exposing, and they become tools on one /mcp endpoint. When a model calls one, the gateway makes the corresponding HTTP request — with the credentials you stored for that service — and hands back the response. Register an MCP server by its endpoint and its tools join the same list, forwarded as tools/call with the credential you stored for it. It is a proxy: nothing is generated ahead of time, nothing is cached, and a change you make in the UI is live on the next tools/list.

  • /mcp — one MCP endpoint (streamable HTTP) for every registered service.
  • Two kinds of upstream — an API described by an OpenAPI or Swagger document, or a server that already speaks MCP over streamable HTTP. One tool list, one token, one Monitoring page, whichever kind is behind each name.
  • Configuration pages — register and edit upstreams, choose operations, name tools, cap how fast each upstream may be called, refresh specs and tool lists and review what changed.
  • Monitoring page — calls, bytes in and out, failures and throttled calls over time, total and per server.
  • Configuration page — the gateway's own settings: how often specs are re-read, who has to sign in, whether /mcp requires a bearer token, and everything else in force with the layer it came from.
  • Both doors, closed from the browser — the admin login and the /mcp bearer token can each be set without editing a file or restarting, and each takes effect on the next request. The token is kept as a digest, so the gateway can check it and can never show it back.
  • Optional metrics export — push the same counters to New Relic, so a gateway going quiet is noticed by whatever notices everything else going quiet. Off unless you turn it on; counts only, never request content.
  • A built-in server, off by default — switch it on and an MCP client can preview a spec or an endpoint, register an upstream of either kind and choose its operations without a human opening the UI. It cannot delete a server or read a stored credential.
  • /api/v1 — the same configuration actions as JSON, for scripts, with kind on every server and in every create.

Self-hosted, single process, SQLite. No Node build step, no external services.

⚠️ Read this before it is reachable by anything but you

Out of the box the gateway has no admin login and no /mcp token, and it has no SSRF protection — an admin can point an upstream at 127.0.0.1 or any private address, and anyone who can reach /mcp can call it. Together that is an open proxy into whatever network the gateway sits in.

The defaults are safe only because it binds to 127.0.0.1. Before you change that, set both doors and read docs/security.md.

Install

Requires Python 3.11 or newer, on Linux, macOS or Windows.

Nothing is on PyPI yet — build a wheel and install that:

python -m pip install build && python -m build --wheel
pipx install ./dist/mcp_api_gateway-0.1.0-py3-none-any.whl
mcp-api-gateway --version

The distribution will be mcp-api-gateway, and so is the command it installs. The only name spelled differently is the Python package you would import, mcp_gateway.

docs/install.md covers pip, pipx, editable checkouts, what the first run creates, and upgrades.

Quickstart

Five minutes, ending with a real MCP client listing real tools.

1. Start it

mkdir gateway && cd gateway
mcp-api-gateway

It writes config.toml and data/ in that directory, migrates a fresh database, and starts listening. Two warnings in the log say the admin pages and /mcp are open — step 5 deals with that.

INFO:     mcp-api-gateway 0.1.0
config file:  /home/you/gateway/config.toml
listening on: http://127.0.0.1:8080
data dir:     /home/you/gateway/data
key file:     /home/you/gateway/data/keys.json
mcp endpoint: /mcp (open)
admin login:  disabled
usage export: off
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8080 (Press CTRL+C to quit)

2. Register a service

Open http://127.0.0.1:8080/ui/servers and press Add a server.

Paste a spec URL — the Swagger Petstore is a good first one, because it needs no credentials:

https://petstore3.swagger.io/api/v3/openapi.json

Press Fetch the spec. Nothing has been saved yet: the gateway downloads the document, reads every operation out of it, and shows you what it found.

3. Choose the operations

The picker lists all 19 operations with the tool name each would get. Two things worth doing before saving:

  • Set the tool prefix to something short — petstore. It leads every tool name from this service, and the default derived from the document's title (swagger_petstore_-_openapi_3_0) makes for long tool names.
  • Untick anything you would not want called. Everything is selected by default; a model that can see deletePet can call deletePet.

Press Save the server. The list page comes back with the service registered and its operations exposed.

4. Point an MCP client at it

The endpoint is http://127.0.0.1:8080/mcp, streamable HTTP, no token yet.

For Claude Code:

claude mcp add --transport http gateway http://127.0.0.1:8080/mcp

For a client configured by file, the shape is the usual one:

{
  "mcpServers": {
    "gateway": {
      "type": "http",
      "url": "http://127.0.0.1:8080/mcp"
    }
  }
}

Once you have set a token (step 5), add it as a header:

claude mcp add --transport http gateway http://127.0.0.1:8080/mcp \
  --header "Authorization: Bearer a-long-random-string"
{
  "mcpServers": {
    "gateway": {
      "type": "http",
      "url": "http://127.0.0.1:8080/mcp",
      "headers": { "Authorization": "Bearer a-long-random-string" }
    }
  }
}

A client that only speaks stdio needs a bridge — npx -y mcp-remote http://127.0.0.1:8080/mcp is the usual one — but prefer a native HTTP client where you have the choice.

The client should now list 19 tools named petstore__addPet, petstore__getPetById, and so on. To check without a client at all:

curl -sS -X POST http://127.0.0.1:8080/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'
event: message
data: {"jsonrpc":"2.0","id":1,"result":{"capabilities":{"experimental":{},"tools":{"listChanged":true}},"protocolVersion":"2025-06-18","serverInfo":{"name":"mcp-api-gateway","version":"0.1.0"}}}

Calling one of those Petstore tools reaches the public demo API, which is frequently down; when it is, the call comes back as isError: true carrying the upstream's own status line and body. That is the gateway working — it reports what the upstream said rather than hiding it.

5. Before you leave it running

Add both doors to config.toml and restart:

[admin]
username = "admin"
password = "something-better-than-this"

[mcp]
auth_token = "a-long-random-string"

Both halves can also be set from /ui/configuration without a restart and without either secret ever reaching a file — the page stores a password hash and a token digest, never the values. mcp-api-gateway --reset-admin is the way back if the login is forgotten; a forgotten token locks out your MCP clients rather than you, and the same page opens the endpoint again.

Now /ui asks for a login and /mcp requires Authorization: Bearer …. See docs/security.md for what is still not protected — the SSRF gap in particular — and docs/configuration.md for storing the password as a hash and the token in the environment instead.

Putting an MCP server behind it

An upstream that already speaks MCP is registered by its endpoint rather than by a document. The one MCP server every reader has is this gateway's own: switch on the built-in Gateway server on the API Servers list — its tools configure the gateway, so do step 5 first — and its /mcp is an MCP server like any other.

Open http://127.0.0.1:8080/ui/mcp-servers and press Add an MCP server. Paste the endpoint:

http://127.0.0.1:8080/mcp

Choose the authentication the endpoint needs — for this one, the bearer token you set, or none if you have not yet — and press Connect and list tools. Nothing is saved: the gateway connects, asks the server what it is and what it offers, and shows the tools it listed. The picker is the same one as for an API, with the tool's own name where the method and path would be. Set the prefix, untick what you would not want called, and press Save the server.

The same thing by script is POST /api/v1/servers — behind the same login as the pages — with the body it takes for a document, except that kind says which and endpoint stands where spec_url would:

{"kind": "mcp", "endpoint": "http://127.0.0.1:8080/mcp", "tool_prefix": "mirror"}

From then on the server is refreshed, monitored, throttled and disabled by the same rules as an API — Refresh tools re-lists them, and a tool that appears after the first listing waits for somebody to tick it. The gateway holds one session open to each MCP server and reconnects when the endpoint or the credential is changed.

How it works

Registering. The spec is fetched (OpenAPI 3.0, 3.1, or Swagger 2.0 — the last is converted), $refs are resolved, every operation becomes a tool with a JSON Schema built from its parameters and request body, and the document is stored alongside them. For an MCP server the gateway connects to the endpoint instead, runs initialize and tools/list, and stores each tool with the input schema the server published.

Naming. A tool is <prefix>__<operationId> by default — or <prefix>__<tool name> for an MCP server's tool. The prefix is what keeps two services that both publish getUser apart, and both halves are editable per server and per operation. A collision is reported, never silently resolved.

Calling. Arguments are validated against the stored schema before anything leaves the process, path and query parameters are substituted, the server's stored credential is applied, and the request goes out through a shared client with a timeout and a response cap. Errors come back as isError: true with the upstream's status and body, because that is usually what a model needs in order to correct itself. A call to an MCP server's tool is forwarded as tools/call on a session the gateway keeps open to it, and its result — isError included — comes back as the upstream sent it.

Refreshing. Manually per server, or automatically on a global interval; for an MCP server a refresh is a fresh tools/list. Operations that changed are flagged, and new operations are never enabled by themselves — the server is marked Needs Attention and waits for somebody to decide.

Credentials. Stored encrypted with a key in data/keys.json, never rendered back into a page or an API response — only set / not set and the auth type.

Documentation

docs/install.md pip, pipx, checkouts, what the first run creates, upgrading
docs/configuration.md every setting, its default, its environment variable, its flag
docs/service-setup.md systemd, launchd, NSSM, Task Scheduler, Docker, reverse proxy
docs/security.md the deliberate v1 gaps, stated plainly, and how to run it anyway
docs/releasing.md for whoever publishes it: tags, trusted publishing, what CI checks
SPEC.md what the thing is meant to be, in full

Development

git clone <repository-url> mcp-api-gateway && cd mcp-api-gateway
python -m venv .venv
.venv/bin/pip install -e ".[dev]"
.venv/bin/pytest
.venv/bin/ruff check . && .venv/bin/ruff format --check . && .venv/bin/mypy src

Three layers. Unit tests; end-to-end scenarios in tests/e2e that drive the whole path — register a document, tick operations, list tools over /mcp, call one — against a stubbed upstream; and integration tests that run a real server on a real port and point the official MCP client at it. Nothing in the suite needs the network.

All of it runs on every commit against CPython 3.11 through 3.14 on Linux, macOS and Windows, and every commit also builds the wheel, installs it into an empty virtualenv, and starts it. docs/releasing.md covers the rest of the pipeline.

License

MIT. See LICENSE.

Release files for mcp-api-gateway 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mcp-api-gateway 0.2.0
File Size Uploaded
mcp_api_gateway-0.2.0.tar.gz 989.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for mcp-api-gateway 0.2.0
File Interpreter ABI Platform
mcp_api_gateway-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 1.5 MB

Release files / mcp_api_gateway-0.2.0.tar.gz

Download URL mcp_api_gateway-0.2.0.tar.gz
Size 989.8 kB
Tags Source
SHA-256 checksum
How to use checksums
eb919758869e0af45980056c77d815715608dcb9861fe74c43980008b1ac64cb
BLAKE2b-256 checksum
How to use checksums
f08f94edb287a313b891481e8ca3e3e351347addb60987174878ea6f0e06f298
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Sep 13, 2026.

Transparency log

Release files / mcp_api_gateway-0.2.0-py3-none-any.whl

Download URL mcp_api_gateway-0.2.0-py3-none-any.whl
Size 546.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1d91f346a9eece3e94d8be535189cfcd100c73496397bf9606d87fdbbd95070d
BLAKE2b-256 checksum
How to use checksums
250c052dc1f08da1f09fc697691118d16dff0d15866e28328a37f5e65c90703e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Sep 13, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page