先说结论:这可能是目前我做过性价比最高的一个 AI 编码代理项目。它不是一个 IDE 插件,也不是那种只能在终端里跑命令行的 Agent,而是一个自带 GUI 操控能力、原生支持 MCP、全部打包成单文件的免费工具。简单说,你给它一个任务,它能打开软件、点按钮、填表单、读界面信息,还能通过 MCP 协议去调用外部工具链,整个过程不需要装 Python 环境、不需要配 Node.js、不用拖着一堆依赖到处跑。
这个项目最适合三类人:一是天天写自动化脚本但总被环境问题折磨的开发者,二是想用自然语言让 AI 直接操作桌面软件的业务分析师,三是做 RPA 或测试平台建设、想快速验证"Agent 到底能不能自动跑通一个流程"的人。我自己最初就是被"AI 写完代码还要自己复制进 IDE 再手动运行验证"这件事烦透了,才决定做这个工具。如果你也有类似的痛点,这篇博文把设计思路、核心实现、踩坑记录全部摊开讲,你可以直接照着复现,也可以只挑其中一部分能力用到自己的项目里。
1. 为什么做这个 AI 编码代理
1.1 现有 AI 编程助手卡在哪
我用过不少 AI 编程工具,Codex、Copilot、Cursor 都试了一圈,它们解决"写代码"这个环节确实很强,但一旦任务超出"生成代码片段"的范畴就立刻变得很别扭。具体卡在三个地方:
第一个痛点是AI 生成完代码以后没法自己验证。它不知道这段代码能不能编译,跑起来会不会报错,界面交互是不是符合预期。传统做法是我把代码拷到项目里,手动执行一遍,再把报错信息丢回给 AI,让它继续改。这个循环一多就很浪费时间,而且上下文一长,模型很容易前面改好后面又改坏。
第二个痛点是AI 碰不到 GUI 应用。很多业务场景根本不在命令行里,比如我经常要操作某个数据库管理工具、抓取某个老系统的界面数据、或者给别人演示一套软件操作流程,这些都必须用鼠标去点。市面上的 AI Agent 大多只能操作终端,遇到 GUI 就只能干瞪眼。结果就是,自动化脚本能覆盖的场景极其有限。
第三个痛点是扩展工具太麻烦。要让 AI 调用外部能力,以前得自己写一堆工具函数、设计 JSON Schema、管理调用循环,工程量不小。而且不同 AI 平台各有各的接口,换一家模型就要改一遍对接逻辑。后来 MCP 协议出来后,理论上工具可以标准化了,但市面上把它和 GUI 操控结合起来的 Agent 几乎没有,多数只是接几个玩具级 MCP Server 做演示。
这三件事叠加起来,我当时的感受就是:市面上缺的是一个能自己动手操作软件、能通过标准协议扩展工具、又不需要我费劲搭环境的 AI Agent。既然没有现成的,我就自己做一个。
1.2 我的设计目标:不只是会写代码
这个项目的目标从一开始就不是"做个更好的代码补全工具"。我给它定的调子是:
- 免费。工具本身不卖钱,模型接口用户自己带,甚至可以用本地模型跑。
- 能看屏幕。Agent 必须能拿到当前屏幕的画面,并且能理解界面上有什么。
- 能动手操作 GUI。鼠标点击、键盘输入、窗口切换这些动作必须支持。
- 原生支持 MCP。不需要写定制工具,直接通过 MCP 协议把外部能力接进来。
- 单文件运行。分发时只有一个可执行文件,双击就能跑,不依赖解释器和额外运行时。
做完以后我发现,这几个目标其实是互相成就的。"单文件"逼着我重新思考依赖怎么打包,"GUI 操控"逼着我把视觉识别和模拟输入做扎实,"MCP 支持"保证了工具扩展不用走回老路。整个项目最终大概只有几千行核心代码,但能干的活比很多动辄几万行的框架都多。
2. 整体架构与核心设计思路
2.1 三大能力怎么拆
这个 AI 编码代理从功能上可以拆成三个互相独立、又能协作的模块:
GUI 操控模块是整个工具最特别的地方。我采用的方案是"截图加视觉理解加模拟输入"的闭环。Agent 先截一张当前屏幕的图,把图片交给视觉模型,模型输出"看到了什么、下一步该点哪里",然后工具层根据模型给出的坐标执行鼠标点击或者键盘输入。点击完以后再截一张图,继续下一个循环。这就是我常说的"看一步做一步"模式,本质上和人在操作电脑时"看一眼屏幕、决定怎么点、点完再看一眼"是一模一样的。
这里有一个很关键的工程取舍:要不要用 UI Automation 去精确识别控件?我后来是视觉方案为主、UI 自动化方案为辅。因为真实世界的软件千奇百怪,有的是自家绘制的控件、有的是远程桌面窗口、有的是游戏画面,UI Automation 在这些场景经常拿到一堆抽象元素却不知道该怎么操作。视觉方案反而通用,只要截屏能截到、坐标能定位,什么软件都能操作。
MCP 接入模块负责把外部工具标准化接入。MCP,全称 Model Context Protocol,是一个开放协议,用来让 AI 应用统一调用外部工具、读取资源、使用提示词。我在项目里内置了一个 MCP 客户端,可以同时连接多个 MCP Server。每个 Server 暴露的工具会统一变成"工具名加参数 JSON Schema"的形式,模型的函数调用机制可以直接触发这些工具,返回值再塞回对话上下文,让模型根据结果决定下一步动作。
单文件运行模块是整个工具的"分发层"。我选择了 Go 作为主语言,因为它交叉编译非常方便,可以直接产出 Linux、macOS、Windows 三个平台的原生可执行文件,而且编译出来的二进制文件天生就是单文件。配合 Go 标准库里的embed包,我可以把前端页面、内置的 MCP Server 代码、默认配置模板全部打进二进制里。运行时再把必要的资源释放到临时目录,用完后自动清理。用户拿到的就是一个干干净净的 exe 或者 bin 文件。
2.2 为什么选这个技术栈
说实话,做这个项目之前我也犹豫过要不要用 Python。Python 在 AI 生态里确实方便,OpenAI、Anthropic、Ollama 的 SDK 都有 Python 版,图像处理也有 Pillow。但致命问题是分发。一个 Python 项目要让别人跑起来,要么要求对方装 Python 环境和一堆 pip 包,要么用 PyInstaller 打成巨大的文件夹,里面还全是碎文件。这和"单文件运行"的目标直接冲突。
Go 解决了分发问题,但它本身不带 GUI 操控库,MCP 客户端也要自己写协议。所以我在 Go 基础上做了两层封装:向外调用操作系统接口模拟输入,向内实现 MCP 客户端和模型接口对接。模拟输入在不同平台上调用不同接口,Windows 上是SendInput,macOS 上用CGEventPost,Linux 上走XTest或者uinput。MCP 协议其实就是一个基于 JSON-RPC 2.0 的通信协议,支持 stdio、SSE、HTTP 三种传输方式,理解协议之后用 Go 实现客户端并不复杂,我大概花了一天就把核心逻辑写完了。
最终技术栈是这样的:
| 模块 | 技术方案 | 选择原因 |
|---|---|---|
| 主语言 | Go 1.22+ | 交叉编译方便,单文件产物,无运行依赖 |
| GUI 操控 | go-vnc 原理加自研模拟输入层 | 按平台调用系统输入 API,通用性最强 |
| 视觉识别 | 对接多模态大模型 API | 理解截图内容,输出操作坐标 |
| MCP 客户端 | 自研轻量实现 | 支持 stdio、SSE、HTTP 传输 |
| 模型接口 | OpenAI 兼容格式 | 一套代码适配 OpenAI、Anthropic、Ollama、DeepSeek 等 |
| 内嵌资源 | go:embed | 把 Web UI 和静态资源打进二进制 |
这个组合的好处是,整个项目编译完只有 15MB 左右,不管放到什么机器上都能直接跑。有人可能会问,为什么 GUI 操控不再封装一个大而全的库?我的想法是,Agent 的操作需求其实很聚焦,核心只有移动鼠标、点击、按键、输入文本、窗口切换这几个动作,自己封一层反而比引一个重库更可控,出问题时也好排查。
2.3 核心运行流程
整个 Agent 的运行时流程是这样的:用户通过 Web 界面或命令行输入一个自然语言任务,模型先规划第一步,然后循环执行"观察、决策、行动"三步,直到任务完成或者人为中断。
具体来说,Agent 的主循环可以简化成这段伪代码逻辑:
for { screenshot := captureScreen() result := model.Observe(screenshot, taskHistory) if result.CanFinish { return result.Answer } if result.NeedTool { toolResult := mcpClient.CallTool(result.ToolName, result.Arguments) taskHistory = append(taskHistory, toolResult) continue } executeAction(result.Action) // 鼠标点击、键盘输入、滚动等 sleep(800) // 给界面渲染留出时间 }一个很关键的细节是每次操作后的延时。我一开始没有加延时,结果模型经常在一个还没渲染好的界面上乱点。后来调了几轮,发现屏幕截图、模型思考、执行动作、再截图的周期控制在 2 到 3 秒左右比较合理,太快容易误判,太慢用户又没耐心。
3. 核心细节解析与实操要点
3.1 让 Agent 真正"看"到屏幕:视觉闭环的 3 个关键点
GUI 自动化的第一环是截图。这个环节有三个容易踩坑的地方,都是我实测留下来的教训。
第一个坑是屏幕坐标系的坑。如果用了多显示器,坐标可能是负数;如果屏幕开了缩放,应用里的逻辑坐标和物理像素坐标可能不一致。Windows 上常见的是 125%、150% 甚至 200% 的 DPI 缩放,截图出来的像素坐标和系统 API 里使用的逻辑坐标如果不做换算,点击就会偏得离谱。我的解决办法是统一使用逻辑坐标作为 Agent 交互的标准坐标,截图时把物理像素图缩放回逻辑坐标尺寸,这样模型看到的图和它输出的坐标就是同一个坐标系。在做这个换算时,写一个简单的工具函数校验:
func LogicalToPhysical(x, y int, dpiScale float64) (int, int) { return int(float64(x) * dpiScale), int(float64(y) * dpiScale) }第二个坑是截图权限。macOS 上要授权屏幕录制权限才能截到屏幕内容,Windows 上如果程序不是以管理员权限运行,截某些高权限窗口时也会得到黑屏。这个问题我只在某个远程桌面场景里遇到过,当时排查了半天才发现是权限问题。所以我在工具里做了一个健康检查命令,用来验证截图、模拟输入、窗口枚举这三个核心能力是否正常,任何一步失败都会给出明确提示。
第三个坑是视觉模型的上下文管理。如果不加控制,每次循环都把全屏截图丢给模型,几十轮下来上下文会爆炸,费用也会非常夸张。我的做法是每轮只保留最近三张截图和对应的操作记录,更早的交互总结成文本描述。另外,模型输出的坐标必须做边界检查,防止它返回超出屏幕范围的坐标导致程序空等。
3.2 MCP 集成:把 Agent 变成"万能插座"
MCP 这套协议解决了一个我一直很头疼的问题:AI 应用接入外部工具时,每个工具都要单独写对接代码。MCP 一标准化,事情就变成了——只要对方提供了一个 MCP Server,我就能在 Agent 里直接调用它的能力,不用关心它是怎么实现的。
我在项目里做了一个轻量的 MCP 客户端,核心能力是四个:发现工具列表、读取资源列表、调用工具、读取提示词。用 Go 实现的时候,关键是把 JSON-RPC 的消息封装好。MCP 里传输层可以是标准输入输出(stdio),也可以是 SSE 或者 HTTP。stdio 适合本地工具,比如一个文件操作助手;HTTP 适合远程服务,比如团队内部的 API 网关。
一个很实用的场景是把文件系统 MCP Server 接进来。Agent 需要读写文件时,不用自己实现文件操作函数,直接调用 MCP Server 暴露的read_file、write_file、list_directory就行,等于把"操作电脑文件"这个能力变成标准工具。配合 GUI 操控,Agent 就能做到"读取配置文件、打开对应软件、相关配置填进去、点击保存"这一整套流程。
接入 MCP Server 的配置非常灵活,我在项目里用 JSON 配置:
{ "mcpServers": { "filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "./workspace"], "transport": "stdio" }, "remote-tools": { "url": "http://localhost:8080/mcp", "transport": "http" } } }接好以后,Agent 在规划任务时会收到额外的工具列表。模型发现某一步用工具更靠谱,就会发起工具调用,Agent 再把工具结果回传给模型。这个机制配合好以后,整个工具的扩展性非常强,今天接一个数据库 MCP,明天接一个设计稿 MCP,都不用改 Agent 本体的代码。
3.3 单文件运行:免安装是怎么实现的
单文件运行这个目标,看起来只是一个打包问题,实际上牵涉到资源管理、临时目录清理、配置存储三个层面的设计。
资源管理上,我用go:embed把 Web 静态资源、内置图标、默认的 MCP 配置模板全部编译进二进制。启动的时候,程序会在系统临时目录下创建一个带随机后缀的文件夹,把内嵌资源释放进去,然后启动本地 HTTP 服务提供 Web 界面。关掉程序以后,临时目录会被自动清理。这样既做到了单文件分发,又不会在系统里留下垃圾文件。
配置存储上,我不走常规的写配置文件到程序目录的做法,因为 Windows 下这个目录经常没写权限。我把配置写在用户配置目录下的一个子目录里(Windows 上是%APPDATA%,Linux 上是~/.config),然后通过环境变量或者启动命令来覆盖路径。这样升级软件时配置还在,又不污染单文件原本的目录。
这里有一个实操心得:单文件打包的时候,一定要把版本信息和图标做进去。Windows 用户看到未签名、无图标的 exe,第一反应往往是不敢运行。我没有钱做代码签名证书,就用了自签名加编译时嵌入版本信息,至少能让右键属性里看到这个软件叫什么、版本是多少。实际分发出去以后,被问"你是不是病毒"的次数明显少了。
4. 实操过程与核心环节实现
4.1 环境准备与项目初始化
整个项目需要 Go 1.22 以上版本。如果你是在 Windows 上开发,建议先把gcc环境装好,因为后续如果要 CGO 的话会需要,但我实际主流程完全用了纯 Go,默认关闭 CGO 也能编译。初始化项目就两步:
mkdir ai-gui-agent && cd ai-gui-agent go mod init ai-gui-agent然后我把核心包结构建成了这样,方便按功能维护:
├── main.go // 入口:解析参数、启动服务 ├── agent/ // Agent 主循环、任务执行 ├── vision/ // 截图、图像发送、坐标换算 ├── input/ // 模拟输入(鼠标、键盘) ├── mcp/ // MCP 客户端实现 ├── models/ // 模型接口对接(OpenAI 兼容) └── web/ // 内嵌 Web UI4.2 30 分钟跑通"看屏幕点按钮"闭环
其实核心闭环代码不算多,我简化以后,关键逻辑也就几十行。这里我直接分享启动 Agent 最核心的一段代码,读完你就知道整个流程是怎么串联起来的。
func RunAgent(task string, cfg Config) { client := models.NewOpenAICompatibleClient(cfg.APIBase, cfg.APIKey, cfg.Model) mcpClients := mcp.ConnectAll(cfg.MCPServers) history := []Message{{Role: "user", Content: task}} for step := 0; step < cfg.MaxSteps; step++ { screenshot := vision.CaptureScreen() prompt := vision.BuildPrompt(screenshot, history) resp, err := client.Chat(prompt) if err != nil { log.Printf("模型调用失败: %v", err) break } decision := parseDecision(resp) if decision.Done { fmt.Println("任务完成:", decision.Answer) break } if decision.ToolCall != nil { result := mcpClients.Call(*decision.ToolCall) history = append(history, result.ToMessage()) continue } input.Execute(decision.Action) time.Sleep(cfg.ActionDelay) history = append(history, decision.ToMessage()) } }这段代码最核心的思想就是循环里的这个判断:如果模型决定调用工具,则走 MCP;如果决定执行操作,则走模拟输入;如果认为任务已经完成,则退出。实际跑起来以后,你会发现模型经常在"调用工具"和"操作 GUI"之间切换,比如先读文件了解需求,然后打开软件执行操作,最后再读一次文件确认结果。
我第一次跑通的测试任务是:打开系统自带的记事本,输入一行文字,然后保存到桌面。模型先通过截图确认桌面图标的位置,双击打开记事本,然后判断光标在输入区,开始打字,接着用 Ctrl+S 打开保存对话框,输入文件名,点击保存。整套动作大概用了 40 秒,中间没有一次人为干预。那个时刻确实有点激动,因为这意味着"AI 操作电脑"这件事已经从演示变成了可以落地的工具。
4.3 接入一个真实 MCP Server 的完整示例
为了展示 MCP 有多好用,我做一个真实场景的演示:让 Agent 统计一个目录下所有 Go 文件的行数。这个任务如果纯靠 GUI 操作可能很笨拙,但是接入一个文件系统 MCP Server 以后,Agent 只需要调用list_directory遍历目录、read_file读取文件内容,然后自己数一下行数就行。关键是这个能力不是写死在 Agent 里的,而是完全通过 MCP 动态获得。
我本地用 npx 启动一个文件系统 MCP Server:
npx -y @modelcontextprotocol/server-filesystem ./demo然后在配置文件里加上这个 Server,重新启动 Agent,让它执行任务。你会在日志里看到 Agent 自动发现了 MCP 提供的几个工具,并且正确生成了调用参数。这就是 MCP 的价值:工具能力可以即插即用,Agent 不需要提前知道工具的实现细节。
如果你只有远程 MCP Server,或者写了一个自定义 HTTP 传输的 Server,也可以直接配置 HTTP 地址。这给团队协作带来了很大便利:后端同事把能力封装成 MCP Server,前端的 AI Agent 直接调用,两边完全解耦。
4.4 编译出单文件并分发
编译这个项目非常简单,因为所有资源都已经内嵌了,一条命令就能出结果:
CGO_ENABLED=0 go build -ldflags "-s -w" -trimpath -o ai-gui-agent.exe ./main.go加上-ldflags "-s -w"可以把二进制文件体积缩小不少,去掉调试信息。我这里 -o 后面的是 Windows 可执行文件名,如果你是在 macOS 或 Linux 上编译,按照目标平台正常命名就行。交叉编译时需要在命令行前面加上对应的GOOS和GOARCH环境变量,比如编 Linux 版本就是GOOS=linux GOARCH=amd64。
编译完成以后,把整个目录压缩成一个 zip,里面只有一个 exe 文件和一个说明文档,发给别人直接双击就能用。我实测过一台干净的 Windows 机器,没有安装任何开发环境,双击后浏览器自动打开 Web 界面,整个启动时间不到两秒。这是单文件方案给我带来的最大爽感。
5. 常见问题与排查技巧实录
5.1 GUI 自动化失灵:先查这三件事
GUI 操控是实际问题最多发的环节。遇到"Agent 点了但没反应"或者"点偏了",不要急着改代码,先按顺序排查。
第一查坐标系。把 Agent 截到的图保存下来,和实际屏幕对照,看模型给出的坐标落在哪里。如果有偏差,优先怀疑 DPI 缩放没有换算正确。Windows 的显示设置里有一个缩放比例,把这个比例拿到,回看 LogicalToPhysical 函数有没有被正确调用。
第二查权限。macOS 上如果没有屏幕录制权限,截图会是桌面壁纸或者空白。Windows 上如果运行在一个没有图形会话的环境里,比如通过远程终端注入的方式启动,截图也会失败。建议在任何 GUI 操作前做一次环境自检,把截图、鼠标移动、键盘输入三件事全部验证一遍。
第三查窗口状态。窗口如果最小化或被遮挡,模型的视觉理解再怎么强也看不到目标内容。我通常在任务开始前让 Agent 先执行一次"窗口恢复和前置"的操作,确保关键应用处于可见状态。这个过程也可以做成一个工具,让 Agent 自己去枚举窗口并激活特定窗口。
5.2 MCP 工具加载失败:排查清单
接入 MCP Server 时遇到最多的问题是"工具列表为空"和"调用超时"。如果工具列表为空,大概率是协议握手的初始化消息没有按预期完成。MCP 客户端连接后要先发送initialize请求,拿到响应后还要发送notifications/initialized通知,然后才能调用tools/list。有好几次是我忘了发 initialized 通知,导致服务端一直不返回工具列表。
调用超时则要区分 stdio 和 HTTP。stdio 类型的 Server 如果启动时缺少参数、路径不对,进程会直接退出,表现为一调用就报错。我的排查办法是先手动在命令行里启动一遍 MCP Server,看有没有报错输出。HTTP 类型的则要看网络和鉴权。如果你在代理环境下使用,还需要注意 HTTP 客户端的底层传输,某些环境下需要手动指定不走代理或配置代理地址。建议把所有 MCP Server 的连接健康检查放在 Agent 启动时统一执行,不给任务中途才暴露问题的机会。
5.3 单文件启动慢或被拦截怎么办
单文件体积小,但如果里面内嵌了较大的 Web UI 资源,每次启动都释放到临时目录,第一次启动会略慢。我的优化方案是启动时异步解压资源,同时先拉起一个最小化的 HTTP 服务,等资源就绪以后再加载完整界面。另外,临时目录释放出来的文件建议加只读属性,避免被其他进程误修改。
被安全软件拦截是单文件 Go 程序最常见的反馈。我没有代码签名证书,就只能多做几件事降低误报概率:编译参数去掉调试信息、文件名不要用稀奇古怪的名字、附上 SHA256 校验值。这不能百分之百解决问题,但能大幅减少误报。如果你要正式分发,个人建议去申请一个代码签名证书,虽然要花钱,但对用户体验的提升非常明显。
5.4 模型反复试错停不下来:任务编排控制
很多 Agent 跑着跑着会陷入"操作失败、截图、再操作、再失败"的死循环。我看了一下,原因通常是模型对任务目标的理解不够清晰,或者是每一步之后没有充分反馈。我的应对办法是给任务编排增加三套控制机制。
第一是最大步数限制,任何任务默认最多执行 50 步,防止无限循环烧 token。第二是重复操作检测,如果模型连续三次执行完全相同的 GUI 操作且没有产生新状态,就自动切换策略并提示模型换一种方式。第三是阶段性总结,每十步触发一次让模型重新审视当前进度和原始任务的差距,这个总结会重新点醒模型,避免它陷在细节里出不来。
这三个机制加进去以后,任务完成率提升非常明显。尤其是在复杂流程里,模型走着走着忘了最初的意图是常见现象,定时拉回来比什么都管用。
6. 我后续还想扩展什么
这个项目最让我兴奋的是它已经验证了"单文件 AI Agent 可以很轻、很快、很通用"。后续我打算做几件事:一是把更多内置能力做成 MCP Server,比如数据库查询、HTTP 请求、文件监听,让用户通过配置就能获得更多工具;二是支持工作流的持久化,把一次成功的操作序列记录下来,下次可以直接回放;三是做一个轻量的任务日志面板,让用户能看到 Agent 每一步的截图、动作和思考过程。
最后分享一个我在实际使用中的小技巧:不要把所有任务都交给 Agent 全程自动执行,尤其是在操作危险动作之前,先让它"停下来确认"一下。我在项目里加了一个 human-in-the-loop 模式,Agent 在遇到删除文件、提交表单、点击不可逆按钮这类动作前,会先停下来等用户确认。这个模式在自动化和安全性之间找到了一个很好的平衡,亲测能让这套工具从"玩具"变成"能真正放心用的生产工具"。如果你也想做同类项目,我强烈建议你从一开始就把这个机制考虑进去,它会在未来帮你省下很多麻烦。