Initial commit: xmcp — OpenAPI-to-MCP server
MCP server generated from an OpenAPI document with FastMCP v4. Configurable via env vars (spec URL/file, transport stdio/http, host, port, API token), built with uv, multi-stage Dockerfile with non-root runtime.
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
# xmcp
|
||||
|
||||
Turns any [OpenAPI](https://spec.openapis.org/oas/latest.html) document
|
||||
(JSON or YAML) into a ready-to-run [MCP](https://modelcontextprotocol.io)
|
||||
server using [FastMCP](https://gofastmcp.com). One MCP tool is generated per
|
||||
API operation, and requests to the target API are authenticated with a token
|
||||
supplied via environment variables.
|
||||
|
||||
Everything is configured through environment variables: the spec location, the
|
||||
transport (stdio or streamable HTTP), the listen host/port, and the API token.
|
||||
|
||||
## Quick start
|
||||
|
||||
### Docker Compose (HTTP)
|
||||
|
||||
```sh
|
||||
cp .env.example .env # then edit OPENAPI_SPEC_URL / API_TOKEN etc.
|
||||
docker compose up --build
|
||||
```
|
||||
|
||||
The MCP endpoint is served at `http://localhost:8000/mcp`.
|
||||
|
||||
### Docker (HTTP)
|
||||
|
||||
```sh
|
||||
docker build -t xmcp .
|
||||
docker run --rm -p 8000:8000 \
|
||||
-e OPENAPI_SPEC_URL=https://petstore3.swagger.io/api/v3/openapi.json \
|
||||
xmcp
|
||||
```
|
||||
|
||||
### Docker (stdio)
|
||||
|
||||
For stdio the container must run with an open stdin (`-i`); logs go to stderr,
|
||||
so stdout stays protocol-clean:
|
||||
|
||||
```sh
|
||||
docker run -i --rm \
|
||||
-e MCP_TRANSPORT=stdio \
|
||||
-e OPENAPI_SPEC_URL=https://petstore3.swagger.io/api/v3/openapi.json \
|
||||
xmcp
|
||||
```
|
||||
|
||||
Example MCP client configuration for the stdio container:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"petstore": {
|
||||
"command": "docker",
|
||||
"args": [
|
||||
"run", "-i", "--rm",
|
||||
"-e", "MCP_TRANSPORT=stdio",
|
||||
"-e", "OPENAPI_SPEC_URL=https://petstore3.swagger.io/api/v3/openapi.json",
|
||||
"xmcp:latest"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Local development with uv
|
||||
|
||||
```sh
|
||||
uv sync
|
||||
OPENAPI_SPEC_URL=https://petstore3.swagger.io/api/v3/openapi.json uv run xmcp
|
||||
# equivalent: uv run python -m xmcp
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
| Variable | Required | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `OPENAPI_SPEC_URL` | yes | – | `http(s)://` URL or local file path of the OpenAPI document (JSON or YAML) |
|
||||
| `OPENAPI_SPEC_TOKEN` | no | – | Bearer token used only when fetching a remote spec document |
|
||||
| `API_BASE_URL` | no | spec `servers[0].url` | Overrides the target API base URL |
|
||||
| `API_TOKEN` | no | – | Credential sent to the target API on every tool call |
|
||||
| `API_AUTH_HEADER` | no | `Authorization` | Header used to carry the token (e.g. `X-API-Key`) |
|
||||
| `API_AUTH_SCHEME` | no | `Bearer` | Scheme prefix; set to an empty value to send the raw token |
|
||||
| `MCP_TRANSPORT` | no | `stdio` (`http` in the image) | `stdio` or `http` (`streamable-http` is an alias) |
|
||||
| `MCP_HOST` | no | `0.0.0.0` | Listen host for the HTTP transport |
|
||||
| `MCP_PORT` | no | `8000` | Listen port for the HTTP transport |
|
||||
| `MCP_SERVER_NAME` | no | spec `info.title` | Name advertised by the MCP server |
|
||||
| `HTTP_TIMEOUT` | no | `30` | Timeout in seconds for spec fetch and API calls |
|
||||
| `LOG_LEVEL` | no | `INFO` | Python log level |
|
||||
|
||||
Authentication examples: for a bearer token leave the defaults; for an API key
|
||||
header use `API_AUTH_HEADER=X-API-Key` with `API_AUTH_SCHEME` set to an empty
|
||||
string.
|
||||
|
||||
### Serving a local spec file
|
||||
|
||||
```sh
|
||||
docker run -i --rm \
|
||||
-v "$PWD/openapi.json:/spec/openapi.json:ro" \
|
||||
-e OPENAPI_SPEC_URL=/spec/openapi.json \
|
||||
xmcp
|
||||
```
|
||||
|
||||
If the document has no absolute `servers[0].url` (for example it only contains
|
||||
a relative path such as `/v1`), set `API_BASE_URL` to the API root explicitly.
|
||||
|
||||
### Base URL resolution
|
||||
|
||||
The base URL for generated tools is chosen as follows:
|
||||
|
||||
1. `API_BASE_URL`, if set.
|
||||
2. The first entry of the spec's `servers` list.
|
||||
3. If that server URL is relative (e.g. `/api/v3`) and the spec was fetched
|
||||
over HTTP(S), it is resolved against the spec's origin.
|
||||
4. Otherwise startup fails with a message asking for `API_BASE_URL`.
|
||||
|
||||
## Security notes
|
||||
|
||||
- The API token is attached to every request the generated tools make to the
|
||||
target API. It is never logged.
|
||||
- The MCP HTTP endpoint (`/mcp`) itself is **not** authenticated. Run it on a
|
||||
private network or behind a reverse proxy / gateway that enforces
|
||||
authentication before exposing it publicly.
|
||||
|
||||
## Project layout
|
||||
|
||||
```
|
||||
src/xmcp/config.py env var parsing and validation
|
||||
src/xmcp/spec.py spec loading, base URL resolution, API client
|
||||
src/xmcp/server.py FastMCP server construction and entrypoint
|
||||
Dockerfile multi-stage build (uv builder -> slim runtime, non-root)
|
||||
```
|
||||
Reference in New Issue
Block a user