MCP server that lets AI agents boot and drive QEMU virtual machines - keystrokes, screenshots, serial console, QMP.
qemu-mcp demonstrates solid tool design with 17 well-named, verb-prefixed tools (qemu_boot, qemu_screenshot, qemu_type, etc.). All tools have descriptions (avg ~250 chars, within 10-1024 baseline). Input schemas are present and typed for all tools. However, output schemas are undocumented, tools return strings or Image objects but lack formal schema declarations. Error handling is minimal; most tools lack recovery guidance. Parameter descriptions are good but some lack constraints (e.g., qemu_boot's 'extra_args' accepts arbitrary shell strings with only a vague validation note in the docstring, not in the parameter description itself). No tool annotations (readOnlyHint, destructiveHint) despite clear risk classifications. Composition is strong, tools chain well (boot → screenshot → type → stop). Naming follows verb_noun convention consistently.
Boot a QEMU VM headless and register it under `name`. name identifies this VM in all later calls (qemu_screenshot, qemu_stop, etc.) - it must be non-empty and contain no "/" or "\\" (it also names a temp directory and is passed to QEMU's -name flag). Provide at least one of: iso (bootable CD image), kernel (multiboot/bzImage, optionally with append/initrd), or disk (raw or qcow2 disk image, auto-detected; qcow2 is required for qemu_snapshot_save/qemu_snapshot_load). memory_mb must be positive. arch picks the qemu-system-<arch> binary (x86_64, i386, aarch64, riscv64...). machine is passed as QEMU's -M and is required on some archs - aarch64 and riscv64 have no default machine and need e.g. machine="virt". smp sets the number of virtual CPUs (QEMU -smp), for testing SMP-aware guest code; must be positive when given. accel is passed as QEMU's -accel (e.g. "kvm", "kvm:tcg" to fall back to software emulation if KVM isn't available, "hvf"/"hvf:tcg" on macOS, "whpx:tcg" on Windows) to opt into hardware acceleration; left unset, QEMU defaults to TCG (software emulation), which is correct but slow for anything beyond a trivial kernel. There's no host-capability probe for this - if the requested accelerator isn't available, QEMU itself fails immediately and that failure (including QEMU's own explanation) is surfaced the same way any other immediate boot failure is, in the raised error. cpu is passed as QEMU's -cpu (e.g. "qemu64,+avx", "max", "host" when accel="kvm"/"hvf") to select or customize the emulated CPU model - useful for testing guest code against a specific feature set (SSE4, AVX, a particular vendor's quirks) rather than whatever QEMU defaults to for the arch. extra_args is passed to QEMU verbatim, e.g. "-netdev user,id=n0 -device rtl8139,netdev=n0" (must be valid shell-style quoting, e.g. no unbalanced quotes). extra_args may not contain a flag qemu_boot already sets itself: -name, -m, -display, -qmp always; -M/-machine, -cdrom/-boot, -kernel, -append, -initrd, -smp, -accel, -cpu when the corresponding param above is also given. -drive is exempt from this check even when disk is given - QEMU allows repeated -drive flags, so extra_args can add further disks, e.g. extra_args="-drive file=data.img,format=raw" for a second drive alongside disk (pointing extra_args's -drive at the same file as disk will still fail, but as a QEMU image-locking error at boot, not a pre-flight one). -chardev/-serial are similarly exempt except for the exact chardev qemu_boot's own serial console uses (id=serial0 / chardev:serial0) - extra_args can add an unrelated -chardev or a second -serial (e.g. a COM2 backed by another chardev) freely, but reusing id=serial0 or pointing another -serial at chardev:serial0 is rejected, since that's the pair qemu_serial/qemu_serial_send depend on. qmp_connect_timeout_s bounds how long to retry connecting to QEMU's QMP socket after launch (raise it on a slow/loaded host where QEMU's first launch is slow, e.g. under AV scanning). qmp_read_timeout_s bounds how long any single QMP command (including ones issued by other qemu_* tools for this VM, like qemu_qmp or qemu_snapshot_save) waits for a response - raise it if you expect a command that can legitimately run long. Both timeouts must be positive. The VM keeps running until qemu_stop; serial output is captured continuously.
Output schemas undocumented. Tools return Image, str, or dict but lack formal schema declarations. LLMs cannot infer downstream field names or structure for chaining.
No tool annotations (readOnlyHint, destructiveHint, idempotentHint). Tools are classified by risk (WRITE, DESTRUCTIVE, READ_ONLY) but this metadata is not exposed in the MCP schema, limiting agent safety planning.
Minimal error recovery guidance. Tools raise exceptions (e.g., TimeoutError, QMPError) but descriptions lack actionable next steps. LLMs cannot self-correct without explicit guidance.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 61 | 2026-07-28+ | v2 |
Press a key or chord: "enter", "esc", "f12", "ctrl-alt-f2", "ctrl-c"... Parts are joined with "-" and pressed together. A literal "-" key is written "--" (e.g. "ctrl--" for ctrl + minus). Use qemu_type for plain text.
List all running VMs.
Move the absolute pointer and optionally click. x and y are fractions of the screen width/height (0.0 = left/top, 1.0 = right/bottom) - read them off a qemu_screenshot. button is "left", "right", or "middle" for a full click at that position; omit it to just move the pointer. Needs the guest to have an absolute pointer (USB tablet or PS/2 mouse) to track it - most GUIs have one by default.
Send a raw QMP command to the VM. QMP (QEMU Machine Protocol) is QEMU's JSON-based control interface. This tool lets you send any QMP command directly - useful for advanced control or querying VM state. command is the QMP command name (e.g. "query-status", "query-block"); arguments are passed as keyword arguments and converted to JSON. Returns the QMP response as a JSON string. Raises QMPError if the command fails.
Capture the VM's display as a PNG image. Works headless - the guest's VGA framebuffer is captured via QMP screendump. Use it to see boot screens, kernel panics, GUIs, or a text console that doesn't write to serial. Raises a clear error (instead of an opaque image-decoding failure) if the VM exits while the screendump is in flight.
Read the VM's serial console output. Serial output is captured continuously from boot onwards. tail_lines trims the output to the last N lines (useful for large logs); omit it to read the entire log. Works even after the VM has exited, so you can read the final output of a crashed guest.
Send text to the VM's serial console. Useful for sending input to a serial-based login prompt or shell. The text is sent as-is; use "\n" for newlines. The VM must be running.
Delete a snapshot from the VM's disk.
List all snapshots saved in the VM's disk.
Load a snapshot, restoring the VM to the state it was in when saved. The VM must be running (booted) - you can't load a snapshot into a stopped VM. After loading, the VM's RAM and all device state match the snapshot; any changes made since the snapshot was saved are lost.
Save a full VM snapshot (RAM + device state) under `tag`. Uses QEMU's internal savevm mechanism, which stores the snapshot inside the disk image itself - the VM's disk must be qcow2 (raw disks don't support snapshots). Snapshots are useful for testing: boot once, snapshot, then reload the snapshot to test different code paths without re-booting.
Stop a running VM. Sends SIGTERM (graceful shutdown) by default; if force=True, sends SIGKILL (immediate termination). Cleans up the VM's resources (QMP connection, serial console, temp directory). After this call, the VM is no longer available for other qemu_* tools.
Type text into the guest as keyboard input. "\n" presses Enter. Handles US-layout characters including shifted symbols. delay_ms is the gap between keystrokes - raise it (e.g. 80) if the guest drops characters during early boot. Must be non-negative.
Get the version of QEMU installed for a given architecture. Runs `qemu-system-<arch> --version` and returns its output. Useful for checking what's installed before committing to a qemu_boot call.
Wait for the VM's display to settle (stop changing), with timeout. Useful for waiting for boot to complete or a GUI to finish rendering before taking a screenshot. Polls the display every poll_interval_s seconds and considers it settled once stable_polls consecutive polls show an identical framebuffer hash. Raises TimeoutError if the display doesn't settle within timeout_s.
Wait for a string to appear in the serial console, with timeout. Polls the serial log every poll_interval_s seconds. Raises TimeoutError if the string doesn't appear within timeout_s. Useful for waiting for boot markers ("login:", "root@", etc.) before proceeding.
Parameter constraints in docstrings, not descriptions. qemu_boot's 'extra_args' validation rules are buried in the tool docstring, not in the parameter description field. LLMs may not parse docstring prose for constraints.
Sparse descriptions for discovery tools. qemu_snapshot_list and qemu_list have minimal descriptions (60 chars). LLMs need context on when to call them and what structure they reveal.