☰
单文件AI编码代理:通过GUI自动化与MCP协议让AI真正“动手”
2026/10/14 5:44:20 网站建设 项目流程

我最初想做这个工具,是因为受够了在AI编码代理和图形界面之间来回折腾的感觉。日常开发里,AI帮我写代码、改bug已经很顺了,可一旦涉及“点两下这个按钮”“把那个窗口里的配置保存一下”,它立刻变回一个只能输出文字的建议箱。市面上不是没有能操控GUI的自动化工具,但它们要么是录制回放脚本,要么是笨重的RPA套件,跟AI编码代理完全脱节。所以,我决定自己做一个免费的东西:一个单文件就能运行的AI编码代理,既能理解自然语言指令,又能直接操作Windows桌面GUI,而且原生支持MCP协议。这个项目目前已经稳定跑在几个真实场景里了,今天把设计和踩坑过程完整分享出来。

1. 为什么我要做一个能“动手”的AI编码代理

1.1 文本对话与手动操作之间的鸿沟

大多数AI编码代理把注意力集中在代码编辑器、终端和文件系统上,核心能力是“读写文件”“执行命令”。这当然有用,可现实中的开发流程从来不只是文本。比如我在逆向分析一个程序时,要操作x32dbg,设置断点、查看寄存器、单步执行;或者我在配置嵌入式项目时,要用STM32的图形化配置工具生成初始化代码。这些工具只有图形界面,没有稳定的CLI接口。让AI去操作它们,就必须要有一个能“动手”的代理。

我见过很多人用截图+OpenCV模板匹配的方式让AI识别按钮位置,但效果非常脆弱:换一个DPI缩放,坐标全偏;窗口稍微移动,模板失效;鼠标被占用,点错地方。这根本不是AI能力的问题,而是缺乏一个结构化的GUI理解层。真正的解法是让AI通过可访问性树(Accessibility Tree)看到窗口里的控件,就像看HTML的DOM一样,然后再用一套受控的操作指令去点击、输入、选择。

1.2 三个被忽略的痛点

我调研过现有的免费方案,发现大家普遍卡在三个痛点:

痛点一:安装和依赖太重。很多自动化工具要求Python环境、一堆第三方库、一个完整的运行时。在我的工作流里,我需要这个代理能随U盘带走,在别人的机器上双击就能跑。单文件可执行文件是唯一能接受的形态。

痛点二:AI与GUI自动化完全割裂。即使有人用AutoHotkey或PowerShell做GUI自动化,也只是给AI一段“能跑的脚本”,AI自己并不知道屏幕上发生了什么。它不能根据窗口状态动态调整下一步操作,天然不具备闭环能力。

痛点三:生态封闭,没有MCP支持。MCP(Model Context Protocol)现在已经成为AI工具互连的事实标准,像Figma、Blender、蓝湖、甚至调试器都开始提供MCP服务。如果一个编码代理不支持MCP,就意味着它只能调用内置的几个工具,无法从外部生态获取上下文,也无法把自己的工具能力开放给其他AI平台。这在这个时代是不可接受的。

所以我给自己定了三个硬性目标:免费、单文件、支持GUI和MCP。下文的整个实现都围绕这三条展开。

2. 单文件运行的技术选型:从依赖地狱到一键启动

2.1 为什么选择Go而不是Python

第一版我用了Python,因为GUI自动化生态最成熟——pywinauto、pyautogui、Pillow一套下来不要太方便。但打包成单文件是灾难:PyInstaller的onefile模式要在启动时解压到一个临时目录,体积轻松破一百兆,而且很多杀毒软件会误报。更要命的是,Python的运行时和第三方库在目标机器上未必兼容,我遇到过不少因为系统缺少VC++运行库而无法启动的情况。

换到Go之后,这些问题消失了。Go编译出来的原生二进制直接依赖Windows系统API,不需要额外的运行时。MCP协议的客户端和服务器实现也很轻量,用标准库加上几个小包就能搞定。GUI自动化这块,虽然生态不如Python丰富,但Windows本身提供了完善的UI Automation API,Go通过syscall可以直接调,或者用github.com/go-ole/go-ole这个COM绑定库也能达到同样效果。最终体积控制在20MB以内,双击就能运行。

2.2 GUI自动化库的Windows实现原理

在Windows上做GUI自动化,绕不开两个底层机制:一个是Win32消息机制(适合经典控件),一个是UI Automation(适合现代现代控件、WinForms、WPF、Qt等)。我选择了UI Automation,因为它更接近控件树的抽象模型,能够拿到控件的名称、类型、状态、位置,以及父子层级关系。这就像给AI提供了“无障碍访问权限”,比OCR识别像素靠谱得多。

Go里调UI Automation的常见方案是go-ole配合IAccessible,不过如果想省事,可以直接用go-uiautomation这类库。我自己实现了一个精简版:

// 伪代码示意:获取当前活动窗口的控件树 func GetControlTree(hwnd uintptr) (*ControlNode, error) { root := UiaGetRootElement(hwnd) node := &ControlNode{Name: root.Name(), Type: root.ControlType()} for _, child := range root.Children() { node.Children = append(node.Children, parseNode(child)) } return node, nil }

关键点是,UI Automation会把控件的关系暴露成树状结构,AI代理可以把这棵树序列化成JSON喂给大模型。大模型看到的是“窗口中有按钮[确定]和[取消],有一个文本框[URL地址]”,而不是一张图片,这样理解准确率会高很多。

2.3 打包单文件的真正难题

如果你以为单文件只是“编译出来一个exe就完事”,那太天真了。当程序需要动态更新规则文件、缓存窗口状态、保存密钥时,路径问题就开始找上门。我一开始把配置文件放在可执行文件旁边,结果发现如果程序放在Program Files目录下,普通用户根本没有写权限。后来我用到os.UserConfigDir(),把配置写到%AppData%下,可执行文件保持只读。这样既能单文件分发,又不会踩权限坑。

还有运行时自解压和防病毒误报的问题。Go没有自解压需求,本身干净,但我在调试UI Automation时用到了go:embed内嵌了一些默认规则模板,发现有些杀毒软件还是会对任意注入层面的代码报风险。后来我把内嵌资源都放在编译时打入二进制,运行时不再动态生成可执行内容,误报率明显下降。

3. 核心实现:让AI看懂屏幕并安全操作

3.1 把GUI状态变成AI能理解的文本

GUI自动化只是“手”,要让AI这个“大脑”发挥作用,得把手能够到的信息翻译成它看得明白的语言。我设计了一个中间表示,叫“控件快照”。每次AI需要决策前,代理会抓取当前焦点窗口的控件树,过滤掉没有名称和没有操作价值的元素,然后压缩成JSON。例如:

{ "window": "STM32CubeMX - config.h", "controls": [ {"id": 1, "type": "button", "name": "Generate Code", "enabled": true}, {"id": 2, "type": "combobox", "name": "Toolchain", "value": "MDK-ARM V5"}, {"id": 3, "type": "input", "name": "Project Name", "value": "demo"} ] }

这段JSON不需要包含坐标,因为AI只需要表达意图(比如“点击生成代码”),坐标由执行层根据控件ID去实时查询。这种设计让AI和GUI操作解耦:模型输出的是“对控件的操作意图”,而不是像素坐标。有意思的是,这种思路跟大模型操作网页时用的“可访问性树转文本”非常相似,桌面GUI也一样可行。

3.2 AI操作GUI的决策执行链路

整个链路是这样跑的:

  1. 用户说:“在这个窗口里把Toolchain改成MDK-ARM V5,然后生成代码。”
  2. 代理抓取控件树,转成JSON快照。
  3. 快照和历史对话一起发给大模型,模型返回计划,比如:“先操作combobox id=2,选择MDK-ARM V5;再操作button id=1,执行点击。”
  4. 执行层根据控件ID重新定位到真实控件,调用UI Automation的Select和Invoke方法执行。
  5. 操作完成后再次抓取控件树,反馈给模型,让它判断是否成功。

这里最容易翻车的是控件ID并不稳定。窗口每次打开,控件ID可能都不同。我后来改成用稳定特征做匹配,比如控件的Name + 类型 + 父控件路径。这样即使ID变化,也能正确定位到目标。

3.3 安全边界与防误操作的取舍

AI操作GUI最大的隐患是误点。为了防止AI在关键系统窗口里乱来,我加了几道安全防线:

  • 命令白名单:只允许操作符合正则规则的控件名称(比如“启用”、“停止”、“生成”),系统级窗口(任务管理器、修改密码的页面)直接拒绝。
  • 操作前确认:如果模型准备执行的指令带有破坏性倾向(删除、覆盖、格式化),代理会在命令行交互界面里要求用户按Y确认。
  • 会话看门狗:每次操作前记录控件快照,操作后若出现异常窗口(比如权限错误弹窗),立即中止并回退。

这些限制会牺牲一部分“全自动”程度,但换来的是可以长时间挂着跑而不提心吊胆。我的原则是:宁可多问一次,也绝不让AI乱点。

4. MCP接入:从一个孤岛到标准协议生态

4.1 代理作为MCP客户端:调用全世界的工具

MCP的价值在于标准化。只要对方实现了一个MCP server,代理就能以MCP client的身份去调用它。这意味着我不需要为每个工具单独写适配器。我接的第一个外部MCP是本地文件系统工具,接着是蓝湖和Figma的设计稿同步——前端开发时我经常要让AI读取设计稿里的颜色和间距,用它生成样式代码。过去得靠手工复制参数,现在直接让AI通过MCP去问设计稿。

实现上,MCP client要处理的内容包括:

  • 通过stdio或SSE连接MCP server
  • 获取server暴露的工具列表
  • 调用工具时传递参数,并把结果返回给模型
  • 支持流式输出,尤其是大体积内容

我用Go写了很薄的一层封装,让工具调用看起来跟内置函数一样:

func callMcpTool(serverUrl, toolName string, args map[string]interface{}) (string, error) { client := mcpc.NewClient(serverUrl) defer client.Close() return client.CallTool(toolName, args) }

4.2 代理作为MCP服务器:向其他AI开放GUI能力

更让我兴奋的是反过来的用法:把我的GUI操作能力注册成一个MCP server,这样其他AI编码代理(比如代码编辑器里的助手)也能通过标准协议调用“点击窗口”、“读取控件状态”这些工具。这等于把“物理操作电脑”的能力开放给了整个AI工具链。

为了稳定,我采用SSE传输,这样其他AI平台可以远程接入。对外开放的工具定义大概是:

{ "name": "gui_click", "description": "点击指定窗口中的按钮或可点击控件", "inputSchema": { "type": "object", "properties": { "windowKey": {"type": "string", "description": "窗口标题或类名"}, "controlName": {"type": "string", "description": "控件名称"} }, "required": ["windowKey", "controlName"] } }

任何一个支持MCP的客户端,都能把自己用户的指令转成对gui_click的调用。我实测在ursor和Cherry Studio里都能正常调用,虽然不同客户端的授权方式略有差异,但MCP协议本身是通用的。

4.3 一个调试场景的实际串联

把GUI和MCP串联起来的典型场景是调试器的自动化。我在用IDA或x32dbg做逆向分析时,想要AI帮忙“在定位到当前函数的入口处下断点”,这个Window的按钮操作原本只有人去做。现在流程是:AI作为MCP client连上调试器的MCP server,直接拿到反汇编/寄存器上下文,判断需要在哪里下断点;然后通过我提供的MCP工具gui_click,在调试器窗口里执行“点下断点按钮”这个动作。整个过程浑然一体,AI既能“看”到代码逻辑,又能“动手”操作GUI。

当然这需要调试器本身提供MCP服务。现在IDA和x32dbg社区都有对应的MCP插件,配置起来并不复杂。我遇到的问题是有些插件只支持Windows特定版本,不过在我们常用的Win10/11上跑得很稳。

5. 踩坑记录:GUI自动化和MCP的常见坑

5.1 元素定位失败:不只是坐标问题

我在早期版本里天真地用控件名加索引去定位,结果经常碰上一类玄学:窗口标题带了个“- x32dbg”,UI Automation树里却拆成两个顶层窗口,其中一个还是隐藏的。后来学会用WindowPattern和ElementFromHandle去过滤可见窗口,情况好了很多。

另一个高频坑是动态加载的列表项。比如下拉菜单在展开前,子项根本不在控件树里。必须先点击下拉框,等半秒再抓取。我在代理里加入了“操作后等待窗口稳定”的逻辑:比较前后两次控件树快照,如果结构不再变化再继续下一步。这个技巧用在了大量GUI操作上,成功率提高很明显。

5.2 单文件程序的路径与权限陷阱

单文件程序看起来清爽,实际上反而会让开发者遇到更多的系统级约束。我之前提到配置文件要写到%AppData%,但还有一个容易被忽略的点:如果你在提权的进程里运行代理,普通用户运行的IDE是没有权限访问这个提权进程创建的文件的。反过来,代理操作一个以管理员身份运行的GUI程序时,UI Automation调用也会被拒绝。处理方式是在启动时检测当前权限,并提示用户保持一致的运行级别。如果你只是普通用户级别,就统一都在普通级别跑,不要混着开。

5.3 MCP工具调用的超时与流式输出

MCP server返回一个很大的JSON或一段长文本时,客户端如果傻等,很容易触发模型层的超时。我一开始就吃过亏,AI等不到回应就开始瞎猜下一步。后来我改成对工具返回值做流式监听:MCP server把输出分成多段推送,客户端边接收边给模型反馈。这个过程有点像你在终端里看tail -f,模型能看到前面的输出就足够了,不必等全部结束。比如cherrystudio里的MCP工具流式输出就是这种模式,我也参考了同样的设计。

还有一次,某个MCP server返回了带BOM头的UTF-8文本,直接导致解析错乱。排查很久才发现。从那以后,我在所有解析入口都做了“先去除BOM再解码”的处理。

6. 后续迭代与我的个人建议

6.1 近期规划:跨平台与更多协议接入

这个代理目前在Windows上运行得很顺手,但我自己有时需要在macOS和Linux下调试嵌入式程序,所以下一步想利用各自的辅助功能API(macOS的AXUIElement和Linux的AT-SPI)抽象出一套统一的控件接口,这样同一个代理可以在三端跑。跨平台GUI自动化没有捷径,每个系统都是独立的构建和测试矩阵,但为了“单文件运行”这个目标,值得去做。

另外,MCP生态每天都在长新东西,我会持续接入更多对开发者有实际帮助的工具,尤其是嵌入式IDE、游戏引擎编辑器这类重型GUI软件。很多人问“虚幻引擎5.8的MCP能不能接”,目前引擎各方支持还在演进,但只要它通过MCP暴露工具,我的代理就能直接调用,顺便还能用GUI操控来处理那些暂时没有MCP接口的菜单项。

6.2 给想在类似项目上动手的人的三点建议

如果你也想自己撸一个AI编码代理,我给你三条实在的建议,都是我交学费换来的。

先固定最小闭环,再谈扩展。一开始别想着GUI+MCP+单文件全做,先把“AI读取GUI快照→执行点击→验证结果”这个循环跑通。哪怕只是让AI自动打开记事本并输入一行字,这个闭环就足够证明架构是可用的。我第一版在闭环验证之后,才逐步加入了MCP。

不要自己造一套控件协议,直接用可访问性树。很多人会想用视觉模型去理解屏幕,复杂度高得吓人。先试试用系统自带的可访问性API拿控件树,很多时候就够用了。对于图片型软件(比如游戏),再考虑OCR和视觉模型兜底。

安全机制一定要在最开始就设计进去。GUI操作代理一旦跑起来,等于给了AI一个能影响真实系统的“手”。我在开发中期就因为没做“破坏性操作确认”,让AI误删过一个配置文件,后来花了大半天才恢复。你不想经历这种痛苦,就在最开始把白名单和确认机制写好。

这个项目到现在还是免费开放的状态,我也在继续完善文档和示例场景。如果你有具体的GUI自动化需求,或者对MCP接入有想法,欢迎在评论区留言,我看到都会回。一个人写工具容易闭门造车,用户反馈才是最好的迭代方向。

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

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

立即咨询