ARTICLE · 1310316
AI控电脑别靠截图:1万星MCP源码实测
2026-10-29 晚上,我在 Mac 上让脚本连点 7 下计算器,屏幕上出现了 37×24=888,整个过程没有一次是「看截图找按钮」。每一步它拿到的是 24 个按钮的名字、坐标和显示屏的当前值;同一思路在 Windows 上的代表项目 Windows-MCP,截至 2026-10-29 已有 10,694 星。

AI 控制电脑最大的难题在于点得准不准、点完知不知道结果;模型能不能看懂屏幕,反倒是次要的。截图路线让模型从像素里反推坐标,点歪了只能再截一张图去猜;无障碍树路线直接问操作系统「这个窗口里有哪些控件、在哪、现在是什么值」。所以我的做法是:桌面应用先读结构,网页用浏览器快照,截图留给前两条都拿不到东西的场合。

上图三张卡片就是全文的选型结论。下面先讲截图路线具体难在哪,再读 Windows-MCP 的源码,最后给出 Mac 上的实测数据和 8 个坑。
一、截图点鼠标,难点不在「看懂」
截图路线的循环是固定的:截全屏,交给多模态模型,模型说「点 (812, 430)」,执行点击,再截一张图确认。这条路的优势是通用,任何画得出来的东西都能看;问题出在中间那一步,坐标是模型从像素里估出来的。
先把一个常见的误解排除掉:截图并不算贵。按 Anthropic 视觉文档给的估算方式,图片 token 约等于宽乘高再除以 750。我这台 Mac 的逻辑分辨率是 1512×982,整屏一张约 1,980 token;只截计算器窗口(230×408)约 125 token。十几步操作下来也就几万 token,账单不是主要矛盾。
麻烦集中在三处。第一处是坐标换算。Peekaboo 返回的坐标上下文里写着 native_scale: 2,同一个计算器窗口,逻辑尺寸是 230×408,Retina 原图是 460×816。模型如果按原图像素报坐标,而执行层按逻辑点去点,落点会偏出一倍。
第二处是验证。点完「等于」之后,截图路线要再截一张图,让模型读出显示屏上的数字;数字字体、深色主题、抗锯齿都会影响识别。无障碍树直接返回显示屏控件的 value 字段,读回来就是字符串。
第三处是状态。按钮是禁用还是可点、复选框勾没勾、滑块停在多少,截图里只能靠颜色猜;树里这些都是现成字段。Windows-MCP 的源码里专门把滑块的取值范围写进输出,理由就是「只知道当前值、不知道能拖多远」时,模型没法决定下一步。
无障碍树本来是给读屏软件准备的。Windows 的叫 UI Automation,macOS 的叫 Accessibility API,系统为每个窗口维护一棵控件树,节点上有角色、名字、位置和当前值。读屏软件靠它把界面念给视障用户听,AI 用它来定位控件,用的是同一套数据。
二、Windows-MCP 源码:一棵 UIA 树怎样压成几行文本
Windows-MCP 是 CursorTouch 维护的 MCP 服务端,MIT 协议,当前版本 0.8.8,要求 Windows 10/11 和 Python 3.14 以上,用 uvx windows-mcp serve 一条命令就能启动。它对外提供 21 个工具,分成输入(Click、Type、Scroll、Shortcut)、观察(Snapshot、Screenshot、WaitFor)和系统(App、PowerShell、FileSystem、Registry)几类。

仓库页右侧能看到 10.7k 星和 v0.8.8 的发布标签。我把 0.8.8 的源码拉下来读了一遍:src/windows_mcp 下的 Python 代码约 2.8 万行,其中 uia/ 目录(UI Automation 的封装)就占了一万四千多行。真正决定 AI 体验的,是 tree/ 目录里的四处细节。
1. 先决定哪些控件算「能点」
tree/config.py 第 1 到 26 行定义了一个集合 INTERACTIVE_CONTROL_TYPE_NAMES,列了 17 类可交互控件:按钮、列表项、菜单项、编辑框、复选框、单选框、下拉框、超链接、标签页、树节点、滑块、滚动条等。遍历整棵树时,不在这个集合里的节点不会出现在给模型的列表中。
这份名单是踩出来的。第 18 到 24 行有一段注释:滑块原本不在名单里,导致 Windows 设置里「文字大小」那个滑块(当前值 100、最大值 225)根本进不了树,后面专门处理滑块的分支成了死代码。漏一类控件,AI 就等于看不见这一类按钮。
2. 一个控件只占一行
tree/views.py 第 84 行决定了输出格式,每个控件渲染成一行:
lines.append(f'{connector}{coords}{ctrl} "{name}" [action: {action}]{meta}')
coords 是控件中心坐标,ctrl 是控件类型,action 由类型推出来:编辑框对应 fill,复选框对应 toggle,下拉框对应 select,其余都是 click。meta 会追加焦点、密码框、当前值、取值范围、勾选状态、展开状态和快捷键。模型读到的就是「坐标 + 类型 + 名字 + 该做什么」,不需要再从图里猜。
3. 给树设预算,防止撑爆上下文
tree/budget.py 第 19 行:DEFAULT_MAX_TREE_ELEMENTS = 500。一次快照最多收 500 个元素,超出后在输出末尾附一句截断说明,提示模型缩小范围、滚动到目标区域,或在浏览器里改用 use_dom=True。这个上限可以用环境变量 WINDOWS_MCP_MAX_TREE_ELEMENTS 调大。
预算解决的是现实问题:一个开了几十个标签页的浏览器,控件数轻松上千,全量塞给模型既慢又费 token。
4. 处理「不守规矩」的应用
tree/service.py 第 490 行附近处理了一种环路:某些控件返回的子节点,RuntimeId 和自己的祖先相同(注释里点名是 Edge 展开的下拉框),递归会一直绕圈直到栈溢出,快照永远不返回。代码记录祖先 RuntimeId,遇到重复直接剪掉这一枝。
同一个文件第 946 到 950 行用 UIA 的 CacheRequest 批量取属性,作用域设为「元素本身 + 直接子节点」,把逐个属性的跨进程调用合并成一次。README 里还写了另一条防线:某个窗口卡住不响应时,跳过它,把其余窗口照常返回,不让一个假死的应用拖住所有后续调用。
这四处细节说明,无障碍树方案的工作量集中在过滤、压缩和容错,调系统接口只占很小一部分。系统给的是一棵又大又乱的树,交给模型的必须是一份短而准的清单。
截图没有被抛弃
Windows-MCP 同时保留了 Screenshot 工具,README 的说法是:只需要视觉上下文时,Screenshot 应该是第一个调用,因为它跳过了树的提取,速度更快。Snapshot 也支持 use_vision=True,在元素清单之外附一张截图。树和截图是分工,不是替代。
它的局限写在 README 末尾,三条都和树有关:

第一条是选不中段落里的一部分文字,因为树里只有整段文本,没有字符级的位置;第二条是 Type 工具会把整段代码一次性敲进去,不适合在 IDE 里写程序;第三条是不能玩游戏,游戏画面是渲染出来的像素,没有控件树。
还有两处配置建议改掉。一是遥测:它默认通过 PostHog 上报匿名使用数据,在 MCP 配置里加环境变量 ANONYMIZED_TELEMETRY=false 可以关闭。二是桌面控制权:AI 操作期间,用户明显移动鼠标或按 Ctrl+Alt+Shift+Backspace 就能收回控制,空闲 10 秒后才交还给 AI。README 自己也说明这只是尽力而为,不是安全边界。
在 Claude Code 里接入,项目级配置一条命令即可(以下来自 README 与 Claude Code 的 MCP 命令格式,我没有 Windows 实机,未实测):
claude mcp add windows-mcp -e ANONYMIZED_TELEMETRY=false -- uvx windows-mcp serve
三、在 Mac 上实测同一思路:Peekaboo
Windows-MCP 只支持 Windows。Mac 上走同一思路、维护活跃的是 Peekaboo:openclaw/Peekaboo,5,407 星,MIT 协议,当前版本 4.9.7,要求 macOS 15 以上。它既是命令行工具,也能以 MCP 服务端的形式接进 Claude Code、Codex 和 Cursor,底层读的是 macOS 的 Accessibility API。
安装与权限
为了不污染系统环境,我把它装在临时目录里,不做全局安装:
npm i @steipete/peekaboo@4.9.7 # 临时目录内安装,node_modules 约 80MB
npx peekaboo permissions status
它需要两项系统权限:屏幕录制(截图用)和辅助功能(读树、发点击用),另有一项可选的事件合成权限。我的终端之前授权过,三项都显示 Granted,permissions status 耗时 1.8 秒。
然后对计算器执行一次观察:

上图 Element Summary 以下的部分就是模型拿到的东西:每个按钮一个编号、一个角色、一个名字。这一次识别出 36 个元素,其中 24 个可交互,用时 0.55 秒。加 --json 时,每个元素还带边框坐标、identifier(如数字 7 的标识是 Seven)和 value。
标注图:框的位置来自系统
把这份清单画回截图上,就是下面这张图:

每个蓝框对应树里的一个节点,框的位置来自系统,不来自模型的估计。这张图是 see --annotate --retina 生成的,只给人看;脚本本身不需要它。
四、7 步算出 888:动作是怎么发出去的
接下来是开头那个实验。脚本逻辑只有一个循环:先观察,按名字找到按钮编号,再用这次观察的快照编号去点击。
forlabelin["All Clear","3","7","Multiply","2","4","Equals"]:
x=see()
eid=next(e["id"]foreinx["ui_elements"]ife.get("label")==label)
subprocess.run([P,"click","--on",eid,"--snapshot",x["snapshot_id"],
"--app","Calculator"],capture_output=True)
print(f"{label:<9} -> {eid}")

倒数第二行是读回的显示屏:列表里两个值,前一个是算式 37×24,后一个是结果 888,都是字符串,不需要 OCR。这次运行总耗时 25.01 秒;当晚稍早我用同一个循环先跑过一次,耗时 8.53 秒,每步观察约 0.6 秒、点击约 0.6 秒。两次相差约三倍;从第一次的分项计时看,观察和点击各占一半,循环越长,每步重新观察的开销越明显。
中文输入也能后台完成
计算器只有按钮,我又用文本编辑测了输入。给文本区写入一句「AI 控制电脑,先读无障碍树,再动鼠标。」共 20 个字符,type 用后台方式投递给进程,0.59 秒完成,命令输出「Typing confirmed」,再观察一次,文本区的 value 与写入内容逐字一致。
还有一种更直接的写法 set-value:不模拟键盘,直接修改控件的值,0.51 秒完成并读回确认。它绕开了输入法,适合填表;代价是有些应用只监听键盘事件,直接改值不会触发它们的校验逻辑,用之前要在目标应用里试一次。
按编号点,和按坐标点不是一回事
Peekaboo 的 MCP 文档里有一句容易被略过的话:按元素编号发起的调用,优先走无障碍动作;按坐标发起的调用,一律走合成鼠标事件。前者相当于告诉系统「对这个按钮执行按下」,系统直接调用控件的 AXPress;后者是在屏幕某个点上模拟一次真实点击。
实测输出能对上这条规则。文本编辑那次 type 的回执里写着 Mode: background to PID 96741,文字是直接投递给进程的,不经过前台焦点。按坐标点击则依赖屏幕上那个点此刻是什么,窗口被挡住、被挪动,点到的就是别的东西。所以只要树里有目标控件,就按编号点,坐标只留给树里没有的东西。
文档还提到窗口清单里的一个字段 observation_capability。值为 pixels_only 时,附带的原因是 no_matching_accessibility_window,意思是这个窗口只能截图、读不到树,调用方应该改用不带元素的观察。我在全屏空间那个坑(第五节坑 4)里用 window list 看到的正是这个状态,它比报错信息更早告诉你「这里只剩截图可用」。
接进 Claude Code
Peekaboo 官方文档给的 MCP 配置是让客户端直接启动 peekaboo mcp,通过标准输入输出通信。在 Claude Code 里,按项目挂载的写法如下(我没有改动自己的 Claude Code 配置,第七节的启动和调用数据是绕过客户端、直接用 MCP 协议测的):
claude mcp add peekaboo -- npx -y @steipete/peekaboo mcp
挂上之后的推荐用法是:第一步让它调用 see 拿元素清单,后续动作都带上这次的快照编号和元素编号;动作完成后再 see 一次,用读回的值判断是否成功。这和上面脚本的循环是同一个结构,区别只是由模型来决定每一步点哪个名字。
如果只是偶尔需要它,比如一次性整理某个应用的设置,更稳妥的方式是用完就从项目配置里移除。它申请的屏幕录制和辅助功能两项权限范围很大,常驻在配置里,任何会话里的模型都能调用它看屏幕、点按钮。
五、8 个坑,都附本机报错原文
下面这些坑都出现在 2026-10-29 的实测里,每条给出现象、原因和处理方式。其中坑 2 和坑 3 与「快照」机制有关,这是树方案和截图方案用法差别最大的地方。
坑 1:后台冷启动被拒绝。 执行 peekaboo app launch Calculator,返回 Cold background app launch is refused before dispatch。Peekaboo 默认后台投递,不抢前台焦点;启动一个没在运行的应用不属于安全的后台操作。加 --foreground 即可,代价是窗口会跳到你面前。
坑 2:快照用一次就作废。 第一版脚本只观察一次,然后连点 7 个编号,结果第一下成功,后 6 下全部报 Snapshot is stale ... already drove a mutation。改成按名字点击也一样。原因是任何一次修改都会让桌面状态变化,旧快照里的坐标不再可信。Peekaboo 强制「观察一次、动作一次」。
坑 3:元素编号每次都变。 第一次观察时 Delete 是 elem_7、All Clear 是 elem_8;几分钟后再观察,变成了 elem_9 和 elem_10。编号是快照内部的临时序号,不能缓存。要找稳定的标识,用 label 或 identifier,计算器的数字 7 始终是 Seven。
坑 4:全屏 Space 让窗口「消失」。 我的终端是全屏独占一个桌面空间,计算器被留在另一个空间。此时 see 报 target was not found ... reason=window minimized,窗口其实没有最小化。window focus --bring-to-current-space 也报「no eligible window」。最后的办法是退出计算器,再用 --foreground 重新启动,让它开在当前空间。
坑 5:不给路径,截图就落在桌面。peekaboo see --app Calculator 不带 --path 时,会把截图存成桌面上的 peekaboo_see_<数字>.png。脚本循环跑几十次,桌面就多出几十张图。脚本里一律显式传 --path,或者用 MCP 方式调用,截图会写进系统临时目录。
坑 6:读回的值带隐形字符。 显示屏的值读出来是 '\u200e888',开头是 U+200E 从左到右标记,算式里每个数字和符号前都有一个。直接拿 value == "888" 比较会失败,断言前先去掉这类方向控制字符。
坑 7:有的应用一启动没有窗口。 文本编辑启动后先弹出文件选择面板,没有文档窗口,see 找不到目标;用 menu click 点「新建」也报 Could not find accessibility element for window。我改用 AppleScript 的 make new document 建出文档窗口,后续操作才正常。文档窗口共 45 个元素,其中十几个是标尺刻度,属于噪声。
坑 8:用完要收拾后台进程。 Peekaboo 运行时会拉起两个常驻进程,空闲 300 秒才退出,同时在 ~/.peekaboo 和 ~/Library/Application Support/Peekaboo 下留下锁文件和套接字。实测结束后我结束了这两个进程,删掉了两个目录。
六、覆盖度:不是每个应用都交出了树
无障碍树能用到什么程度,取决于应用自己有没有认真实现无障碍接口。我对本机正在运行的几个应用各观察一次,统计可交互元素数:

Microsoft To Do 交出 54 个,WPS Office 38 个,计算器 24 个,都够用。Finder 的数字要看清楚:它一共返回 162 个元素,其中 115 个是没有明确角色的节点,可交互的只有 18 个,列表里大量文件条目没有被识别成可点击项。
Clash Verge 是图上高亮的那一项,只返回 7 个元素、3 个可交互,基本就是窗口的关闭、缩放、最小化按钮。它是用 Web 技术打包的桌面应用,界面画在内嵌网页视图里,内容没有暴露给系统的无障碍接口。iPhone 镜像更极端,see 直接报找不到可共享的窗口,它的画面实际上是一路视频流。
这类场合树方案就失效了,只能回到截图。这也是 Windows-MCP 保留 Screenshot、Peekaboo 的 see 默认附带截图的原因:先给结构,结构不够时,图还在。游戏、远程桌面、视频流、自绘界面,都属于必须用截图的范围。
什么时候该切到截图,建议写成明确的判断条件,不要每次让模型自己琢磨。按这次实测的现象,可以归成三条:窗口清单的 observation_capability 是 pixels_only;see 返回的可交互元素只剩关闭、缩放、最小化这类窗口按钮,Clash Verge 就是这种情况;按名字找不到目标控件。命中任意一条,就改用带截图的观察,让模型看图定位。Windows-MCP 一侧对应的信号是快照末尾的截断说明和失败窗口提示。
切到截图之后,验证方式也要跟着换。树方案读回 value 就能断言,截图方案只能让模型描述画面,再由你判断描述是否可信。所以同一个任务里,能用树完成的步骤尽量用树,把截图步骤压到最少,出错时也更容易定位是哪一步看错了。
七、接入成本:工具定义本身就占上下文
MCP 服务端接进客户端后,工具定义默认会交给模型,这部分开销与你这次用不用它无关;部分客户端支持按需加载工具定义,能缓解但不能消除它。我绕过客户端,直接用 MCP 协议和 Peekaboo 对话,量了一下:

第一行是重点:26 个工具,工具清单的 JSON 共 57,431 字节。按英文文本粗略估算,大约一万多 token,不做按需加载时每个会话都要付一次。第二、三行是一次 see 的返回:3,725 字符的元素清单,加一张 89,180 字符(base64 编码)的窗口截图,用时 0.41 秒。
作为对照,我用同样的方式量了微软的 Playwright MCP 0.0.83:25 个工具,工具清单 21,332 字节,不到 Peekaboo 的四成。冷启动一个无头 Chrome 打开 example.com 用了 8.83 秒;页面快照没有直接塞进返回值,而是写成一个 YAML 文件,节点带 ref=e1 到 ref=e13 这样的编号,模型按编号点击。
两者思路相同,都是先给结构化清单再按编号动作;区别是 Playwright MCP 只管浏览器里的页面,Windows-MCP 和 Peekaboo 管整个操作系统。只在浏览器里干活,就用 Playwright MCP,没必要为它付操作系统级工具的上下文开销和系统权限。
由此得出一条配置原则:桌面控制类 MCP 只在需要它的项目里用项目级配置挂上,不要放进全局配置。MCP 装多了的代价,在《Claude Code 与 Codex 的 Skill 与 MCP 加载源码对比》里有加载机制的分析;哪些 MCP 该卸,见《别再照榜单装 MCP》。
八、选型:四种方案各管一段
把上面的实测和源码结论合在一张表里。表里的方案按平台分工:截图式全平台通用,Windows-MCP(10,694 星)管 Windows 10/11,Peekaboo(5,407 星)管 macOS 15 以上,Playwright MCP(0.0.83)只管浏览器;星数为 2026-10-29 查询值,「我的用法」一列是我自己的配置决定。
浏览器内的任务还有一个选项 browser-use,它以 Python 库的形式使用,适合自己写 Agent 时直接调用;在 Claude Code 里操作网页,Playwright MCP 接入更省事。让 Agent 用浏览器走完整条用户旅程的做法,在《E2E 最贵的不是写脚本,是让 Agent 替你点完整条旅程》里有完整的链路。
选型顺序是:任务在网页里,用浏览器快照;在桌面应用里,先读无障碍树;树里拿不到目标控件,再交给截图。
结语
让 AI 控制电脑,常见的直觉是换一个视觉更强的模型,让它把截图看得更准。这次读源码加实测下来,我的结论是换个方向:定位和验证这两件事,操作系统早就替读屏软件做好了,按钮叫什么、在哪、当前是什么值,问一句就能拿到准确答案,用不着让模型从像素里猜。
Windows-MCP 能做到一万多星,靠的是对那棵又大又乱的树做的过滤、压缩和容错:17 类控件的白名单、一个控件一行、500 个元素的预算、环路剪枝。Peekaboo 在 Mac 上用同一思路跑通了按钮点击、中文输入和读回校验,代价是每一步都要重新观察,以及我列出的 8 个具体的坑。
截图仍然有它的位置,游戏、视频流和自绘界面离不开它。正确的分工是让结构化数据承担定位和验证,让截图只处理结构覆盖不到的那部分画面。
往期关联
MCP 装得越多,会话越慢?加载机制看《Claude Code 与 Codex 的 Skill 与 MCP 加载源码对比》 给 AI 开了屏幕和键盘权限,怎么限制它能做什么?见《Claude Code 与 Codex 的权限门控源码对比》 哪些 MCP 装了也是白占上下文?见《别再照榜单装 MCP》















































































