OpenAPI MCP
by pvliesdonk
README.md
# OpenAPI MCP
<!-- mcp-name: io.github.pvliesdonk/openapi-mcp -->
[](https://github.com/pvliesdonk/openapi-mcp/actions/workflows/ci.yml) [](https://codecov.io/gh/pvliesdonk/openapi-mcp) [](https://pypi.org/project/pvliesdonk-openapi-mcp/) [](https://pypi.org/project/pvliesdonk-openapi-mcp/) [](LICENSE) [](https://github.com/pvliesdonk/openapi-mcp/pkgs/container/openapi-mcp) [](https://pvliesdonk.github.io/openapi-mcp/) [](https://pvliesdonk.github.io/openapi-mcp/llms.txt) [](https://github.com/pvliesdonk/fastmcp-server-template)
A generic MCP server that builds its tools at runtime from any OpenAPI specification.
**[Documentation](https://pvliesdonk.github.io/openapi-mcp/)** | **[Config wizard](https://pvliesdonk.github.io/openapi-mcp/latest/configuration-generator/)** | **[PyPI](https://pypi.org/project/pvliesdonk-openapi-mcp/)** | **[Docker](https://github.com/pvliesdonk/openapi-mcp/pkgs/container/openapi-mcp)**
## Features
<!-- DOMAIN-START -->
`openapi-mcp` builds its MCP tools at runtime from any OpenAPI specification,
with no per-API code. Point one container image at a spec (URL or mounted file)
plus upstream credentials, and it exposes the operations of that API as MCP
tools via FastMCP's `OpenAPIProvider`.
Intended for **simple** APIs. Large or complex APIs, or ones using
`oauth2`/`openIdConnect`/`mutualTLS` upstream auth, are better served by a
purpose-built sibling. See
[`docs/superpowers/specs/2026-07-04-openapi-generic-wrapper-design.md`](docs/superpowers/specs/2026-07-04-openapi-generic-wrapper-design.md)
for the design and [`.env.example`](.env.example) for the full `OAPI_*` contract.
```bash
docker run --rm \
-e OAPI_SPEC_URL=https://api.example.com/openapi.json \
-e OAPI_SECURITY_APIKEYAUTH=your-key \
ghcr.io/pvliesdonk/openapi-mcp
```
<!-- DOMAIN-END -->
## What you can do with it
<!-- DOMAIN-START -->
The tool surface is whatever the mounted OpenAPI spec defines: each operation
in the spec becomes one MCP tool, named by its `operationId`. The concrete
prompts depend on the API you wrap. With a spec mounted, you can ask Claude to:
- **Call an operation directly:** "List the open orders" runs the spec's
`list_orders` operation and returns the response.
- **Chain operations:** "Find the customer named Acme, then show their most
recent invoice" composes a search operation with a lookup operation.
- **Confirm the deployment:** "Which server version is running?" calls the
built-in `get_server_info` tool.
Because tools are generated from the spec, the exact tool names and working
prompts match your API. Point the server at a different spec and the tool
surface changes with no code edits.
<!-- DOMAIN-END -->
<!-- ===== TEMPLATE-OWNED SECTIONS BELOW — DO NOT EDIT; CHANGES WILL BE OVERWRITTEN ON COPIER UPDATE ===== -->
## Installation
### From PyPI
```bash
pip install pvliesdonk-openapi-mcp
```
If you add optional extras via the `PROJECT-EXTRAS-START` / `PROJECT-EXTRAS-END` sentinels in `pyproject.toml`, document them below:
<!-- DOMAIN-START -->
<!-- List optional extras and their purpose here (e.g. `pip install pvliesdonk-openapi-mcp[embeddings]`). Kept across copier update. -->
<!-- DOMAIN-END -->
### From source
```bash
git clone https://github.com/pvliesdonk/openapi-mcp.git
cd openapi-mcp
uv sync --all-extras --all-groups
```
### Docker
```bash
docker pull ghcr.io/pvliesdonk/openapi-mcp:latest
```
A `compose.yml` ships at the repo root as a starting point. Copy `.env.example` to `.env`, edit, and `docker compose up -d`.
To attach a remote Python debugger (development only; the protocol is unauthenticated), see [Remote debugging](docs/deployment/docker.md#remote-debugging).
### Linux packages (.deb / .rpm)
Download `.deb` or `.rpm` packages from the [GitHub Releases](https://github.com/pvliesdonk/openapi-mcp/releases) page. Both install a hardened systemd unit; env configuration is sourced from `/etc/openapi-mcp/env` (copy from the shipped `/etc/openapi-mcp/env.example`).
### Claude Desktop (.mcpb bundle)
Download the `.mcpb` bundle from the [GitHub Releases](https://github.com/pvliesdonk/openapi-mcp/releases) page and double-click to install, or run:
```bash
mcpb install openapi-mcp-<version>.mcpb
```
Claude Desktop prompts for required env vars via a GUI wizard, with no manual JSON editing needed.
For manual Claude Desktop configuration and setup options, see [Claude Desktop deployment](docs/deployment/claude-desktop.md).
## Quick start
```bash
openapi-mcp serve # stdio transport
openapi-mcp serve --transport http --port 8000 # streamable HTTP
```
For library usage (embedding the domain logic without the MCP transport), import from the `openapi_mcp` package directly. See the project's domain modules under `src/openapi_mcp/` for entry points.
### Server info
The server registers a built-in `get_server_info` tool (via `fastmcp_pvl_core.register_server_info_tool`) so operators can confirm the deployed version with a single MCP call. The default response carries `server_name`, `server_version`, and `core_version`. Servers that talk to a remote upstream wire upstream version reporting inside the `DOMAIN-UPSTREAM-START` / `DOMAIN-UPSTREAM-END` sentinel in `src/openapi_mcp/server.py`; see [`CLAUDE.md`](CLAUDE.md#server-info-tool-get_server_info) for the wiring pattern.
## Configuration
Core environment variables shared across all `fastmcp-pvl-core`-based services:
| Variable | Default | Description |
|---|---|---|
| `FASTMCP_LOG_LEVEL` | `INFO` | Log level for FastMCP internals and app loggers (`DEBUG` / `INFO` / `WARNING` / `ERROR`). The `-v` CLI flag overrides to `DEBUG`. |
| `FASTMCP_ENABLE_RICH_LOGGING` | `true` | Set to `false` for plain / structured JSON log output. |
| `OAPI_KV_STORE_URL` | `file:///data/state` | Persistent-state backend URL for pvl-core subsystems: `file:///path` (survives restarts), `memory://` (dev/ephemeral). |
Domain-specific variables go below under [Domain configuration](#domain-configuration).
## Authentication
Callers authenticate via a bearer token or OIDC (mutually exclusive). See the [Authentication guide](docs/guides/authentication.md) for setup, mapped multi-subject tokens, OIDC, and troubleshooting.
## Post-scaffold checklist
After `copier copy` and `gh repo create --push`:
1. **Fill in the DOMAIN blocks** (every section marked with a `DOMAIN` sentinel comment) in this README and in `CLAUDE.md`.
2. Configure GitHub secrets (see below).
3. Install dev + docs tooling: `uv sync --all-extras --all-groups`.
4. Install pre-commit hooks: `uv run pre-commit install`.
5. Run the gate locally: `uv run pytest -x -q && uv run ruff check --fix . && uv run ruff format . && uv run mypy src/ tests/`.
6. Push the first commit. CI should be green.
## GitHub secrets
CI workflows reference three repository secrets. Configure them via **Settings → Secrets and variables → Actions** or with `gh secret set`:
| Secret | Used by | How to generate |
|---|---|---|
| `RELEASE_TOKEN` | `release.yml`, `copier-update.yml` | Fine-grained PAT at <https://github.com/settings/personal-access-tokens/new> with `contents: write` and `pull_requests: write` (the `copier-update` cron opens PRs). Scoped to this repo. |
| `CODECOV_TOKEN` | `ci.yml` | <https://codecov.io>: sign in with GitHub, add the repo, copy the upload token from the repo settings page. |
| `CLAUDE_CODE_OAUTH_TOKEN` | `claude.yml`, `claude-code-review.yml` | Run `claude setup-token` locally and paste the result. |
```bash
gh secret set RELEASE_TOKEN
gh secret set CODECOV_TOKEN
gh secret set CLAUDE_CODE_OAUTH_TOKEN
```
`GITHUB_TOKEN` is auto-provided; no action needed.
## Local development
The PR gate (matches CI):
```bash
uv run pytest -x -q # tests
uv run ruff check --fix . && uv run ruff format . # lint + format
uv run mypy src/ tests/ # type-check
```
Pre-commit runs a subset of the gate on each commit; see `.pre-commit-config.yaml` for details, or [`CLAUDE.md`](CLAUDE.md) for the full Hard PR Acceptance Gates.
## Troubleshooting
### Moving a scaffolded project
`uv sync` creates `.venv/bin/*` scripts with absolute shebangs pointing at the venv Python. If you move the repo after scaffolding (`mv /old/path /new/path`), `uv run pytest` fails with `ModuleNotFoundError: No module named 'fastmcp'` because the stale shebang resolves to a different interpreter than the venv's site-packages.
**Fix:**
```bash
rm -rf .venv
uv sync --all-extras --all-groups
```
`uv run python -m pytest` also works as a one-shot workaround (bypasses the stale entry-script shim).
### `uv.lock` refresh after `copier update`
When `copier update` introduces new dependencies (such as a new extra added to `pyproject.toml.jinja`), CI runs `uv sync --frozen` which fails against a stale lockfile. Run `uv lock` locally and commit the refreshed `uv.lock` alongside accepting the copier-update PR.
## Links
- [Documentation](https://pvliesdonk.github.io/openapi-mcp/)
- [llms.txt](https://pvliesdonk.github.io/openapi-mcp/llms.txt)
- [FastMCP](https://gofastmcp.com)
- [fastmcp-pvl-core](https://pypi.org/project/fastmcp-pvl-core/)
<!-- ===== TEMPLATE-OWNED SECTIONS END ===== -->
## Domain configuration
<!-- DOMAIN-START -->
Domain environment variables use the `OAPI_` prefix. `openapi-mcp` derives its
tools from an OpenAPI spec at boot, so configure the spec source, upstream base
URL, timeout, and per-scheme upstream credentials. See
[`.env.example`](.env.example) for a copy-paste template and the
[configuration guide](https://pvliesdonk.github.io/openapi-mcp/latest/configuration/)
for the full contract.
| Variable | Default | Required | Description |
|---|---|---|---|
| `OAPI_SPEC_URL` | (none) | Exactly one of URL/PATH | URL of the OpenAPI spec, fetched at boot. |
| `OAPI_SPEC_PATH` | (none) | Exactly one of URL/PATH | Local or mounted spec file (JSON or YAML). |
| `OAPI_API_BASE_URL` | spec `servers[0].url` | No | Override the upstream base URL. |
| `OAPI_HTTP_TIMEOUT` | `30` | No | Upstream request timeout in seconds. |
| `OAPI_SECURITY_<SCHEMEKEY>` | (none) | If the scheme is referenced | Credential for a referenced security scheme, named by its uppercased key. |
Setting both `OAPI_SPEC_URL` and `OAPI_SPEC_PATH`, or neither, is a fail-loud
boot error. Upstream credentials cover `apiKey` (header or query), `http`
`bearer`, and `http` `basic` (a `user:pass` value); `oauth2`, `openIdConnect`,
and `mutualTLS` are out of scope and belong in a purpose-built sibling.
Domain-config fields are composed inside `src/openapi_mcp/config.py` between the `CONFIG-FIELDS-START` / `CONFIG-FIELDS-END` sentinels; env reads go through `fastmcp_pvl_core.env(_ENV_PREFIX, "SUFFIX", default)` so naming stays consistent.
<!-- DOMAIN-END -->
## Key design decisions
<!-- DOMAIN-START -->
- **Tools are derived from the spec at runtime.** Each spec operation becomes
one MCP tool through FastMCP's `OpenAPIProvider`, so pointing the server at a
different spec changes the tool surface with no code edits.
- **Two auth layers stay strictly separate.** Inbound auth (who may call this
server, inherited from `fastmcp-pvl-core`) and upstream auth
(`OAPI_SECURITY_*`, how this server authenticates to the wrapped API) never
mix.
- **Upstream credentials ride an `httpx.Auth` flow.** They are injected when the
request is dispatched rather than as client-level headers or query
parameters, so they survive the request construction `OpenAPIProvider`
performs and reach the wire for both header and query schemes.
- **Boot fails loud.** A missing or malformed spec, an ambiguous spec source,
a missing credential, or a non-positive timeout raises at startup instead of
surfacing as a per-request error later.
- **Scope is deliberately narrow.** Only `apiKey`, `http bearer`, and
`http basic` upstream schemes are supported, and a credential is required for
every referenced scheme (no "scheme A or B" alternatives). Larger or complex
APIs, `oauth2`/`openIdConnect`/`mutualTLS` auth, and mutually exclusive auth
alternatives belong in a purpose-built sibling.
See
[`docs/superpowers/specs/2026-07-04-openapi-generic-wrapper-design.md`](docs/superpowers/specs/2026-07-04-openapi-generic-wrapper-design.md)
for the full design rationale.
<!-- DOMAIN-END -->
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessResponsive