QMP-MCP
Criar, executar e gerenciar máquinas virtuais qemu
Documentação
qmp-mcp
qmp-mcp é um servidor Model Context Protocol (MCP) que dá a um agente de IA os controles de uma única máquina virtual QEMU. O agente descreve o hardware que deseja; o servidor constrói essa máquina, a inicia e expõe um conjunto de ferramentas para dirigi-la — pausar e retomar, reiniciar, observar a tela, enviar comandos QEMU de baixo nível, reagir a eventos e derrubá-la quando terminar.
Todo o design se apoia em uma ideia: as ferramentas são a fronteira. O agente nunca entrega argumentos brutos ao QEMU nem acessa seu sistema de arquivos. Ele preenche uma descrição estruturada e validada da máquina; o servidor a transforma em uma linha de comando QEMU restrita e media cada requisição. Tudo o que o agente pode tocar — imagens de disco, mídias de boot, os comandos que pode executar contra a VM ativa, as portas que pode abrir — passa por listas de permissão que você controla. A VM é o raio de explosão, e as ferramentas são as paredes.
Ele é distribuído como duas implementações intercambiáveis — uma em TypeScript, outra em Rust — que se comportam de forma idêntica. Esta página explica o que o servidor é e como ele pensa; os READMEs de cada implementação cobrem instalação, execução e implantação de cada uma.
Novo no vocabulário?
CONTEXT.mdé o glossário de uma página. As palavras abaixo — Instância, Convidado, Especificação de Hardware, Política de Comandos, Armazenamento de Imagens, Visualizador — cada uma tem um significado específico, e este README as usa deliberadamente.
Como funciona
Uma máquina por vez: a Instância
O servidor gerencia exatamente uma Instância — o processo qemu-system-* em execução junto
com sua configuração de hardware e a conexão de controle ativa com ele. Nunca há mais de
uma; pedir para criar outra enquanto uma existe é recusado. A vida de uma Instância está
ligada à do servidor: desligue o servidor e ele derruba a VM junto, para que nada fique
órfão.
Uma Instância percorre um pequeno ciclo de vida — de nada, para iniciando, para em execução, opcionalmente pausada e de volta, para parada, e de volta a nada:
NONE → STARTING → RUNNING ⇄ PAUSED → STOPPED → NONE
Se o processo QEMU subjacente sair por conta própria — um desligamento do convidado, uma
falha, um kill externo — o servidor percebe e reconcilia de volta para NONE, para que a
próxima requisição comece do zero.
A coisa que roda dentro da Instância — o sistema operacional ou a carga de trabalho — é o Convidado. O servidor gerencia a máquina; o que você instala e executa nela é por sua conta e do seu agente.
Descrevendo a máquina: a Especificação de Hardware
O agente não executa o QEMU. Ele submete uma Especificação de Hardware — uma descrição estruturada e validada da máquina que deseja: tipo de máquina e CPU, quantos vCPUs e quanta memória, quais discos e mídias de boot, a rede, o display, o acelerador. O servidor valida cada campo e gera a linha de comando QEMU a partir dela. O agente nunca fornece argv bruto.
Uma especificação é apenas os argumentos JSON para 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"
}
A validação não é uma formalidade — é a fronteira de segurança. Os campos são verificados
quanto a intervalo e caracteres, e qualquer coisa que possa contrabandear uma opção extra
para a linha de comando (uma vírgula solta em uma entrada de disco, por exemplo) é escapada
ou rejeitada. Os tamanhos são limitados, com tetos que você define para disco, memória e
vCPUs. Se uma especificação for inválida, create_instance falha antes de o QEMU ser
iniciado, com uma mensagem que diz exatamente o que estava errado.
Existe uma porta de escape — extraArgs, que acrescenta flags QEMU brutas à linha de comando gerada — mas fica desligada a menos que você a habilite explicitamente. É destinada a configurações confiáveis de locatário único, onde você decidiu que o agente pode receber as chaves.
Qual arquitetura você emula decorre do machine: o servidor escolhe o emulador
para você — q35/pc iniciam qemu-system-x86_64, enquanto virt e as placas raspi*
iniciam qemu-system-aarch64 — então trocar de arquitetura é apenas um machine diferente,
sem reiniciar. QMP_MCP_QEMU_BINARY substitui essa escolha para toda Instância (por exemplo, um
build personalizado ou qemu-system-riscv64), e accel: auto só usa KVM quando a arquitetura do
convidado corresponde à do host, caindo para TCG entre arquiteturas (ADR-0013).
Algumas máquinas não inicializam a partir de um disco. As placas Raspberry Pi do QEMU (raspi3b e
amigas) têm hardware fixo — CPU, número de núcleos e RAM definidos — e esperam o kernel
entregue diretamente a elas, em vez de lido de um bootloader de cartão SD. Para essas, a
especificação ganha três campos opcionais: kernel e dtb (uma imagem de kernel e um blob de device-tree, cada um
um nome no Armazenamento de Imagens) e appendCmdline (a linha de comando do kernel). O servidor
emite -kernel/-dtb/-append e, como o hardware da placa é fixo, omite
-cpu/-smp/-m; anexe a imagem SD com "interface": "sd" (dimensionada para uma potência de dois, ou
o QEMU a recusa). Essas placas também não têm barramento PCI, então a NIC padrão não pode
ser anexada — escolha network.model usb-net (a NIC USB delas) ou network.mode none; o servidor recusa uma
NIC não anexável de antemão, em vez de deixar o QEMU abortar. Nada disso é exclusivo do Pi —
qualquer boot direto de kernel (uma máquina virt pura, por exemplo) pode usar kernel/appendCmdline junto com as
configurações usuais de CPU e memória.
Quão rápido roda: o acelerador
accel: "auto" (o padrão) usa KVM de hardware quando o host consegue alcançar um /dev/kvm,
e caso contrário cai para emulação de software TCG — reportando qual escolheu. Peça
kvm explicitamente e ele falha ruidosamente se KVM não estiver disponível; peça tcg e você sempre
obtém emulação portátil e sem privilégios. KVM nunca é exigido — é uma melhoria de desempenho
na qual você opta, não um privilégio que o servidor demanda.
Dirigindo a VM em execução: a Sessão QMP
Quando uma Instância está ativa, o servidor fala com ela pela Sessão QMP — o próprio
Protocolo de Máquina do QEMU, um canal de controle JSON em um socket privado que o servidor
possui e nunca expõe na rede. O servidor negocia a sessão na inicialização (lê a saudação,
envia qmp_capabilities), e a partir daí toda ferramenta de "dirigir a VM" é um comando QMP
por baixo: pause_instance para as CPUs, get_status pergunta ao QEMU seu estado de execução,
screendump captura um instantâneo do framebuffer, e assim por diante.
Para qualquer coisa sem uma ferramenta dedicada, existe qmp_execute — um "execute este comando
QMP" genérico — o que nos traz à proteção sobre ele.
O que o agente pode comandar: a Política de Comandos
qmp_execute poderia em princípio executar qualquer comando QMP, o que é ao mesmo tempo
poderoso e perigoso. A Política de Comandos decide quais realmente passam. De fábrica
é uma lista de permissões segura por padrão; comandos genuinamente perigosos — migrate,
dump-guest-memory, human-monitor-command e seus semelhantes — ficam atrás de uma lista de negação
rígida que não pode ser reativada. Você pode ampliar ou estreitar o meio-termo com uma
variável de ambiente ou um arquivo de política.
Uma sutileza: a política limita comandos por nome, não por seus argumentos. Então um comando cujos argumentos poderiam ser perigosos — uma captura de tela que grava em um arquivo do host, por exemplo — não é exposto pela ferramenta genérica. Ele ganha uma ferramenta dedicada que valida os argumentos para você.
Onde os arquivos vivem: o Armazenamento de Imagens e o Armazenamento de ISOs
O agente se refere a discos e mídias de boot por nome, nunca por caminho de host — e esses nomes são resolvidos dentro de duas pastas que você designa:
- O Armazenamento de Imagens é um único diretório de leitura-escrita para imagens de disco do convidado. O agente pode listar o que está lá e criar novas imagens em branco nele, e discos em uma especificação são procurados por nome dentro dele.
- O Armazenamento de ISOs é um diretório separado, somente leitura, para ISOs de instalação e boot. Mantê-lo distinto significa que a mídia de instalação nunca pode ser gravada.
Ambos são aplicados com contenção de caminho real: um nome que tenta escapar — ../, um
caminho absoluto, um symlink apontando para outro lugar — é recusado. Essas duas pastas são a
visão do agente sobre o sistema de arquivos — a única exceção é um compartilhamento de pasta
virtio-9p opcional que o operador pode habilitar (QMP_MCP_HOST_SHARE_DIR), no qual uma especificação
opta com share: true; ele também é configurado pelo operador (o agente nunca nomeia o
caminho do host) e somente leitura por padrão (ADR-0014).
Rede em sandbox
Os convidados obtêm rede em modo usuário por padrão — uma pilha NAT em sandbox, sem privilégios de host, sem bridge. Para alcançar um serviço dentro do convidado, você adiciona encaminhamentos de host, e eles são limitados: apenas portas em uma faixa sem privilégios, vinculadas a loopback.
{ "network": { "hostForwards": [{ "hostPort": 2222, "guestPort": 22 }] } }
Rede em nível de host (tap/bridge) existe, mas fica bloqueada a menos que você a ative — ela
exige privilégios que não se encaixam na postura sem privilégios do servidor.
Observando o que acontece: eventos, o Display, o Visualizador — e gravação
Três maneiras de ver o que a VM está fazendo:
- Eventos. O QEMU emite eventos assíncronos — um reset, um desligamento, uma mudança de
dispositivo. O servidor mantém um buffer circular limitado dos recentes para a Instância
atual, e o agente o lê no estilo pull:
get_eventsdrena o que é novo desde um cursor,wait_for_eventbloqueia até um evento nomeado chegar (ou expirar). Sem mangueira de incêndio para gerenciar. - O Display e o Visualizador. Peça um Display
vncna especificação e o QEMU expõe a tela do convidado via VNC, apenas em loopback. Ative o Visualizador — uma ponte noVNC opcional, no processo — e você pode assistir e controlar essa tela em um navegador. O Visualizador é protegido por senha e lê o Display apenas; ele nunca toca a Sessão QMP. É ideal para vigiar um instalador de SO, ou apenas ver o que o agente vê. A maioria das máquinas (virt,q35, …) não tem display embutido, então combinedisplay: vnccom umdisplayDevice—virtio-gpu(uma GPU real com DRM, para que desktops Wayland/X renderizem),vgaouramfb. Usevgapara uma ISO ao vivo ou qualquer boot em que o menu de boot / console inicial deva estar visível:virtio-gpunão mostra nada até o convidado carregar seu driver DRM, então o bootloader de uma ISO não pode desenhar nele. As placasraspi*renderizam via framebuffer embutido, então permanecemdisplayDevice: none. (Inicializar uma distro dessa forma também exigeinitrdjunto comkernel— o kernel usual + initramfs + rootfs.) - Gravação. Com um Display
vncativo,start_recordingcaptura a tela para um arquivo de vídeo: um loop de screendumps QMP canalizados para um ffmpeg co-localizado, codificado para conteúdo de tela a uma taxa de quadros limitada com CRF de qualidade constante (ADR-0017). É limitado por capacidade, não por permissão — gravação é captura, nada é digitado no Convidado — e está disponível apenas quando ffmpeg é executável e você definiuQMP_MCP_RECORDING_DIR. O agente apenas nomeia a saída; o arquivo cai como<name>.mkvsob essa raiz de propriedade do operador, nunca em um caminho de host, e os vídeos não são retornados inline — você os coleta do host.stop_recordingfinaliza o arquivo;get_recordingreporta se a gravação está disponível (e exatamente por que não, quando não está) além do codec ativo, CRF, limite de fps e formato de pixel. As imagens Docker incluem ffmpeg, então a gravação funciona de fábrica em contêiner; em metal nu é uma instalação opcional.
Falando com o servidor: transportes e autenticação
O servidor fala MCP sobre stdio (o padrão — como a maioria dos clientes inicia um servidor
diretamente; sem rede, sem autenticação) ou sobre HTTP (para uma implantação em rede), ou ambos ao
mesmo tempo. O transporte HTTP é fail-closed: ele se recusa a iniciar sem autenticação —
uma chave de API, ou um token HS256 assinado — a menos que você opte explicitamente pelo modo inseguro para
uso local. Um servidor que pode construir e executar VMs não tem o direito de ser acessível
sem autenticação. Ele roda como um usuário não-root em todos os modos e nunca precisa de --privileged.
As ferramentas
O vocabulário do agente — as ações que ele pode executar:
| Ferramenta | O que ela faz |
|---|---|
create_instance / destroy_instance | construir e iniciar a Instância a partir de uma Especificação de Hardware / derrubá-la |
get_instance / get_status | a Instância atual + estado do ciclo de vida / o estado de execução ao vivo do convidado |
get_share | relatar a configuração de compartilhamento de pastas host↔guest + o comando exato de montagem 9p para o convidado |
get_serial / read_serial / write_serial | relatar a configuração da Porta Serial + dispositivo de console / drenar a saída serial do convidado / digitar entrada no console (controlado por QMP_MCP_ALLOW_SERIAL_WRITE) |
pause_instance / resume_instance | congelar / descongelar as CPUs do convidado |
reset_instance / powerdown_instance | reset forçado / solicitar um desligamento ACPI gracioso |
list_block_devices / query_cpus | os discos e mídias de suporte da VM / informações por CPU |
screendump | uma captura de tela PNG do Display |
start_recording / stop_recording / get_recording | gravar o Display em um arquivo de vídeo sob o diretório de gravação do operador (precisa de QMP_MCP_RECORDING_DIR + um ffmpeg executável) / finalizar o arquivo / relatar a capacidade de gravação + configurações do codificador |
get_events / wait_for_event | eventos QEMU recentes / bloquear até que um nomeado chegue |
qmp_execute | um comando QMP bruto, controlado pela Política de Comandos |
create_image / list_images / list_isos | criar uma imagem de disco / listar discos / listar ISOs de boot |
list_iso_catalog / download_iso / get_download | listar o catálogo de download de imagens de SO / buscar uma para o ISO Store (controlado por QMP_MCP_ALLOW_DOWNLOAD) / consultar o progresso do download |
Para as tabelas exatas de ferramentas por implementação, veja os READMEs TypeScript e Rust.
Início rápido: cenários comuns
O servidor roda onde quer que o QEMU esteja instalado. Primeiro, coloque uma das implementações em execução
e aponte seu cliente MCP para ela —
execute a variante TypeScript ou
execute a variante Rust — depois peça ao seu agente para fazer algo.
Os cenários abaixo são como isso se parece: cada um é uma Especificação de Hardware (os argumentos para
create_instance) mais o que você precisou colocar em prática primeiro.
1. Uma VM de teste para explorar
Nada para configurar — apenas peça uma máquina pequena e dirija-a.
"Inicie uma VM Linux de 1 GB e me diga seu estado de execução."
O agente chama create_instance com uma especificação mínima, depois get_status; destroy_instance
faz a limpeza:
{ "machine": "q35", "cpu": "host", "vcpus": 1, "memoryMb": 1024, "accel": "auto" }
(Sem disco ou ISO não há nada para bootar — perfeito para um teste de fumaça; adicione mídia para a coisa real.)
2. Instalar um SO a partir de um ISO
Coloque o ISO do instalador na sua pasta ISO Store; o agente cria um disco em branco para ele
e faz o boot pelo CD primeiro (boot: "dc").
"Crie um disco de 20 GB e instale o Debian a partir de debian-13.iso nele."
Ele chama create_image (no Image Store), depois 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"
}
Como pediu display: "vnc", você pode assistir ao instalador rodar — veja o cenário 4.
3. Um servidor headless ao qual você pode acessar via SSH
Adicione um host forward para que uma porta no seu host alcance uma porta no convidado.
"Rode minha imagem de servidor headless e encaminhe a porta 2222 do host para a 22 do convidado."
{
"machine": "q35", "cpu": "host", "vcpus": 2, "memoryMb": 2048, "accel": "auto",
"disks": [{ "image": "server.qcow2" }],
"network": { "hostForwards": [{ "hostPort": 2222, "guestPort": 22 }] }
}
Depois que ele inicializar, ssh -p 2222 user@localhost a partir do host alcança o SSH do convidado.
4. Assista em um navegador
Defina QMP_MCP_VIEWER_PASSWORD, peça um display vnc e abra o Viewer. Os detalhes de configuração
estão nos READMEs TypeScript /
Rust; qualquer especificação com "display": "vnc" então recebe uma
tela ao vivo e interativa em http://<host>:6080/.
5. Emular uma arquitetura diferente
Escolha uma máquina e CPU ARM — o emulador qemu-system-aarch64 é escolhido automaticamente
a partir do machine (sem necessidade de QMP_MCP_QEMU_BINARY).
"Levante uma máquina virtual ARM64."
{ "machine": "virt", "cpu": "cortex-a72", "vcpus": 2, "memoryMb": 2048, "accel": "tcg" }
Em um host x86, accel: auto já resolve para TCG (um convidado aarch64 não pode usar KVM x86).
Em um host ARM, ele usaria KVM, que só aceita uma CPU host/max — então um modelo nomeado
como cortex-a72 lá precisa de accel: tcg (como acima), e as placas raspi*
sempre rodam sob TCG (sua CPU embutida não pode ser virtualizada).
(Se você também precisar compilar o binário Rust para um host não-x86, veja o guia de compilação cruzada.)
6. Emular uma placa Raspberry Pi
As máquinas Raspberry Pi do QEMU inicializam um kernel diretamente e renderizam um framebuffer que você pode assistir
no Viewer do navegador. Coloque o kernel extraído e a árvore de dispositivos no Image Store (as
máquinas raspi* selecionam qemu-system-aarch64 para você), e:
"Inicie um Raspberry Pi 3 e me mostre o console."
{
"machine": "raspi3b",
"accel": "tcg",
"kernel": "kernel8.img",
"dtb": "bcm2710-rpi-3-b.dtb",
"appendCmdline": "console=tty1 root=/dev/mmcblk0p2 rootwait rw",
"disks": [{ "image": "raspios.img", "interface": "sd", "format": "raw" }],
"network": { "model": "usb-net" },
"display": "vnc"
}
console=tty1 coloca o console no framebuffer, então o Viewer noVNC mostra o Pi inicializando
— logotipos e tudo. Sem cpu/vcpus/memoryMb: o hardware da placa é fixo. O Pi não tem
barramento PCI, então a NIC padrão não pode ser anexada — use "network": { "model": "usb-net" } para sua NIC
USB, ou "network": { "mode": "none" } para nenhuma rede. (Em um Pi 3, mescle o
overlay de árvore de dispositivos disable-bt no dtb primeiro, ou o console permanece preso à
UART compartilhada com Bluetooth em vez da tela.)
Escolhendo uma implementação
As duas são intercambiáveis — mesmas ferramentas, mesmas especificações, mesmo comportamento, continuamente verificadas uma contra a outra. Escolha pelo ecossistema:
| TypeScript | Rust | |
|---|---|---|
| Construído sobre | Node + mcp-framework | rmcp + tokio |
| Distribuído como | um pacote npm / node dist/index.js | um único binário autocontido |
| Coloque em execução | Execute → | Execute → |
| No Docker | Docker → | Docker → |
Tudo específico de implantação e uso vive nesses dois READMEs:
- TypeScript — Execute · Transportes e autenticação · Docker · Viewer de navegador · Configuração · Desenvolvimento
- Rust — Execute · Transportes e autenticação · Docker · Aceleração KVM · Viewer de navegador · Compilação cruzada · Configuração · Desenvolvimento
Configuração
Ambas as implementações são configuradas inteiramente por meio de variáveis de ambiente QMP_MCP_* —
os mesmos nomes e padrões para cada uma. A referência totalmente comentada é
.env.example, e o formato do arquivo de política de comandos é
policy.example.yaml. As que você vai usar:
| Variável | Padrão | O que ela faz |
|---|---|---|
QMP_MCP_TRANSPORT | stdio | stdio, http ou both |
QMP_MCP_API_KEYS | (não definido) | chaves de API para o transporte HTTP (obrigatórias a menos que inseguro) |
QMP_MCP_QEMU_BINARY | (derivado de machine) | geralmente não definido — o emulador é derivado do machine (q35→x86_64, virt/raspi*→aarch64, ADR-0013); defina-o para forçar um emulador para cada Instância |
QMP_MCP_IMAGE_DIR / QMP_MCP_ISO_DIR | caminhos XDG | as pastas Image Store / ISO Store |
QMP_MCP_ALLOW_DOWNLOAD | false | habilite download_iso para buscar imagens de SO no ISO Store (desligado por padrão; o agente nunca pode habilitá-lo) |
QMP_MCP_ISO_CATALOG | (embutido) | caminho para um JSON de catálogo de download personalizado (não definido ⇒ a lista embutida de 24 distros) |
QMP_MCP_RECORDING_DIR | (não definido) | habilita a gravação de Display: a raiz absoluta do host sob a qual os arquivos <name>.mkv são gravados (não definido ⇒ start_recording indisponível) |
QMP_MCP_FFMPEG_BINARY | ffmpeg | o ffmpeg com o qual a gravação codifica — um nome PATH ou caminho absoluto (incluído nas imagens Docker; opcional em metal nu) |
QMP_MCP_VIEWER_PASSWORD | (não definido) | habilita o Viewer de navegador |
QMP_MCP_VIEWER_USER | (não definido) | nome de usuário opcional aplicado na autenticação HTTP Basic do Viewer (padrão: nome de usuário ignorado, apenas senha) |
QMP_MCP_HOST_SHARE_DIR | (não definido) | diretório absoluto do host compartilhado com convidados via virtio-9p quando uma especificação define share: true (não definido ⇒ compartilhamento desligado; ADR-0014) |
QMP_MCP_GUEST_SHARE_DIR | (não definido) | ponto de montagem pretendido do convidado (consultivo) — get_share relata o comando exato mount -t 9p |
QMP_MCP_ALLOW_SHARE_WRITE | false | montar o compartilhamento leitura-escrita (padrão somente leitura; o agente nunca pode escalar) |
QMP_MCP_ALLOW_RAW_ARGS | false | permitir o extraArgs de uma especificação (a saída de emergência) |
…mais limites de disco/memória/vCPUs, o intervalo de portas do host forward, os controles do codificador de gravação
(codec, CRF, max-fps, formato de pixel), as listas de permitir/negar da Política de Comandos e o arquivo de política,
e o tamanho do Buffer de Eventos. Veja .env.example para
a lista completa, ou a seção de Configuração de cada variante em contexto
(TypeScript · Rust).
Para desenvolvedores
Estrutura
qmp-mcp/
├── typescript/ the Node / mcp-framework implementation
├── rust/ the Rust / rmcp implementation
├── testdata/ shared golden fixtures both implementations assert
├── docs/ design notes and rationale
├── CONTEXT.md the domain glossary — the shared vocabulary
├── .env.example every QMP_MCP_* variable, commented
└── policy.example.yaml the command-policy file format
As duas implementações são bases de código independentes que compartilham três coisas na raiz: o
modelo de domínio (CONTEXT.md — leia primeiro), os fixtures dourados
(testdata/) e a superfície de configuração (.env.example).
Como as duas permanecem idênticas
Paridade aqui não é uma promessa, é um teste. testdata/ contém fixtures dourados
neutros em linguagem que fixam a linha de comando exata do QEMU que cada Especificação de Hardware deve produzir e o
veredito exato que a Política de Comandos deve retornar — e ambas as implementações são testadas
contra esse mesmo corpus. Mude como uma especificação se torna uma linha de comando, ou o que a política
permite, e você atualiza o fixture compartilhado; a suíte TypeScript e a suíte Rust têm que
concordar, ou a compilação falha. Ensine um truque novo a uma implementação e você adiciona o fixture que a
outra tem que satisfazer.
Trabalhar em uma variante é autocontido em sua pasta —
desenvolvendo TypeScript ·
desenvolvendo Rust. A pasta docs/ contém a
justificativa em formato mais longo por trás das decisões mais complicadas.
Licença
MIT.