Xen Orchestra's Full REST API — 252 Tools from One DADL
Xen Orchestra has spent the last two releases turning its REST API into something you can build on. XO 6.4 (April 2026) marked /rest/v0/ as stable and added fine-grained RBAC v2; 6.5 added PATCH /vms/{id} for partial VM updates and a separate /vm-controllers collection; 6.6 (June 2026) continued the same trickle — scoped Storage and Network Administrator roles, writable backup repositories, and PATCH for VDIs and VIFs. In February, Vates also shipped a first-party MCP server bundled with XO 6.2 — seven tools, deliberately read-only, so teams can enable it on production from day one.
We took a different cut of the same API. The DADL for Xen Orchestra covers the entire 252-endpoint REST surface — read and write — as one YAML file. This post is the build log, and an honest answer to the obvious question: why two MCP surfaces for the same product?
What the REST API now covers
Section titled “What the REST API now covers”The XO REST API is no longer a thin read layer. As of 6.6 it exposes the full object graph — VMs (including PATCH partial updates and snapshot revert), VM controllers, VM templates, VM/VDI snapshots, hosts, pools, storage (SR/VDI/VBD, with PATCH rename and disk-resize on VDIs since 6.6), networking (VIF/PIF/PBD, with PATCH on VIFs since 6.6), hardware passthrough (PCI/PGPU/SM) — plus every lifecycle action: power, snapshot, clone, migrate, export/import, hotplug. On top of the objects sit the operational surfaces: tasks, backups (jobs, logs, repositories — writable since 6.6 — restore, schedules), real-time change events over Server-Sent Events, RBAC v2 (users, groups, acl-roles, acl-privileges), and host/pool maintenance (rolling reboot, rolling update, emergency shutdown).
That is a large API, and almost all of it mutates infrastructure. None of the write side is reachable through the first-party MCP server — by design, not by omission.
The numbers
Section titled “The numbers”DADL (xen-orchestra.dadl) | @xen-orchestra/mcp (first-party) | |
|---|---|---|
| Tools | 252 | 7 |
| Scope | full read and write | read-only by design |
| Coverage | every REST object + lifecycle action (incl. PATCH on VMs, VDIs, VIFs), backups with writable repositories, RBAC v2, SSE events, SDN traffic rules, dashboards, auth tokens | infrastructure summary, list/inspect pools / hosts / VMs, pool dashboard, documentation search |
| Create / start / migrate / delete? | yes | no — intentionally |
| Distribution | one 252-tool YAML file | Node server bundled with XO 6.2+ |
| Tracking a new API release | ~30 lines of YAML per endpoint | ships with the XO release |
| Governance layer | credentials, authorization, audit (via ToolMesh) | none — local, in-process |
The local validation, run from the registry checkout:
> cd dadl-registry && npm run validate✅ xen-orchestra.dadl — 252 toolsThe full definition is live and browsable at dadl.ai/d/xen-orchestra — the authoritative source, always current with the latest XO release. The DADL spec itself: dadl.ai/spec.
A worked example: a VM change, made undoable
Section titled “A worked example: a VM change, made undoable”The point of the write side is that it actually changes infrastructure — safely, with a trail. Here is a three-call round trip: find a running VM, snapshot it so the change is reversible, then patch it.
// 1. Find the VM by name (XO filter syntax, compact fields)const [vm] = await toolmesh.xen_orchestra_list_vms({ filter: "name_label:web-fra-01", fields: "uuid,name_label,tags"});
// 2. Snapshot first, so the change is undoableawait toolmesh.xen_orchestra_snapshot_vm({ id: vm.uuid, name_label: `pre-change ${new Date().toISOString().slice(0, 10)}`});
// 3. PATCH the live VM — tags is a hot field, applied while running (XO 6.5)await toolmesh.xen_orchestra_update_vm({ id: vm.uuid, tags: [...vm.tags, "managed:toolmesh"]});Three calls. No glue code. Steps 2 and 3 are exactly what a read-only MCP server cannot do — and the DADL transparently handles:
- the unusual cookie-based auth (the token is injected server-side as
Cookie: authenticationToken=…— the model never sees it); - the async-by-default action model — most writes return a task reference; you add
synconly for short operations and pollget_taskfor long ones (migrate, export) to avoid HTTP client timeouts; - the cold-vs-hot field distinction on
update_vm—memoryStaticMax,cpusStaticMax,secureBootneed a halted VM, whiletags,nameLabel, andcpusapply live (the example usestags, so it runs without downtime); - retries on transient failures and terminal classification for
404(“no such VM”) and RBAC403.
Same calls from Claude or any other agent. No Xen-Orchestra-specific code on the client side.
Read-only is a feature, not a gap
Section titled “Read-only is a feature, not a gap”The natural framing — “252 beats 7” — is the wrong one. The two surfaces answer different questions.
The first-party MCP server is read-only on purpose. It runs in-process, ships with XO, needs no external infrastructure, and cannot break anything. If the question is “let an assistant look at my infrastructure and answer questions about it,” that is the right shape, and you should use it. We are not trying to replace it — the DADL even includes a get_mcp_status tool that reports whether XO’s own built-in MCP server is enabled.
The DADL answers a different question: “let an agent operate my infrastructure — create, patch, snapshot, migrate, back up, provision RBAC — without handing it raw admin credentials.” That only makes sense with a control layer underneath. Through ToolMesh, every one of the 252 tools runs behind centralized credentials (the XO token never reaches the model), authorization (an agent can be granted list_vms and snapshot_vm but not delete_sr), and an audit log (every call recorded with what, when, and why). The write side is the whole point, and the governance layer is what makes the write side safe to expose.
Both definitions live in the same registry. They are complements: read-only discovery in-process, full lifecycle management through a governed gateway.
Tracking the API is a YAML diff
Section titled “Tracking the API is a YAML diff”XO 6.4 added RBAC v2; 6.5 added PATCH /vms/{id}, /vm-controllers, and a REST snapshot-revert endpoint; 6.6 added scoped Storage and Network Administrator roles, writable backup repositories, and PATCH for VDIs and VIFs. Reflecting each of those in the DADL is the same small unit of work: open xen-orchestra.dadl, add the tool block (~30 lines of YAML per endpoint), run npm run validate, open a PR, review the diff, merge. ToolMesh picks the new tools up on next reload. No SDK changes, no release to cut, no npm publish, no “please upgrade” to send users.
That is what every XO release looks like from here. 6.6 was seven new tool blocks and a version bump from 245 to 252 — an afternoon’s diff, not an event. The releases worth a second look are not the ones that add endpoints, but the ones that close a gap you had written down as a limitation.
Two concrete examples from 6.6:
- The release added
PATCH /vdis/{id}. An earlier version of this post listed “VDIs cannot be renamed via REST” under honest caveats — 6.6 closed that gap, and reflecting it was a singleupdate_vditool block (rename a disk or grow it), annotated “added in XO 6.6 — returns 404 on older builds” so an agent against an older host gets a clean terminal error instead of a silent failure. - Backup repositories became writable:
POST/PATCH/DELETE /backup-repositories. Three new tool blocks, the same shape as every other write endpoint, behind the same credentials, authorization, and audit — nothing about the gateway changed. The new built-in Storage Administrator and Network Administrator roles need no new tools at all; they compose out of the RBAC v2 surface the DADL already exposed.
We made the general version of this argument in an earlier post; XO is the concrete case study for a write-heavy API.
The context-window argument
Section titled “The context-window argument”There is a second reason the tool count matters less than it looks: Code Mode. Instead of injecting all 252 XO tool descriptions into the model’s context window, ToolMesh exposes two meta-tools (list_tools and execute_code). The model discovers what is available on demand and calls flat tool names from inside a sandboxed JavaScript runtime — toolmesh.xen_orchestra_list_vms, toolmesh.xen_orchestra_snapshot_vm, not a nested namespace.
The token cost is roughly constant: ~1 k tokens of meta-tool description, whether the registry holds one backend with 7 tools or 30 backends with 4,010. A 252-tool backend adds zero tokens to every Claude conversation that does not actively touch Xen Orchestra. That is also why the JavaScript in the worked example above is not an illustration — it is literally what the model writes to call the tools.
Honest caveats
Section titled “Honest caveats”The DADL is not a universal win. Real limits:
- The SSE event stream is half-declarative.
open_events_streamreturns a subscription ID and the DADL describes the subscribe handshake, but the Server-Sent Events stream itself is a streaming protocol — DADL describes REST, not long-lived streams. For polling task state,get_task?wait=truelong-polls cleanly; for a true real-time feed you are at the edge of what a declarative REST definition models. - Host power actions aren’t in the REST API. Hosts have no REST route for
start/shutdown/reboot/restart_toolstack— onlydisable,enable, andmanagement_reconfigureare exposed. For host power you use pool-levelrolling_reboot/rolling_updateor drop to the JSON-RPC API. We expose what exists; we cannot expose what does not. (The entry that used to sit here — “VDIs cannot be renamed via REST” — was retired when 6.6 shippedPATCH /vdis/{id}.) - SDN traffic rules need a Premium plugin. The six traffic-rule tools (
add/delete/update_network_traffic_rule,add/delete/update_vif_traffic_rule— the twoupdatevariants new in 6.6) drive XO’s SDN Controller — a plugin bundled with XOA Premium, not part of the documented core REST API. Without it loaded the routes return404, so the agent gets a clean terminal error rather than a silent no-op. The other 246 tools work against a stock REST API. - The write side needs privileges. Full coverage assumes an admin user or an RBAC v2 user with the right
acl-privileges. If you only need to look, the read-only first-party server is less setup and a smaller blast radius — use it. - Every call hops through ToolMesh. For interactive operation this is invisible; for high-throughput batch jobs against XO, calling the REST API directly is faster.
DADL is good at one specific thing — making a REST API surface available to agents quickly, with a real control layer underneath. For Xen Orchestra, that surface happens to be 252 endpoints of mostly-mutating operations, which is exactly the case where a governance layer earns its keep.
What is actually different
Section titled “What is actually different”The substantive question is not “DADL vs the first-party MCP server” — ToolMesh runs both, and they coexist in the same registry. The question is: what does it cost to give an agent the write side of an API, safely?
For a read-only server, the answer is “you don’t” — that is the deliberate trade. For a hand-coded read-write server, it costs an engineering cycle per endpoint: SDK shapes, handlers, tests, a release, an npm publish, user upgrades, plus the authorization and audit you have to build yourself. For a DADL behind ToolMesh, it costs one PR with a YAML diff, and the credentials, authorization, and audit come from the runtime.
That is the asymmetry. Not “everyone should ditch the first-party server” — keep it for read-only discovery. Just: when you need an agent to actually operate Xen Orchestra, the unit of work is a YAML diff, and the safety is in the layer underneath rather than in withholding the write tools.