给 AI 装上操作 Windows 的手:Windows-MCP 部署、加固与实测
把 Windows-MCP 接进 MCP 宿主(WorkBuddy / Cursor)的完整过程:三处官方文档与实测不符的坑、权限策略该写在哪、三步验证法、一条长链路的效率实测(8 分钟 → 8.77 秒),附速查清单、已知脆弱点与回滚方案。
起因:想让 AI 直接操作 Windows 桌面——点按钮、填表单、跑那些没有 API 的老软件。 本文记录把 Windows-MCP 接进 MCP 宿主(WorkBuddy / Cursor)的完整过程,包含三处官方文档与实测不符的坑、权限策略该写在哪的判断,以及一次长链路实测的效率数据。 所有命令与配置都在 Windows 11 + 中文系统上实际跑过并验证。
零、先决条件
- Python ≥ 3.12(uvx 会自带 3.12+;宿主侧只需装 uv 即可)
- 不需要视觉模型:Windows-MCP 走 UI Automation 元素树,任何支持 tool calling 的 LLM 都能驱动,截图只是可选兜底
- 中文 Windows 可用,但要留意
App工具按开始菜单名匹配不稳(非致命,可用PowerShell兜底启动)
一、为什么是 Windows-MCP
选它的原因很实际:它不需要视觉模型。
市面上多数 Computer Use 方案要么依赖微调过的 GUI 专用模型,要么靠纯截图 + 坐标回归,对本地硬件要求高、稳定性差。Windows-MCP 走的是另一条路——读 Windows 的 UI Automation 元素树,直接拿到”这里有个按钮叫 XX、坐标是 (x,y)“这类结构化信息。
这带来两个直接好处:
- 任何支持 tool calling 的 LLM 都能驱动,不需要显卡、不需要本地模型;
- 动作延迟实测 0.2~0.9 秒(官方标称区间,实测符合),因为没有图像推理这一步。
它暴露的工具面大致分四类:状态感知(Snapshot / Screenshot / DisplayInventory)、键鼠原语(Click / Type / Scroll / Move / Shortcut / Clipboard)、等待(Wait / WaitFor)、以及高风险类(PowerShell / Registry / FileSystem / Process)。
它的坑也很明确:对 Electron / Chromium 应用,元素树是”断裂”的——后面第七节会详细说。
二、部署三步
2.1 装 uv
Windows-MCP 官方唯一的启动方式是 uvx,所以先装 uv:
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
装到 %USERPROFILE%\.local\bin,并自动写入用户级 PATH(无需管理员权限)。
⚠️ 不要用
pip install uv装到某个托管运行时里——会污染宿主运行时,独立安装器是更干净的路径。
2.2 先冒烟测试,别急着常驻
uvx windows-mcp serve --transport streamable-http --host 127.0.0.1 --port 8000
这一步产生零持久改动,但必须做——因为下面三个坑只有跑起来才能发现(见第三节)。
首次运行会拉几十个依赖包,可能要等一两分钟。官方也提示:首跑可能超时,忽略并重试一次即可。
2.3 注册为常驻服务
uvx windows-mcp install --transport streamable-http --host 127.0.0.1 --port 8000
产物:
| 项 | 位置 |
|---|---|
| 计划任务 | windows-mcp-server(每用户,登录自启,装完立即启动) |
| 包装脚本 | ~/.windows-mcp/start-server.cmd |
| 日志 | ~/.windows-mcp/server.log / server.error.log |
已知无害噪音:在中文 Windows 上执行 install 会往 stderr 抛一串 UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb4——这是 uvx 用 UTF-8 读取 GBK 子进程输出的解码问题。exit code 仍为 0,任务正常创建,可以直接忽略。
三、三个官方文档与实测不符的坑
这三个坑如果不实测,配出来的东西就是坏的。建议任何 MCP server 接入都先做一次冒烟测试。
坑一:端点路径是 /mcp,不是 /mcp/
README 和 OAuth 配置示例都写的是 /mcp/(带尾斜杠)。实测:
POST /mcp/ → 307 Temporary Redirect
POST /mcp → 200 OK
服务自身的启动日志也确认端点是 http://127.0.0.1:8000/mcp。
结论:配置里用不带尾斜杠的版本,省掉一次重定向。 对部分 MCP 客户端来说,多一跳重定向可能就是连接失败。
坑二:工具名不是 Shell-Tool / App-Tool
README 的正文里出现过 Shell-Tool、App-Tool、Launch-Tool 这些名字,但实际工具清单里没有它们。实测 20 个工具的真实名称是:
App, DisplayInventory, PowerShell, FileSystem, Snapshot, Screenshot, Click, Type,
Scroll, Move, Shortcut, Wait, WaitFor, Scrape, MultiSelect, MultiEdit, Clipboard,
Process, Notification, Registry
对应关系:Shell-Tool → PowerShell;App-Tool + Launch-Tool → 合并进 App。
为什么这很要命:工具过滤是靠名字匹配的。名字写错,策略会静默失效——你以为禁用了某个工具,其实什么都没禁。
坑三:配置文件里只能”排除”,不能”白名单”
源码 infrastructure/config.py 里:
@dataclass
class ToolsConfig:
exclude: list[str] = field(default_factory=list)
只有 exclude,没有白名单键。 想要白名单只能走 CLI 的 --tools 参数。
三种过滤方式(优先级:CLI > 环境变量 > 配置文件):
| 方式 | 写法 |
|---|---|
| CLI 白名单 | serve --tools "Snapshot,Click,Type" |
| CLI 黑名单 | serve --exclude-tools "Registry,PowerShell" |
| 环境变量 | WINDOWS_MCP_TOOLS / WINDOWS_MCP_EXCLUDE_TOOLS |
| 配置文件 | config.toml → [tools] exclude = [...] |
策略口径速查
| 口径 | 配置 | 说明 |
|---|---|---|
| 最小权限 | CLI --tools "Snapshot,Screenshot,DisplayInventory,Click,Type,Scroll,Move,Shortcut,Clipboard,Wait,WaitFor,MultiSelect" | 白名单只能走 CLI;未来新增工具默认不放行 |
| 标准 | exclude = ["Registry", "App"] | App 在中文系统按名匹配不稳 |
| 宽松 | exclude = ["Registry"] | 仅挡唯一能直接破坏系统的工具 |
四、权限策略该写在哪(本文最重要的一条)
这是整个接入过程中最容易做错、也最不容易发现的地方。
Windows-MCP 支持把工具过滤写进两个地方:命令行参数,或 config.toml。看起来等价,实际上有天壤之别。
关键事实:windows-mcp install 生成的 ~/.windows-mcp/start-server.cmd 是安装器的产物。如果你把权限策略写在那个脚本里,下次执行 windows-mcp install(比如升级版本、或修复问题)时,它会被原样覆盖,你的策略静默消失——而且不会有任何报错。
查源码可以确认:install() 函数根本不读写 config.toml,唯一调用 write_config 的是 auth 命令。
所以:
✅ 策略写进 ~/.windows-mcp/config.toml ← 用户配置,重装不丢
❌ 策略写进 ~/.windows-mcp/start-server.cmd ← 安装器产物,重装被覆盖
完整 schema:
[server]
transport = "streamable-http" # stdio | sse | streamable-http
host = "127.0.0.1"
port = 8000
stateless_http = false # true 时不发 Mcp-Session-Id,抗服务重启
auth_key = "" # 端点 Bearer 鉴权
[security]
ip_allowlist = [] # 服务只绑 loopback 时没用(源 IP 恒为 127.0.0.1)
cors_origins = []
[tools]
exclude = ["Registry"] # ← 唯一可用的工具过滤键
关于”该禁哪些工具”的一点诚实意见
网上很多配置模板会一口气禁用 PowerShell、FileSystem、Process、Registry 显得很安全。但这里有个逻辑问题:
一旦开放了 PowerShell,再单独禁用 FileSystem 和 Process 就没有任何实际安全意义——PowerShell 本来就能读写文件、结束进程。把它们关掉只会制造”我已经收紧过了”的错觉。
真正能直接破坏系统的只有一个:Registry(注册表写入)。其余的,要么被 PowerShell 覆盖,要么只是影响便利性。
另一个容易被滥用的配置是 ip_allowlist:当服务只绑 loopback 时,加上它零收益——本机任何进程连过来的源 IP 恒为 127.0.0.1,这条规则拦不住任何东西,却多了一个让连接莫名失败的故障点。
建议的判断标准:只拦”能造成不可逆破坏”的;其余按实际需要开放,并且如实说明这不是安全措施。
改完策略要重启服务
Stop-ScheduledTask -TaskName 'windows-mcp-server' -ErrorAction SilentlyContinue
Start-Sleep 2
Get-NetTCPConnection -LocalPort 8000 -State Listen -ErrorAction SilentlyContinue |
ForEach-Object { Stop-Process -Id $_.OwningProcess -Force -ErrorAction SilentlyContinue }
Start-Sleep 2
Start-ScheduledTask -TaskName 'windows-mcp-server'
注意:只
Stop-ScheduledTask不会杀掉已启动的 python 子进程,必须按端口找 PID 补刀。
可选的包装脚本增强(易失,install 会覆盖)
@echo off
setlocal
set ANONYMIZED_TELEMETRY=false
set WINDOWS_MCP_DISABLE_FLASH=1
<uvx 解析出的 python.exe 绝对路径> -m windows_mcp serve --transport streamable-http --host 127.0.0.1 --port 8000 1>>"%USERPROFILE%\.windows-mcp\server.log" 2>>"%USERPROFILE%\.windows-mcp\server.error.log"
五、接入 MCP 宿主
WorkBuddy
配置文件在 ~/.workbuddy/mcp.json(不是 .mcp.json):
{
"mcpServers": {
"windows-mcp": {
"type": "streamableHttp",
"url": "http://127.0.0.1:8000/mcp",
"timeout": 60000,
"disabled": false
}
}
}
timeout 建议放宽到 60000——默认 30000 对多步 GUI 链路偏紧。
改完还需要手动:连接器管理 → 找到该服务 → 信任 → 启用 → 从系统托盘完全退出再重开(只关窗口不生效)。
- 信任记录写在
~/.workbuddy/mcp-approvals.json,不点信任不会加载 - 改配置前先备份
mcp.json为.bak.<时间戳>
Cursor
~/.cursor/mcp.json(用户级,全项目生效)或项目内 .cursor/mcp.json,mcpServers 结构同上。
为什么推荐单实例 HTTP,而不是 stdio
stdio 模式下,每个宿主各起一个子进程。两个进程同时驱动同一套鼠标键盘与焦点窗口会互相抢焦点,表现为点击错位、输入丢字,而且极难定位原因。
用常驻 HTTP 服务、两个宿主连同一个实例,这个问题在架构层面就消失了。
配套纪律:同一时刻只让一个宿主接管桌面。
连不上时的回退
回退 stdio 形态(代价:每个宿主各起一个进程,会争抢同一套键鼠焦点):
"windows-mcp": {
"command": "%USERPROFILE%\\.local\\bin\\uvx.exe",
"args": ["windows-mcp", "serve"]
}
command两种写法:
- 环境变量形式
"%USERPROFILE%\\.local\\bin\\uvx.exe"—— 更去个人化,但依赖宿主展开环境变量;- 展开后的绝对路径
"C:\\Users\\<用户名>\\.local\\bin\\uvx.exe"—— 宿主不展开变量时用(等价于spawn(shell:false)不解析%VAR%),注意 JSON 里反斜杠要双写。
六、验证:三步法,第三步最容易漏
| 步骤 | 做法 | 通过标准 |
|---|---|---|
| 1. 清单 | tools/list | 工具数与策略一致(如排除 Registry 后应为 19) |
| 2. 正向 | 调一个只读工具(如 DisplayInventory) | 返回真实数据(分辨率、DPI、work area),不是幻觉 |
| 3. 反向 | 调一个被禁用的工具 | 返回 isError: true + Unknown tool |
第 3 步是唯一能区分**“策略生效”和”配置文件根本没被读到”**的方法。跳过它,你永远不知道自己是安全还是裸奔。
实测输出(正向):
[{"index":0,"device":"\\\\.\\DISPLAY1","primary":true,
"bounds":{"left":0,"top":0,"right":1920,"bottom":1080},
"work_area":{"left":0,"top":0,"right":1920,"bottom":1032},
"resolution":"1920x1080","effective_dpi":96,"scale":1.0}]
反向(调用被禁的 Registry):
HTTP: 200 | isError: true
Unknown tool: 'Registry'
服务自报版本
4.0.4(PyPI 包版本号与之不同,属正常)。
宿主侧联通后,可用一个只读用例试水:「列出当前打开的所有窗口标题」(Snapshot + use_vision=false)。
Snapshot的use_vision默认false,因此不会把整屏截图带进上下文——做验证时保持默认,尊重用户隐私。 另注意Snapshot返回的窗口列表里混有大量非可见后台窗口(无标题窗口、任务栏、显卡驱动 overlay、输入法窗口等),汇报时要把它们和用户意义上的”打开的应用”区分开。
七、长链路实测:让 AI 自己发一条扣扣消息
验证单个工具能调通是一回事,能不能跑完一条多步、有状态、还可能中途蹦弹窗的链路是另一回事。
测试任务:打开扣扣(已开启自动登录)→ 找到「我的手机」→ 发送一条消息。
结果:成功送达,全程无人介入,零卡死。
实测发现(这部分最有价值)
1. Electron 应用的 UIA 覆盖是断裂的
扣扣新版客户端是 Electron 架构。它的 UI Automation 元素树里:
| 能拿到 | 拿不到 |
|---|---|
| 标题栏按钮 | 聊天面板全部内容 |
| 左侧导航项 | 消息输入框 |
| 会话列表每一行 | 发送按钮 |
也就是说,纯文本模式(不给模型视觉能力)在 Electron 应用上做不了完整链路,必须切 use_vision=true 看画面来定位输入框。
顺带发现一个工具缺陷:Snapshot(use_ui_tree=false) 模式下,窗口枚举会返回 No windows found / No active window found,但窗口明明开着。这个模式只能纯看图,不能信它的窗口列表。
2. 窗口状态一变,所有坐标立刻失效
一次最大化操作,让同一个按钮从 (583,830) 变成了 (30,966)。按旧坐标点的结果是白点一次,而且不会有任何报错——它只会安静地点在错误的位置上。
规则:任何窗口状态变化(最大化 / 还原 / 缩放 / 切换视图)之后,必须重新获取元素树。
3. 点击列表行 ≠ 打开会话
在联系人列表里单击甚至双击某一行,只会让它高亮选中,右侧聊天面板不渲染。真正可靠的是用应用内置的搜索框按名字寻址——这是整个任务的关键转折点。
4. 同名入口陷阱
左侧导航栏里有一个「我的手机」按钮,看起来名字完全对得上。但点它只会弹出跨设备内容同步卡片,根本没有输入框。真正的聊天对象藏在 联系人 → 我的设备 → 我的手机。
别只按名字匹配就点,先确认入口类型。
5. 一条必须记住的纪律:终态验证不可省
Click 工具返回的是:
Single left clicked at (1515,966).
这只证明”点击事件注入成功”,完全不证明”消息发出去了”。
每轮测试我都靠截图确认蓝色气泡出现、输入框清空,才算真的完成。任何把”工具返回 OK”当作”任务成功”的自动化,都会在某次失败时静默地什么都不做,而且没人发现。
6. 隐私面:agent 看到的和你看到的一样
这次为了定位输入框,一共取了 4 张全屏截图,另有多次元素树文本导出。这些内容里包含:完整的会话列表、未读消息预览、私聊/群聊名称、历史文件传输记录的文件名。
这不是 bug,是桌面自动化的固有代价。 任何”让 AI 操作我的电脑”的方案都得接受这一点——它不该被当成纯技术问题处理。
所以:在方案评估和实施阶段都应主动向用户说明这一点。优先用 Snapshot(use_vision=false) 取文本元素树;确有必要再取截图,并说明取了几张。
八、效率:从 8 分钟到 8.77 秒
同一个任务跑三遍,对比”探索态”和”成熟态”的成本:
| 维度 | ① 探索态 | ② 熟悉流程·Agent 优化 | ③ 脚本化 |
|---|---|---|---|
| 墙钟耗时 | 约 8 分钟 | 约 3 分 17 秒 | 8.77 秒 |
| 工具调用数 | 41 | 17 | 5 |
| 元素树导出 | 3 次 | 0 | 0 |
| 全屏截图 | 6 张 | 4 张 | 0 |
| 试错/无效动作 | 6 次(15%) | 1 次(6%) | 0 |
| 决策点 | 每步重新判断 | 模型逐步决策 | 0(无模型参与) |
时间到底花在哪
脚本化的单步实测延迟:
| 步骤 | 耗时 |
|---|---|
App resize(钉住窗口几何) | 1501 ms |
Type 输入搜索词 | 1518 ms |
Click 搜索结果 | 562 ms |
Type 输入消息 | 2144 ms |
Click 发送 | 559 ms |
| 5 次调用合计 | 6284 ms |
而第①轮:41 次调用 × 约 0.6 秒 ≈ 25 秒的”机器时间”——剩下 7 分半全是模型每一步重新推理的开销。
结论:瓶颈从来不是 MCP 的执行速度,而是”每一步都要模型重新想一遍”。 缩短时间的正确做法是减少决策点,不是优化单步延迟。
(顺带:Type 是最慢的动作,1.5~2.1 秒/次,因为逐字符注入;Click 只要 0.56 秒。能用一次 Type 表达的内容不要拆成多次。)
收益最大的一招:把坐标常量化
App(mode="resize", name="<窗口名>", window_loc=[0,0], window_size=[1600,1000])
窗口几何一旦钉死,所有坐标从”每次都要探测的变量”变成”一次算好的常量”。
探索态最大的浪费正是来自这里:一次最大化让按钮位置全变,旧坐标失效。固定几何让这类问题在结构上消失,而不是”下次注意点”。
第二招:名字寻址 > 位置寻址
走搜索框输入名称这条路,而不是去点列表里那一行。会话列表顺序会随新消息浮动(实测 3 分钟内同一个会话挪了约 80px),按位置点迟早会漂;按名字搜不漂。
实测细节:搜索输入后不一定自动打开会话(第一次成功是巧合)。稳定做法是再多点一次搜索结果里的首项。
第三招:元素树导出应降为 0
Snapshot(use_ui_tree=true) 输出量极大(列表每一行的每个字都展开),单次约 7k token。坐标已常量化之后,它没有信息增量,只剩成本。Snapshot(use_vision=true) 只保留 1~2 次用于终态验证。
脚本化:天花板,以及它的代价
真正的成熟形态是把流程固化成脚本,不再让模型参与执行:
决策点 0 | 截图 0 | 元素树 0 | 试错 0 | 5 次调用 8.77 秒
设计要点:先 App resize 钉住几何 → 搜索框按名字寻址 → 直接对 http://127.0.0.1:8000/mcp 发 JSON-RPC。
但必须说清代价:
| 它没有的能力 | 后果 |
|---|---|
| 不会看画面 | 窗口没成功 resize、弹了广告窗、应用改版换了布局——全都发现不了,会对着错误坐标盲点 |
| 不会纠偏 | 任一步失败就整体停住 |
| 不会验证 | 返回码只代表”点击已注入”,不代表任务完成 |
所以脚本化不是”更高级”,是拿鲁棒性换速度。它适合高频、稳定、失败可容忍的重复操作。
建议的架构
探索期 → Agent(慢,但会找路、会纠偏)
稳定期 → 脚本(快,零试错)
恢复期 → 脚本失败时交回 Agent 诊断并重建脚本
不要把 Agent 优化成脚本——它的价值本来就不在速度,而在”遇到没见过的情况能自己想办法”。两者是探索器与执行器的分工,不是替代关系。
九、路线判断:什么时候不该用 Windows-MCP
用例是 Electron 应用(扣扣这类)时,其实有更优解。
如果一个项目有现成的三层自动化策略(CDP 协议层 / UIA 控件层 / 视觉+注入层),Electron 和浏览器应该优先走 CDP 协议层——可以直接拿到 DOM 节点,免截图、免坐标猜测,稳定性和速度都显著更好。这条路线的完整原理与实测数据,见《让 AI 操作桌面程序:从「截图点击」到三层降级架构》。
Windows-MCP 的不可替代价值在:
- 原生 Win32 窗口
- 系统设置界面
- 没有 CDP 入口的传统桌面软件
它在这里没有竞品。选型时按”目标应用有没有协议层入口”来分流,比”哪个工具更强”更有意义。
十、回滚
| 撤销什么 | 做法 |
|---|---|
| 宿主配置 | 用备份 mcp.json.bak.<时间戳> 覆盖回去 |
| 常驻服务 | uvx windows-mcp uninstall |
| 权限策略 | 删除 ~/.windows-mcp/config.toml(恢复全部工具) |
| 包装脚本改动 | 用 start-server.cmd.bak.<时间戳> 覆盖 |
| 彻底卸载 | 删 %USERPROFILE%\.local\bin 并从用户 PATH 移除 |
一个容易被忽略的脆弱点
包装脚本 start-server.cmd 里写死了 uv 缓存目录下的一个绝对路径:
%LOCALAPPDATA%\uv\cache\archive-v0\<hash>\Scripts\python.exe
如果你执行 uv cache clean 或 uv cache prune,这个路径会消失,常驻服务就再也起不来了,而且报错信息不会直接指向原因。
恢复方法:重跑一次 windows-mcp install(注意:包装脚本里手加的其它配置会一并丢失,需要重新施加;写在 config.toml 里的策略不受影响——这正是第四节那条结论的价值)。
十一、已知脆弱点速查
| 项 | 症状 | 应对 |
|---|---|---|
| uv 缓存路径写死 | 包装脚本指向 ...\uv\cache\archive-v0\<hash>\Scripts\python.exe;执行 uv cache clean / uv cache prune 后服务起不来 | 重跑 windows-mcp install,并重新施加包装脚本里的环境变量(config.toml 策略不受影响) |
| 中文 Windows 上 App 工具不准 | 按开始菜单名启动失败 | 用 PowerShell 启动,或用可执行文件全路径 |
| 双宿主争抢键鼠 | 点击错位、输入丢字 | 单实例 HTTP 常驻 + 同一时刻只让一个宿主接管桌面 |
| 长任务失控 | 多步任务遇弹窗 / 锁屏 / 权限框会卡住 | 先跑 ≤5 步短任务;长任务需人在旁边盯 |
| 首跑超时 | 首次拉依赖较慢,宿主可能判定启动失败 | 忽略并重启宿主一次 |
附录 A:把整套流程沉淀成 Skill
上面这些内容如果每次都靠记忆操作,一定会漏掉步骤。所以把它们固化成了一份 Skill(MCP 宿主可直接加载的指令包)。
职责边界:它只负责「装好、收紧、接入、验证、可回滚」;真正操作桌面的部分属于另一份桌面自动化 Skill(三层路线)。两者互补,不重叠。
Skill 覆盖的内容:
| 章节 | 内容 |
|---|---|
| 先决条件 | Python 版本要求;澄清”不需要视觉模型”这个常见误解 |
| 装 uv | 官方安装器的正确姿势与反面做法 |
| 冒烟测试 | 附完整探测脚本;三处文档纠错对照表 |
| 常驻服务 | install 产物清单;中文系统的无害报错说明 |
| 权限策略 | 完整 config.toml schema;写在哪 / 为什么;口径取舍的诚实意见 |
| 接入宿主 | WorkBuddy 与 Cursor 的配置;单实例 vs stdio 的取舍 |
| 验证 | 三步法;反向验证的具体命令 |
| 回滚 | 逐项撤销表 |
| 已知脆弱点 | uv 缓存路径、中文系统工具失效、双宿主争抢、长任务失控 |
| 附录 | 工具行为坑清单 / 隐私面 / 路线选择 / 成熟流程固化手法 |
其中「成熟流程固化手法」一节记录了从探索态到脚本化的六条具体手法,以及探索器/执行器的分工结论。
一个值得强调的设计:策略写在”抗重装”的位置
Skill 里反复强调第四节的判断——把权限策略放在安装器不会覆盖的文件里。
这类”看起来等价、实际差一个数量级”的选择,在自动化运维里非常常见:配置文件放对位置,比配置内容写得漂亮重要得多。
附录 B:接入检查清单
□ uv 已安装,uvx 可用(用户级 PATH)
□ 冒烟测试通过,已确认真实工具清单(名字必须与实测一致)
□ 端点确认为 /mcp(不带尾斜杠)
□ 常驻服务 install 完成,计划任务状态 Running
□ 权限策略写入 config.toml(不是 start-server.cmd)
□ 策略重启后生效,工具数已核对
□ 反向验证通过(被禁工具返回 Unknown tool)
□ 宿主配置已写入并备份原文件
□ 宿主侧已信任 + 启用 + 完全重启
□ 正向验证通过(只读工具返回真实数据)
□ 终态验证机制已确定(每一步如何确认真实结果)
□ 回滚路径已验证(知道怎么撤)
附录 C:工具行为实测坑(补充清单)
| 坑 | 现象 | 应对 |
|---|---|---|
| 坐标随窗口状态失效 | 窗口最大化后,同一按钮从 (583,830) 变 (30,966) | 任何窗口状态变化后必须重新 Snapshot,禁止复用旧坐标 |
use_ui_tree=false 时窗口枚举返回空 | 报 No windows found,但窗口明明开着 | 这个模式只看图,不要信它的窗口列表 |
| Electron 应用 UIA 覆盖断裂 | 元素树只有标题栏 + 导航项 + 列表行;聊天面板、输入框、发送按钮全不可见 | 必须切 use_vision=true;纯文本模式做不了完整链路 |
| 点击列表行 ≠ 打开会话 | 点/双击只高亮选中,右侧不渲染 | 改用应用内置搜索框按名字寻址 |
| 同名入口陷阱 | 导航栏里同名入口其实是功能面板(无输入框) | 别只按名字匹配就点,先确认入口类型 |
| 中文系统控制台乱码 | 捕获 PowerShell stdout 时中文变 GBK 乱码 | 诊断脚本内显式设 [Console]::OutputEncoding,或忽略(不影响判定) |
Click / Type 的 label 参数 | schema 声明为 integer(元素 id),而 Snapshot 输出里没有显式数字 id | 一律用 loc=[x,y],别指望 label |
Wait 的 duration | 只接受 integer,传 1.5 会被 schema 拒绝 | 用整数秒;需要更细粒度就用 WaitFor |
Type 是最慢的动作 | 逐字符注入,实测 1.5~2.1 s/次,明显慢于 Click(约 0.56 s) | 能用一次 Type 表达的内容别拆成多次 |
实测效率基线:首次探索性完成一个多步 GUI 任务 41 次工具调用、85% 有效;单步延迟约 0.6 s,符合官方标称 0.2~0.9 s,零卡死。瓶颈是定位稳定性,不是执行速度。
附录 D:无工具环境下的探测技巧
若所在会话的 shell 缺 coreutils(ls / head / grep 全部 command not found),且 PowerShell 工具的 stdout 不回传,可用:
node -e "require('child_process').execSync(<命令>, {stdio:['ignore','pipe','ignore']}).toString()"
或写一个 node exec.js "<powershell 命令>" 包装器统一代跑并打印 stdout / stderr / exit code。
结语
Windows-MCP 解决的是一个很实在的问题:那些没有 API 的软件,终于也能被自动化了。
但接入它真正花时间的地方不在安装,而在三件事上:确认文档与实现是否一致、把权限策略放在不会被覆盖的位置、以及接受”agent 看到的和你看到的是一样的”这个隐私事实。
最后一句话给所有做桌面自动化的人:工具返回 OK,从来不等于任务完成。 只有看到真实世界的状态变化,才算数。