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
An operator puts a policy, issues a client key into OpenBao, and the client lists the models its key can use.
An operator puts a policy, issues a client key into OpenBao, and the client lists the models its key can use.

What it does

  1. Give coding agents one URL and one client key for subscriptions, AI gateways and MCP servers.

  2. Keep every real credential in OpenBao, never in the agents' containers.

  3. Spread requests over several accounts per provider and move to the next when one reaches its quota.

  4. Translate between the Anthropic Messages, OpenAI Responses and Chat Completions formats, so a client can use a model from another provider.

  5. Serve one MCP server at /mcp with code-mode access to the upstream MCP servers a key's policy allows.

  6. Set providers, models, MCP tools and limits per client key, and administer from a web UI or the CLI.

Install

docker pull ghcr.io/wyrd-company/xolotl:latest

Documentation

Get started

Introduction

Use xolotl

Providers and keys

Clients and tools

Releases

Latest 0.6.1 Release notes on GitHub

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 takes xolotl.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 anthropic and openai take 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/messages and /v1/messages/count_tokens natively, with the key in x-api-key and the subscription OAuth beta flag dropped, so Claude Code requests and their cache breakpoints reach the API unchanged; an OpenAI account serves /v1/responses and /v1/chat/completions natively. 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_kind column 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 the ALTER TABLE that 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 /status has none before the first turn.
All releases