AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
MCP verified MIT Self-run

QMP MCP

mcp-r0guesch0lar-qmp-mcp · by r0gueSch0lar

An MCP server that lets an AI agent safely build, boot, drive, and tear down a single QEMU VM through a validated hardware spec — with allowlisted storage, a command policy, and a browser viewer. Ships in TypeScript and Rust.

No reviews yet
0 installs
39 views
0.0% view→install

Install

$ agentstack add mcp-r0guesch0lar-qmp-mcp

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/mcp-r0guesch0lar-qmp-mcp)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
2mo ago

Declared compatibility

Claude CodeClaude DesktopCursorWindsurf

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of QMP MCP? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

qmp-mcp

[](https://github.com/r0gueSch0lar/QMP-MCP/actions/workflows/ci.yml)

qmp-mcp is a Model Context Protocol (MCP) server that gives an AI agent the controls of a single QEMU virtual machine. The agent describes the hardware it wants; the server builds that machine, boots it, and exposes a set of tools to drive it — pause and resume it, reset it, watch its screen, send it low-level QEMU commands, react to its events, and tear it down when it's finished.

The whole design rests on one idea: the tools are the boundary. The agent never hands raw arguments to QEMU or reaches into your filesystem. It fills in a structured, validated description of the machine; the server turns that into a locked-down QEMU command line and mediates every request. Everything the agent can touch — disk images, boot media, the commands it can run against the live VM, the ports it can open — passes through allowlists you control. The VM is the blast radius, and the tools are the walls.

It ships as two interchangeable implementations — one in [TypeScript](typescript/), one in [Rust](rust/) — that behave identically. This page explains what the server is and how it thinks; the per-implementation READMEs cover installing, running, and deploying each one.

> New to the vocabulary? [CONTEXT.md](CONTEXT.md) is the one-page glossary. The words > below — Instance, Guest, Hardware Spec, Command Policy, Image Store, Viewer > — each mean something specific, and this README uses them deliberately.

How it works

One machine at a time: the Instance

The server manages exactly one Instance — the running qemu-system-* process together with its hardware configuration and the live control connection to it. There's never more than one; asking to create another while one exists is refused. An Instance's life is tied to the server's: shut the server down and it tears the VM down with it, so nothing is left orphaned.

An Instance moves through a small lifecycle — from nothing, to starting, to running, optionally paused and back, to stopped, and back to nothing:

NONE → STARTING → RUNNING ⇄ PAUSED → STOPPED → NONE

If the underlying QEMU process exits on its own — a guest shutdown, a crash, an external kill — the server notices and reconciles back to NONE, so the next request starts from a clean slate.

The thing running inside the Instance — the operating system or workload — is the Guest. The server manages the machine; what you install and run on it is up to you and your agent.

Describing the machine: the Hardware Spec

The agent doesn't run QEMU. It submits a Hardware Spec — a structured, validated description of the machine it wants: machine type and CPU, how many vCPUs and how much memory, which disks and boot media, the network, the display, the accelerator. The server validates every field and generates the QEMU command line from it. The agent never supplies raw argv.

A spec is just the JSON arguments to create_instance:

{
  "machine": "q35",
  "cpu": "host",
  "vcpus": 2,
  "memoryMb": 2048,
  "accel": "auto",
  "disks": [{ "image": "root.qcow2" }],
  "cdrom": { "iso": "debian-13.iso" },
  "boot": "dc",
  "display": "vnc"
}

Validation isn't a formality — it's the safety boundary. Fields are range- and character-checked, and anything that could smuggle an extra option into the command line (a stray comma in a disk entry, say) is escaped or rejected. Sizes are capped, with ceilings you set on disk, memory, and vCPUs. If a spec is invalid, create_instance fails before QEMU is launched, with a message that says exactly what was wrong.

There is an escape hatch — extraArgs, which appends raw QEMU flags to the generated command line — but it's off unless you explicitly enable it. It's meant for trusted, single-tenant setups where you've decided the agent can be handed the keys.

Which architecture you emulate falls out of the machine: the server picks the emulator for you — q35/pc launch qemu-system-x86_64, while virt and the raspi* boards launch qemu-system-aarch64 — so switching architectures is just a different machine, no restart. QMP_MCP_QEMU_BINARY overrides that choice for every Instance (e.g. a custom build or qemu-system-riscv64), and accel: auto only uses KVM when the guest arch matches the host, falling back to TCG across architectures (ADR-0013).

Some machines don't boot from a disk at all. QEMU's Raspberry Pi boards (raspi3b and friends) have fixed hardware — a set CPU, core count, and RAM — and they expect the kernel handed to them directly rather than read off an SD-card bootloader. For those the spec grows three optional fields: kernel and dtb (a kernel image and device-tree blob, each a name in the Image Store) and appendCmdline (the kernel command line). The server emits -kernel/-dtb/-append and, because the board's hardware is fixed, omits -cpu/-smp/-m; attach the SD image with "interface": "sd" (sized to a power of two, or QEMU refuses it). These boards also have no PCI bus, so the default NIC can't attach — pick network.model usb-net (their USB NIC) or network.mode none; the server refuses an unattachable NIC up front rather than letting QEMU abort. None of this is Pi-only — any direct-kernel boot (a bare virt machine, say) can use kernel/appendCmdline alongside the usual CPU and memory settings.

How fast it runs: the accelerator

accel: "auto" (the default) uses hardware KVM when the host can reach a /dev/kvm, and otherwise falls back to TCG software emulation — reporting which it chose. Ask for kvm explicitly and it fails loudly if KVM isn't available; ask for tcg and you always get portable, zero-privilege emulation. KVM is never required — it's a performance upgrade you opt into, not a privilege the server demands.

Driving the running VM: the QMP Session

Once an Instance is up, the server talks to it over the QMP Session — QEMU's own Machine Protocol, a JSON control channel on a private socket the server owns and never exposes on the network. The server negotiates the session at launch (reads the greeting, sends qmp_capabilities), and from then on every "drive the VM" tool is a QMP command underneath: pause_instance stops the CPUs, get_status asks QEMU its run state, screendump grabs a framebuffer snapshot, and so on.

For anything without a purpose-built tool, there's qmp_execute — a generic "run this QMP command" — which brings us to the guardrail on it.

What the agent may command: the Command Policy

qmp_execute could in principle run any QMP command, which is both powerful and dangerous. The Command Policy decides which ones actually go through. Out of the box it's a safe-by-default allowlist; genuinely dangerous commands — migrate, dump-guest-memory, human-monitor-command, and their kin — sit behind a hard denylist that can't be re-enabled. You can widen or narrow the middle ground with an environment variable or a policy file.

One subtlety: the policy gates commands by name, not by their arguments. So a command whose arguments could be dangerous — a screen capture that writes to a host file, for instance — isn't exposed through the generic tool at all. It gets a purpose-built tool that validates the arguments for you.

Where files live: the Image Store and ISO Store

The agent refers to disks and boot media by name, never by host path — and those names resolve inside two folders you designate:

  • The Image Store is a single read-write directory for guest disk images. The agent

can list what's there and create new blank images in it, and disks in a spec are looked up by name within it.

  • The ISO Store is a separate read-only directory for installation and boot ISOs.

Keeping it distinct means install media can never be written to.

Both are enforced with real-path containment: a name that tries to climb out — ../, an absolute path, a symlink pointing elsewhere — is refused. These two folders are the agent's view of the filesystem — the only exception is an optional virtio-9p folder share the operator can enable (QMP_MCP_HOST_SHARE_DIR), which a spec opts into with share: true; it too is operator-configured (the agent never names the host path) and read-only by default (ADR-0014).

Sandboxed networking

Guests get user-mode networking by default — a sandboxed NAT stack, no host privileges, no bridge. To reach a service inside the guest you add host forwards, and those are bounded: only ports in a non-privileged range, bound to loopback.

{ "network": { "hostForwards": [{ "hostPort": 2222, "guestPort": 22 }] } }

Host-level networking (tap/bridge) exists but is gated off unless you turn it on — it needs privileges that don't fit the server's unprivileged posture.

Watching what happens: events, the Display, and the Viewer

Two ways to see what the VM is doing:

  • Events. QEMU emits async events — a reset, a shutdown, a device change. The server

keeps a bounded ring buffer of the recent ones for the current Instance, and the agent reads it pull-style: get_events drains what's new since a cursor, wait_for_event blocks until a named event arrives (or times out). No firehose to manage.

  • The Display and the Viewer. Ask for a vnc Display in the spec and QEMU exposes

the guest's screen over VNC, on loopback only. Turn on the Viewer — an optional, in-process noVNC bridge — and you can watch and control that screen in a browser. The Viewer is password-gated and reads the Display only; it never touches the QMP Session. It's ideal for babysitting an OS installer, or just seeing what the agent sees. Most machines (virt, q35, …) have no built-in display, so pair display: vnc with a displayDevicevirtio-gpu (a real GPU with DRM, so Wayland/X desktops render), vga, or ramfb. Use vga for a live ISO or any boot where the boot menu / early console must be visible: virtio-gpu shows nothing until the guest loads its DRM driver, so an ISO's bootloader can't draw on it. The raspi* boards render over their built-in framebuffer, so they stay displayDevice: none. (Booting a distro this way also takes initrd alongside kernel — the usual kernel + initramfs + rootfs.)

Talking to the server: transports and authentication

The server speaks MCP over stdio (the default — how most clients launch a server directly; no network, no auth) or over HTTP (for a networked deployment), or both at once. The HTTP transport is fail-closed: it refuses to start without authentication — an API key, or a signed HS256 token — unless you explicitly opt into insecure mode for local use. A server that can build and run VMs has no business being reachable unauthenticated. It runs as a non-root user in every mode and never needs --privileged.

The tools

The agent's vocabulary — the actions it can take:

| Tool | What it does | | --- | --- | | create_instance / destroy_instance | build & launch the Instance from a Hardware Spec / tear it down | | get_instance / get_status | the current Instance + lifecycle state / the live guest run state | | get_share | report the host↔guest folder-sharing config + the exact 9p mount command for the guest | | get_serial / read_serial / write_serial | report the Serial Port config + console device / drain the Guest's serial output / type input into the console (gated by QMP_MCP_ALLOW_SERIAL_WRITE) | | pause_instance / resume_instance | freeze / unfreeze the guest CPUs | | reset_instance / powerdown_instance | hard reset / request a graceful ACPI shutdown | | list_block_devices / query_cpus | the VM's disks & backing media / per-CPU info | | screendump | a PNG screenshot of the Display | | get_events / wait_for_event | recent QEMU events / block until a named one arrives | | qmp_execute | a raw QMP command, gated by the Command Policy | | create_image / list_images / list_isos | make a disk image / list disks / list boot ISOs |

For the exact per-implementation tool tables, see the [TypeScript](typescript/README.md#the-tools) and [Rust](rust/README.md#the-tools) READMEs.

Quick start: common scenarios

The server runs wherever QEMU is installed. First get one of the implementations running and point your MCP client at it — [run the TypeScript variant](typescript/README.md#run-it) or [run the Rust variant](rust/README.md#run-it) — then ask your agent to do something. The scenarios below are what that looks like: each is a Hardware Spec (the arguments to create_instance) plus whatever you had to put in place first.

1. A scratch VM to poke at

Nothing to set up — just ask for a small machine and drive it.

> "Boot a 1 GB Linux VM and tell me its run state."

The agent calls create_instance with a minimal spec, then get_status; destroy_instance cleans up:

{ "machine": "q35", "cpu": "host", "vcpus": 1, "memoryMb": 1024, "accel": "auto" }

(With no disk or ISO there's nothing to boot — perfect for a smoke test; add media for the real thing.)

2. Install an OS from an ISO

Put the installer ISO in your ISO Store folder; the agent creates a blank disk for it and boots from the CD first (boot: "dc").

> "Create a 20 GB disk and install Debian from debian-13.iso onto it."

It calls create_image (into the Image Store), then create_instance:

{
  "machine": "q35", "cpu": "host", "vcpus": 2, "memoryMb": 2048, "accel": "auto",
  "disks": [{ "image": "debian.qcow2" }],
  "cdrom": { "iso": "debian-13.iso" },
  "boot": "dc",
  "display": "vnc"
}

Because it asked for display: "vnc", you can watch the installer run — see scenario 4.

3. A headless server you can SSH into

Add a host forward so a port on your host reaches a port in the guest.

> "Run my server image headless and forward host port 2222 to guest 22."

{
  "machine": "q35", "cpu": "host", "vcpus": 2, "memoryMb": 2048, "accel": "auto",
  "disks": [{ "image": "server.qcow2" }],
  "network": { "hostForwards": [{ "hostPort": 2222, "guestPort": 22 }] }
}

Once it's booted, ssh -p 2222 user@localhost from the host reaches the guest's SSH.

4. Watch it in a browser

Set QMP_MCP_VIEWER_PASSWORD, ask for a vnc display, and open the Viewer. The setup details are in the [TypeScript](typescript/README.md#browser-viewer) / [Rust](rust/README.md#browser-viewer) READMEs; any spec with "display": "vnc" then gets a live, interactive screen at http://:6080/.

5. Emulate a different architecture

Pick an ARM machine and CPU — the qemu-system-aarch64 emulator is chosen automatically from the machine (no QMP_MCP_QEMU_BINARY needed).

> "Bring up an ARM64 virtual machine."

{ "machine": "virt", "cpu": "cortex-a72", "vcpus": 2, "memoryMb": 2048, "accel": "tcg" }

On an x86 host accel: auto already resolves to TCG (an aarch64 guest can't use x86 KVM). On an ARM host it would use KVM, which only accepts a host/max CPU — so a named model like cortex-a72 there needs accel: tcg (as above), and the raspi* boards always run under TCG (their baked CPU can't be virtualized).

(If you also need to build the Rust binary for a non-x86 host, see its [cross-compilation guide](rust/README.md#building-for-other-platforms).)

6. Emulate a Raspberry Pi board

QEMU's Raspberry Pi machines boot a kernel directly and render a framebuffer you can watch in the browser Viewer. Put the extracted kernel and device tree in the Image Store (the raspi* machines select qemu-system-aarch64 for you), and:

> "Boot a Raspberry Pi 3 and show me the console."

{
  "machine": "raspi3b",
  "accel": "tcg",
  "kernel": "kernel8.img",
  "dtb": "bcm2710-rpi-3-b.dtb",
  "ap

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [r0gueSch0lar](https://github.com/r0gueSch0lar)
- **Source:** [r0gueSch0lar/QMP-MCP](https://github.com/r0gueSch0lar/QMP-MCP)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.