使用说明
MCP QEMU VM控制
安全地让你的AI完全访问计算机。
让Claude(或任何兼容MCP的LLM)看到您的屏幕,移动鼠标,在键盘上键入,并运行命令——所有这些都在一个隔离的QEMU虚拟机内。非常适合人工智能驱动的自动化、测试和计算机使用实验,而不会危及您的主机系统。
用于通过SSH控制QEMU虚拟机的模型上下文协议(MCP)服务器。此服务器使LLM能够通过鼠标/键盘控制、屏幕截图和SSH命令执行与VM交互。
目录
特征
- 鼠标控制 -移动光标并单击按钮
- 键盘输入 -键入文本并发送组合键
- 动作批处理 -在一次调用中执行UI操作序列
- 截图 -捕获和检索虚拟机屏幕截图
- SSH命令执行 -在VM上运行shell命令
- 文件传输 -通过SFTP上传和下载文件
- 项目管理 -将输出与日志、结果和建议一起组织到项目文件夹中
- 咨询系统 -保存和检索未来LLM会话的提示
先决条件
主机系统
- Python 3.12+
uv(推荐)或pip- 带libvirt的QEMU/KVM
- virt管理器(可选,用于GUI管理)
VM要求
- Linux与X11桌面环境
- SSH服务器已启用
- 所需软件包:
openssh,xdotool,scrot,xrandr,xinput
QEMU/libvirt设置
1.安装虚拟化软件包
拱门/曼加洛:
sudo pacman -S qemu-full libvirt virt-manager dnsmasq iptables-nft
Debian/Ubuntu:
sudo apt install qemu-kvm libvirt-daemon-system libvirt-clients virt-manager bridge-utils
Fedora:
sudo dnf install @virtualization
2.配置libvirt
# Enable and start libvirtd
sudo systemctl enable --now libvirtd
# Add your user to libvirt group
sudo usermod -aG libvirt $USER
# Log out and back in, then verify
groups # should show 'libvirt'
3.设置默认网络
libvirt提供默认NAT网络(192.168.122.0/24)VM用于与主机通信:
# Check network status
virsh -c qemu:///system net-list --all
# If 'default' is not active, start it
virsh -c qemu:///system net-start default
# Enable autostart
virsh -c qemu:///system net-autostart default
默认网络配置:
- 桥梁:
virbr0 - 主机IP:
192.168.122.1 - DHCP范围:
192.168.122.2-192.168.122.254 - 模式:NAT(虚拟机可以访问互联网,主机可以访问虚拟机)
4.使用virt manager创建虚拟机
- 启动virt管理器
- 创建新VM(文件→ 新建虚拟机)
- 选择安装介质(ISO)
- 分配资源:
- 内存:建议4096 MB
- CPU:建议使用2+
- 重要:在“网络选择”下,选择“虚拟网络‘默认’:NAT”
- 完成安装
5.配置虚拟机
安装来宾操作系统后:
# Inside the VM - Install required packages
# Arch/Manjaro
sudo pacman -S --needed openssh xdotool scrot xorg-xrandr xorg-xinput
# Debian/Ubuntu
sudo apt install openssh-server xdotool scrot x11-xserver-utils xinput
# Enable SSH
sudo systemctl enable --now sshd
6.创建自动化用户
在VM上:
# Create vmrobot user
sudo useradd -m -s /bin/bash vmrobot
sudo passwd vmrobot
# Set up SSH key authentication
sudo -u vmrobot mkdir -p /home/vmrobot/.ssh
sudo -u vmrobot chmod 700 /home/vmrobot/.ssh
在主机上:
# Copy your public key to the VM
ssh-copy-id vmrobot@192.168.122.XX
# Or manually add to /home/vmrobot/.ssh/authorized_keys on VM
7.授予X11访问vmrobot的权限
vmrobot用户需要权限才能访问X显示器。在虚拟机上,作为拥有桌面会话的用户:
# Quick fix (run once per session)
xhost +local:vmrobot
# Permanent fix - add to ~/.xprofile or ~/.xinitrc
echo "xhost +local:" >> ~/.xprofile
8.选择SSH用户策略
SSH用户有两种方法:
选项A:专用 vmrobot 用户(默认)
- 更安全——权限有限,不会意外破坏桌面配置
- 需要
xhost +local:vmrobotX11访问(步骤7) - 集
VM_DESKTOP_USER如果你需要需要桌面命令 用户的上下文(剪贴板、密码管理器、dbus):然后设置# On the VM, allow vmrobot to run commands as your desktop user echo 'vmrobot ALL=(sergey) NOPASSWD: ALL' | sudo tee /etc/sudoers.d/vmrobot-desktop sudo chmod 440 /etc/sudoers.d/vmrobot-desktopVM_DESKTOP_USER=sergey在您的配置中。 使用ssh_execute("xclip -selection clipboard -o", as_desktop_user=True).
选项B:SSH直接作为桌面用户
- 更简单——开箱即用的完全桌面访问,无需xhost或sudo
- 集
VM_USER到您的桌面用户名(例如。,sergey) - 所有命令都以完全桌面权限运行
- 最适合不需要担心隔离的个人/开发虚拟机
9.查找虚拟机的IP地址
# From the host
virsh -c qemu:///system domifaddr manjaro
# Or from inside the VM
ip addr show | grep "inet 192.168.122"
10.测试连接
# Test SSH
ssh vmrobot@192.168.122.XX
# Test X11 automation
ssh vmrobot@192.168.122.XX 'DISPLAY=:0 xdotool getmouselocation'
# Test screenshot
ssh vmrobot@192.168.122.XX 'DISPLAY=:0 scrot /tmp/test.png && echo Success'
安装
1.克隆存储库
git clone https://github.com/Neanderthal/mcp-qemu-vm.git
cd mcp-qemu-vm
2.安装依赖项
使用 uv (推荐):
uv venv && source .venv/bin/activate
uv pip install -r requirements.txt
使用 pip:
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
配置
设置环境变量或创建 .env 文件:
| 变量 | 默认值 | 描述 |
|---|---|---|
VM_HOST |
192.168.122.79 |
VM IP地址 |
VM_USER |
vmrobot |
SSH用户名 |
VM_PORT |
22 |
SSH端口 |
VM_DISPLAY |
:0 |
X11显示器 |
VM_IDENTITY |
(空) | SSH私钥路径(可选) |
VM_DESKTOP_USER |
(空) | 桌面会话所有者,如果与 VM_USER |
VM_LOCALE |
C.UTF-8 |
强制使用UTF-8语言环境 xdotool type (非ASCII输入) |
VM_KNOWN_HOSTS |
(无) | SSH known_hosts文件路径(可选) |
VM_CONNECT_TIMEOUT |
10 |
SSH连接超时(秒) |
看 .env.example 用于记录模板。
用法
MCP客户端配置
添加到您的MCP客户端配置中(例如,Claude Desktop claude_desktop_config.json):
{
"qemu-vm-control": {
"command": "python3",
"args": ["/path/to/mcp-qemu-vm/server.py"],
"env": {
"VM_HOST": "192.168.122.79",
"VM_USER": "vmrobot",
"VM_PORT": "22",
"VM_DISPLAY": ":0"
}
}
}
配置文件位置:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - 窗户:
%APPDATA%/Claude/claude_desktop_config.json - Linux:
~/.config/Claude/claude_desktop_config.json
使用MCP Inspector进行开发
uv run mcp dev server.py
# With custom environment
VM_HOST=192.168.122.79 VM_USER=vmrobot uv run mcp dev server.py
独立运行
python server.py
工具参考
项目管理
项目将所有输出(屏幕截图、日志、结果、建议)组织到以下带时间戳的文件夹中 data/projects/.
| 工具 | 说明 |
|---|---|
project_init(name, description) |
创建新项目(截图前需要) |
project_load(project_path) |
加载现有项目 |
project_list() |
列出所有项目 |
project_info() |
获取当前项目统计信息 |
project_log(message, level) |
添加日志条目 |
project_read_logs(lines, level_filter) |
读取项目日志 |
project_save_result(filename, content) |
保存结果文件 |
project_save_advice(title, content) |
为以后的会话保存提示 |
project_read_advice() |
阅读所有已保存的建议 |
鼠标和键盘
| 工具 | 说明 |
|---|---|
move_mouse(x, y, mode) |
移动光标(模式:“绝对”或“相对”) |
click(button, count, x, y) |
点击按钮;可选的 x, y 在一个操作中移动并单击 |
click_in_window(x, y, button, count) |
点击与活动窗口客户端区域相关的坐标 |
get_active_window_info() |
活动窗口id、标题、位置和几何图形 |
scroll(direction, amount) |
鼠标滚轮在光标处滚动(上/下/左/右) |
drag(x1, y1, x2, y2, button) |
开始时按,拖动到结束,释放(选择/滑块/DnD) |
type_text(text, human) |
键入文本(换行→ 返回,UTF-8安全); human=True 以较慢、逼真的节奏打字(每字速度不同+随机停顿) |
press_keys(keys) |
按下组合键。, ["Ctrl", "L"] |
key_down(keys) / key_up(keys) |
按住/松开键或修改器(例如Shift键单击) |
set_clipboard(text) |
加载VM剪贴板(快速插入大ASCII码) |
paste(text) |
设置剪贴板(如果 text)Ctrl+V |
activate_window(title, window_id) |
按标题或id聚焦并打开窗口 |
wait(seconds) |
暂停执行 |
run_actions(actions) |
在一次调用中执行一系列操作 |
批处理操作示例
[
{"action": "press_keys", "keys": ["Ctrl", "Shift", "p"]},
{"action": "wait", "seconds": 0.5},
{"action": "type_text", "text": "Terminal: Focus Terminal"},
{"action": "press_keys", "keys": ["Return"]}
]
对象位置(OCR)
按可见文本定位屏幕元素——精确的像素坐标,否 协调猜测。主机OCR全分辨率屏幕截图(tesseract)和 将匹配直接映射到点击路径中。适用于任何可见文本,包括 嵌套的Citrix/web,可访问性API无法访问;找不到未标记的 图标。
| 工具 | 说明 |
|---|---|
find_text(query, min_conf) |
OCR屏幕;归还每场比赛的中锋和禁区 |
click_text(query, index, button, count) |
找到文本并单击其中心(精确) |
click_text("Submit") # finds "Submit" and clicks its exact center
find_text("File") # lists all matches with coordinates
click_text("OK", index=1) # click the 2nd "OK" if several match
缩放(放大一个区域,然后精确地单击它)
当细节太小/对比度太低而无法在完整屏幕截图中解决时,请放大 一个区域并在其中单击。服务器 保持作物映射,所以有一点 您可以将缩放后的图像映射选择回精确的全屏像素——否 坐标数学。
| 工具 | 说明 |
|---|---|
zoom(x, y, width, height, scale) |
裁剪(x,y)并放大;返回可视图像+映射 |
click_zoomed(zx, zy, button, count) |
单击上次缩放图像坐标中给出的点 |
zoom(800, 600, width=400, height=300, scale=4) # view a 4× magnified crop
click_zoomed(610, 250) # click that spot → exact full-screen pixel
标记集(按数字选择)
对于密集或模糊的屏幕,请叠加 编号标记 在每个检测到的文本上 元素并按其编号选择一个——这是一个更可靠的离散选择 而不是估计坐标。
| 工具 | 说明 |
|---|---|
mark_screen(min_conf, max_marks) |
用编号框对屏幕进行注释;返回图像+图例 |
click_mark(n, button, count) |
单击标记的元素 n |
mark_screen() # view the annotated screenshot + legend (0 -> "File", 1 -> "Edit", …)
click_mark(1) # click element #1 at its exact center
主机要求: tesseract (二进制)加号 pillow 和 pytesseract
在服务器的Python环境中。这些是可选的——服务器的其余部分运行
没有他们;只有OCR(find_text/click_text)和缩放(zoom)工具
需要他们(zoom 只需要 pillow):
# Arch/Manjaro host
sudo pacman -S tesseract tesseract-data-eng
uv pip install pillow pytesseract
SSH操作
| 工具 | 说明 |
|---|---|
ssh_execute(command, as_desktop_user) |
在VM上运行shell命令 |
ssh_upload(local_path, remote_path) |
将文件上传到VM |
ssh_download(remote_path, local_path) |
从VM下载文件 |
ssh_connection_info() |
获取连接状态 |
截图
| 工具 | 说明 |
|---|---|
take_screenshot() |
截图(需要活动项目) |
屏幕截图保存到项目的 screenshots/ 文件夹,并在以下位置作为MCP资源公开 vm://screenshot/{id}.
显示校准
| 工具 | 说明 |
|---|---|
display_calibration_info(recalibrate) |
显示xdotool↔屏幕截图比例因子; recalibrate=True 重新探测它们 |
启动时自动检测缩放因子(HiDPI/缩放不匹配);坐标工具透明地应用它们。
典型工作流程
1. project_init("my-task", "Description")
2. take_screenshot()
3. ... perform VM operations ...
4. project_read_logs()
5. project_save_result("output.txt", data)
6. project_save_advice("Title", "Lessons learned...")
继续工作:
1. project_list()
2. project_load("data/projects/...") # Shows any saved advice
3. ... continue work ...
LLM自动化的最佳实践
这些经验教训是从现实世界的使用中吸取的,有助于避免常见的陷阱。
1.操作前始终进行截图
在任何互动之前:
take_screenshot()- 分析图像
- 确定当前焦点(哪个窗口/字段处于活动状态)
- 只有这样才能继续行动
切勿跳过截图以“节省时间” -盲目的行为会导致错误。
2.不要相信鼠标点击可以聚焦
点击窗口/终端并不能可靠地切换焦点,尤其是在以下情况下:
- 嵌套环境(Citrix、远程桌面)
- 高延迟连接
- 具有多个面板的应用程序(VS代码、IDE)
请改用键盘快捷键:
[
{"action": "press_keys", "keys": ["Ctrl", "Shift", "p"]},
{"action": "wait", "seconds": 0.5},
{"action": "type_text", "text": "Terminal: Focus Terminal"},
{"action": "wait", "seconds": 0.3},
{"action": "press_keys", "keys": ["Return"]},
{"action": "wait", "seconds": 0.5}
]
然后 take_screenshot() 在打字前进行验证。
3.所需等待时间
| 此操作后 | 等待时间 |
|---|---|
| 打开命令面板 | 0.5秒 |
| 键入搜索文本 | 0.3s |
| 按下回车键 | 0.5-1.0秒 |
| 命令执行 | 1.0-2.0秒 |
| 窗口/焦点开关 | 0.5秒 |
切勿快速开火 -它们可能会出现故障。
4.使用批处理操作
使用 run_actions() 而不是单独调用工具来减少延迟并确保排序:
# Instead of 5 separate calls:
run_actions([
{"action": "press_keys", "keys": ["Ctrl", "Shift", "p"]},
{"action": "wait", "seconds": 0.5},
{"action": "type_text", "text": "command"},
{"action": "wait", "seconds": 0.3},
{"action": "press_keys", "keys": ["Return"]}
])
5.SSH范围限制
ssh_execute 仅达到 第一VM层.对于嵌套环境(VM→ 思杰→ Windows),使用UI自动化在可见终端中键入命令。
6.恢复命令
| 问题 | 解决方案 |
|---|---|
| 在错误的窗口中键入(几个字符) | Escape → u (在Vim中撤消) |
| 多条线放错了地方 | Escape → uuuuuuu |
| 文件已损坏 | Escape → :e! → Enter (重新加载) |
| VS代码还原 | Ctrl+Shift+P → “还原文件” |
7.要避免的常见错误
- 点击终端后立即打字(焦点可能没有切换)
- 跳过屏幕截图以“节省时间”
- 使用
ssh_execute用于嵌套环境命令 - 不在行动之间等待
- 假设焦点在未经验证的情况下切换
建筑
┌─────────────┐ SSH ┌──────────────┐
│ │ ◄──────────────────► │ │
│ MCP Server │ │ QEMU VM │
│ (Host) │ │ (Linux) │
│ │ │ │
└──────┬──────┘ └──────────────┘
│ │
│ MCP Protocol │
│ (stdio) │
│ │
▼ ▼
┌─────────────┐ xdotool, scrot
│ LLM Client │ X11 automation
│ (Claude) │
└─────────────┘
网络拓扑:
┌────────────────────────────────────────────────────┐
│ Host (192.168.122.1) │
│ ┌──────────┐ │
│ │ virbr0 │◄── NAT bridge │
│ └────┬─────┘ │
│ │ │
│ ┌────┴─────┐ │
│ │ QEMU VM │ 192.168.122.79 │
│ │ (manjaro)│ │
│ └──────────┘ │
└────────────────────────────────────────────────────┘
UI动作调度
所有的xdotool相互作用都是由一小部分纯 命令构建器
(_type_cmd, _keys_cmd, _click_cmd, _move_cmd)因此,shell命令用于
动作只在一个地方构建。每个建设者都需要一个
shlex.quote()d显示字符串,并返回在VM上运行的命令;这
构建器还拥有自己的输入验证(键名模式、按钮映射、点击计数
clamp)和UTF-8语言环境前缀用于打字。
两条路径消耗了这些构建器:
- 独立工具 (
move_mouse,click,type_text,press_keys,wait) --具有键入签名和丰富文档字符串的单独公开的MCP工具。 run_actions--批处理路径。它通过发送ACTION_HANDLERS, 一{name: async handler}注册表,即 单一事实来源 为了 批处理支持哪些操作。每个处理程序共享签名async (app_ctx, display, action_dict) -> summary。未知操作名称引发 并停止该批处理(与“第一次出错时停止”合同一致)。
run_actions(actions)
│ for each action
▼
ACTION_HANDLERS[name] ──► _act_*(app, display, action)
│ uses
▼
_type_cmd / _keys_cmd / _click_cmd / _move_cmd
│
▼
run_vm_cmd(ssh, …) ──► xdotool over SSH
添加新的批处理操作: 写一个 _act_<name>(app, display, action) 处理器
(重复使用或添加 _*_cmd 构建器)并添加一个条目 ACTION_HANDLERS不
需要对调度循环进行更改。
项目结构
mcp-qemu-vm/
├── server.py # Main MCP server (single file)
├── pyproject.toml # Project metadata, ruff & pytest config
├── requirements.txt # Python dependencies
├── .env.example # Documented env var template
├── test_ssh_tools.py # Unit tests (no-VM) + manual SSH smoke check
├── LICENSE # MIT
├── data/
│ └── projects/ # Project folders
│ └── YYYYMMDD-HHMMSS_name/
│ ├── screenshots/
│ ├── logs/
│ ├── results/
│ └── advice/
└── README.md
已知问题和限制
实际嵌套环境使用中确认的问题(主机→ 思杰→ 视窗→ 展望)。 每个都列出了症状、根本原因和当前的解决方法。
#1和#2固定在 server.py. #3-#6是嵌套的固有局限性
环境(Citrix/RDP会话策略)或体系结构(SSH登录到第一个VM上
仅限层)——它们无法在此服务器中修复,因此解决方法仍然是
推荐的方法。
1. type_text 在西里尔文/非ASCII文本上失败--已修复
- 症状:
type_text(以及任何xdotool type非ASCII)错误 退出状态1。直接运行显示:Invalid multi-byte sequence encountered / xdo_enter_text_window reported an error.ASCII文本类型很好。 - 根本原因:
xdotool type使用当前区域设置对多字节输入进行解码, 但是vmrobot/桌面用户SSH环境具有 没有UTF-8区域设置 (LANG空,键盘布局裸露us).没有UTF-8LC_CTYPE,多字节 UTF-8(西里尔字母等)无法解码。 - 修复(已应用):
type_text和那个run_actions现在键入step前缀 xdotool调用LC_ALL=$VM_LOCALE(默认值C.UTF-8),因此为非ASCII文本 开箱即用。用覆盖VM_LOCALE如果VM缺少env varC.UTF-8(例如设置VM_LOCALE=ru_RU.utf8;检查可用的区域设置locale -a).
2.键入文本中嵌入的换行符变为文字字形,而不是Enter--FIXED
- 症状: 键入多行文本(例如。
xdotool type随着\n,或type --file -)进入像Outlook这样的丰富编辑器,生成一个 按段落运行 在换行符应该所在的地方使用杂散的方框/控制字符字形——段落 休息时间丢失。 - 根本原因: 在这个嵌套的Citrix中→ Windows路径
\n(LF)作为 文本控制字符到编辑器,而不是被解释为Return 按键。 - 修复(已应用):
type_text(以及run_actions键入step)现在分割文本 换行符,通过stdin键入每一行,并显式发送换行符Return钥匙 按下而不是文字LF。\r\n和\r首先是正常化。这适用于 终端和富编辑器——不需要调用端拆分。
3.访客会话中可能禁用剪贴板重定向
- 症状: 设置主机/X剪贴板(
xclip -selection clipboard)和粘贴 随着Ctrl+V做 不 将文本传输到Windows/Citrix层。 - 根本原因: 在Citrix/RDP会话策略中关闭剪贴板重定向, 因此,内部会话有自己的隔离剪贴板。
- 解决方法: 不要依赖复制/粘贴在嵌套边界上注入文本; 回到打字上来(见问题1和问题2)。
4.长时间操作后,注意力悄然被盗
- 症状: 长类型/自动化序列成功,但后续按键
(例如。
BackSpace更正文本) 无效 --通过零像素差异进行验证 在截图之前/之后。 - 根本原因: 桌面/邮件通知吐司(例如新邮件弹出窗口)抓住焦点 中途,所以后来的钥匙放错了窗户。
- 解决方法: 通过单击目标窗口/字段重新确认焦点 立即 在每个键盘爆裂之前,以及 验证 带有屏幕截图的结果(裁剪 区域和差异),而不是信任工具的退出代码。保持键盘爆裂 短,所以焦点窃取腐败更少。
5.鼠标点击不可靠,无法切换窗口/焦点
看 最佳实践§2在嵌套环境中
点击通常会引发 不同的 背景窗口超出预期;没有可靠的
Alt+Tab (它泄漏到主机WM)。更喜欢应用内任务栏/窗口控件
用屏幕截图验证每个开关。
6. ssh_execute 仅到达第一个VM层
ssh_execute 仅登录到主机/第一个VM上。命令做 不 到达Citrix内部/
Windows层——使用UI自动化(type_text, press_keys, run_actions)对于那些。
看 最佳实践§5.
故障排除
无法连接到VM
-
检查VM是否正在运行:
virsh -c qemu:///system list -
检查网络是否处于活动状态:
virsh -c qemu:///system net-list # If default is inactive: virsh -c qemu:///system net-start default -
检查VM是否具有IP:
virsh -c qemu:///system domifaddr <vm-name> -
测试SSH连接:
ssh vmrobot@192.168.122.XX
鼠标/键盘不工作
- 验证
xdotool已安装在VM上:which xdotool - 检查X11显示:
echo $DISPLAY(应该是:0) - 手动测试:
DISPLAY=:0 xdotool getmouselocation - 非ASCII文本失败,退出1/“无效的多字节序列”?缺少UTF-8 区域设置--请参见 已知问题#1.
- 换行符不起作用/在文本上运行?看 已知问题#2.
截图失败/X11授权错误
如果你看到 Authorization required, but no authorization protocol specified:
快速修复 (在VM上以X会话所有者身份运行):
xhost +local:vmrobot
永久修复 -添加到 ~/.xprofile:
xhost +local:
验证访问权限:
# Check current xhost settings
DISPLAY=:0 xhost
# Should show:
# access control enabled, only authorized clients can connect
# LOCAL:
VM网络问题
# Restart the default network
virsh -c qemu:///system net-destroy default
virsh -c qemu:///system net-start default
# Check virbr0 bridge exists
ip addr show virbr0
许可证
根据 MIT许可证 --©2026谢尔盖·伊斯托明。