Files
xmcp/README.md
T
woggioni 8ee7b07b4b
CI / Build and push docker image (push) Successful in 1m14s
docker: base the image on alpine
Both build and runtime stages now use python3.13-alpine3.23 (uv image
for the builder, python image for the runtime), with BusyBox
addgroup/adduser for the non-root xmcp user. All native dependencies
ship musllinux wheels, so no compiler is needed in the builder.
2026-10-19 04:26:25 +00:00

143 lines
4.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.
## Releasing
Pushing a `release/*` tag triggers the Gitea Actions pipeline, which builds the
Docker image and pushes it to the Gitea container registry:
```sh
git tag release/0.1.0
git push origin release/0.1.0
```
This publishes `gitea.woggioni.net/woggioni/xmcp:0.1.0` and
`gitea.woggioni.net/woggioni/xmcp:latest`.
## 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 -> alpine runtime, non-root)
.gitea/workflows/build.yaml release pipeline (tag push -> docker build & push)
```