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

4.6 KiB
Raw Blame History

xmcp

Turns any OpenAPI document (JSON or YAML) into a ready-to-run MCP server using FastMCP. 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)

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)

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:

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:

{
  "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

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

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:

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)