内容
# qmp-mcp
[](https://github.com/r0gueSch0lar/QMP-MCP/actions/workflows/ci.yml)
**qmp-mcp** 是一个 [模型上下文协议](https://modelcontextprotocol.io)(MCP)服务器,它为人工智能代理提供了一个单一的 [QEMU](https://www.qemu.org) 虚拟机的控制权。该代理描述了它想要的硬件;服务器构建了该机器,启动它,并提供了一组工具来驱动它 — 暂停和恢复它,重置它,监视它的屏幕,向它发送低级别的QEMU命令,响应它的事件,并在它完成后销毁它。
整个设计都基于一个理念:**工具是边界**。代理永远不会将原始参数交给QEMU或访问您的文件系统。它填写了一个结构化、验证过的机器描述;服务器将其转换为一个受限的QEMU命令行,并协调每一个请求。代理可以触及的一切 — 磁盘镜像、启动媒体、它可以运行的命令、它可以打开的端口 — 都通过您控制的允许列表。
它作为 **两个可互换的实现** 发布 — 一个用 **[TypeScript](typescript/)** 编写,一个用 **[Rust](rust/)** 编写 — 它们的行为相同。本页面解释了服务器是什么以及它的工作原理;每个实现的README分别介绍了如何安装、运行和部署它们。
> 对词汇不熟悉吗? [`CONTEXT.md`](CONTEXT.md) 是一份单页词汇表。下面出现的词语 — *实例*、*访客*、*硬件规格*、*命令策略*、*镜像存储*、*查看器* — 每个都有特定的含义,本README故意使用它们。
## 工作原理
### 每次一个机器:实例
服务器管理着一个 **实例** — 正在运行的 `qemu-system-*` 进程及其硬件配置和到它的实时控制连接。实例的生命周期与服务器的生命周期绑定在一起:关闭服务器,它也会销毁虚拟机,这样就不会留下任何孤立的实例。
一个实例经历了一个小的生命周期 — 从无到有,到启动,到 **运行**,可选地 **暂停** 并返回,到停止,再返回到无:
```
NONE → STARTING → RUNNING ⇄ PAUSED → STOPPED → NONE
```
如果底层的QEMU进程自行退出 — 访客关闭,崩溃,外部终止 — 服务器会注意到并恢复到 `NONE`,这样下一个请求就可以从干净的状态开始。
运行在实例内部的东西 — 操作系统或工作负载 — 就是 **访客**。服务器管理机器;您在上面安装和运行什么就由您和您的代理决定。
### 描述机器:硬件规格
代理不运行QEMU。它提交了一个 **硬件规格** — 一个结构化、验证过的机器描述:机器类型和CPU,有多少个虚拟CPU和多少内存,哪些磁盘和启动媒体,网络,显示, 加速器。服务器验证每个字段并从中 **生成** QEMU命令行。代理永远不会提供原始的参数。
规格只是 `create_instance` 的JSON参数:
```json
{
"machine": "q35",
"cpu": "host",
"vcpus": 2,
"memoryMb": 2048,
"accel": "auto",
"disks": [{ "image": "root.qcow2" }],
"cdrom": { "iso": "debian-13.iso" },
"boot": "dc",
"display": "vnc"
}
```
验证不是一种形式;它是安全边界。字段进行范围和字符检查,任何可能将额外选项偷运到命令行中的内容(例如磁盘条目中的逗号)都会被转义或拒绝。大小有上限,您可以设置磁盘、内存和虚拟CPU的上限。如果规格无效,`create_instance` 在启动QEMU之前会失败,并附有准确描述错误的错误消息。
有一个逃生阀 — **extraArgs**,它将原始QEMU标志附加到生成的命令行中 — 但除非您明确启用它,否则它是关闭的。它适用于您信任的单租户设置,您已经决定可以将钥匙交给代理。
您模拟的架构也是其中的一部分:默认情况下,服务器启动 `qemu-system-x86_64`,但将 `QMP_MCP_QEMU_BINARY` 指向 `qemu-system-aarch64`(或 `riscv64`,...),同一个规格会构建一个具有该架构的访客,其 `machine` 和 `cpu` 字段塑造了其余部分。
### 运行速度:加速器
`accel: "auto"`(默认)使用硬件 **KVM**,当主机可以访问 `/dev/kvm` 时,否则会回落到 **TCG** 软件仿真 — 并报告它选择了哪一个。显式请求 `kvm`,如果KVM不可用,则会大声失败;请求 `tcg`,您将始终获得便携式、无特权仿真。KVM不是必需的 — 它是一种性能提升,您需要时再选择使用,而不是服务器要求的特权。
### 驱动运行中的虚拟机:QMP会话
一旦实例启动,服务器就会通过 **QMP会话** 与其通信 — QEMU自己的机器协议,一个私有的JSON控制通道,服务器拥有并在网络上从未暴露过。服务器在启动时协商会话(读取问候,发送 `qmp_capabilities`),从那时起,每个“驱动虚拟机”的工具在底层都是QMP命令:`pause_instance` 停止CPU,`get_status` 向QEMU询问其运行状态,`screendump` 获取帧缓冲区快照等。
对于没有内置工具的功能,可以使用 `qmp_execute` — 一个通用的“运行此QMP命令” — 我们来看看其中的防护措施。
### 代理可能下达的命令:命令策略
`qmp_execute` 在原则上可以运行 **任何** QMP命令,这既强大又危险。 **命令策略** 决定哪些命令实际通过。开箱即用,它是一个安全的默认允许列表;真正危险的命令 — `migrate`,`dump-guest-memory`,`human-monitor-command`,以及它们的同类 — 位于一个 **硬性拒绝列表**,无法重新启用。您可以使用环境变量或策略文件来扩大或缩小中间范围。
一个微妙之处:策略按命令 **名称** 而不是按其参数进行控制。因此,某个命令的 **参数** 可能很危险 — 例如,可以写入主机文件的屏幕捕获 — 但不会通过通用工具暴露。它会获得一个自定义工具来为您验证参数。
### 文件存储位置:镜像存储和ISO存储
代理通过名称引用磁盘和启动媒体,而不是通过主机路径 — 而这些名称在两个您指定的文件夹中解析:
- **镜像存储** 是一个用于访客磁盘镜像的单一读写目录。代理可以列出其中有什么并创建新的空白镜像,磁盘在规格中按名称查找。
- **ISO存储** 是一个独立的只读目录,用于安装和启动ISO。将其与镜像存储分开意味着安装媒体永远不会被写入。
两者都通过真实路径限制:尝试爬出名称 — `../`,绝对路径,指向其他位置的符号链接 — 都会被拒绝。这两个文件夹是代理对文件系统的视图;它没有其他视图。
### 沙盒网络
访客默认使用用户模式网络 — 一个沙盒化的NAT栈,没有主机特权,没有网桥。要访问访客内部的服务,请添加 **主机转发**,并且这些转发是有限制的:仅限非特权范围的端口,绑定到环回地址。
```json
{ "network": { "hostForwards": [{ "hostPort": 2222, "guestPort": 22 }] } }
```
主机级网络(`tap`/`bridge`)存在但被关闭,除非您打开它 — 它需要不适合服务器无特权姿势的特权。
### 观察发生了什么:事件,显示和查看器
两种方法可以查看虚拟机正在执行的操作:
- **事件。** QEMU发出异步事件 — 重置,关闭,设备更改。服务器为当前实例保留一个有界的环形缓冲区,代理读取它以拉取样式:`get_events` 清空新的事件,`wait_for_event` 阻塞直到命名事件到达(或超时)。无需管理火花流。
- **显示和查看器。** 在规格中请求一个 `vnc` **显示**,QEMU通过VNC暴露访客的屏幕,仅限环回。启用 **查看器** — 一个可选的进程内noVNC桥 — 就可以在浏览器中监视和控制该屏幕。查看器受密码保护,仅读取 **显示**;它从不触摸QMP会话。
### 与服务器通信:传输和身份验证
服务器通过 **stdio**(默认 — 大多数客户端直接启动服务器;无需网络,无需身份验证)或 **HTTP**(用于网络部署),或两者同时进行通信。HTTP传输是 **关闭失败的**:如果没有身份验证,它会拒绝启动 — API密钥或签名HS256令牌 — 除非您明确选择不安全的本地使用模式。可以在任何模式下运行的服务器都作为非根用户运行,从不使用 `--privileged`。
## 工具
代理的词汇 — 代理可以采取的动作:
| 工具 | 功能 |
| --- | --- |
| `create_instance` / `destroy_instance` | 根据硬件规格构建和启动实例 / 销毁实例 |
| `get_instance` / `get_status` | 当前实例 + 生命周期状态 / 实时访客运行状态 |
| `pause_instance` / `resume_instance` | 冻结 / 解冻访客CPU |
| `reset_instance` / `powerdown_instance` | 硬重置 / 请求礼貌的ACPI关闭 |
| `list_block_devices` / `query_cpus` | 虚拟机的磁盘和后台媒体 / 每个CPU的信息 |
| `screendump` | 显示的PNG快照 |
| `get_events` / `wait_for_event` | 最近的QEMU事件 / 阻塞直到命名事件到达 |
| `qmp_execute` | 原始QMP命令,由命令策略控制 |
| `create_image` / `list_images` / `list_isos` | 创建磁盘镜像 / 列出磁盘 / 列出启动ISO |
有关每个实现的工具表的详细信息,请参阅 [TypeScript](typescript/README.md#the-tools) 和 [Rust](rust/README.md#the-tools) README。
## 快速入门:常见场景
服务器在安装了QEMU的任何地方运行。首先,让其中一个实现运行,并将您的MCP客户端指向它 — **[运行TypeScript变体](typescript/README.md#run-it)** 或 **[运行Rust变体](rust/README.md#run-it)** — 然后要求您的代理执行某些操作。下面是一些场景:每个场景都是一个硬件规格(`create_instance`的参数),以及您需要事先配置的内容。
### 1. 一个用于测试的虚拟机
无需设置 — 只需请求一个小型机器并驱动它。
> *"启动一个1 GB的Linux虚拟机,并告诉我其运行状态。*
代理调用 `create_instance`,使用最小的规格,然后调用 `get_status`;`destroy_instance` 清理:
```json
{ "machine": "q35", "cpu": "host", "vcpus": 1, "memoryMb": 1024, "accel": "auto" }
```
(没有磁盘或ISO可供启动 — 非常适合烟雾测试;为实际操作添加媒体。)
### 2. 从ISO安装操作系统
将安装程序ISO放入您的 **ISO存储** 文件夹中;代理创建一个空白磁盘并从CD启动(`boot: "dc"`)。
> *"创建一个20 GB磁盘并从debian-13.iso安装Debian。*
它调用 `create_image`(进入镜像存储),然后调用 `create_instance`:
```json
{
"machine": "q35", "cpu": "host", "vcpus": 2, "memoryMb": 2048, "accel": "auto",
"disks": [{ "image": "debian.qcow2" }],
"cdrom": { "iso": "debian-13.iso" },
"boot": "dc",
"display": "vnc"
}
```
因为它请求了 `display: "vnc"`,所以您可以观看安装程序运行 — 请参见场景4。
### 3. 一个可以SSH连接的无头服务器
添加一个 **主机转发**,使主机上的端口可以访问访客上的端口。
> *"运行我的服务器镜像,无头,并将主机端口2222转发到访客端口22。*
```json
{
"machine": "q35", "cpu": "host", "vcpus": 2, "memoryMb": 2048, "accel": "auto",
"disks": [{ "image": "server.qcow2" }],
"network": { "hostForwards": [{ "hostPort": 2222, "guestPort": 22 }] }
}
```
一旦它启动,`ssh -p 2222 user@localhost` 从主机到达访客的SSH。
### 4. 在浏览器中观看
设置 `QMP_MCP_VIEWER_PASSWORD`,请求一个 `vnc` 显示,并打开查看器。设置细节在 [TypeScript](typescript/README.md#browser-viewer) / [Rust](rust/README.md#browser-viewer) README中;任何具有 `"display": "vnc"` 的规格现在在 `http://<host>:6080/` 处获得实时、交互式的屏幕。
### 5. 模拟不同的架构
将服务器指向另一个模拟器并选择匹配的机器和CPU。
> *"启动一个ARM64虚拟机。*
使用 `QMP_MCP_QEMU_BINARY=qemu-system-aarch64` 启动服务器,然后:
```json
{ "machine": "virt", "cpu": "cortex-a72", "vcpus": 2, "memoryMb": 2048, "accel": "tcg" }
```
(如果您还需要为非x86主机 **构建** Rust二进制文件,请参阅其 [交叉编译指南](rust/README.md#building-for-other-platforms)。)
## 选择实现
两者是可互换的 — 相同的工具,相同的规格,相同行为,持续相互检查。按生态系统选择:
| | TypeScript | Rust |
| --- | --- | --- |
| 基于 | Node + `mcp-framework` | `rmcp` + tokio |
| 发布形式 | npm包 / `node dist/index.js` | 单个自包含二进制文件 |
| 启动 | [运行 →](typescript/README.md#run-it) | [运行 →](rust/README.md#run-it) |
| 在Docker中 | [Docker →](typescript/README.md#docker) | [Docker →](rust/README.md#docker) |
所有与部署和使用相关的信息都位于这两个README中:
- **TypeScript** — [运行它](typescript/README.md#run-it) ·
[传输和身份验证](typescript/README.md#transports) ·
[Docker](typescript/README.md#docker) ·
[浏览器查看器](typescript/README.md#browser-viewer) ·
[配置](typescript/README.md#configuration) ·
[开发](typescript/README.md#developing)
- **Rust** — [运行它](rust/README.md#run-it) ·
[传输和身份验证](rust/README.md#transports) ·
[Docker](rust/README.md#docker) ·
[KVM加速](rust/README.md#faster-vms-with-kvm) ·
[浏览器查看器](rust/README.md#browser-viewer) ·
[交叉编译](rust/README.md#building-for-other-platforms) ·
[配置](rust/README.md#configuration) ·
[开发](rust/README.md#developing)
## 配置
两个实现都通过 `QMP_MCP_*` 环境变量进行配置 — **相同的名称和默认值**。完整的注释参考是 [`.env.example`](.env.example),命令策略文件格式是 [`policy.example.yaml`](policy.example.yaml)。您会用到的:
| 变量 | 默认值 | 功能 |
| --- | --- | --- |
| `QMP_MCP_TRANSPORT` | `stdio` | `stdio`,`http` 或 `both` |
| `QMP_MCP_API_KEYS` | _(未设置)_ | HTTP传输的API密钥(需要除非不安全) |
| `QMP_MCP_QEMU_BINARY` | `qemu-system-x86_64` | 要启动的模拟器 — 即访客架构 |
| `QMP_MCP_IMAGE_DIR` / `QMP_MCP_ISO_DIR` | XDG路径 | 镜像存储 / ISO存储文件夹 |
| `QMP_MCP_VIEWER_PASSWORD` | _(未设置)_ | 启用浏览器查看器 |
| `QMP_MCP_ALLOW_RAW_ARGS` | `false` | 允许规格中的`extraArgs`(逃生阀) |
… 加上磁盘/内存/虚拟CPU的上限,主机转发端口范围,命令策略允许/拒绝列表和事件缓冲区大小。请参阅 [`.env.example`](.env.example) 获取完整列表,或在上下文中参阅每个变体的配置部分([TypeScript](typescript/README.md#configuration) · [Rust](rust/README.md#configuration))。
## 开发者
### 布局
```
qmp-mcp/
├── typescript/ Node / mcp-framework 实现
├── rust/ Rust / rmcp 实现
├── testdata/ 共享的金色夹具,供双方实现验证
├── docs/ 设计笔记和原理
├── CONTEXT.md 领域词汇表 — 共享词汇
├── .env.example 每个 QMP_MCP_* 变量,带注释
└── policy.example.yaml 命令策略文件格式
```
两个实现是独立的代码库,它们在根目录下共享三项:**领域模型**([`CONTEXT.md`](CONTEXT.md) — 先阅读)、**黄金夹具**([`testdata/`](testdata/))和 **配置表面**([`.env.example`](.env.example))。
### 两个保持相同
这里的相同性不是承诺,而是测试。 [`testdata/`](testdata/) 包含语言中立的黄金夹具,它们固定了每个硬件规格必须生成的确切QEMU命令行和命令策略的精确裁决 — 并且 **两个** 实现都针对同一个语料库进行了测试。更改规格如何变成命令行,或策略允许什么,您会更新共享的夹具;TypeScript套件 **和** Rust套件必须同意,否则构建失败。在一个变体中教授新技巧时,您添加另一个必须满足的夹具。
在各自的文件夹中开发变体 — [开发TypeScript](typescript/README.md#developing) ·
[开发Rust](rust/README.md#developing)。 [`docs/`](docs/) 文件夹包含更深入的推理。
## 许可证
[MIT](LICENSE)。