Documentation
Customer setup, operator playbook, and a small reference section. Read top to bottom on first setup; jump via the side nav for day-2 ops.
Sign up
Email hello@xanadu.run with your
team name and we'll mint a tenant_id on the spot (10
seconds, no credit card needed for the public beta). You'll get back a
ready-to-paste .mcp.json snippet.
Wire your orchestrator's .mcp.json
{
"mcpServers": {
"xanadu": {
"type": "http",
"url": "https://xanadu.run/mcp",
"headers": {
"X-Xanadu-Tenant-Id": "your_tenant_id"
}
}
}
}
Restart Claude Code / Cline / your orchestrator. You'll see three MCP tools available:
mcp__xanadu__dispatch_agent
mcp__xanadu__list_agents
mcp__xanadu__ledger_summary
From inside your session, just ask in plain language:
"use the code-reviewer agent via xanadu to review the recent diff"
BYOT — bring your own provider keys
Add X-Anthropic-Key and/or X-OpenRouter-Key
to your .mcp.json headers. Every dispatch then bills
against your provider quota; we only collect the per-token
routing fee. Keys are held in a per-request context for the
lifetime of the HTTP request and never persisted.
"headers": {
"X-Xanadu-Tenant-Id": "your_tenant_id",
"X-Anthropic-Key": "sk-ant-...",
"X-OpenRouter-Key": "sk-or-v1-..."
}
The header override is gated by the agent's secrets:
allowlist — a key for a provider the dispatched agent isn't configured
for is silently dropped. That stops a hostile client from tricking
a Claude-only agent into making OpenRouter calls.
Available agents
| name | role | model |
|---|---|---|
code-reviewer | focused review, verdict line | anthropic/claude-sonnet-4.6 |
refactorer | behavior-preserving refactor | anthropic/claude-sonnet-4.6 |
test-writer | tests matching project conventions | anthropic/claude-sonnet-4.6 |
kimi-reviewer | same review duty via OpenRouter → Kimi K2.6 | openrouter/moonshotai/kimi-k2.6 |
Operator: prerequisites
You're running Xanadu in hosted mode on Fly (or any container host) and taking customers. Required env / secrets:
OPENROUTER_API_KEY— foropenrouter/*models via opencodeANTHROPIC_API_KEY— foranthropic/*models via the Claude CLI (and the currently-working billing path on Fly firecracker VMs)XANADU_REQUIRE_TENANT=1— hard-fail any dispatch missing atenant_idXANADU_ADMIN_TOKEN— bearer secret forPOST /admin/tenants(unset → endpoint returns 503)CLAUDE_CODE_SIMPLE=1— forces Claude CLI into API-key auth instead of interactive OAuth (already baked into the Docker image)
After secrets land, flyctl ssh console -C "xanadu doctor" should
report OK on every check.
Onboard a customer
Two commands from inside the running container:
flyctl ssh console -a xanadu-mcp
xanadu tenants add cust_42 \
--name "Customer Name" \
--contact "alice@customer.example" \
--markup 0.10 \
--monthly-cap 500.00 \
--tenants /data/tenants.json
xanadu tenants config cust_42 \
--tenants /data/tenants.json \
--format text
The second command prints the customer-facing setup blurb plus the
exact .mcp.json blob — copy, email, done.
TenantRegistry.reload_if_changed() stat()s the file on every
dispatch, so the next inbound call serves the new tenant — no restart.
Or via HTTP (signup form / Stripe webhook / CF Worker)
curl -X POST https://xanadu.run/admin/tenants \
-H "Authorization: Bearer $XANADU_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Customer Name","markup_rate":0.10,"monthly_budget_usd":500.0}'
# Response includes the pasteable .mcp.json blob
# {"tenant_id":"cust_abc123","mcp_json":{...}}
Per-tenant visibility
xanadu tenants info cust_42 --tenants /data/tenants.json
# title verdict: "clean" / "93% used" / "OVER CAP"
# body: config + MTD + remaining + failures-this-month + last 5 dispatches
xanadu ledger by-tenant --current-month --tenants /data/tenants.json
# Sorted by billed spend so loudest customers float to the top
Caps that actually enforce
Per-agent and per-tenant monthly_budget_usd are enforced at
dispatch time. Two checks fire:
- Pre-sizing short-circuit: if MTD already meets/exceeds the cap, refuse before paying for sizing or spawning the backend.
- Post-estimate gate: if MTD +
est_max_usdwould exceed, refuse. Held underledger.exclusive_lock()so concurrent dispatches can't both pass when only one fits.
Refusals write a structured budget_exceeded ledger row
with the cap, MTD, and tenant_id; the customer sees a clear
BudgetExceededError at the MCP layer.
Invoicing flow
# 1. Pre-flight: catches markup drift, MTD over cap, orphan tenants.
xanadu billing audit --current-month --tenants /data/tenants.json
# 2. Dry-run the Stripe push — see what would POST.
xanadu billing push --current-month --map /etc/xanadu/si_map.json --tenants /data/tenants.json
# 3. Commit when audit is clean.
xanadu billing push --current-month --commit --map /etc/xanadu/si_map.json
si_map.json is {tenant_id: subscription_item_id}.
push refuses to send if any tenant in the window lacks a
Stripe SI mapping — silent skips were the worst billing failure mode.
Deprovision a tenant
xanadu tenants remove cust_42 --yes --tenants /data/tenants.json
Hot-reload removes them from the live registry immediately. Existing
ledger rows are kept (append-only) and show up under
(unknown) in subsequent by-tenant rollups so the
final invoice can still be audited.
Doctor — start every triage here
flyctl ssh console -a xanadu-mcp -C "xanadu doctor"
Walks the deploy and flags every misconfiguration that has caused production pain. Hosted-mode-relevant checks:
tenants_file— exists + parseshosted_mode_coherence— strict-tenant mode + at least one usable provider keyclaude_code_simple— Claude CLI auth modeapi_keys— at least one provider key presentbackend_cli—claudeand/oropencodeon PATH
MCP tools
| tool | does |
|---|---|
dispatch_agent | Run an agent with a task; returns text + cost + dispatch_id. |
list_agents | Catalog of available agents (name, description, default_model, budget). |
ledger_summary | Aggregate ledger across all dispatches; totals by agent / model / status / tenant. |
HTTP headers
| header | role |
|---|---|
X-Xanadu-Tenant-Id | Required in XANADU_REQUIRE_TENANT mode. Bound to a request-scoped ContextVar; dispatch_agent picks it up automatically. |
X-Anthropic-Key | Optional BYOT. Customer's Anthropic key forwarded into the backend subprocess for this request only. |
X-OpenRouter-Key | Optional BYOT. Same, for OpenRouter. |
Authorization: Bearer … | Only used by POST /admin/tenants; auth is timing-safe via secrets.compare_digest. |
Built-in model pricing
Used to budget-gate before dispatch. When the backend reports a provider-billed cost, we bill on the provider's number instead and keep this only for estimation.
| model | $/M in | $/M out |
|---|---|---|
anthropic/claude-opus-4.7 | 5.00 | 25.00 |
anthropic/claude-sonnet-4.6 | 3.00 | 15.00 |
anthropic/claude-haiku-4.5 | 1.00 | 5.00 |
openrouter/moonshotai/kimi-k2.6 | 0.684 | 3.42 |
openrouter/deepseek/deepseek-v4-pro | 0.435 | 0.87 |
openrouter/qwen/qwen3-coder-next | 0.11 | 0.80 |