
1. 为什么我要自己造一个编码代理市面上能用的 AI 编码工具其实已经不少了从 IDE 插件到命令行助手选择很多。但我用了大半年之后始终有几个地方觉得别扭最后干脆自己动手做了一个。这个项目最核心的三个特点就是免费、支持操控 GUI、支持 MCP 协议而且整个程序是单文件运行的。先说免费这件事。很多编码代理工具按 token 计费或者按月订阅个人开发者偶尔用用还行但如果想把它当成日常主力工具成本会迅速累积。我这个项目本身不绑定任何付费服务你只需要自己准备一个模型接口就行程序本身不产生额外费用。再说 GUI 操控。这是我觉得现有工具最欠缺的一块。大部分编码代理只能在终端里跑命令、读写文件但真实的开发工作里有大量操作是发生在图形界面里的——比如配置某个 IDE 的插件、在数据库客户端里执行查询、在浏览器里调试前端页面。这些操作纯靠命令行很难完成或者需要写一堆自动化脚本。我这个代理可以直接操控 GUI模拟鼠标点击、键盘输入、窗口切换把图形界面操作也纳入到自动化流程里。MCP 协议的支持是第三个关键点。MCP 是这两年逐渐流行起来的一个协议标准简单说就是让 AI 模型能够以统一的方式调用外部工具和数据源。支持 MCP 意味着我的代理可以接入各种已有的 MCP 服务比如文件系统访问、数据库查询、API 调用等等不用每接一个新工具就重新写一遍适配代码。单文件运行这个设计是我在踩了无数次环境配置的坑之后做出的决定。你肯定遇到过这种情况clone 一个项目下来装依赖装了半天结果某个包的版本冲突或者 Python 版本不对折腾两小时还没跑起来。单文件意味着你下载下来就能用不需要 pip install 一堆东西不需要配虚拟环境不需要担心依赖冲突。对于我这种经常换机器、经常在别人电脑上临时干活的人来说这个特性太重要了。这个项目适合谁呢如果你是个独立开发者想有个趁手的自动化编码助手如果你经常需要做重复性的 GUI 操作想把它自动化掉如果你对 MCP 协议感兴趣想找个实际项目来理解和实践——那这个项目应该对你有用。下面我会从设计思路、核心实现、GUI 操控的具体做法、MCP 集成、以及实际使用中的各种坑一步步拆开来讲。2. 单文件架构的设计取舍与实现细节2.1 为什么单文件比模块化更合适这个场景一般做项目我们都会强调模块化、分层架构、职责分离。但这些原则的前提是项目会长期维护、多人协作、功能不断扩展。对于一个个人用的编码代理工具情况不太一样。我一开始也是按标准项目结构来搭的agents/目录放代理逻辑tools/放工具实现mcp/放协议适配gui/放界面操控utils/放辅助函数。结果用了两周就发现几个问题。第一是部署麻烦每次换机器都要把整个目录拷过去还得确保目录结构不变。第二是调试链路长改一个函数要跳好几个文件才能理清调用关系。第三是分享困难想发给朋友试用得打包整个项目对方还得配环境。后来我决定把所有代码合并到一个文件里。这个决定听起来有点反直觉但实际用下来收益很明显。整个程序就是一个.py文件大概几千行下载下来python agent.py就能跑。想改哪里直接搜索函数名不用在目录树里找。想分享直接发文件对方不需要任何额外配置。当然单文件也有代价。最大的问题是代码组织会变得依赖注释和分区而不是目录结构。我的做法是在文件里用明显的注释块划分区域比如# MCP 协议适配 这样的分隔线让视觉上仍然有清晰的模块边界。另外单文件不适合多人协作但个人项目本来就不涉及这个问题。2.2 依赖最小化的具体策略单文件要真正能做到下载即用光合并代码还不够依赖也得控制住。我的原则是能用标准库解决的绝不引入第三方包。Python 标准库其实比很多人想象的强大。subprocess可以调外部命令json处理数据交换urllib做 HTTP 请求threading做并发tkinter甚至能搭简单界面。我统计了一下最终整个项目只依赖了三个第三方包一个用于 GUI 操控的库、一个用于和模型 API 通信的 HTTP 客户端、一个用于处理 MCP 协议消息的解析库。其他全部用标准库实现。这里有个细节值得说GUI 操控库的选择。市面上做 GUI 自动化的方案有好几种有基于图像识别的有基于系统 API 的有基于无障碍接口的。我最终选了一个跨平台支持比较好、API 相对简洁的库。它的原理是通过系统底层的输入事件模拟来实现点击和输入不依赖图像匹配所以不受分辨率和主题变化的影响。缺点是不同操作系统的权限配置不一样这个后面会专门讲。依赖最小化还有一个好处是启动速度快。我实测过从执行命令到代理准备好接收输入冷启动大概 1.5 秒左右。如果用了一堆重型框架光 import 就要好几秒。对于需要频繁调用的工具来说这个差异很明显。2.3 配置管理单文件怎么存设置单文件运行有个现实问题配置放哪总不能每次都改代码里的常量。我的方案是约定优于配置同时支持外部覆盖。程序启动时会按顺序查找配置先看当前目录有没有agent.config.json没有就看用户主目录下的~/.ai-agent/config.json都没有就用内置的默认值。配置项包括模型接口地址、API 密钥、默认工作目录、GUI 操控的灵敏度参数等等。这样设计的好处是临时用可以直接跑长期用可以把配置放到主目录换项目也不用改。API 密钥这种敏感信息我建议不要写在配置文件里明文存储。我的做法是优先读环境变量环境变量没有才读配置文件。这样在共享机器或者容器环境里可以通过环境变量注入密钥不落盘。提示如果你打算把这个工具放到服务器上跑记得把 GUI 操控相关的功能关掉。服务器通常没有图形界面相关调用会直接报错。配置文件里有个enable_gui开关设成 false 就行。3. GUI 操控是怎么实现的3.1 从只能跑命令到能点鼠标的跨越传统的编码代理能力边界基本就是文件系统和命令行。你让它改代码、跑测试、装依赖都没问题。但一旦涉及图形界面它就抓瞎了。比如你想让它帮你配置某个软件的设置那个设置只存在于 GUI 里没有对应的命令行接口传统代理就无能为力。GUI 操控的核心思路是把看屏幕和操作屏幕这两件事拆开。看屏幕就是截屏加图像分析或者通过系统接口读取窗口和控件信息。操作屏幕就是模拟鼠标移动点击、键盘按键输入。把这两件事组合起来代理就能像人一样操作图形界面了。我的实现里GUI 操控被封装成一组工具函数代理可以像调用普通函数一样调用它们。比如gui_click(x, y)在指定坐标点击gui_type(text)输入文字gui_hotkey(ctrl, s)按快捷键gui_screenshot()截取当前屏幕。代理在规划任务时如果判断需要 GUI 操作就会调用这些函数。这里有个关键设计坐标系统一用相对坐标还是绝对坐标。绝对坐标的问题是换分辨率就失效相对坐标的问题是计算复杂。我最终用的是绝对坐标但在配置里记录了一个基准分辨率如果实际分辨率不同会自动按比例缩放。这样既简单又能在常见分辨率变化下工作。3.2 窗口定位怎么找到要操作的那个窗口光有坐标点击还不够因为屏幕上的窗口位置会变。今天你的编辑器在左边明天可能拖到右边了。所以代理需要能定位窗口。我的做法是通过系统接口枚举当前所有窗口拿到每个窗口的标题、位置、大小。代理可以按标题关键词查找窗口比如找标题里包含 Visual Studio Code 的窗口拿到它的位置后再计算窗口内某个控件的相对坐标转换成屏幕绝对坐标去点击。这个流程听起来简单实际有不少坑。不同操作系统的窗口接口差异很大Windows 用 Win32 APImacOS 用 Accessibility APILinux 用 X11 或 Wayland 的接口。我在代码里做了抽象层上层调用统一的find_window(title_keyword)底层根据操作系统走不同实现。目前 Windows 和 macOS 支持比较完善Linux 在 X11 下能用Wayland 下受限比较多。还有一个坑是窗口层级。有时候你要找的窗口被其他窗口挡住了直接点击会点到上面的窗口。我的处理是先调用系统的窗口激活接口把目标窗口提到最前等一小段时间让动画结束再执行点击。这个等待时间很关键太短了窗口还没到位太长了整体效率低。我实测下来 300 毫秒是个比较稳的值。3.3 输入模拟的可靠性问题模拟键盘输入看起来简单实际做起来问题不少。最典型的是输入法干扰。如果你的系统开着中文输入法代理模拟按键输入英文时可能会被输入法拦截变成拼音候选。我的解决方案是在输入前先通过系统接口切换到英文输入法输入完再切回来。这个操作在不同系统上实现方式不同但思路是一样的。另一个问题是输入速度。如果模拟输入太快有些应用会丢字符。我一开始设的间隔是 10 毫秒结果在某个 Electron 应用里经常丢字。后来调到 50 毫秒就稳定了。这个值可以在配置里调不同应用可能需要不同的设置。鼠标点击也有类似问题。单击通常没问题但双击如果两次点击间隔太长会被识别成两次单击。系统对双击的时间阈值一般是 500 毫秒我设的是 200 毫秒确保能被识别为双击。注意GUI 操控在有些系统上需要额外授权。macOS 需要在辅助功能里给终端或 Python 解释器授权否则模拟的点击和按键会被系统忽略。Windows 一般不需要额外授权但如果程序以管理员权限运行而目标窗口是普通权限可能会因为权限隔离导致操作失败。3.4 截图与视觉反馈代理要操作 GUI光能点还不够还得能看到操作的结果。截图功能就是代理的眼睛。我的实现里gui_screenshot()会截取当前屏幕返回图像数据。代理可以把这张图发给多模态模型分析判断当前界面状态决定下一步操作。这里有个性能考量。全屏截图在高分辨率下数据量很大传给模型既慢又贵。我的优化是支持区域截图代理可以只截取关心的那块区域。比如它知道要操作的按钮大概在屏幕右下角就只截右下角那一块数据量能减少百分之八九十。截图频率也需要控制。如果每一步操作都截图分析整个流程会非常慢。我的策略是只在关键节点截图操作前截一张确认初始状态操作后截一张确认结果中间过程不截。这样既保证了准确性又控制了开销。4. MCP 协议集成让代理接入外部能力4.1 MCP 到底解决了什么问题MCP 这个词这两年出现频率很高但很多人对它的理解还停留在又一个协议的层面。我用一句话解释MCP 是让 AI 模型和外部工具之间用统一方式对话的标准。在没有 MCP 之前每接一个新工具你都得写一套适配代码。接文件系统写一套接数据库写一套接某个 API 又写一套。这些适配代码逻辑类似但实现各异维护起来很累。MCP 把这些统一了工具方按照 MCP 规范暴露自己的能力模型方按照 MCP 规范调用中间不需要为每个工具单独写适配。我的代理支持 MCP 之后能力扩展变得非常简单。想让它能查数据库找一个现成的数据库 MCP 服务接上就行。想让它能操作浏览器找一个浏览器 MCP 服务接上。不用改代理本身的代码只需要在配置里加上 MCP 服务的地址。4.2 MCP 服务的接入流程接入一个 MCP 服务大致分几步。首先要知道这个服务是怎么通信的MCP 支持几种传输方式常见的是标准输入输出和 HTTP。标准输入输出的服务通常是本地启动一个进程通过管道通信。HTTP 的服务就是一个网络地址。我的代理在启动时会读取配置里的 MCP 服务列表逐个建立连接。连接建立后会调用服务的能力发现接口拿到这个服务支持哪些工具、每个工具需要什么参数。这些信息会被注册到代理的工具列表里代理在规划任务时就能看到这些工具并调用。调用的时候代理把工具名和参数打包成 MCP 格式的消息发过去服务执行完把结果返回。整个过程对代理来说是透明的它不需要知道底层是本地进程还是远程 HTTP。这里有个实际经验MCP 服务的启动顺序很重要。如果代理启动时某个 MCP 服务还没准备好连接会失败。我的处理是加了重试机制启动时连不上就等几秒再试最多试三次。另外配置文件里可以标记某个服务是可选的连不上就跳过不影响代理主体功能。4.3 工具注册与调用的内部机制代理内部维护着一个工具注册表。每个工具有一条记录包含工具名、描述、参数 schema、实际执行函数。MCP 服务提供的工具在注册时会包装一层把 MCP 的调用协议转换成内部统一的调用接口。这样做的好处是代理的核心逻辑不需要区分这是内置工具还是这是 MCP 工具。它看到的都是统一的工具接口调用方式完全一样。这大大简化了代理的决策逻辑。工具描述的质量直接影响代理的使用效果。如果描述写得太模糊代理不知道该什么时候用这个工具。我在接入 MCP 服务时会检查工具描述是否清晰不清晰的会手动补充。比如一个数据库查询工具描述里应该说明它支持什么数据库、参数格式是什么、返回什么结构。这些信息代理需要知道才能正确调用。参数 schema 也很关键。MCP 用 JSON Schema 描述参数代理会根据 schema 生成调用参数。如果 schema 写得不严谨代理可能生成错误的参数格式导致调用失败。我遇到过某个 MCP 服务的 schema 把数字类型写成了字符串结果代理传数字过去被拒绝。这种问题只能在使用中发现并反馈给服务方。4.4 流式输出与结果处理有些 MCP 工具的执行时间比较长比如跑一个大型查询或者处理一批文件。如果等全部完成才返回结果代理会卡住很久。MCP 支持流式输出工具可以分多次返回中间结果。我的代理对流式输出的处理是收到中间结果就先展示给用户同时继续等待后续结果。这样用户能看到进度不会觉得程序卡死了。对于编码代理来说这个特性在跑测试或者构建项目时特别有用能实时看到输出。结果处理还有一个细节是错误处理。MCP 工具执行失败时会返回错误信息。代理需要能识别这是错误并决定是重试、换工具、还是报告给用户。我的做法是把错误信息也作为一种结果传给模型让模型判断怎么处理。模型看到错误信息后通常会尝试其他方案或者向用户说明情况。5. 代理的决策循环与任务规划5.1 从用户输入到任务分解代理接收到用户的一句话需求比如帮我把这个项目的测试跑一遍失败的修一下它需要把这句话拆解成可执行的步骤。这个拆解过程依赖模型的理解能力。我的实现里代理会把当前环境信息工作目录、项目类型、可用工具列表和用户需求一起发给模型让模型输出一个执行计划。计划是一系列步骤每一步指定用哪个工具、传什么参数。代理按顺序执行这些步骤每步执行完把结果反馈给模型模型根据结果决定下一步。这个循环是代理的核心。它不是一次性规划好所有步骤然后机械执行而是走一步看一步。因为实际执行中经常出现意外比如某个命令报错了某个文件不存在了这些都需要模型根据实际情况调整计划。循环的终止条件是模型判断任务完成或者达到最大步数限制。最大步数限制很重要防止模型陷入死循环。我设的默认值是 50 步一般任务够用复杂任务可以在配置里调大。5.2 工具选择时的优先级逻辑代理面对一个任务时往往有多种工具可以完成。比如要读取一个文件可以用内置的文件读取工具也可以用 MCP 提供的文件系统工具。这时候需要有优先级逻辑。我的优先级原则是内置工具优先于 MCP 工具专用工具优先于通用工具。内置工具经过充分测试稳定性和性能都有保障。MCP 工具虽然扩展性强但质量参差不齐。专用工具比如读取 Python 文件并解析 AST比通用的读取文件更适合特定场景。这个优先级不是硬编码的而是通过工具描述里的优先级字段来控制的。代理在规划时会参考这个字段。用户也可以在配置里覆盖优先级比如某个 MCP 工具特别好用可以把它提到内置工具前面。5.3 上下文管理与长任务处理代理跑长任务时上下文会越来越长。每一步的工具调用和结果都累积在对话历史里很快就会超出模型的上下文窗口。这是个必须解决的问题。我的方案是分层上下文管理。最近的几步完整保留包括工具调用的详细参数和结果。稍早的步骤只保留摘要比如执行了测试3 个失败。更早的步骤只保留结论比如测试阶段完成有失败需要修复。这样既保留了关键信息又控制了上下文长度。摘要的生成也是让模型做的。当上下文超过阈值时代理会把较早的对话历史发给模型让它生成摘要。摘要会替换掉原始历史释放上下文空间。这个过程对用户是透明的用户看到的是代理持续工作不会感知到上下文被压缩了。提示上下文管理策略对代理的长期表现影响很大。如果摘要太激进代理会丢失重要细节导致重复劳动或者做出错误决策。如果太保守上下文很快爆掉。我的经验是保留最近 10 步的完整信息10 到 30 步用摘要30 步以上只留结论。这个比例可以根据任务复杂度调整。6. 实际使用中的坑与应对6.1 GUI 操控在不同系统上的权限问题前面提过权限问题这里展开说。macOS 的权限控制最严格。第一次运行 GUI 操控功能时系统会弹窗要求授权。如果用户点了拒绝后续所有模拟操作都会被静默忽略程序不会报错但什么都不会发生。这个坑我踩过排查了半天才发现是权限没给。解决办法是引导用户去系统设置 - 隐私与安全性 - 辅助功能里手动添加授权。程序启动时可以检测是否有权限没有就提示用户去设置。检测的方法是尝试执行一个无害的模拟操作看是否成功。Windows 的权限问题主要出现在权限隔离上。如果代理以普通权限运行要操作一个以管理员权限运行的窗口操作会失败。反过来也一样。解决办法是让代理和目标窗口保持相同权限级别。如果目标程序需要管理员权限代理也得用管理员权限启动。Linux 下 X11 一般不需要特殊授权但 Wayland 对模拟输入的限制比较多很多操作无法实现。如果必须在 Wayland 下用可以考虑切换到 X11 会话或者用支持 Wayland 的输入模拟方案。6.2 MCP 服务连接失败的排查思路MCP 服务连不上是常见问题原因可能有很多。我的排查顺序是这样的先看服务本身是否在运行。如果是本地进程方式检查进程是否启动。如果是 HTTP 方式用 curl 或浏览器访问一下地址看是否有响应。再看配置是否正确。地址、端口、认证信息任何一项错了都连不上。我遇到过配置文件里地址写成了localhost但服务只监听127.0.0.1的情况虽然通常这两个等价但某些环境下会有差异。然后看协议版本是否匹配。MCP 协议还在演进不同版本之间可能有兼容性问题。如果服务用的协议版本和代理支持的不一致连接会失败。这种情况需要升级其中一方。最后看日志。MCP 服务通常会输出日志代理这边也有连接日志。对照两边的日志通常能定位到问题所在。我建议在配置里打开详细日志开关排查问题时很有用。6.3 模型接口不稳定时的降级策略代理依赖模型接口如果接口不稳定整个代理就没法工作。我遇到过接口偶尔超时、偶尔返回格式错误的情况。如果每次失败都直接报错退出体验很差。我的降级策略是接口调用失败时自动重试重试间隔递增。连续失败超过阈值后切换到备用接口如果配置了的话。如果所有接口都不可用代理会暂停当前任务保存状态等接口恢复后可以继续。状态保存是个关键设计。代理会把当前的任务计划、已执行的步骤、上下文摘要保存到本地文件。接口恢复后从保存点继续不用从头再来。这个功能在跑长任务时特别有用避免了因为网络波动导致前功尽弃。6.4 单文件带来的调试挑战与应对单文件虽然部署方便但调试确实不如多文件项目方便。没有模块化的 import 关系函数之间的调用全靠搜索。代码量大了之后找一个函数的定义要翻很久。我的应对方法是在文件开头维护一个目录注释列出所有主要函数和它们所在的行号范围。改代码时同步更新这个目录。虽然手动维护有点麻烦但查找效率提升明显。另一个方法是充分利用编辑器的跳转功能。现代编辑器都支持跳转到定义、查找引用即使单文件也能快速导航。我用的是 VS Code配合 Python 插件跳转体验和多文件项目差不多。还有一个技巧是把调试代码和正式代码分开。我在文件末尾放了一个if __name__ __main__块里面是各种调试入口。想测试某个功能直接在这个块里调用对应函数不用走完整的代理流程。这样调试单个功能时很快。7. 这套方案还能怎么扩展7.1 接入更多 MCP 服务扩展能力边界目前我接入了文件系统、数据库、浏览器三个 MCP 服务基本覆盖了日常开发的大部分场景。但 MCP 生态在快速发展新的服务不断出现。比如有人做了设计工具的 MCP 服务可以让代理直接操作设计稿有人做了项目管理工具的 MCP 服务可以让代理更新任务状态。接入新服务的过程很简单在配置里加一条记录重启代理就行。我建议按需接入不要一次接太多。每个服务都会占用上下文空间工具太多反而会让模型选择困难。我一般同时启用不超过五个 MCP 服务。7.2 自定义工具的编写方法内置工具不够用时可以自己写。工具的本质就是一个函数接收参数返回结果。我的代理在启动时会扫描指定目录下的 Python 文件把里面符合规范的函数注册成工具。写自定义工具需要注意几点。函数要有清晰的文档字符串代理会根据这个生成工具描述。参数要有类型标注代理会根据这个生成参数 schema。返回值要是可序列化的通常是字符串或字典。异常要妥善处理抛出的异常会被代理捕获并作为错误结果处理。我写过一个自定义工具用来查询公司内部的 API 文档。实现很简单就是发 HTTP 请求然后解析返回。但有了这个工具代理在写代码时就能自动查阅内部 API 的用法大大提高了准确性。7.3 多代理协作的可能性单个代理的能力有上限复杂任务可以考虑多代理协作。比如一个代理负责规划一个负责写代码一个负责测试。它们之间通过消息传递协调。这个方向我还在探索。初步的想法是让主代理把子任务分发给子代理子代理完成后把结果返回。子代理可以是同一个程序的不同实例配置不同的系统提示词来扮演不同角色。这样架构上不需要大改只是启动多个进程的事。多代理的挑战在于协调开销。代理之间通信需要时间如果任务拆分得太细协调开销可能超过收益。我的经验是只在任务确实复杂、单个代理搞不定时才用多代理简单任务单代理更高效。7.4 性能优化的几个方向目前代理的性能瓶颈主要在模型调用上。每次决策都要调模型网络延迟加上模型推理时间一步操作可能要好几秒。优化方向有几个。一是减少模型调用次数。有些决策其实不需要模型参与比如连续执行几个已知的命令可以合并成一次模型调用。我在代码里加了一些启发式规则能合并的步骤自动合并。二是缓存模型响应。相同的输入往往得到相同的输出可以把响应缓存起来。下次遇到相同情况直接读缓存省去模型调用。这个优化在重复性任务上效果明显。三是用更小的模型做简单决策。不是所有决策都需要大模型简单的工具选择用小模型就够了。我在配置里支持为不同类型的决策指定不同的模型简单决策走小模型复杂规划走大模型。这样整体速度和成本都能优化。GUI 操控这块的性能主要受截图和图像分析影响。我的优化是尽量用区域截图代替全屏截图用控件信息代替图像分析。如果能通过系统接口直接拿到控件的位置和状态就不需要截图分析速度快很多。只有在拿不到控件信息时才退回到截图方案。这套东西我陆陆续续写了几个月中间推翻重来过两次。最大的体会是做工具不要追求功能大而全要把一两个核心场景做到极致。我这个代理的核心场景就是自动化处理那些需要 GUI 操作的重复任务围绕这个场景把 GUI 操控和 MCP 集成做扎实比堆一堆用不上的功能有价值得多。