
这阵子把手头几个自动化脚本项目收尾之后我做了一件惦记了很久的事给自己写一个免费的 AI 编码代理工具。和市面上一堆编码代理不太一样它除了能读代码、改代码、跑终端命令之外还能直接操控 GUI——打开窗口、点击按钮、填写表单并且原生支持 MCP 协议能接入各种外部工具服务。最关键的是整个程序只打包成一个单文件扔到哪台机器都能直接跑不需要配 Python 环境也不需要装一堆依赖。这个项目我从零开始写了大概十天走了不少弯路也踩了不少坑。之所以想把整个过程整理出来是因为我发现很多人对 AI 编码代理的理解还停留在“对话式补全代码”的层面根本没意识到它其实可以变成真正替你操作电脑的“自动化助手”。尤其是 GUI 操控加 MCP 这套组合做完之后的实用价值超出我预期。如果你也在考虑自己做一个类似的工具或者想给现有 Agent 加上 GUI 能力这篇文章可以帮你少走很多弯路。1. 项目起源与核心设计思路1.1 从痛点出发AI编码代理为何普遍“看不见”界面目前主流的 AI 编码代理基本都停留在“代码文件 终端命令”这两个维度。它们能帮你读取仓库内容、生成补丁、运行测试但一旦遇到必须通过图形界面完成的任务就无能为力。我手头有个实际场景帮客户维护一个老旧桌面应用它的配置界面没有命令行替代方案每次打包发布前都要手动点十几个按钮。代理商沟通的时候对方问我能不能让 AI 帮我点这些按钮。当时市面上没有合适的轮子。通用 RPA 工具太重UI 自动化框架需要单独维护脚本而我已经有了一个基于 LLM 的 Agent 框架缺的只是“看得见屏幕、动得了鼠标键盘”的能力。于是这个项目的核心需求变得非常明确做一个轻量的 AI 编码代理在原有代码能力基础上叠加 GUI 操控和 MCP 协议支持让 AI 能真正操作外部软件、调用外部工具链。免费开源是我的一个自我要求因为这本来就是为了解决个人效率问题做的东西没必要商业化。1.2 技术选型GUI操控能力如何与MCP协议配合项目命名叫“FreeCodingAgent”本质上是一个多模态 Agent 壳子。GUI 操控我采用了“截图 视觉模型识别 坐标点击”的技术路线而不是走 Windows UI Automation 或 macOS Accessibility 那套原生 API。主要原因有三个跨平台一致性好不管 Windows、macOS 还是 Linux截图接口都是现成的不依赖目标软件是否暴露 UI 元素树信息老程序、非原生界面、远程桌面里的界面都能处理视觉模型天然理解界面语义不需要为每个软件单独写选择器。MCP 协议这块我参考官方 Python SDK 实现了一个轻量客户端支持 stdio 和 SSE 两种传输方式。这样既能连接本地 MCP 服务器比如文件系统、数据库工具也能接入远程 MCP 服务。GUI 操控和 MCP 的配合逻辑是GUI 负责“手动操作”MCP 负责“读数据和写数据”。比如让 Agent 在 GUI 里填表格时它可以通过 MCP 查询数据库拿到真实数据再驱动鼠标键盘填进去。这个组合让 Agent 不再是个盲操作者。1.3 为什么必须坚持“单文件运行”项目立项时我就定了一个硬性指标编译产物必须是一个独立可执行文件。用户拿压缩包解压后得到一个文件双击或者命令行一行启动不依赖系统里预装 Python、Node.js 或 .NET 运行时。技术选型上最终用了 Go 作为 Agent 主框架语言GUI 操控层通过调用系统原生接口实现。Go 编译出的静态二进制天然满足单文件分发需求交叉编译也很方便。这个决策牺牲了一些开发效率。Go 生态里直接可用的 GUI 自动化库不如 Python 丰富很多功能需要自己封装系统 API。但收益明显实测在 2008 年的老笔记本上能直接跑在 Windows Server 无桌面环境的最小化安装里也能运行只要有图形会话完全不用装任何依赖。对于需要分发到客户机器上的工具来说单文件的运维成本几乎为零。2. GUI操控与MCP接入的核心实现2.1 GUI操控落地细节从视觉识别到动作执行GUI 操控链路拆开看就是四个环节截屏、识别、定位、执行。截屏我用了 Go 的kbinani/screenshot库支持多显示器返回原始 RGBA 数据。识别环节没有直接接云端多模态大模型而是优先用本地的 OCR 加模板匹配做初筛只有初筛置信度低于阈值时才调用大模型接口做二次判断。这样做的原因是成本日常自动化场景里大部分按钮和输入框都可以靠 OCR 图标模板匹配搞定完全不需要大模型介入。实测下来只有识别复杂表格、图表区域时才需要大模型出马。定位环节有个关键细节——高 DPI 缩放。Windows 系统显示缩放设置为 125%、150% 时截图坐标和真实鼠标坐标并不一致两者的映射关系是屏幕物理分辨率与逻辑分辨率之比。如果忽略这一点坐标偏移非常严重。我最终的方案是截屏时获取逻辑分辨率W/H执行鼠标点击时除以缩放因子映射到物理坐标。坐标计算的伪代码大致是这样# 截图坐标到屏幕物理坐标的转换 scale_x physical_width / logical_width scale_y physical_height / logical_height # 假设视觉模型识别到图形界面元素中心点位于截图中的 (xs, ys) real_x int(xs * scale_x) real_y int(ys * scale_y) # 执行点击 mouse.move(real_x, real_y) mouse.click()实际测试中在 150% 缩放下如果不做映射直接点击偏差能达到几十像素按钮多点不中。这个坑几乎每个做屏幕自动化的人都会遇到建议读者直接抄这个映射公式。2.2 MCP客户端实现的关键逻辑MCP 协议本身是 JSON-RPC 2.0基础交互就是客户端向服务器发送initialize请求握手成功后通过tools/list拿到工具列表再通过tools/call调用具体工具。我在 Go 里实现了一个最小客户端核心结构体就几个客户端会话、传输通道、工具注册表。一个值得分享的细节是工具调用的流式输出处理。MCP 服务器返回结果时可能一次性返回大 JSON 对象也可能分多次返回小片段。为了让用户看到实时输出我没有等整个响应体攒完而是逐块解码并边写边显示。这个体验细节在终端里感知很明显——大模型生成代码时如果一直黑屏等最终结果用户会觉得卡死了。MCP 客户端的核心调用流程简化如下func (c *Client) CallTool(ctx context.Context, name string, args map[string]interface{}) ([]byte, error) { // 构造 JSON-RPC 请求 req : JSONRPCRequest{ JSONRPC: 2.0, ID: atomic.AddInt64(c.seq, 1), Method: tools/call, Params: map[string]interface{}{ name: name, arguments: args, }, } // 发送请求 resp, err : c.transport.Send(ctx, req) if err ! nil { return nil, err } // 返回结构化输出 return parseResponse(resp) }2.3 单文件打包的技术路径与踩坑单文件运行用 Go 是天然支持的但真正落地时有几个容易翻车的地方。第一个是嵌入外部资源比如内置的提示词模板、OCR 模型文件这些我全部用 Go 的embed.FS编译进二进制运行时不读取外部文件。第二个坑是 Windows 下静态二进制的图标和版本信息。Go 默认生成的 exe 没有图标点击右键看属性也没有版本详情看起来像病毒文件。这个问题需要借助goversioninfo工具在构建时生成.syso文件嵌入资源这一步在自动化构建脚本里是必须的。第三个坑是交叉编译。开发机是 macOS但主要运行环境是 Windows。交叉编译 Windows 版时凡是涉及 CGO 的库都要注意GUI 操控层如果用了 Windows API必须通过 syscall 方式调用不能依赖 CGO。我最终把所有 Windows 专属调用封装在一个独立包中通过 build tag 隔离平台代码。这样保证了GOOSwindows go build一步到位。3. 实操全过程从零构建一个可用的AI编码代理3.1 环境准备与依赖清单整个项目核心代码约 3500 行依赖如下Go 1.22用于 Agent 主框架和 MCP 客户端gopkg.in/telebot.v4用于 Telegram 机器人接口没错我做了一个 Telegram 远程控制入口kbinani/screenshot用于跨平台截屏moutend/go-windows和mousetrap用于 Windows 鼠标键盘操控joho/godotenv用于读取环境变量配置开发时用 Windows 11 真机做 GUI 测试macOS 做交叉编译。建议读者如果是开发类似工具一定准备两台机器或者虚拟机因为 GUI 自动化非常依赖真实系统环境纯容器里跑不起来的。3.2 核心代码结构与实现思路项目结构分为三层Agent 调度层、工具执行层、硬件访问层。Agent 调度层负责与大模型对话把用户指令拆解为一系列工具调用工具执行层实现了read_file、run_shell、click_ui、input_text、fetch_url等工具硬件访问层封装鼠标、键盘和截屏操作。其中最有意思的工具是click_ui它接收两个参数目标界面的文字描述和置信度阈值。内部流程是先截屏再 OCR 定位候选区域然后返回坐标并执行点击。这个工具可以让用户用自然语言指挥 Agent比如“点击右上角的发布按钮”Agent 自己判断按钮位置。// 视觉定位工具的简化实现 func (a *Agent) ClickUI(target string, threshold float64) (bool, error) { // 1. 截取当前屏幕 img, err : screen.Capture() if err ! nil { return false, err } // 2. OCR目标文字 regions : ocr.FindText(img, target) if len(regions) 0 { return false, fmt.Errorf(目标元素未找到: %s, target) } // 3. 取置信度最高的中心点 best : regions[0] for _, r : range regions { if r.Score best.Score { best r } } if best.Score threshold { return false, fmt.Errorf(置信度低于阈值: %.2f %.2f, best.Score, threshold) } // 4. 坐标缩放并点击 realX, realY : scaleCoordinates(best.CenterX, best.CenterY) mouse.Click(realX, realY) return true, nil }3.3 联调运行与效果验证第一次完整跑通一个任务是让它打开记事本并输入一段文字。这条指令被拆解成三步通过系统命令启动 notepad等待窗口出现然后输入文字。这里等待窗口出现非常关键。新启动的 GUI 程序需要几百毫秒到几秒的加载时间如果 Agent 马上去 OCR 截图大概率找不到目标。我的方案是轮询检测每 300 毫秒截一次屏一旦 OCR 找到窗口标题栏文字就立刻执行下一步最多超时 10 秒。这个简单策略避免了大量“窗口还没打开就点击”的竞态问题。实测中这个任务从输入指令到完成耗时大约 5 秒其中大部分时间花在 OCR 识别上。从用户体验来看完全在可接受范围。4. 常见问题与排查技巧实录4.1 GUI坐标识别不准的四类典型场景场景一高 DPI 缩放导致偏移。这个问题前面提过解决方法是用逻辑分辨率和物理分辨率的比例换算坐标。Windows 下可以通过系统 API 直接获取缩放因子代码里做一个乘除法映射即可。场景二多显示器布局产生负坐标。副屏在主屏左边时副屏控件坐标是负数。screenshot 库捕获多屏后返回的图片是全屏拼接但 OCR 得到的是相对图片的坐标必须再加上主屏偏移量才能得到真实屏幕坐标。这里最容易犯错建议调试时先打印原始截图尺寸和坐标值人工对比一次屏幕布局。场景三窗口被遮挡导致识别失败。目标窗口虽然在前台但如果有弹窗、悬浮窗遮挡OCR 识别文字的区域可能被破坏。我的处理是先尝试将目标窗口强制置前用SetForegroundWindowAPI 把窗口带起来再重新截图识别。场景四输入法状态干扰文本输入。在中文系统上直接通过事件注入输入英文字母时如果当前输入法处于中文模式字母会被替换成拼音候选。这个问题坑了很多做 RPA 的人。我的方案是统一使用SendInputAPI 并附加扫描码绕过输入法直接发送原始按键。4.2 MCP连接失败的排查顺序接入 MCP 服务器时最常见的问题按出现频率排基本是这四类服务器进程启动失败、JSON-RPC 握手失败、工具列表为空、调用超时。我的排查经验是从底层往上查。比如本地 stdio 服务器启动失败先用命令行手动启动一次看有没有报错输出很多 MCP 服务器需要指定环境变量。握手失败通常是协议版本不匹配MCP 从 2024-12-31 到 2025-03-26 的版本有过不兼容更新客户端必须用自己的版本号换取服务器协商。工具列表为空的常见原因是服务器配置了声明的工具但实际注册失败超过一半是这个原因要去服务器日志里找线索。我把排查过程整理成了一张速查表错误现象可能原因排查动作连接被拒绝stdio路径错误或端口不通手动启动服务器验证进程是否能正常运行initialize失败协议版本不匹配检查客户端和服务端的支持版本工具列表为空服务器内部工具注册异常查看服务器日志确认工具初始化没有报错调用超时工具执行时间长或网络延迟调大客户端超时时间到 30 秒以上响应格式解析失败服务器返回了非JSON-RPC格式数据开启协议日志比对原始字节流这个表我打印出来贴在显示器边上排查问题效率高很多建议做同类项目的人直接照抄。4.3 单文件跨机器运行的兼容性问题单文件发布虽然省心但跨机器跑的时候还是有几个问题值得警惕。最典型的是目标机器缺少 Microsoft Visual C 运行库。如果 Go 代码用到需要 CGO 的库编译出来的二进制会动态链接msvcp140.dll等文件换一台新机器就报 0xc000007b 错误。解决办法是编译时强制CGO_ENABLED0确保静态链接。另一个问题是杀毒软件误报。单文件 Go 程序频繁截图、模拟鼠标点击行为特征确实接近远控木马第一次编译完就收到 360 和 Defender 的报警。我是通过申请代码签名证书解决的虽然有免费的自签名方案但信任链依然不如商业签名靠谱。这个问题的用户体验影响很大读者如果打算分发工具建议预留签名证书的成本。还有一个用户环境特有的问题Windows 的锁屏状态。如果用户离开前按了WinL屏幕被锁定截图全是黑的鼠标点击也无效。我会在启动时检查会话状态若锁屏则提醒用户先解锁避免白白等待。5. 实战效果回顾与后续演进方向5.1 用真实任务刁难它一组测试结果为了检验项目可用性我设计了三组测试任务覆盖不同复杂度的场景第一组是“打开 Git 客户端克隆一个仓库”。Agent 需要启动 GitKraken等待界面加载点击左上角“Clone”按钮输入仓库地址再点 Clone。整个过程消耗 20 秒一次通过。这个任务验证了基础 GUI 操控链路。第二组是“登录内部系统从指定页面抓取表格数据汇总成 Markdown 文件”。这个场景结合了 GUI 和 MCP。Agent 通过 MCP 读取数据库拿到筛选条件再通过 GUI 在网页上点击查询最后截图识别表格内容生成 Markdown。总共耗了 90 秒中间出现了两次识别不准调用大模型重新判断后成功。第三组是“扫描项目代码里的 TODO按文件树导出报告”。这个任务纯代码主要验证原有编码代理能力没有被 GUI 改造带崩输出完全正常。整体测试下来最有价值的感受是自由度和可靠性是矛盾的。纯 GUI 视觉识别解决不了复杂组件如下拉菜单嵌套或右键菜单这类操作必须手动写规则辅助而 MCP 正好填补了“确定性获取数据”的缝隙。真正好用的 Agent 是让视觉当眼睛、键盘鼠标当手、MCP 当大脑的信息通道三者协同而不是互相替代。5.2 使用心得与后续规划这里分享三个我个人的使用心得。第一别追求界面识别百分之百准确。日常自动化任务中90% 以上的 GUI 操作其实都很固定组合好 OCR、窗口置前、等待超时这老三样已经能覆盖绝大多数场景。为了剩下 10% 增加复杂规则性价比太低。第二MCP 工具要按“最小可用”原则设计。我最初写了 20 多个 MCP 工具后来删减到 12 个因为 tool 越多大模型在意图识别时越容易选错。做 Agent 工具的人应该把工具边界做清晰减少歧义。第三单文件发布的推广价值被低估了。产品咕咕一段时间没发这次体验完成之后我把编译产物发到几个工作群结果因为“一个文件就能跑”这个特性不少人主动问怎么获取源码。降低分发成本有时候比功能本身更能带来用户。后续的演进方向我主要有三个一是增加语音输入入口在操控 GUI 时直接说“点击确定”Agent 就能执行交互会更自然二是给 MCP 客户端增加动态发现和权限管理功能避免工具滥用三是支持更细粒度的 GUI 操作比如拖拽、方向键、组合快捷键这些场景在自动化测试里很常见。最后分享一个调试 GUI Agent 时的小技巧环境变量里加一个DEBUG1让 Agent 每次点击前先在终端输出“即将点击坐标 (x,y) 对应的元素是XXX”连续跑几次就能轻松定位到底是识别错了还是坐标映射错了。这个调试开关我大概加了两行代码省下来的排查时间远超预期。如果你也在做类似工具建议一上来就把这个钩子埋好。