背景:把 Windows 桌面当成“操作系统级 DOM”
Windows-MCP 是 CursorTouch 组织开源(MIT 协议)的一个 MCP server,作者 Jeomon George,用 Python 实现。它的定位不是再造一套自动化框架,而是一层适配器:把 Windows 桌面的操作能力以 MCP 工具的形式暴露出去,挂载到 Claude Desktop、Cursor、Claude Code 等客户端后,模型就能读界面、点按钮、敲键盘、跑 PowerShell。
它最值得关注的技术主张写在 README 里:不依赖传统计算机视觉,也不使用针对特定任务微调的模型,可以配合任意 LLM 工作。落到实现层面,就是主要读 Windows UI Automation(UIA)的无障碍树,而不是靠截图猜像素。
作者在 XDA 实测文章的评论区解释过这个选择:如果网页可以靠 DOM 稳定自动化,操作系统层面为什么不能做类似的事?Windows-MCP 本质上就是把 Windows 无障碍 API 当作“操作系统级 DOM”来探索。这个类比抓住了关键——DOM 好自动化,是因为元素有稳定的标识,不需要靠像素去猜;UIA 树在 Windows 上承担的就是同一个角色。
它暴露了什么能力
在客户端里挂载成功之后,通过 tools/list 实际能拉到大约 20 个工具,可以按用途分成几类:
感知类:Snapshot 抓取完整桌面状态,包含 UI 树、可交互元素和坐标;Screenshot 只截画面,跳过 UI 树,适合快速看当前屏幕;DisplayInventory 返回显示器布局、分辨率、DPI 与缩放。
输入类:Click、Type、Scroll、Move、Shortcut、Wait,以及 WaitFor(等待某个元素或文本出现)。
批量输入类:MultiSelect、MultiEdit,用于批量勾选和批量填表。
系统类:App 负责应用启停、切窗口、改尺寸;PowerShell 执行命令;Process 管进程;Registry 操作注册表;Notification 发系统通知。
数据类:FileSystem 读写文件、Clipboard 操作剪贴板、Scrape 抓取网页。
客户端覆盖面比较全,Claude Desktop、Perplexity Desktop、Gemini CLI、Qwen Code、Codex CLI、Claude Code、Autohand Code 都有配置说明,其中 Claude Desktop 那边已经进入官方的 Desktop Extensions 目录。
从代码规模看,整个项目约 24860 行 Python,体量最大的目录是 src/windows_mcp/uia/,其中 controls.py 一个文件就有 6381 行。这里是把它知名的开源库 UIAutomation(yinkaisheng 那套)内联了进来。换句话说,项目的“操作系统底子”大部分站在现成实现之上,自己主要写的是把 UIA 信息翻译成模型可读结构的那一层。
三条感知通道与它们的取舍
它的感知能力不是单点方案,而是按场景分了三档:
第一档是 UIA 树,也就是 Snapshot 默认的 use_ui_tree=True。信息最全,能拿到元素名、类型、自动化 ID、坐标和可执行动作,但复杂界面下会明显变慢。
第二档是纯截图 Screenshot。速度快,但只能依赖视觉判断,拿不到元素 ID,可靠性取决于模型视觉能力和坐标计算是否准确。
第三档是浏览器 DOM,用 Snapshot(use_dom=True),对 Chrome、Edge、Firefox 生效,可以直接拿网页 DOM 并过滤掉浏览器外壳。这里有个细节值得记一笔:Firefox 不通过 UIA 暴露 RootWebArea,项目改用 IAccessible2 兜底。宁可换第二个无障碍 API 也不退回截图,基本说明了整个项目的路线性格。
模型看到的 Snapshot 大概长这样。下面这段是本机开着记事本时抓取的,只摘了记事本这一棵子树:
- ├── window "Untitled - Notepad 2e x64 [ZH]"
- │ ├── (939,92) 按钮 "New" [action: click]
- │ ├── (963,92) 按钮 "Open" [action: click]
- │ ├── (987,92) 按钮 "Browse" [action: click]
- │ ├── (1011,92) 按钮 "Save" [action: click]
- │ ├── (1235,92) 按钮 "Word Wrap" [action: click]
- │ ├── (1410,955) 滚动条 "水平滚动条" [action: click]
- │ ├── (1747,35) 按钮 "最小化" [action: click]
- │ ├── (1866,35) 按钮 "关闭" [action: click]
- │ └── 菜单栏 "应用程序"
- │ ├── (946,66) 菜单项 "File" [action: click]
- │ ├── (991,66) 菜单项 "Edit" [action: click]
- │ └── (1040,66) 菜单项 "View" [action: click]
复制代码
模型不需要猜“那个长得像保存的图标在哪”,它知道有个按钮叫 Save,坐标是 (1011, 92),可以点击。官方演示的闭环也是这样跑的:看状态 → 启动应用 → 等待加载 → 再确认状态 → 输入文字,每一步都是一次独立的工具调用。
边界:UIA 读不到的地方怎么办
结构化的代价是定位绑死在可访问性接口上。自绘画布、游戏、DirectX 界面以及部分 Electron 窗口,UIA 树基本是空的,靠元素名点击这条路就走不通,README 自己也承认不适合用来玩游戏。
但要说得准确一点:UIA 读不到并不等于完全瞎了。Screenshot 这条通道可以把画面交给支持视觉输入的模型;README 把视觉明确定位成可选能力(Use Any LLM, Vision Optional),Snapshot 也支持 use_vision=True 把截图一起带上。真正的含义是——没有可用 UIA 信息时,模型可以退回到截图加坐标,但可靠性不再由项目兜底,取决于客户端与模型自身的视觉能力,以及坐标算得准不准。
性能瓶颈作者解释得很直接:性能依然严重依赖界面里可交互元素的数量,因为一切都由无障碍树驱动,庞大复杂的 UI 层级会拖慢速度,所以项目投入了大量通用、非硬编码的启发式规则来裁剪和压缩这些树。代码里能对应上,src/windows_mcp/tree/ 下有 budget.py(树预算)和 cache_utils.py(缓存)负责这件事。
另外两个工程细节也值得知道:截图后端是一条三级降级链 dxcam → mss → pillow,我本机上直接落到了最慢的 pillow;截图后会画一圈橙红色边框作为视觉反馈,可以通过环境变量 WINDOWS_MCP_DISABLE_FLASH=1 关闭。
真机跑一遍:握手与首次工具调用
我用 uvx 在本机拉起服务,以 stdio 方式启动,直接按 MCP 协议的 JSON-RPC 手动调用。初始化握手返回的关键信息:
- {
- "protocolVersion": "2025-06-18",
- "serverInfo": { "name": "windows-mcp", "version": "4.0.10" },
- "instructions": "Windows MCP server provides tools to interact directly with the Windows desktop..."
- }
复制代码
第一次真实调用是让它报本机 PowerShell 版本:
- tools/call PowerShell {"command": "$PSVersionTable.PSVersion.ToString()"}
- → Response: 5.1.26100.9549
- → Status Code: 0
复制代码
调用成功。这里有个容易被忽略的差别:它走的是 Windows PowerShell 5.1,而不是 PowerShell 7。如果你要写的脚本依赖 PS7 的语法或模块,这一点会直接影响结果。
随后走了一遍完整 GUI 闭环:App 启动记事本 → Wait 等待 → Click 定位 → Type 输入 → Screenshot 截图。文字确实是通过 Type 工具写进去的,全程没有碰键盘。
需要提醒的是,握手返回的 serverInfo 版本号与文档里建议锁定的版本号并不一致,前者是 4.0.10,而配置示例里给的是 0.8.6、演示源码里是 0.8.5。这类版本号错位在快速迭代的开源项目里常见,但用之前最好自己确认一遍实际装到的是哪个版本。
上手建议与安全边界
如果你打算试,几条实操心得很值得提前知道:
第一,先放虚拟机或 Windows Sandbox。这不是个人谨慎,而是厂商安全文档里明写的要求。一个能读屏、能敲键盘、能执行 PowerShell 的进程,本质上就是一双手。
第二,关掉用不到的高危工具。启动参数 --exclude-tools "PowerShell,Registry,FileSystem" 能砍掉一大片攻击面,默认全开的设定并不适合随手尝试。注意这只是工具级开关,管不到命令级——只要 PowerShell 工具还开着,它内部能跑什么就没有限制。
第三,锁定版本。不要让 uvx 自己挑,明确写 windows-mcp==0.8.6,并在安装前确认本地 Python 版本满足要求,否则可能在不知情的情况下用了旧版。
第四,远程访问必须配鉴权。--auth-key + --ip-allowlist + TLS 三件套要一起上,不要把 --host 0.0.0.0 裸着暴露。这个项目就因为没有鉴权吃过一个 HIGH 级漏洞:CVE-2026-48989,0.7.5 之前的 HTTP 模式控制面不带任何认证,可以被任意来源访问。
第五,关遥测。项目使用 PostHog 收集匿名使用数据,ANONYMIZED_TELEMETRY 默认开启(README 环境变量表写 true,实测的 0.8.5 源码里也是 os.getenv("ANONYMIZED_TELEMETRY", "true"))。项目声明不采集个人信息、工具参数和输出,如果在意,在客户端配置里加一行即可,注意值要写成字符串:
- "env": { "ANONYMIZED_TELEMETRY": "false" }
复制代码
第六,中文系统下注意 App 工具的匹配问题。按开始菜单名字启动应用可能失败,优先用 launch_executable 加完整路径。
小结
Windows-MCP 的技术路线选得对:用 UIA 当操作系统级 DOM,把结构化元素清单喂给任意 LLM,避开视觉方案对模型能力的高依赖;同时用截图通道作为兜底,用树预算和缓存来控制复杂界面的开销。但它目前的工程细节还偏毛糙,性能强依赖界面元素数量,安全默认值也需要使用者自己收紧。
它适合的目标是:目标明确、允许试错、失败代价低、界面由标准 Win32 控件构成的自动化任务。而需要精确点击和填表的场景,浏览器侧仍然建议走 CDP(Playwright 那一套)更稳。两者各管一段,并不互相替代。 |