关于 dsh-cua - Windows Computer Use
dsh-cua 是面向 Windows 的计算机操作 MCP 服务和 Agent 技能,可供 Claude Code、Codex、Cursor、Cline 等客户端使用。它优先通过可访问性元素执行操作,并协调多个 Agent;检测到有人使用电脑时会让出物理输入。
主要功能
- 01通过 stdio 提供 19 个计算机操作工具
- 02读取和操作 UI Automation 元素
- 03串行化多个 Agent 进程的操作
- 04有人使用时让出物理输入
- 05以回执区分操作发送与效果验证
适用场景
- 01让 Agent 检查和操作 Windows 应用
- 02协调共享一台 Windows 电脑的多个 Agent
- 03避免在人使用电脑时执行物理输入操作
MCP 相关
通过 stdio MCP 服务提供 19 个工具,名称均以 `tool_` 开头。任何 stdio MCP 客户端都能枚举工具;调用工具需要 Windows。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
19收录的工具列表 · 更新于 2 周前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| filter_title | string | 否 |
详细介绍
dsh-cua
English · 中文
用于 Windows 电脑操作的 MCP 服务 + Agent 技能:优先使用无障碍元素操作,截图仅作后备方案。它内置跨会话仲裁器——当多个 Agent 共用一台机器时,会对它们的操作进行串行处理;当你亲自使用电脑时,仲裁器会让出控制权。
此仓库仅包含 MCP 服务和技能。它对任何 stdio MCP 客户端(dsh / Claude Code / Codex / Cursor / Cline / ZCode …)都保持中立——这里没有任何内容要求使用 dsh。
平台语义(0.3.1 及更高版本):这些工具仅适用于 Windows——它们通过 user32/kernel32 和 UI Automation 执行操作。但此软件包也可在其他平台导入,服务也能在这些平台启动,并照常响应 tools/list;因此,任何客户端或目录爬虫都可以枚举全部 19 个工具及其完整架构;实际调用工具时会返回清晰的“需要 Windows”错误,而不会导致进程无法启动。在 0.3.0 中,导入本身会引发错误,导致此类爬虫完全无法发现该服务(tests/linux-handshake.py 是针对这一属性的回归测试,CI 会在 ubuntu-latest 上运行该测试)。
内容简介
这是一个通过 stdio 通信的 MCP 服务,提供 19 个工具。每个工具名称都以 tool_ 为前缀,与
tools/list 返回的名称完全一致。
- 观察(只读,可随时调用):
tool_skyshot(以紧凑、可对比的文本树读取窗口——比截图小三个数量级)、tool_element_at_point、tool_read_element、tool_find_elements、tool_capture_window(感知 DPI,并裁剪至客户区)、tool_list_windows/tool_find_window/tool_get_window_rect、tool_list_displays、tool_cursor_position、tool_clipboard_read、tool_coexistence_status - 元素操作(软门控:在各 Agent 之间串行执行,不注入物理输入):
tool_element_action/tool_element_action_at— press / set_value / select / toggle / expand / collapse / scroll_into_view / focus,直接发送给 UIA 元素,因此 绝不会抢占焦点,也无需考虑 Z 序 - 其他会修改状态的调用(同样采用软门控:使用互斥锁,但不会因人类操作而让出):
tool_type_text(通过 PostMessage 定向输入,并提供effect_verified)、tool_clipboard_write、tool_open_application。它们 不会模拟物理输入,因此不会等待你停止操作——但写入剪贴板仍会覆盖你上次复制的内容,所以操作时请先说明。 - 物理输入(硬门控:在各 Agent 之间串行执行,并会让出给人类):只有两类操作共用你的鼠标和键盘——
tool_click_at的 raw_event 路径和tool_send_keys的全局快捷键。门控会等到机器输入静默,然后返回user-active,而不是和你争抢鼠标。tool_click_at会先尝试其元素路径 (ax_press);该路径不会注入物理输入,因此只需获取互斥锁。回执中的method字段会说明实际执行的路径。
此处的“只读”是指不会执行修改操作,也不会模拟输入,因此有人正在使用机器时也可以安全调用。其中有两个工具会产生值得了解的副作用:
tool_capture_window 会将截图写入磁盘(指定 save_path;若省略则使用临时文件),而
tool_skyshot 会更新服务端的差异基线,供下一张截图进行比对。
小型树结构并不能证明窗口是空的。tool_skyshot 和 tool_find_elements 会报告
minimized,因为最小化窗口可能显示的是“未显示任何内容”,而不是“里面什么都没有”——且隐藏内容的多少取决于应用,因此回执会报告状态,而不会猜测原因。此处的实测结果:在读取前最小化的 Edge 窗口会显示浏览器框架,完全没有页面内容(include_offscreen 也无法恢复内容);Explorer 最小化时,元素数量会从 40 个降至 8 个;Notepad 则不受影响。如果结果很重要,请先还原窗口,再重新读取。
每项操作都会返回回执,而不是仅报告自身是否成功:action_sent /
effect_verified / foreground_changed / user-active / arbiter-busy。“调用已被接受”和“效果已发生”是两回事,工具会将两者区分开来,供 Agent 判断。
差异
目前已有几个成熟的 Windows 开源实现。dsh-cua 的差异集中在一点上:与人共用一台机器。
| dsh-cua | cua-driver | ahk-mcp | lean-computer-use-mcp | |
|---|---|---|---|---|
| 通过 UIA 模式执行元素操作(不抢焦点,z 顺序无关) | ✅ | ✅(ax 模式) | ❌ 通过 UIA 读取,按坐标点击执行操作 | 通过 cua-driver |
| 最近有人类输入 → 拒绝操作 | ✅ user-active | ❌ | ❌ | ❌ |
| 跨 Agent 序列化(多进程) | ✅ 命名互斥体 | ❌ | ❌ | ❌ |
| 每项操作的效果断言 | ✅ 三态 effect_verified | 报告交付等级 | ❌ | ❌ 仅有 state_changed 启发式判断 |
| 测量抢占前台的副作用 | ✅ foreground_changed | ❌ | ❌ | ❌ |
| 工具数量 | 19 | 59 | 15 | 6 |
关键区别在于两件经常被混为一谈的事:
- “不抢焦点”是一项机制保证——通过 UIA 模式或定向的
PostMessage实现,因此不会实际触碰光标和键盘焦点。cua-driver 支持此功能(ax 模式)。ahk-mcp 不支持,而且区别并不只是“是否使用 UIA”:它通过 UIA 读取(ahk_uia_tree/ahk_uia_find/ahk_uia_url),但没有 UIA 模式操作——根据其 README,它通过坐标点击或模拟按键执行操作,因此操作会移动真实光标。 - “你一开始操作就让出控制权”是一项时序保证——它通过
GetLastInputInfo读取人类上次输入距今的时间,检测到你正在使用机器时会等待,超时后则拒绝操作(user-active),而不是贸然介入。截至 2026-09-25,对表中另外三个代码库进行模式搜索后,未发现等效功能——这只是搜索所得证据,并非证明;而且仅涵盖输入时间检测:cua-driver 确实有其他类型的面向人类的防护措施(要求征得同意,以及检测抢占前台并进行恢复)。
effect_verified 也是替代方案所缺少的功能:它区分“调用已被接受”和“效果已发生”,并提供三种状态(true 表示已按预期发生变化;false 表示已接受但没有变化,降级为失败;null 表示没有可比较的状态,即未确认)。通常的替代做法是在操作后再观察一次,并由模型自行判断。
dsh-cua 不提供的功能(提前说明,以免产生误解):不提供自身的定位能力——服务不会分析像素,因此纯文本模型无法操作树无法表达的界面(画布、游戏、远程桌面)。使用支持视觉的模型时,端到端支持像素操作路径:capture_window 会返回图像以及经过验证的图像→屏幕映射(bounds、scale、dpi_verified),定位由模型完成。还不提供录制和回放,也不提供隔离沙箱。这些功能有更合适的工具。
常见问题
为什么不让 Agent 在第二个桌面或虚拟显示器上运行,这样就不会触碰我的桌面?
因为 Windows 没有可用于此用途的构建基础,而可行的移动端设计恰恰依赖于 Windows 缺少的功能。在 Android 上,应用可以创建 VirtualDisplay 并向其发送输入——输入事件带有显示器 ID,因此 Agent 的点击会发送到自己的屏幕,人类使用的触摸屏则不会受到影响。**Windows 按桌面而非显示器路由输入:**一个桌面只有一个输入队列和一个光标位置。
因此,显而易见的类似方案都行不通:
| 思路 | 为什么无法隔离 |
|---|---|
| 添加虚拟显示器(间接显示驱动程序) | 只是多了一个画布,而不是多了一个光标——指针在所有显示器上仍只有一个位置 |
Windows 虚拟桌面(Win+Ctrl+D) | 同一会话中的视图切换:输入队列和光标都相同 |
| 第二个会话(RDP 或另一个用户) | 确实实现了隔离,但客户端 Windows 每个用户一次只能有一个交互会话——远程连接会锁定控制台,因此人就无法使用自己的屏幕,而这恰恰是整个方案的目的 |
隐藏的 Win32 桌面(CreateDesktop) | Agent 会拥有自己的输入队列和光标,但它的窗口不可见,因此只能操作自己启动的实例,无法操作你正在看的程序。(根据窗口站/桌面模型推断;此处未进行测量。) |
这张表讨论的是输入。第二台显示器究竟能带来什么,值得准确说明,因为答案并非“什么都没有”。在 Edge 上使用隔离的配置文件进行测量(tests/verify-visible-vs-foreground.py):对于仅仅显示出来的窗口,Chromium 会构建页面的无障碍树。查询前已最小化的窗口报告没有页面;同一窗口一经显示,就会报告一个有名称的页面——此时它不是活动窗口(GetGUIThreadInfo(hwndActive) 为 false),且被完全遮挡(其区域露出在最上层的部分为 0%)。所有状态都从操作系统读取,而非凭假设得出;每次遍历期间的 ForegroundWatch 监测(140 万至 270 万次采样,前台窗口始终未变)表明,读取过程完全不会抢占焦点。因此,把浏览器窗口停放在第二台显示器上,就能用 skyshot 读取内容,同时焦点始终留在你的屏幕上。
窗口移到第二台显示器后,输入能力并不会保留。向同一窗口发送的 WM_CHAR 会被 PostMessage 接受,却不会产生任何变化——无论窗口是否被遮挡;而在同一窗口处于活动状态时发送完全相同的消息,则会插入字符。因此,第二台显示器带来的是读取能力,而非操作能力;而需要操作的界面(Chromium 内部界面、画布、游戏),恰恰都需要它无法提供的激活状态。限制在于输入:对于输入,每个桌面仍只有一个光标和一个前台窗口。
需要说明一点,因为“我的浏览器在第二台显示器上,运行正常”确实描述了另一条路径。以上所有内容都针对操作系统级注入,并不适用于浏览器自身的协议。在同一台机器上,通过 CDP 操作一个带界面的 Edge 窗口进行测量(Input.dispatchMouseEvent,即 Playwright 的 page.mouse.click 所发送的消息):点击到达时是可信事件,系统光标没有移动哪怕一个像素(GetCursorPos 前后完全相同),前台窗口没有改变;即使进一步将窗口最小化(IsIconic 为 true,前台窗口是另一个窗口),后续点击仍然成功。CDP 将输入注入浏览器自身的输入管线,位于操作系统输入队列之上,因此单光标/单前台窗口规则根本无法影响它——同样,这条规则也无法影响通过应用自身 API 而非屏幕进行操作的对象。(窗口最小化时,页面的 document.visibilityState 仍显示为 visible;这是 Playwright 启动 Chromium 时设置的标志导致的现象,并非 Chromium 的普遍行为。点击能否送达是协议本身的特性,与此无关。)
决定结果的是输入进入的层级:
| 路径 | 是否需要光标/前台 | 是否跨越显示器或遮挡边界 |
|---|---|---|
| 应用自身的协议(CDP、Playwright、DOM/JS 事件、应用 API) | 否 — 不会注入任何 OS 级输入 | 是,窗口甚至不必可见 |
element_action(发送给元素的 UIA 模式) | 否 — 但窗口必须存在并公开一个元素 | 是 |
向窗口发送 PostMessage(type_text) | 在 Chromium 上的实测结果:是 — 目标必须处于活动状态,文本才会显示 | 否 |
物理输入注入(SendInput、click_at 的原始事件路径、send_keys) | 是 — 整个桌面只有一个光标和一个前台窗口 | 否 |
因此,对于网页来说,“将浏览器放到虚拟显示器上”是个不错的方案,而且本项目并不与之竞争:网页本身就是可寻址、可脚本化的界面。dsh-cua 面向的是没有这种界面的目标 — Explorer、原生对话框、Office、画布和游戏、没有脚本 API 的应用 — 在这些场景中,OS 输入是唯一可用的途径,而第二台显示器恰恰无法帮助这条途径。两种说法都成立。
因此,冲突并非实现上的缺陷,而是 OS 的限制:Windows 没有第二个光标。 鉴于此,让出控制权才是唯一正确的响应 — 而大多数调用根本不会遇到这个问题,因为它们完全不会注入输入:
element_action/element_action_at— 将 UIA 模式发送给元素:没有物理输入、没有光标移动、没有焦点变化。这些路径“可以在用户打字时运行”。type_text— 面向窗口的PostMessage,而非全局按键。它会先解析窗口内的文本控件(顶层窗口不会将WM_CHAR转发给其子编辑控件),并读取该控件的内容,因此回执会携带effect_verified,而不只是报告已发送消息。 一项实测限制,针对 Chromium: 向非活动窗口中的 Chromium 窗口发送WM_CHAR,PostMessage会接受该消息,但不会产生任何变化;而向同一窗口(处于活动状态时)发送相同消息,则会插入字符(tests/verify-visible-vs-foreground.py)。因此,如果浏览器窗口在后台,不要指望文本会显示出来 — 请查看回执:你会得到effect_verified: null(未确认),而不是错误的ok。未经实测: 在实时网页中,解析器实际会选中哪个控件(Chromium 的渲染窗口并非EDIT类控件,因此输入位置可能会退回到窗口本身)。回执中的resolved_by/target_hwnd会告诉你实际选中了什么 — 这是用你自己的目标快速确认结果的方法。- 所有只读工具(
skyshot、element_at_point、capture_window、…)— 不会触碰任何内容。
只有两条路径会注入物理输入,之所以存在,是因为画布、游戏和 Chromium 内部界面不会公开可寻址的元素:tool_click_at 的原始事件路径,以及 tool_send_keys 的全局热键。这两条路径正是仲裁器要防护的对象。
安装
要实际操作桌面,你需要 Windows x64 + 交互式桌面会话 + Python ≥3.10。(此软件包也可在 Linux/macOS 上安装并启动,tools/list 会正常响应,但调用工具时会提示“requires Windows”——参见上文“平台语义”。)
# Option 1: uvx, zero install (recommended)
uvx --from dsh-cua dsh-cua-server # runs the stdio MCP server directly
# Option 2: pip
pip install dsh-cua
# Option 3: from source
pip install git+https://github.com/Hutusion/dsh-cua.git
无论采用哪种安装方式,都要使用 python -m dsh_cua 启动服务:
python -m dsh_cua # depends on no executable being on PATH
为什么
uvx命令要写出脚本名称。 此软件包提供 两个 控制台脚本:dsh-cua-server和dsh-cua,二者都会启动同一个服务。上文使用了显式形式(--from dsh-cua dsh-cua-server),因为它适用于所有已发布版本:短名称是在 0.4.1 中添加的,因此在 0.4.0 上运行uvx dsh-cua仍会失败,并提示 “An executable nameddsh-cuais not provided by packagedsh-cua”。在uvx中,你需要显式指定脚本;uvx会在自己的临时环境中运行该脚本,因此不涉及PATH。出于另一个无关原因,
pip下的同一脚本不太理想:pip会将控制台脚本安装到解释器的Scripts目录中,而该目录不一定在 PATH 中——在原版 python.org 3.12 安装中实测,用户 PATH 和系统 PATH 均未包含此目录,因此pip install dsh-cua安装成功后,运行dsh-cua-server却提示找不到命令。python -m dsh_cua完全不需要 PATH 条目。此控制台脚本仍会随软件包提供,只要 PATH 中包含它,就可以正常使用。选项 1 和 2 目前都可用:此软件包已发布到 PyPI(https://pypi.org/project/dsh-cua/)。如果
uvx/pip以后返回 404,请使用选项 3——它始终有效。
接入
任意 MCP 客户端均可;将服务命名为 win32(技能的工具名称约定为 mcp__win32__*)。
python -m(不依赖 PATH,推荐):
{ "mcpServers": { "win32": { "command": "python", "args": ["-m", "dsh_cua"] } } }
uvx:
{ "mcpServers": { "win32": { "command": "uvx", "args": ["--from", "dsh-cua", "dsh-cua-server"] } } }
更多配置示例见 examples/:Claude Code / 通用客户端 / dsh cordis.patch.yml 片段 / 如果你希望模型读取屏幕截图,则需要的路由模态声明(tr-route-settings.yml)。
技能(可选,但强烈推荐)
skill/computer-use/SKILL.md 是使用这些工具的配套准则:观察 → 定位 → 操作 → 验证循环、回执语义、重试安全性,以及与人类共存的规范。模型无需它也能使用这些工具,但有了它,模型就能自行选择正确的路径——实测差异很大。
技能位于此仓库中,而不在软件包中——uvx 和 pip 不会在你的磁盘上创建 skill/ 目录,因此只有在检出仓库后,下面的 cp 命令才能使用。若没有检出仓库,请获取此文件:
mkdir -p ~/.dsh/skills/computer-use
curl -fsSL https://raw.githubusercontent.com/Hutusion/dsh-cua/main/skill/computer-use/SKILL.md \
-o ~/.dsh/skills/computer-use/SKILL.md # use ~/.agents/skills/ for Claude Code
如果已克隆仓库,则复制整个目录:
# Claude Code / generic agents
cp -r skill/computer-use ~/.agents/skills/
# dsh
cp -r skill/computer-use ~/.dsh/skills/
安全模型
| 层级 | 操作 | 限制机制 |
|---|---|---|
| 只读 | 12 个观察工具 | 无限制,可随时调用 |
| 软性 | 元素操作、PostMessage 输入、剪贴板写入、启动应用程序 | 跨 Agent 互斥锁(命名互斥锁,多进程,自动串行化) |
| 严格 | 原始点击、全局热键 | 互斥锁 + GetLastInputInfo 让出控制:如果用户近期输入过内容,操作会等待;超时后会拒绝操作并返回 user-active,而不是抢夺光标 |
需如实说明的边界:让出控制是一种协作协议,并非绝对保证(注入前 150 ms 的紧密检查会尽可能缩小操作窗口);少数应用程序即使在调用 set_value 时也会自行激活(回执会如实报告 foreground_changed);对于同一窗口上的两个操作者,目前没有技术解决方案——不要操作 Agent 正在操作的同一窗口。
测试
python tests/verify-coexistence.py # 25 checks: zero-input proof / cross-process mutex / synthetic human contention / kill switch
python tests/verify-p0-fixes.py # the three P0s fixed in 0.2.0: each fails before the fix
测试无需人工配合——“用户输入”由一次真实的 1 像素光标移动模拟,之后光标会恢复原位。
测试需要真实的交互式桌面会话(某些检查会创建窗口并通过 UIA 与其交互),因此无法在 GitHub 托管的运行器上运行。CI 覆盖的是无需桌面的部分:打包和安装、模块导入、差异索引和树形行转义的回归测试,以及仲裁器的决策逻辑——请参阅
.github/workflows/ci.yml。
python tests/ci-desktop-free.py # the local equivalent of the above, no desktop needed
许可证
MIT
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。