☰
Windows-MCP实战:用MCP协议让大模型操控Windows桌面
2026/11/4 3:11:30 网站建设 项目流程

Windows-MCP 这个东西,我第一次看到的时候以为又是某个包装过度的开源壳子,真正拉起来跑通第一轮之后才改了看法——它解决的是一个非常具体的断层:大模型在对话框里能给你写出完美的操作步骤,却没法替你按下那个按钮。Windows-MCP 就是架在“能说”和“能做”之间的那座桥,把截图、点击、敲键盘、切窗口、读剪贴板这些动作封装成模型可以调用的工具,让 AI 从“顾问”变成“操作员”。它适合三类人:天天在桌面上做重复劳动的运维和测试同学、想给自己的 Agent 加一双眼睛和一双手的开发者、以及对自动化好奇但被传统 RPA 工具劝退的普通用户。这篇我按实际搭建的顺序来写,从概念、环境、工具集拆到实操和踩坑,中间该给的配置和参数一个不落。

1. 概念先捋直:MCP 协议和 Windows-MCP 到底什么关系

1.1 用一个生活类比讲透 MCP 协议

MCP 全称 Model Context Protocol,模型上下文协议,本质是一套“模型怎么调用外部能力”的约定。你可以把它想成电脑主板上的 USB 接口标准:以前每个外设厂商都要给自己写一套驱动,主板也得为每个设备单独适配,乱成一锅粥;有了统一接口之后,鼠标、键盘、U 盘插上去就能用,主板不需要知道具体是什么设备,只要按标准协议对话就行。

MCP 干的就是这件事。协议定义了几个核心概念:Server(能力提供方,也就是干活的)、Client(发起调用的一方,通常是桌面端的大模型应用)、Tool(Server 暴露出来的具体能力,带参数结构和返回格式)、Resource(可读取的数据源)。模型看到的是工具的名字和 JSON Schema 描述,它决定调哪个、传什么参数,Client 负责真正把请求转发过去,Server 执行完把结果原样返回。整个过程模型不需要知道底层是 Python 还是 C# 实现,也不需要知道鼠标是怎么动的。

这里有个细节值得单独说:工具的 description 字段极其重要。模型选工具的唯一依据就是这段自然语言描述加参数 schema,写得含糊它就会乱选。所以很多自己写 MCP Server 的人踩的第一个坑,不是代码逻辑错,而是工具描述写得太简略,模型压根不知道什么时候该用。Windows-MCP 这类项目在描述上下了功夫,每个工具都写清楚了“什么时候用、参数是什么单位、返回什么”,这一点后面写自己的 Server 时可以直接抄作业。

1.2 Windows-MCP 具体能干哪几件事

拆开看,它的能力大致分四层,从感知到执行:

感知层负责让模型“看见”当前桌面状态。核心是截图工具,返回一张当前屏幕的图片(通常是 base64 编码的 PNG),模型靠视觉能力去理解界面上有什么。另一条路是 UI 自动化树,读取当前焦点窗口里所有可交互元素的层级结构、名称、控件类型、坐标范围,这相当于给模型一份“界面说明书”,比纯看图更精确也更省 token。

动作层负责真正动手。鼠标相关有移动、单击、双击、右键、拖拽、滚轮;键盘相关有文本输入、单键按下、组合快捷键。这些动作最终都会落到系统的输入模拟接口上,效果跟人手操作没有区别,所以在系统看来这就是真人在用电脑。

窗口层负责应用生命周期。启动程序、切换前台窗口、枚举当前打开的窗口列表、等待某个窗口出现。这一层是很多人忽略但实际最影响稳定性的部分——你让模型点某个按钮,但它点的时候目标窗口根本没在前台,动作就打到别的地方去了。

扩展层是锦上添花,比如剪贴板读写、执行命令行、抓取窗口文本。有了命令行工具,很多原本要靠点击完成的事情可以换成一条命令,效率差着数量级。我个人的习惯是:能用命令行解决的绝不走点击,点击只留给那些没有命令行入口的图形界面。

1.3 能力边界:它做不到什么

这一点必须提前说清楚,否则很容易产生不切实际的期待。第一,它看不懂没有文字和明显控件的纯图像内容——一个画图软件里的自由曲线,模型没法靠截图判断“这条线画歪了”。第二,它处理不了跨屏幕的复杂时序——比如等一个加载了 30 秒的进度条,模型只能傻等或者轮询截图,没有事件驱动的能力。第三,它在权限隔离面前无能为力——用户账户控制弹出的提权窗口、某些安全软件的防护界面,普通权限进程的模拟输入会被系统直接丢弃,这个后面第 5 章会细讲。第四,它的准确率高度依赖模型本身的视觉定位能力,小图标、密集排列的按钮、高分屏下缩得很小的控件,误点率会明显上升。

1.4 和传统自动化脚本比,到底值不值得换

这个问题我被问过很多次。传统方案(按键精灵式的坐标录制、基于控件树的自动化框架、流程编排工具)的优势是确定性:路径写死,跑一万次结果一样,速度快,不吃 token。缺点是脆弱:界面改一版、按钮挪个位置、弹窗顺序变一下,脚本就废了,维护成本极高。

Windows-MCP 走的是另一条路:用模型的泛化能力换确定性。你不需要写死每一步,只需要说清楚目标,模型自己看着屏幕决定下一步点哪。界面小改版它大概率还能跑,因为它每次都是重新“看”的。代价是慢——每一步都要截图、推理、决策,一个原本 3 秒的脚本任务可能要跑 30 秒;代价是贵——每一步都在消耗 token;代价是不稳定——同一个任务跑两次路径可能不一样。

我的判断标准很简单:流程固定、跑得频繁、界面稳定的,写脚本;流程多变、跑得少、界面经常改的,交给模型。两者不是替代关系,实际项目里我经常混着用——脚本负责主干流程,模型负责处理“万一弹了个没见过的对话框”这种分支。这个思路后面第 4 章的任务设计里会体现。

2. 环境搭起来:从裸机到第一次握手

2.1 前置条件清单

先把底子摸清楚,别急着装东西。系统侧建议 Windows 10 1903 以上或 Windows 11,老版本的系统在 UI 自动化接口上有兼容性问题,某些控件读不出名字。Python 建议 3.10 及以上,3.11/3.12 更稳,低于 3.10 会因为类型标注语法报错。

一个容易被忽略的前置条件是屏幕分辨率与缩放设置。如果你的显示器开了 150% 缩放,而脚本进程没有声明 DPI 感知,拿到的坐标和实际物理像素之间会有一个 1.5 倍的偏移,表现就是“点哪儿都差一截”。这个问题在第 5 章会详细展开,但搭环境的时候就应该先确认:把缩放临时调成 100% 跑第一轮,确认链路通了再调回去调参数。

还有一件事:别在主力工作机上做第一次实验。倒不是危言耸听,而是模型第一次接触桌面控制权的时候,行为确实不可预测——它可能误关窗口、误发快捷键、在错误的地方输入文字。我一般用虚拟机或者一个独立的本地账户来跑初期验证,等把系统提示词和工具权限磨稳了,再放到真实环境。

2.2 Python 环境与依赖装法

推荐用虚拟环境隔离,别往全局环境里灌。装法上,现在主流的做法是用 uv 这个包管理器,速度快而且能锁版本;用传统 pip 也完全没问题。

# 用 uv 的做法 uv venv uv pip install -r requirements.txt # 或者传统 pip python -m venv .venv .venv\Scripts\activate pip install -r requirements.txt

依赖清单里通常会包含这几类:屏幕截图相关的图像库、输入模拟库、UI 自动化库(读取控件树)、剪贴板库、以及MCP 协议 SDK本身。装完之后建议先做一次冒烟测试,不接模型,直接写几行代码验证最基础的能力是否可用:

import pyautogui print(pyautogui.size()) # 屏幕分辨率 print(pyautogui.position()) # 当前鼠标位置 pyautogui.screenshot("test.png") # 截一张图

如果这三行都正常,说明输入模拟和截图链路是通的;如果size()返回的数值和你系统设置里看到的不一致,那基本可以确定是 DPI 感知问题,先解决它再往下走,否则后面所有的坐标调试都是在错误的基础上做。

还有一步很多人会漏掉:把 Python 的可执行文件路径加到系统 PATH,或者记下完整路径。因为客户端拉起 MCP Server 的时候,是通过命令行调用子进程的,它不认你终端里的虚拟环境激活状态。配置里要写绝对路径,或者用uv run这类能自己找环境的命令。

2.3 客户端配置文件的写法

MCP 的客户端配置基本都是同一个套路,一个 JSON 文件描述有哪些 server、每个 server 怎么启动。不同客户端的文件名和位置不一样,但结构大同小异:

{ "mcpServers": { "windows-mcp": { "command": "uv", "args": [ "--directory", "C:/tools/windows-mcp", "run", "main.py" ], "env": { "PYTHONUNBUFFERED": "1" } } } }

几个参数值得逐条解释。command是启动命令,如果用 uv 就是uv,如果用系统 Python 就是python或 python.exe 的绝对路径。args是参数数组,每个元素单独一项,千万别把整条命令写成一个字符串,那样会被当成一个完整的可执行文件名,直接启动失败。--directory指定工作目录,很多 Server 需要读取同目录下的资源文件,不设这个参数就会找不到。

env里我习惯加PYTHONUNBUFFERED=1。原因很实在:stdio 模式下如果 Python 的输出被缓冲住了,客户端会以为 server 没响应,一直等到超时。加了这个环境变量让输出立即刷新,能省掉很多“连上了但工具列表是空”的玄学问题。

如果你的 server 支持 HTTP 传输,配置会变成写一个 URL 的形式,适合多个客户端共享同一个 server 实例。但初次上手还是建议用 stdio,链路短、问题少、日志好抓。

改完配置文件记得完全重启客户端,不是关窗口,是退到托盘退出或者任务管理器结束进程。不少客户端只在启动时读一次配置,热重载支持得不完整。

2.4 第一次握手怎么验证成功

配置写完,重启客户端,正常的话在工具列表里能看到一串以 windows 相关命名的工具。能列出来就说明两个进程握手成功了,协议层没问题。

但列出来不等于能用,还要验一次实际调用。最稳的第一个测试是只读操作——让模型截一张图描述屏幕上有什么,或者读一下当前窗口的标题。这类操作不改任何状态,即使参数错了也不会造成破坏。如果截图返回了一张黑图或者空白图,通常是权限问题(进程没有桌面访问权,比如跑成了服务)或者多显示器选了错误的屏幕。如果返回的图是缩放的、模糊的,那是 DPI 缩放没处理,图像尺寸被系统放大了。

第二步再测一个无破坏性的写操作:让它在某个空白位置单击一次,然后你肉眼确认鼠标是否真的动了。这一步能验证输入模拟链路。有些环境(远程桌面会话断开时、锁屏状态下)输入模拟会被系统拦截,表现就是“调用返回成功但屏幕没反应”,这种假成功最难排查,所以一定要肉眼确认。

第三步才轮到键盘输入,找个记事本随便打几个字。到这一步全通了,就可以开始正式折腾任务了。

3. 工具集拆解:AI 怎么“看见”、怎么“动手”

3.1 感知层:截图和 UI 元素树这两条路怎么选

这是整个系统里最核心的设计取舍,值得单独拎出来讲。

截图路线的思路是:给模型一张图,让它像人一样看图找目标。好处是通用——任何界面都能处理,包括游戏画面、自绘控件、Canvas 渲染的内容,这些在控件树里往往是空白。坏处是三个:一是贵,一张 1920×1080 的截图编码后动辄几千个 token,一个任务跑二十步就是十几万 token;二是不精确,模型给出的坐标是基于它看到的图像网格估算的,误差十几像素很常见;三是不稳定,同一个界面在不同主题、不同分辨率下,模型判断可能不一样。

UI 元素树路线的思路是:读取系统的无障碍/自动化接口,拿到一份结构化的控件清单,每个控件带名称、类型、位置、是否可点击。好处是精确——控件有明确的边界框,点中心点基本不会错;省 token——一棵树的文本描述可能只有几百 token;还带语义——“保存按钮”这个名字本身就说明了一切。坏处是有盲区:自绘界面、某些跨平台框架渲染的界面、游戏引擎界面,控件树里可能是空的或者全是无名容器。

实际项目里我是这么用的:优先读元素树,树里找不到目标再退回截图。Windows-MCP 这类项目通常会同时提供两种工具,提示词里明确写“先用状态工具读取界面结构,只有在结构里找不到目标时才截图”,模型就会按这个优先级来。这一条写进系统提示词能省下大量 token,也能显著提升点击准确率。

还有个技巧:截图前先切换到目标窗口。模型看到的是“当前屏幕”,如果目标窗口被别的窗口挡住了一半,它看到的也是被挡住的样子,很容易误判。养成“切换窗口 → 等待 → 截图 → 决策”的习惯,稳定性提升非常明显。

3.2 动作层:鼠标和键盘的参数细节

鼠标工具的常见参数包括坐标 x/y、按键类型(左中右)、点击次数、滚动量。这里有几个坑必须提前知道。

坐标系原点在左上角,y 轴向下增长,这个和数学坐标系是反的,写提示词的时候别说“往上移动 100 像素”然后又给个正数。多显示器的情况下,坐标是相对于整个虚拟桌面的,副屏在主屏左边的话坐标会是负数,这个在处理多屏环境时要留意。

双击的间隔是个隐藏参数。系统默认的双击判定时间是 500 毫秒,如果你的模拟双击间隔太长(比如中间夹了一次截图导致延迟),系统会识别成两次单击,结果就是“打开了两个窗口”或者“选中又取消”。稳妥的做法是调用专门的双击工具而不是连点两次单击。

键盘侧分两类:文本输入和按键。文本输入适合填表单、写内容,但要注意它对输入法敏感——如果当前是中文输入法状态,输入英文字符可能被当成拼音候选。稳妥的做法是在输入前先确认输入法状态,或者干脆用剪贴板粘贴的方式替代直接输入(先把文本写入剪贴板,再发 Ctrl+V)。这个技巧在处理长文本和特殊字符时特别管用,速度快而且零输入错误。

组合快捷键的参数格式各项目不太一样,有的是["ctrl", "shift", "s"]数组,有的是"ctrl+shift+s"字符串。用之前一定要看工具描述,写错了不会报错,只是没反应,很容易误判成“功能不支持”。

3.3 应用与窗口层:启动、切换和等待

这一层的工具看起来简单,实际上决定了整个任务的稳定性。

启动应用的参数通常是可执行文件路径或者命令名。这里有个细节:直接写notepad比写完整路径更稳,因为系统会走 PATH 查找;但写完整路径更可控,避免启动到同名的其他程序。我的习惯是常用程序写完整路径,不常用的写命令名。

切换窗口是必须养成的习惯动作。前面说过,模型看到的是当前屏幕,操作也是打到当前前台窗口上的,所以任何“点击某个按钮”之前,都应该先切换目标窗口到前台。切换之后不要立刻动手,中间加一个短暂的等待,因为窗口动画和内容渲染需要时间,切过去马上就点,很可能点到还没绘制完成的位置。

等待工具看着最没用,其实最关键。它的作用不是让模型“休息”,而是给界面留出响应时间。我的经验值是:窗口切换后等 300~500 毫秒,点击按钮后等 500~1000 毫秒,启动应用后等 2~3 秒(大型软件更久)。这些数字不是拍脑袋,是在实际调试中反复试出来的——等得太短会拍到过渡动画,等得太长整个任务就拖沓。可以在系统提示词里把这些基准值写死,让模型按标准来。

3.4 关键参数速查表

工具类别关键参数常见取值/单位易错点
截图屏幕编号、是否缩放整数编号、布尔值多屏时默认抓主屏,副屏内容抓不到
点击x, y, 按键, 次数像素坐标、left/right/middley 轴向下为正;双击要用专用工具
拖拽起点 x/y, 终点 x/y, 时长像素坐标、毫秒时长太短会被识别成滑动而非拖拽
滚动x, y, 方向, 格数像素坐标、up/down、整数滚动是相对量,不同应用一格的实际距离不同
文本输入文本内容字符串受输入法状态影响,长文本建议走剪贴板
按键按键名或组合标准键名、组合数组组合键格式各项目不统一,务必看描述
启动应用路径或命令字符串相对路径依赖工作目录,建议用绝对路径
等待毫秒整数别省这一步,省下来的时间最后都花在重试上

表格里最后一条我特意加了,因为太多人为了“快”而删掉等待,结果任务失败率翻倍,反而更慢。这个道理跟做菜一样,该焖的时间省不得。

4. 实操:三个由浅入深的任务

4.1 任务一:最小闭环,让它在记事本里写一段话

第一个任务尽可能简单,目的是验证整条链路:感知 → 决策 → 动作 → 再感知。

提示词可以这么写(这是我的实际版本,可以直接抄):

你的任务是打开记事本,写入下面这段文字,然后保存到桌面。 文字内容:「这是一个自动化测试文件,由桌面助手生成。」 步骤要求:每次执行动作后重新截图确认结果;如果目标窗口没有出现在前台,先切换窗口再操作;任何一步失败不要重试超过两次。

实际跑下来的动作序列大致是:调用启动工具打开记事本 → 等待 → 截图确认 → 点击编辑区 → 输入文本 → 按 Ctrl+S → 等待保存对话框 → 输入文件名 → 点击保存 → 截图确认文件出现在桌面。

这个过程里模型最容易出错的环节是保存对话框。因为对话框是模态的,弹出后焦点自动切过去,但如果模型没意识到焦点变了,继续往编辑区输入,文字就打到了主窗口。解决办法是在提示词里明确写“按下 Ctrl+S 之后,必须先截图确认是否弹出了对话框,再决定下一步”。这就是为什么我一直强调“每次动作后重新截图”,它把模型从“按剧本演”变成了“边看边做”。

第二个易错点是编辑区的定位。记事本的编辑区是一个大的文本控件,模型如果读元素树,能直接拿到这个控件的边界框,点中心点就行;如果只能靠截图,它可能会点到标题栏或者菜单栏。这就是 3.1 节说的,优先走元素树的价值。

4.2 任务二:系统提示词怎么写,模型才不会乱点

这是整个项目里最需要打磨的部分,比装环境难多了。工具调用本身是确定性的,但“什么时候调哪个工具、传什么参数”完全由模型的判断决定,而判断质量九成取决于系统提示词。

我总结出四条必须写进去的约束:

第一条,动作纪律。明确要求“每次只执行一个动作,执行完必须重新观察再决定下一步”。不写这条,模型会尝试一口气连做几个动作,中间界面早就变了,后面全错。这个约束的代价是变慢,但换来的是稳定性,值得。

第二条,失败处理。写清楚“同一个操作连续失败两次就停下来,报告当前状态,不要继续尝试”。模型的默认倾向是死磕,它会在点不中的按钮上反复点二三十次,烧掉大量 token 还占着桌面。设个硬上限非常必要。

第三条,优先级的明确指引。比如“优先使用命令行工具完成任务,只有命令行无法完成时才使用鼠标点击”;“优先读取元素树定位目标,读取失败才用截图”。这些优先级写清楚,模型的行为会立刻变得有章法。

第四条,危险的边界。明确列出禁止行为:不点击任何带删除、卸载、格式化字样的按钮;不打开浏览器以外的网络应用;不操作系统设置面板;遇到权限弹窗立刻停止并报告。这一条不是形式主义,是实打实的保险丝。

还有一个实用技巧:把常用的坐标基准和等待时长写进提示词。比如“窗口切换到前台后等待 500 毫秒再操作”“点击按钮后等待 800 毫秒再截图”。这些硬编码的数值放在提示词里,比让模型自己猜要稳得多。模型对时间的感知是模糊的,你让它“稍等一下”,它可能理解成立刻。

4.3 任务三:桌面截图批量归档(带条件判断)

前两个任务都是线性的,这个任务加上了判断分支,更接近真实场景。

目标:把桌面上所有以“屏幕截图”开头的 PNG 文件,按日期分文件夹归档。

提示词的写法:

任务:整理桌面上以「屏幕截图」开头的 PNG 文件。 要求:

  1. 先列出桌面所有文件,筛选出符合命名规则的;
  2. 按文件的修改日期分组,日期格式为 YYYY-MM;
  3. 在「图片/归档」目录下为每个月份建一个文件夹;
  4. 把对应文件移动进去,已存在同名文件时跳过并在最终报告里列出;
  5. 优先用命令行完成,只有在命令行不可用时才用图形界面操作。

这个任务的关键设计在于优先用命令行这条约束。一个熟练的从业者会用一条 PowerShell 命令搞定,模型如果用图形界面去拖拽文件,二十个文件就要跑几十步,慢且容易错。给了命令行工具之后,模型会生成类似这样的脚本:

$desktop = [Environment]::GetFolderPath("Desktop") $target = Join-Path $desktop "屏幕截图*.png" $dest = Join-Path ([Environment]::GetFolderPath("MyPictures")) "归档" Get-ChildItem -Path $target | ForEach-Object { $month = $_.LastWriteTime.ToString("yyyy-MM") $dir = Join-Path $dest $month if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir | Out-Null } $targetFile = Join-Path $dir $_.Name if (Test-Path $targetFile) { Write-Output "SKIP: $($_.Name)" } else { Move-Item -Path $_.FullName -Destination $dir Write-Output "MOVED: $($_.Name) -> $month" } }

模型生成脚本之后,不要直接执行,这是我踩过坑之后养成的习惯。让模型先把脚本内容打印出来,人工确认一遍逻辑,再让它执行。为什么?因为模型生成的路径拼接、通配符匹配在处理中文文件名、空格、特殊字符时经常出问题,一条Move-Item写错就可能把文件挪到奇怪的地方。这一步的人工审核成本很低,但能避掉大部分灾难。

执行完之后也要验证:让模型重新列一次桌面文件,确认目标文件确实不在了,再列一次归档目录,确认文件数量对得上。这个“执行后自检”的环节,我在所有任务里都会要求,它把“我以为成功了”变成“我确认成功了”。

4.4 跑完之后怎么复盘

每个任务跑完,我都会花几分钟做三件事,比跑任务本身更重要。

看调用序列。把模型调用的工具和参数导出来,看它走了多少步、哪些步是无效的、有没有反复重试的地方。我第一个版本的任务平均要跑 18 步,复盘之后发现其中 6 步是在反复截图确认同一个状态,改了提示词之后降到 11 步。

记失败模式。每次都记下来“这次为什么失败”,积累下来就是一份极有价值的故障清单。我自己的清单里排前三的是:焦点丢失、等待不足、坐标偏移。这三类占了八成以上的失败。

固化成功的提示词。一旦某个任务的提示词调稳了,就把它存成模板,下次同类任务直接改改就用。提示词是这个项目里最值钱的资产,比代码还值钱,因为它凝结了你对这个特定界面的所有经验。

5. 踩坑实录:五类高频问题与排查路径

5.1 客户端连不上、工具列表是空的

这是新手上路第一个坎。表现是配置改完了,重启客户端,工具列表里什么都没有,也没有明显报错。

排查顺序我固定成四步。第一步,手动跑一遍启动命令。把配置里command和args拼起来,在终端里直接执行,看能不能正常启动、有没有报错输出。这一步能滤掉 90% 的问题——多半是路径写错了、依赖没装全、或者 Python 环境不对。

第二步,检查路径里的斜杠和空格。Windows 路径用反斜杠,但 JSON 里反斜杠是转义符,必须写成双反斜杠或者统一用正斜杠。路径里带空格的话,参数数组里每一项要单独成元素,不能拼成一个字符串。这两个错误非常常见,而且报错信息通常很含糊。

第三步,看 stderr 输出。很多客户端会把 server 的错误输出记到日志文件里,翻一下就能看到真实的异常栈。stdio 模式下 Python 的 traceback 走的是标准错误流,不在标准输出里,所以要专门找。

第四步,验证协议握手。如果进程能起来但客户端还是列不出工具,可能是 server 启动太慢被客户端判超时了,或者 stdout 被缓冲住了(这就是 2.3 节加PYTHONUNBUFFERED的原因)。可以在启动脚本开头加一行日志输出,看客户端有没有收到。

5.2 点击偏移:DPI 缩放的隐形坑

这个坑最坑的地方在于:它不报错,只是点偏。你会看到模型信誓旦旦地说“我已经点击了保存按钮”,但实际鼠标落在了按钮下方三十像素的地方。

原理是这样的:Windows 的显示缩放(125%、150%、200%)是在物理像素和逻辑像素之间加了一层换算。如果进程没有声明自己“能处理高 DPI”,系统会给它返回逻辑坐标,而输入模拟接口用的可能是物理坐标,两者差一个缩放倍数。150% 缩放下,视觉上 100 像素的位置,实际坐标是 150,偏差 50 像素,足以点到隔壁的按钮。

解决办法是在程序入口处声明 DPI 感知,Windows 提供了几种方式:

import ctypes # 方式一:告诉系统这个进程自己处理 DPI try: ctypes.windll.shcore.SetProcessDpiAwareness(2) # 2 = 每显示器感知 except Exception: ctypes.windll.user32.SetProcessDPIAware()

SetProcessDpiAwareness(2)是“每显示器 DPI 感知”,多显示器不同缩放比例时也能正确处理,优先用这个;老系统上退回到SetProcessDPIAware()。声明之后,进程拿到的坐标就是物理像素,和输入模拟接口一致了。

如果改动代码不方便,还有个临时方案:把系统缩放临时调成 100%,所有坐标标定完之后,再把缩放调回去并用比例换算。但这个方案在多显示器、不同缩放的环境下会很麻烦,能改代码还是改代码。

5.3 模型死循环、原地打转

典型场景:模型点某个按钮没反应,于是再点一次,还没反应,再点……二十次之后 token 烧完了,任务还没完成。

造成死循环的原因通常有三个。一是目标真的不可达,比如按钮被禁用、被弹窗挡住、或者根本没在前台。二是反馈缺失,模型点完之后没有重新截图,不知道自己点没点中,只能靠重试来赌。三是提示词里没有止损条件,模型不知道该什么时候放弃。

对策分三层。提示词层加硬约束:“同一操作连续失败两次必须停止并报告”。流程层加自检:“每次点击后截图确认界面是否发生变化,无变化则判断为失败”。工具层可以用循环检测——记录最近几次调用的工具名和参数,如果完全重复就主动打断,把控制权交回并返回一个提示。这一层需要改代码,但效果最硬。

还有一个隐蔽的死循环来源:动画。有些界面在加载时有持续的动画效果,每次截图都不一样,模型误以为状态在变化,就一直等下去。这种情况的识别特征是“模型一直在截图但从不动作”,解决办法是在提示词里加上“如果连续三次截图内容差异很小,视为界面已稳定,可以执行下一步”。

5.4 提权窗口点不动

场景:任务进行到一半,弹出一个需要管理员权限的确认框,模型调用点击工具,返回成功,但窗口纹丝不动。

这不是 bug,是系统的安全设计。不同权限级别的进程之间存在输入隔离,低权限进程的模拟输入无法作用于高权限窗口,这是为了防止恶意程序劫持提权流程。表现就是“调用成功但没效果”,特别容易误判成坐标错误。

处理方式有两个方向。方向一,主动规避:在提示词里明确写“遇到任何权限确认窗口,立即停止操作并报告”,把这类情况交回给人处理。这是最稳妥的做法,我一直用这个。方向二,整体提权:让整个 MCP Server 以管理员权限运行,这样它发起的输入就能作用于提权窗口了。但这个方案会放大风险——一个拥有管理员权限、能自由操作桌面的自动化程序,误操作的后果严重得多。除非在完全隔离的环境里,否则我不建议。

顺带说一个相关现象:锁屏和远程会话断开时,输入模拟也会失效。因为此时没有活跃的交互式桌面会话,模拟输入找不到投递目标。跑长任务的时候一定要保证机器不锁屏、会话不断开,这是很多人“晚上挂机跑任务,早上发现啥也没干”的真实原因。

5.5 问题速查表

现象最可能的原因优先排查动作
工具列表为空启动命令错误 / 依赖缺失 / stdout 被缓冲终端手动执行启动命令看报错
调用成功但鼠标没动无活跃桌面会话 / 权限隔离确认未锁屏、未断连;检查目标窗口权限级别
点击位置偏移DPI 缩放未声明声明 DPI 感知,或临时改 100% 缩放验证
截图是黑屏进程无桌面访问权 / 抓错屏幕换显示器编号重试;确认不是服务方式运行
模型反复点同一处无反馈重截图 / 无止损约束提示词加“失败两次停止”+“点击后必须截图”
双击变成两次单击两次点击间隔过长改用专用双击工具,不要连点两次单击
输入文字乱码或进候选框输入法状态干扰改用剪贴板粘贴,或输入前先切英文状态
保存对话框没出来焦点已切换但模型未察觉提示词要求按键后先截图确认再决策
任务跑到一半卡住不动等待不足或界面有持续动画加大等待时长;加入“连续三帧无变化视为稳定”判断

这张表我贴在便签上,出问题先对一遍,八成能直接定位。

6. 安全边界与落地建议

6.1 隔离环境怎么搭

让一个程序自由操作你的桌面,风险等级要按“给陌生人你的鼠标键盘”来算。它可能点错按钮、可能在你没注意的时候往输入框里打字、可能在错误的窗口执行快捷键。所以隔离是必须的,不是可选项。

最省事的方案是虚拟机,快照一打,跑崩了直接回滚,一分钟恢复。资源占用稍高,但现在机器普遍够用。次选方案是独立的本地用户账户,权限降到普通用户,桌面环境干净,跑任务的时候切过去,跑完切回来。这个方案的好处是不占额外资源,坏处是切换麻烦,不适合需要频繁交替操作的场景。

不管用哪种,有几条基线要守住:不给管理员权限;不登录任何个人账号(浏览器、通讯软件一律不登);不挂载重要目录;任务跑完检查一遍关键文件有没有被误改。听起来有点夸张,但真出过一次误操作之后,你会觉得这些准备一点都不多余。

6.2 哪些任务适合交,哪些绝对别交

适合交出去的:格式固定的表单填写、重复的截图归档、批量重命名、按规则整理文件、定时抓取某个界面的数据、多步骤但流程稳定的软件操作。这些任务的特点是目标明确、失败代价低、人工做起来枯燥。

谨慎交出去的:涉及删除操作的任务(回收站清理、文件删除),一定要加二次确认;涉及外部通信的任务(发邮件、发消息),一定要人工过目内容;涉及金钱和账号的操作,我个人的做法是压根不交给它。

绝对不交的:系统设置修改、软件卸载安装、涉及权限变更的操作、任何需要输入密码的环节。这些要么影响面太大,要么根本没有回滚余地。

一句话总结:让 AI 干“做错了重做一遍就行”的活,别让它干“做错一次就麻烦大了”的活。这个边界划清楚,用起来心态会很放松。


最后分享两个我在实际使用里摸出来的小技巧。第一个是给每个任务写一份“成功判据”,在提示词末尾明确写“任务完成的标志是什么”,比如“桌面上出现名为 XXX 的文件且大小不为零”。模型有了明确的终点判断,就不会在完成后继续瞎折腾,也避免了“它说完成了但其实没完成”的尴尬。第二个是把常用任务的提示词存成一个文本文件,按任务名分类,用的时候复制粘贴改两行就行。我最初每次手写提示词,写着写着发现自己在重复造轮子,而且新写的版本经常忘掉之前踩坑总结出来的约束。固化下来之后,成功率稳定了很多,也省了大量时间。这个项目真正的门槛从来不在代码,而在于你有没有把界面的脾气和自己的经验,变成模型能看懂的文字。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询