xolotl
One proxy for your LLM subscriptions, AI gateways and MCP servers.
A self-hosted Rust proxy that gives coding agents one URL and one client key for Codex, Claude, Grok and OpenCode Go subscriptions, gateway providers and MCP servers, with every real credential kept in OpenBao.
Run with Docker
docker pull ghcr.io/wyrd-company/xolotl:latest
What it does
-
Give coding agents one URL and one client key for subscriptions, AI gateways and MCP servers.
-
Keep every real credential in OpenBao, never in the agents' containers.
-
Spread requests over several accounts per provider and move to the next when one reaches its quota.
-
Translate between the Anthropic Messages, OpenAI Responses and Chat Completions formats, so a client can use a model from another provider.
-
Serve one MCP server at /mcp with code-mode access to the upstream MCP servers a key's policy allows.
-
Set providers, models, MCP tools and limits per client key, and administer from a web UI or the CLI.
Install
brew install wyrd-company/tools/xolotl
brew install wyrd-company/tools/xolotl
docker pull ghcr.io/wyrd-company/xolotl:latest
Documentation
Use xolotl
Providers and keys
Clients and tools
Operate
Administer
Monitor and limits
Releases
Features
- NATS export: with
[export.nats], xolotl publishes each exported exchange to a NATS JetStream stream, and ClickHouse loads the stream with its NATS table engine. Rows published while ClickHouse is down wait in the stream; rows waiting while NATS restarts are held in xolotl’s queue and retried until NATS takes them. A row too large for a message has its bodies cut, or is refused and counted. Xolotl checks at startup that the stream exists and takesxolotl.exchanges.>. The new NATS export page gives the stream, consumer and table commands, permissions, sizing and alerts. The direct ClickHouse export is unchanged. Building from source needs Rust 1.89. - Anthropic and OpenAI API accounts: the new provider kinds
anthropicandopenaitake an API key (personal, project or service account) and serve the same endpoints as Claude and Codex subscription accounts, so a client can use either. An Anthropic account serves/v1/messagesand/v1/messages/count_tokensnatively, with the key inx-api-keyand the subscription OAuth beta flag dropped, so Claude Code requests and their cache breakpoints reach the API unchanged; an OpenAI account serves/v1/responsesand/v1/chat/completionsnatively. Alias a model to a subscription and an API account to move to the API account when the subscription is at its limit. In the config file, an Anthropic or OpenAI account can authenticate with workload identity federation (workload_identity) instead of a key: xolotl exchanges a JWT from a file for a service account access token and renews it before it expires. The Accounts page documents both. - Provider accounts: a provider is now one more kind of account. Add and remove Vercel, Mistral and OpenAI-compatible providers at runtime, on the web UI’s Add account screen or with
xolotl accounts add-provider, without editing the config file. The Accounts screen lists provider accounts next to pool accounts; the Providers & models screen is now Models. Config-file providers and their stored keys keep working. - The ClickHouse and NATS exports now cover every kind of account, not only the sessions of subscription accounts: provider accounts (Vercel, Mistral, OpenAI-compatible) and API key accounts are exported too, and so are requests without a session, such as embeddings, OCR and passthroughs. A new
account_kindcolumn names the kind of the account that served each row. Responses that are not text, such as audio, and passthrough uploads that are not JSON are exported without their bodies. A table made earlier keeps receiving rows without the new column; the ClickHouse and NATS export pages give theALTER TABLEthat adds it.
Fixes
- Codex shows its usage limits when the Codex pool runs out. When every Codex account is cooling down, the 429 now carries the
x-codex-*usage headers of the account that comes back first. A quota error at the start of a stream keeps the account’s usage headers and uses the error shape Codex reads. Before, Codex showed no limits at that point. The Codex client guide explains where Codex gets its limits through xolotl, and why/statushas none before the first turn.