虚拟机控制服务

通过SSH控制QEMU虚拟机,实现鼠标键盘控制、屏幕截图和命令执行的AI驱动自动化服务。

  • by Neanderthal
  • Python
  • 本地部署
  • 23 星标
  • 更新于
在 GitHub 查看 Neanderthal/mcp-qemu-vm

使用说明

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创建虚拟机

  1. 启动virt管理器
  2. 创建新VM(文件→ 新建虚拟机)
  3. 选择安装介质(ISO)
  4. 分配资源:
    • 内存:建议4096 MB
    • CPU:建议使用2+
  5. 重要:在“网络选择”下,选择“虚拟网络‘默认’:NAT”
  6. 完成安装

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:vmrobot X11访问(步骤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-desktop
    
    然后设置 VM_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.操作前始终进行截图

在任何互动之前:

  1. take_screenshot()
  2. 分析图像
  3. 确定当前焦点(哪个窗口/字段处于活动状态)
  4. 只有这样才能继续行动

切勿跳过截图以“节省时间” -盲目的行为会导致错误。

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.要避免的常见错误

  1. 点击终端后立即打字(焦点可能没有切换)
  2. 跳过屏幕截图以“节省时间”
  3. 使用 ssh_execute 用于嵌套环境命令
  4. 不在行动之间等待
  5. 假设焦点在未经验证的情况下切换

建筑

┌─────────────┐         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-8 LC_CTYPE,多字节 UTF-8(西里尔字母等)无法解码。
  • 修复(已应用): type_text 和那个 run_actions 现在键入step前缀 xdotool调用 LC_ALL=$VM_LOCALE (默认值 C.UTF-8),因此为非ASCII文本 开箱即用。用覆盖 VM_LOCALE 如果VM缺少env var C.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

  1. 检查VM是否正在运行:

    virsh -c qemu:///system list
    
  2. 检查网络是否处于活动状态:

    virsh -c qemu:///system net-list
    # If default is inactive:
    virsh -c qemu:///system net-start default
    
  3. 检查VM是否具有IP:

    virsh -c qemu:///system domifaddr <vm-name>
    
  4. 测试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谢尔盖·伊斯托明。

相关