boxes-mcp
Provides tools for managing GNOME Boxes virtual machines, including lifecycle operations (start, stop, reboot, suspend), snapshots, screenshots, keyboard and mouse input, and optional SPICE clipboard/transfer features.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@boxes-mcplist all my virtual machines"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
boxes-mcp
A local Model Context Protocol (MCP) server that enables compatible agents and developer harnesses to manage GNOME Boxes and virt-manager virtual machines through libvirt/virsh. It provides safe, reversible VM operations, snapshots, screenshots, bounded keyboard and mouse input, and capability-gated SPICE features.
The project intentionally targets GNOME Boxes' Linux libvirt/QEMU stack. VMware and VirtualBox are not currently supported; their display, input, guest-agent, clipboard, and drag/drop APIs have different trust and capability contracts and should be added as separate, evidence-backed providers rather than inferred from the libvirt implementation.
Contents
Related MCP server: kwin-mcp
Features
š„ļø VM Lifecycle Management - Start, stop, reboot, suspend, and resume VMs
šø Snapshot Operations - Create, list, revert, and delete VM snapshots
š VM Discovery - List and inspect all VMs with detailed information
š Dual Connection Support - Manages both
qemu:///system(virt-manager) andqemu:///session(GNOME Boxes) simultaneously; per-domain operations automatically route to the owning connectionš Safe Operations - Storage preservation by default, no destructive actions
šÆ GNOME Boxes Compatible - Works seamlessly with GNOME Boxes VMs
š±ļø Controlled Interaction - Screenshot, allowlisted keyboard, and typed mouse tools
š Capability-Gated SPICE - Optional native helper protocol for SPICE input, clipboard, and transfer
ā” Fast & Lightweight - Minimal overhead, direct virsh integration
Installation
Host prerequisites
Ubuntu 22.04/24.04 (or compatible Linux distribution)
libvirt-daemon-system, qemu-kvm installed
Node.js 18+ and npm
User in
libvirtandkvmgroupsvirshavailable onPATHfor lifecycle, screenshot, keyboard, and QMP fallback operations
SPICE-backed tools additionally require a SPICE display, a guest virtio-serial agent
channel, and a running spice-vdagent (or equivalent guest agent). Clipboard support
also depends on the guest desktop integration supplied by that agent. The standard
spice-vdagent session component is X11-oriented; a Wayland/Hyprland guest may have
the package and service running while still reporting capability-missing for
clipboard. Build the optional
native helper only when the host provides spice-client-glib, json-glib, and GLib
development files. For libvirt domains whose graphics XML uses listen type='none',
the helper uses libvirt's local graphics-FD API; no remote-viewer, virt-viewer, or
public SPICE URI is required:
npm run build:spice-helper
BOXES_SPICE_HELPER="$PWD/native/boxes-spice-helper" npm testThe helper is not installed or selected automatically. Set BOXES_SPICE_HELPER only
to the reviewed executable built from this repository or another process implementing
the versioned protocol below.
# Install dependencies
sudo apt install -y libvirt-daemon-system qemu-kvm virt-manager
# Add your user to required groups
sudo usermod -aG libvirt,kvm "$USER"
newgrp libvirtInstall from npm
The npm package includes a guided installer for local MCP hosts. It installs the Node
server only; libvirt, virsh, QEMU, and optional SPICE development libraries remain
host prerequisites.
# Detect installed MCP hosts and configure them
npx -y boxes-mcp@2.0.0 setup
# Or install the command globally
npm install --global boxes-mcp@2.0.0
boxes-mcp setupPreview configuration without writing files:
npx -y boxes-mcp@2.0.0 setup --dry-runConfigure one host explicitly when it is not discoverable on PATH:
npx -y boxes-mcp@2.0.0 setup --client codex
npx -y boxes-mcp@2.0.0 setup --client claude
npx -y boxes-mcp@2.0.0 setup --client openclawThe installer detects or can explicitly configure Codex, Claude Code, OpenClaw,
Antigravity, Gemini CLI, OpenCode, Cursor, Windsurf, VS Code, Pi, Cline, Zed, and
Goose. Use --client generic to print a portable JSON configuration for another
stdio-capable agent:
npx -y boxes-mcp@2.0.0 setup --client genericThe setup command writes only the selected MCP entry, creates a one-time
.boxes-mcp.bak backup before changing an existing config, uses atomic replacement,
and never installs operating-system packages or changes VM definitions. Restart the
configured agent or harness after setup. Run boxes-mcp doctor to inspect Node,
virsh, and detected hosts.
Optional host settings can be persisted during setup (--libvirt-uri accepts a
single URI or a comma-separated list, e.g. qemu:///system,qemu:///session):
npx -y boxes-mcp@2.0.0 setup \
--libvirt-uri qemu:///system,qemu:///session \
--input-backend auto \
--spice-helper /absolute/path/to/native/boxes-spice-helper \
--transfer-root /absolute/path/to/approved/filesThe native SPICE helper is not bundled as a universal binary. Build it on a compatible
Linux host after installing the host's SPICE/libvirt development packages, then pass
its reviewed absolute path with --spice-helper or BOXES_SPICE_HELPER.
Install from source
# Clone the repository for unreleased changes or development
git clone https://github.com/EF-Code/boxes-mcp.git
cd boxes-mcp
# Install dependencies
npm install
# Build the project
npm run build
# Run tests
npm test
# Configure a local checkout with the same guided installer
npm run setup:guided -- --client codexConfiguration
For a manual setup, add the server to your Claude Code config (~/.claude.json):
{
"mcpServers": {
"boxes": {
"command": "node",
"args": ["/absolute/path/to/boxes-mcp/dist/src/index.js"],
"env": {
"LIBVIRT_URI": "qemu:///system,qemu:///session",
"BOXES_INPUT_BACKEND": "auto"
}
}
}
}Available Tools
VM Management
Tool | Description | Parameters |
| List all VMs | - |
| Get VM details |
|
| Start a VM |
|
| Shutdown VM (graceful) |
|
| Reboot a VM |
|
| Suspend a VM |
|
| Resume suspended VM |
|
| Remove VM (keeps storage) |
|
| Get SPICE/VNC address |
|
Snapshot Management
Tool | Description | Parameters |
| List VM snapshots |
|
| Create snapshot |
|
| Revert to snapshot |
|
| Delete snapshot |
|
Display and interaction
Tool | Description | Parameters |
| Capture a running domain display as MCP image content | `nameOrUuid, screen?: number, backend?: auto |
| Send a bounded allowlisted Linux key sequence through virsh |
|
| Send typed move/button/click/scroll input |
|
| Explicit UTF-8 clipboard read/write through the SPICE helper |
|
| Experimental confined transfer plus pointer sequence and separate evidence |
|
Interaction tools never accept shell fragments, raw QMP JSON, arbitrary virsh flags, guest commands, or arbitrary transfer destinations. New operations require a running domain and return a stable capability/error code when their backend is not available.
Optional environment variables
Variable | Default | Purpose |
|
| Comma-separated list of libvirt connections. Every URI is scanned by |
|
| Default mouse backend preference: |
| unset | Explicit executable implementing the versioned SPICE helper protocol |
|
| Maximum one helper request duration |
| process temp directory | Controlled parent directory for temporary screenshots |
|
| Screenshot payload limit |
| unset | Required canonical host root for drag/drop source files |
|
| Transfer source size limit |
|
| UTF-8 clipboard payload limit |
BOXES_TRANSFER_ROOT is deliberately required rather than inferred. Paths are
canonicalized and symlink escapes, directories, and special files are rejected.
boxes.capabilities reports observed states. Configuration alone is not treated as
connected: use probeQmp: true and/or probeSpice: true when an external status
probe is required. SPICE clipboard and transfer require a connected guest agent;
boxes.drag_drop reports applicationAccepted: "unknown" unless an external viewer
harness supplies application-level evidence.
Keyboard input uses one fixed Linux virsh codeset. Public key names are
case-insensitive and canonicalized to uppercase, but each key may occur only once
per bounded chord. The allowlist is: ALT, BACKSPACE, CAPSLOCK, CTRL,
DELETE, DIGIT_0 through DIGIT_9, DOWN, END, ENTER, ESC, ESCAPE,
F1 through F12, HOME, INSERT, LEFT, META, NUMLOCK, PAGEDOWN,
PAGEUP, PAUSE, PRINT, RIGHT, SHIFT, SPACE, SUPER, TAB, UP,
and A through Z. Guest keyboard layout determines the resulting character;
the key allowlist does not guarantee text independent of that layout.
Usage Examples
With Claude Code
User: "List all my VMs"
Claude: [Uses boxes.list tool]
User: "Start ubuntu-24.04"
Claude: [Uses boxes.start with nameOrUuid="ubuntu-24.04"]
User: "Create a snapshot called 'before-update' for my fedora VM"
Claude: [Uses boxes.snapshots.create]Direct Usage
# Run the MCP server
LIBVIRT_URI=qemu:///system node dist/src/index.jsDevelopment
Project Structure
boxes-mcp/
āāā src/
ā āāā index.ts # MCP server entry point
ā āāā tools.ts # Side-effect-free tool registry and handler boundary
ā āāā libvirt.ts # virsh operations & parsers
ā āāā virsh.ts # Shared executable and libvirt URI arguments
ā āāā exec.ts # Safe command execution
ā āāā screenshot.ts # Controlled libvirt screenshot capture
ā āāā keyboard.ts # Allowlisted virsh send-key adapter
ā āāā mouse.ts/qmp.ts # Typed mouse actions and QMP fallback
ā āāā spice.ts # Versioned companion-helper protocol client
ā āāā clipboard.ts # Explicit SPICE clipboard orchestration
ā āāā transfer.ts # Confined host-file validation
ā āāā drag-drop.ts # Experimental transfer/input coordination
ā āāā *.test.ts # Unit tests
āāā systemd/
ā āāā boxes-mcp.service # Systemd user service
āāā dist/ # Compiled JavaScript
āāā coverage/ # Test coverage reports
āāā package.json
āāā tsconfig.json
āāā vitest.config.tsTesting
# Run all tests
npm test
# Run tests in watch mode
npm run test:watch
# Generate coverage report
npm run test:coverageLocal test coverage: the current checkout runs 95 passing tests and 9 gated live tests skipped by default. The default suite is safe to run without libvirt access.
exec.ts: 100% statementslibvirt.ts: 81.3% statements, 92.85% branchesInteraction validation, command construction, QMP response mapping, artifact cleanup, helper framing, capability discovery, and path-confinement tests
Run the explicit local native-helper process checks with:
npm run test:spice-helperRun the disposable-VM suite only with all three safety variables set:
BOXES_INTEGRATION=1 \
BOXES_TEST_VM=an-explicit-disposable-domain \
BOXES_TEST_VM_DISPOSABLE=1 \
npm run test:integrationThe live suite never selects a listed VM, changes VM definitions, or stops a guest
service itself. Guest-agent disconnect coverage requires the operator to manually
disconnect spice-vdagent in the explicitly disposable guest and add
BOXES_TEST_AGENT_DISCONNECTED=1; never do this to a non-disposable guest.
The default suite is mocked/local: it does not prove that QMP, SPICE, clipboard, or drag-and-drop works against a real VM. Live tests must be opt-in and target a specifically named disposable VM with snapshots; no arbitrary first-listed domain is ever selected by the interaction tools.
Building
# Build TypeScript
npm run build
# Watch mode for development
npm run devOptional systemd user service
The checked-in unit is intended for a source checkout. It is not needed when the server is launched by an agent's MCP configuration or installed globally with npm. Install it as a user service for automatic startup after building the checkout:
BOXES_MCP_DIR="$(pwd)"
NODE_BIN="$(command -v node)"
mkdir -p ~/.config/systemd/user
cp systemd/boxes-mcp.service ~/.config/systemd/user/
sed -i \
-e "s|/usr/bin/node|$NODE_BIN|g" \
-e "s|%h/projects/boxes-mcp|$BOXES_MCP_DIR|g" \
~/.config/systemd/user/boxes-mcp.service
systemctl --user daemon-reload
systemctl --user enable --now boxes-mcp
journalctl --user -fu boxes-mcpSecurity Considerations
ā Sandboxed Execution: Uses Node.js
execFilewith timeout and buffer limitsā No Arbitrary Commands: Only predefined virsh operations allowed
ā Typed Input Boundary: QMP commands and SPICE operations are internal enums with validated arguments
ā Bounded Payloads: Key counts, hold durations, coordinates, scroll deltas, screenshots, clipboard, and transfers are capped
ā Path Confinement: Drag/drop sources must remain beneath
BOXES_TRANSFER_ROOTafter canonicalizationā Storage Preservation: VM storage not deleted by default
ā LIBVIRT_URI Isolation: Respects environment-specified libvirt connections; per-domain operations resolve the owning connection before acting, so a same-named domain on another connection is never targeted by mistake
ā ļø Permissions Required: User must have libvirt group membership
ā ļø Network Exposure: Not designed for remote access without additional security
ā ļø Expanded Control Surface: Screenshots and guest clipboard data are untrusted; keep the MCP server on local stdio
ā ļø SPICE Helper Trust: The helper executable is an explicit host dependency and must not log credentials, clipboard contents, or file contents
SPICE helper protocol
The TypeScript server starts one persistent helper child and sends newline-delimited version-1 JSON requests over stdin, correlating responses by request ID. The helper is called with an explicit executable path and no caller-controlled arguments. The request envelope is shaped like:
{
"version": 1,
"id": "request-123",
"operation": "clipboard.read",
"domain": "guest-name",
"display": { "uri": "spice://127.0.0.1:5900" },
"arguments": { "selection": "clipboard", "maxBytes": 1048576 },
"libvirtUri": "qemu:///session"
}libvirtUri is optional and carries the resolved libvirt connection for the target
domain; when present, the helper prefers it over its own LIBVIRT_URI environment
value. This keeps libvirt-fd transport working when the server is configured with
multiple comma-separated connections.
Supported operation names are internal (status, mouse, clipboard.read,
clipboard.write, file.transfer, and drag-drop). A helper error is mapped to a
stable MCP error such as SPICE_AGENT_DISCONNECTED, SPICE_CAPABILITY_MISSING, or
SPICE_UNAVAILABLE. Payloads, lines, pending requests, transfer sizes, clipboard
bytes, and operation time are bounded. Progress events never complete a request.
The helper does not log clipboard contents, file contents, SPICE tickets, or
credentials.
Capability matrix
Capability | Libvirt/virsh | QMP fallback | SPICE helper |
Screenshot | Implemented via | Not used | Adapter reserved, unavailable without helper |
Keyboard | Implemented via allowlisted | Not used | Not used |
Mouse | Not used | Typed | Selected by |
Clipboard | Not available | Not available | Real agent protocol in native helper; Wayland/Hyprland guests may report |
File transfer | Not available | Not available | Real SPICE async file-copy path in native helper; live transport completion observed when the guest agent advertises it |
Drag-and-drop | Not available | Not available | Experimental transfer + pointer evidence; application acceptance remains unknown |
Clipboard support depends on guest desktop integration. The current SPICE guest
agent is X11-oriented, so Wayland guests such as Hyprland/Omarchy may report
SPICE_CAPABILITY_MISSING even when spice-vdagent is installed and running. Mouse
and file transfer can still work independently.
Troubleshooting
No VMs Listed
# Check libvirt URI
virsh -c qemu:///system list --all
virsh -c qemu:///session list --all
# Verify permissions
groups # Should include 'libvirt' and 'kvm'Permission Denied
# Re-add to groups and re-login
sudo usermod -aG libvirt,kvm "$USER"
# Then logout/login or:
newgrp libvirtVMs Not Showing in Boxes
By default the server scans both the system connection (virt-manager) and the user
session connection (GNOME Boxes), and boxes.list reports which connection each
domain was found on. Verify what each connection sees:
virsh -c qemu:///system list --all
virsh -c qemu:///session list --allTo restrict the server to specific connections, set LIBVIRT_URI to one URI or a
comma-separated list:
LIBVIRT_URI=qemu:///session node dist/src/index.js # GNOME Boxes only
LIBVIRT_URI=qemu:///system,qemu:///session node dist/src/index.js # both (default)Domain names may contain spaces (for example Kali Live); they are parsed and
resolved correctly.
SPICE capability errors
If virsh domdisplay reports No graphical display found and the domain XML has
<graphics type='spice'><listen type='none'/></graphics>, that is an intentional
libvirt configuration with no public listener. Do not invent a port or change the VM
definition just to obtain a viewer URI. With the native helper configured, boxes-mcp
uses the internal spice+libvirt-fd://local transport and asks libvirt for a graphics
FD for each SPICE channel. The helper must use the same libvirt connection as the MCP
process:
LIBVIRT_URI=qemu:///session npm run build:spice-helper
BOXES_SPICE_HELPER="$PWD/native/boxes-spice-helper" \
LIBVIRT_URI=qemu:///session node dist/src/index.jsThe domain must be running, the helper must be linked against libvirt and
spice-client-glib, and the guest must expose the virtio SPICE agent channel. A
connected agent may still lack clipboard capability; inspect boxes.capabilities with
probeSpice: true instead of inferring support from the XML alone.
Use boxes.capabilities with probeSpice: true and inspect the returned state:
configured: a reviewed helper and SPICE endpoint are configured, but connection proof has not been requested;connecting: the helper observed an incomplete channel set;connected: the required channels are connected;agent-disconnected: the guest agent is not connected;capability-missing: the backend, channel, helper, or guest capability is absent.
For example, a connected guest agent that supports file transfer but does not announce
clipboard is capability-missing, not agent-disconnected. To enable clipboard, the
guest must have its distribution's spice-vdagent service installed, running in the
desktop session, and connected through the virtio SPICE agent channel. On a
Wayland/Hyprland desktop, verify that the distro's agent actually supports that
compositor; an active service alone is not proof. The live Omarchy guest had
spice-vdagent 0.23.0-1 and an active user service, but logged xrandr output ID NOT FOUND and no owner for org.gnome.Mutter.DisplayConfig, so boxes-mcp correctly
returned SPICE_CAPABILITY_MISSING. Use an X11 guest session for the current
upstream agent, or provide a separately validated Wayland clipboard bridge. The
server does not install guest packages or start guest services automatically.
The persistent SPICE client also accepts an abort signal. Cancellation terminates the
current helper process, fails all pending operations deterministically, and allows the
next request to create a clean session; this is reported as OPERATION_CANCELLED.
Check the host dependencies and helper directly without sending input to a VM:
pkg-config --modversion spice-client-glib-2.0 json-glib-1.0 gio-unix-2.0
npm run build:spice-helperThe helper's local protocol test intentionally connects to 127.0.0.1:1 and
expects a typed unavailable/disconnected result. That is not live SPICE proof.
Roadmap
VM creation via
virt-installintegrationNetwork management (
virsh net-list, port forwarding)Storage pool information (
virsh vol-list)VM import from OVA/QCOW2
Remote libvirt connection support
Performance metrics and monitoring
Contributing
Contributions welcome! Please read CONTRIBUTING.md for guidelines.
Fork the repository
Create a feature branch (
git checkout -b feature/amazing-feature)Run tests (
npm test)Commit changes (
git commit -m 'Add amazing feature')Push to branch (
git push origin feature/amazing-feature)Open a Pull Request
License
This project is licensed under the MIT License - see the LICENSE file for details.
Acknowledgments
Built for Claude Code
Integrates with libvirt virtualization API
Support
Issues: GitHub Issues
Discussions: GitHub Discussions
Documentation: Project Wiki
Made with ā¤ļø for the Claude Code community
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseBqualityAmaintenanceEnables AI assistants to manage virtual machines, sandboxes, and dev environments through VirtualBox, Hyper-V, and Windows Sandbox, supporting VM lifecycle, ISO downloads, networking, and unattended installs.913MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to automate Linux desktop GUI by launching and interacting with Wayland applications in isolated virtual KWin sessions, or connecting to live desktops for collaborative automation.39MIT
- AlicenseNot gradedqualityDmaintenanceEnables AI models to securely query and manage virtual machines and virtualized resources via the libvirt API through the Model Context Protocol.1MIT
- AlicenseNot gradedqualityDmaintenanceEnables management of KVM/QEMU virtual machines on remote libvirt hosts via SSH, with tools for inspection, lifecycle management, snapshots, and cloning.1AGPL 3.0
Related MCP Connectors
Operate Linux, macOS and Windows from your LLM. Every action runs through an auditable allowlist.
Eyes and hands on real Windows PCs ā observe, click, type via Glasswarp API.
Control Unreal Engine to browse assets, import content, and manage levels and sequences. Automateā¦
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/EF-Code/boxes-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server