Your first virtual machine
In this tutorial we install mcqemu, create a blank disk, install Alpine Linux onto it from an installer ISO while watching the screen, and finish with a VM whose guest agent answers, so the agent can run commands inside it.
We will drive everything through an LLM agent (the examples assume Claude Code). Each step shows the tool call to ask for, and what the result should look like, so you can tell at a glance whether the step worked.
Expect this to take around twenty minutes, most of which is the operating system installer doing its own thing.
What you need
Section titled “What you need”A Linux host with QEMU installed (qemu-system-x86_64 and qemu-img on your
PATH), uv, and about 2 GB of free disk space.
Access to /dev/kvm makes everything much faster, but the tutorial works
without it under software emulation.
Download the Alpine “virt” ISO before starting, from
alpinelinux.org/downloads. It is around
60 MB. We will assume it is at ~/isos/alpine-virt.iso, and we will keep our
VM disk at ~/vms/alpine-agent.qcow2, so create those directories now:
mkdir -p ~/isos ~/vmsInstall and connect the server
Section titled “Install and connect the server”-
Check that the server starts.
uvxfetches mcqemu and runs it; the version banner appears on stderr and then it waits for a client, which is what an MCP server is supposed to do.Terminal window uvx mcqemuPress Ctrl-C to stop it. Seeing
mcqemu v...is all we needed. -
Register it with Claude Code:
Terminal window claude mcp add mcqemu -- uvx mcqemu -
Start Claude Code in any directory and run
/mcp. The list should includemcqemuas connected. If it does not, Install and connect covers the usual causes. -
Ask the agent to call
list_vms. On a fresh install it answers with an empty list:{"vms": [], "registry_warnings": []}An empty
registry_warningsmeans the bookkeeping is healthy. We now have a working server with nothing to manage yet.
Create a disk and start the installer
Section titled “Create a disk and start the installer”-
Ask the agent to create the disk:
image_create(path="~/vms/alpine-agent.qcow2", size="8G")The result reports
format: qcow2and a virtual size of 8 GB. Check the file on the host and you will see it occupies only a couple of hundred kilobytes. qcow2 images grow as the guest writes, so a generous virtual size costs almost nothing up front. -
Launch the VM with the installer ISO attached:
launch_vm(name="alpine", disks=["~/vms/alpine-agent.qcow2"],iso="~/isos/alpine-virt.iso", memory_mb=2048)The result tells us the VM is running, which acceleration it got (
kvmortcg), the process ID, and paths to its logs. When a VM has both a disk and an ISO, it boots the ISO this once and the disk on every later boot, so we will not have to detach anything by hand. -
Give it fifteen seconds, then look at the screen:
vm_screenshot(name="alpine")The agent gets a PNG of the guest display back. You should see Alpine’s boot messages or a
localhost login:prompt. Nothing is installed inside the guest to make this work; QEMU is handing over the framebuffer directly, which is why screenshots work in bootloaders and BIOS menus too.If the screen still shows a bootloader, wait and take another screenshot. Watching, rather than assuming, is the habit this whole workflow is built on.
Install Alpine onto the disk
Section titled “Install Alpine onto the disk”The installer is an interactive console session, so we type into it the same way a person at a keyboard would.
-
Log in as root (Alpine’s live image has no root password):
vm_type_text(name="alpine", text="root", enter=true)Take a screenshot to confirm we landed at a shell prompt before continuing.
-
Start the installer:
vm_type_text(name="alpine", text="setup-alpine", enter=true) -
Work through the questions with
vm_type_text, taking a screenshot after each answer to see what is being asked next. Most answers can be the default (press Enter withvm_send_keys(name="alpine", keys=["ret"])). The three that matter:- Set a root password when asked, and remember it. We need it to log in later.
- When asked which disk to use, answer
vda. Virtio disks appear under that name inside the guest. - When asked how to use it, answer
sys. That installs to the disk rather than running from RAM.
Alpine asks for a final
yto erase the disk. Nothing else on your machine is at risk; the only disk the VM can see is the image file we created. -
When the installer reports it is done, shut the guest down cleanly:
stop_vm(name="alpine")stop_vmpresses the virtual power button and waits for the guest to shut itself down, so filesystems get flushed properly.
Boot from disk
Section titled “Boot from disk”-
Launch again, this time with no ISO, and forward a host port to the guest’s SSH port so we have a way in later:
launch_vm(name="alpine", disks=["~/vms/alpine-agent.qcow2"],port_forwards=["auto:22"])auto:22means “pick any free host port”; the result reports which one it chose, for exampleport_forwards: ["43617:22"]. You can also ask for a specific one with"2222:22", and mcqemu checks up front that the port is free instead of letting QEMU fail obscurely. -
Screenshot after a few seconds. This time the login prompt comes from the installed system on the disk, not from the ISO.
-
Log in with
rootand the password you set:vm_type_text(name="alpine", text="root", enter=true)vm_type_text(name="alpine", text="<your password>", enter=true)
Install the guest agent
Section titled “Install the guest agent”Everything so far worked through the screen and keyboard, with no cooperation
from the guest operating system. The guest_* tools are different: they need
qemu-guest-agent running inside the guest. mcqemu wires up the host side of
that channel on every launch, so installing the package is the only step left.
-
Confirm the agent is genuinely missing, so the difference is visible:
guest_ping(name="alpine")This fails, and the error says the guest does not have the agent installed or running.
-
Type these three commands into the guest console, one at a time, with
vm_type_text(..., enter=true):apk add qemu-guest-agentrc-update add qemu-guest-agent defaultrc-service qemu-guest-agent startThe VM has outbound network access (we did not restrict it), so
apkcan reach Alpine’s mirrors. Take a screenshot after the first command to check the download succeeded; ifapkcannot find the package, runsetup-apkrepos -c -1to enable the community repository and try again. -
Ask again:
guest_ping(name="alpine")Now it answers
guest_agent: responding. -
Run a command inside the guest without touching the keyboard:
guest_exec(name="alpine", command="uname", args=["-a"])The result carries the guest’s stdout, stderr, and exit code. Note that
commandis an executable andargsare its arguments; this is exec, not a shell. For pipelines, usecommand="/bin/sh"withargs=["-c", "your | pipeline"]. -
Shut the VM down. We want the image quiescent for the next tutorial.
stop_vm(name="alpine")
What we built
Section titled “What we built”~/vms/alpine-agent.qcow2 is now an installed Alpine system with a working
guest agent. Along the way we saw the two ways to interact with a VM:
- Through the display and keyboard (
vm_screenshot,vm_type_text,vm_send_keys), which works on any guest at any stage of boot, including installers and bootloaders, because it operates below the operating system. - Through the guest agent (
guest_ping,guest_exec), which is faster and gives you structured output, but only after you have put the agent inside the guest.
We also saw that disks and VMs are separate things: image_create makes a
disk, launch_vm runs a VM around it, and the same disk can be booted again
later with different settings.
Keep that image. In Disposable sandboxes we use it as a base to spin up throwaway clones in seconds, run untrusted things in them, and delete them without the base ever changing.
If you want to install something bigger than Alpine next, read Drive an installer for the screenshot and input loop in detail, including mouse control for graphical installers.