资讯动态

AI编码代理实战:GUI操控+MCP集成+单文件打包

发布时间:2026/10/15 9:10:22 来源:尧图企业网站定制
我最近一直在打磨一个东西一个免费的 AI 编码代理支持操控 GUI 和 MCP最后打包成单个可执行文件。简单说你把它丢到任何一台 Windows/Linux 机器上双击就能跑它会自己读屏幕、操作界面、调用外部工具还能作为 MCP 客户端对接各种服务。这玩意儿解决的是我自己的一个老大难问题AI 写代码容易但让它真正“上手干活”很难——比如让它自己打开一个配置界面在表单里填参数再点保存然后编译项目。这些操作以前需要人手动介入或者得专门写一堆自动化脚本。现在这个代理能直接看屏幕、理解界面、点击按钮、输入文本同时通过 MCP 协议把外部数据源和工具统一接进来真正做到让 AI 从一个“只会聊天的助手”变成“能自己动手的同事”。这篇文章把项目从头到尾拆开讲包括设计思路、GUI 操控的实现细节、MCP 集成的原理和配置方式、单文件打包的踩坑过程以及实际调试时遇到的典型问题。内容面向有一定编程基础、想自己搞一个 AI 代理或者做 GUI 自动化的朋友如果你只是好奇 MCP 和 GUI 操控是什么也能在这里找到通俗的解释和可复现的代码片段。1. 项目概述与整体设计思路1.1 为什么做这个 AI 编码代理先说说背景。现在的大模型编程助手很多像 GitHub Copilot、Cursor 这些它们擅长在编辑器里补全代码、解释报错甚至能根据需求生成完整文件。但它们的边界非常清晰停留在“编辑代码”这个层面。一旦需要让 AI 去操作开发环境之外的软件比如打开某个芯片厂商的烧录工具、配置一个数据库连接客户端、在调试软件里设置断点这些助手就无能为力了。我日常的工作流里经常要处理一些“限定的 GUI 工具”比如 Altium Designer 的 PCB 设计软件、IDA 反汇编工具、各种配置管理界面。这些工具往往没有完善的命令行接口或者命令行操作太麻烦。我以前写过不少 pyautogui 脚本做自动化但那些脚本是死的界面一变就废。我需要的是一个能“看着屏幕”做决策的智能体它能看到当前窗口是什么状态然后基于任务目标决定下一步点哪里、输入什么。再加上最近 MCP 火得不行相当于给 AI 加了一堆标准化的“手和眼睛”。如果把这两个能力结合起来再做成单文件可执行我就可以把这代理扔给团队里任何一个同事用不需要装 Python 环境不需要配库双击就跑。这就是我做这个项目的初衷。1.2 技术选型GUI 操控和 MCP 怎么设计先说 GUI 操控。目前主流方案有三种基于坐标的宏点击pyautogui 那种、基于 UI 自动化框架Windows 上的 UIA、macOS 的 Accessibility API、基于视觉识别截图OCR目标检测。我一开始想只用 UIA 框架因为速度最快、定位最准但问题也明显很多开发工具的自定义控件根本暴露不出 UIA 接口比如 IDA 的图形视图、Altium 的原理图面板你能拿到屏幕位置但拿不到控件属性。所以我的方案是“视觉为主、UIA 为辅”的混合策略优先尝试 UIA 获取控件树和解构拿不到就退回 OCR 目标检测再结合屏幕坐标执行点击和输入。识别速度上我用了一个轻量级的本地 OCR 模型单张截图大概 200 毫秒加上目标检测区域裁剪实际交互延迟能控制在 1 秒左右完全可以接受。MCP 这边就清晰多了。MCP 本身是一个基于 JSON-RPC 的标准化协议客户端通过 stdin/stdout 或 SSE 与服务器通信。我的代理内置了 MCP 客户端功能可以连接任意符合 MCP 规范的工具服务器比如文件系统工具、数据库查询工具、浏览器调试工具等等。我还额外做了一个 MCP 服务端把代理自身的 GUI 操控能力也暴露成 MCP 资源这样其他 AI 工具比如 Claude Desktop 之类的客户端也可以远程调用我的代理去操作 GUI。这个双向设计一开始没想到后来发现特别有用等于说我的代理既是工具消费者又是工具提供者。1.3 单文件运行的打包思路单文件运行看着简单实际操作有不少坑。我最终用 PyInstaller 打包但 PyInstaller 把 Python 解释器、依赖库和资源文件压缩成一个可执行文件运行时需要解压到临时目录启动速度会慢一些。我的程序里还有 OCR 模型文件、GUI 模板图片这些资源如果直接塞进 PyInstaller 的 bundle运行时路径处理不对就会找不到文件。我的做法是把资源文件用 base64 编码后直接写进 Python 代码里或者打包时用--add-data加进去然后在代码里通过sys._MEIPASS来定位资源路径。实测下来Windows 上的单文件体积在 80MB 左右因为包含了 PyTorch 的 CPU 版和 OCR 模型启动时间大概 3 秒可以接受。如果换成 Go 重写纯视觉模块可以压到 20MB 以内但开发周期太长我这个版本还是以 Python 为主。2. 核心功能拆解GUI 操控模块怎么实现2.1 视觉识别与坐标映射原理GUI 操控的本质就是三件事看、想、做。看就是获取屏幕信息想就是让大模型决定下一步动作做就是执行鼠标和键盘事件。这里面最关键的是“看”这一步如何转化为模型能理解的语义。典型的流程是这样的先通过截图库我用的是mss比 pyautogui 自带的截图函数更快抓取当前屏幕然后把截图传给 OCR 引擎识别文字和位置。OCR 输出包含文本内容和边界框坐标比如“保存”这个按钮在屏幕的 (1320, 780) 到 (1410, 810) 这个矩形区域内。同时我会额外运行一个目标检测模型识别一些非文本元素比如图标、下拉箭头、复选框检测模型输出的是类别和框。这样屏幕上的所有可交互元素就变成了一个“UI 元素列表”。大模型拿到这个列表后会结合用户的自然语言指令比如“在工程设置对话框里把优化等级改为 O3然后点编译”输出一个结构化的动作指令例如{ action: click, target: compile_button, coordinate: [1320, 780] }这个 JSON 会被我的代理解析然后调用控制库执行。为了保证点击准确我不会直接用像素坐标点而是使用元素中心点并且在点击前会再做一次截图确认界面没有变化。如果界面发生了弹窗遮挡还会触发“重新识别”逻辑防止误点。2.2 核心代码示例截图、OCR、点击我摘几个关键片段给大家看实际项目里做了很多封装这里展示核心逻辑。首先是快速截图import mss import numpy as np def capture_screen(monitor_id0): with mss.mss() as sct: monitor sct.monitors[monitor_id] img sct.grab(monitor) return np.array(img)然后是 OCR 识别我这里用的是 PaddleOCR 的轻量模型from paddleocr import PaddleOCR ocr PaddleOCR(use_angle_clsTrue, langch, use_gpuFalse) def get_text_elements(img): result ocr.ocr(img, clsTrue) elements [] for line in result[0]: box, (text, confidence) line x_min int(min(p[0] for p in box)) y_min int(min(p[1] for p in box)) x_max int(max(p[0] for p in box)) y_max int(max(p[1] for p in box)) elements.append({ type: text, text: text, bbox: [x_min, y_min, x_max, y_max], center: [(x_min x_max) // 2, (y_min y_max) // 2] }) return elements执行点击用pyautogui和pydirectinput。注意pyautogui在部分游戏和特殊渲染界面上会有延迟pydirectinput直接调 Win32 API响应更快import pydirectinput def click_at(x, y, buttonleft): pydirectinput.moveTo(x, y, duration0.1) pydirectinput.click(buttonbutton)完整的动作循环就是截图 - 识别 - 大模型决策 - 执行动作 - 再截图验证。大模型在这套流程里相当于一个会“看屏幕、做操作”的机器人指挥家。2.3 GUI 操控的几个注意点第一屏幕分辨率适配一定要做。很多机器是 1920x1080但也有 2560x1440 甚至 4K 缩放 150% 的直接用绝对坐标会偏。我做了缩放因子换算从 Windows API 读取当前 DPI 缩放值把所有坐标乘上scale_factor。没做这个之前我同事的 4K 屏上代理全程点歪。第二点击之后一定要等待界面响应不要立刻截图。我用了一个自适应的等待函数先等待 300 毫秒然后检测屏幕是否有变化如果没变化再等 300 毫秒最多等 5 秒。强行固定 sleep 2 秒会拖慢流程不等待又会误判所以这个“动态等待”是 GUI 自动化性能的关键。第三处理弹窗要格外小心。Windows 上经常出现权限确认框、错误提示框这些弹窗的标题栏文字可以在 OCR 里捕获。我维护了一个“常见弹窗特征库”比如“确认”、“取消”、“重试”一旦识别到就优先处理弹窗而不是继续原来的操作序列。这个库不复杂用几行 JSON 就能维护但能避免大量因为系统弹窗打断流程而导致的失败。3. 核心功能拆解MCP 集成的设计与实现3.1 MCP 是什么编码代理为什么需要它MCPModel Context Protocol可以理解成 AI 世界的 USB-C 接口。以前每个 AI 工具对接一个新的数据源或工具都要单独开发一套插件彼此之间互不兼容。MCP 统一了客户端和服务器之间的通信格式AI 应用作为 MCP 客户端工具通过 MCP 服务器暴露自己的功能。客户端调用工具时只需发送一个 JSON-RPC 请求指定工具名和参数服务器执行完把结果返回即可。对编码代理来说MCP 直接扩展了它的能力边界。我的代理不仅能操作 GUI还能通过 MCP 连接到文件系统、数据库、版本控制、浏览器调试等工具。编码代理的核心场景是写代码和修代码但写代码往往需要读取项目文件、搜索 API 文档、执行编译命令、看测试输出。之前这些要么靠 langchain 之类的框架自己接要么靠写死的外部命令现在用 MCP 标准协议统一接入配置一个工具就多一份能力而且每个工具服务器都是独立进程崩溃了不影响主代理。3.2 我把代理接入的 MCP 工具目前我接了三类 MCP 工具基本覆盖了日常编码需求一个是文件系统工具允许代理读写项目文件、创建删除目录。这本来自己写也不难但用了 MCP 之后代理可以同时访问多个目录而不需要我在 prompt 里反复声明路径。第二个是数据库工具我接了一个 SQLite 的 MCP Server代理可以直接查询数据库表结构、执行 SELECT 语句、分析数据分布。以前这些是通过让 AI 生成 SQL 再让我手动执行现在它自己能跑。第三个是浏览器调试工具起到“让代理能看到网页实际效果”的作用。因为 GUI 操控只能操作本地应用对于 Web 前端调试代理可以启动一个浏览器 MCP 工具执行 JavaScript、抓取控制台日志、模拟点击网页元素和 GUI 操控形成互补。让我觉得有意思的是MCP 服务器的数量现在增长极快。网上看到有人在给 Altium Designer 做 MCP 接口有人给 IDA 做 MCP 插件也有人给 x32dbg 做 MCP 插件。这说明 MCP 已经成了调试工具和电子设计工具接入大模型的通用桥梁。我的代理作为 MCP 客户端可以直接复用这些第三方服务器这就意味着我不需要自己为每个工具写适配代码只需要写好配置然后连接即可。3.3 MCP 配置示例和调用流程MCP 客户端的调用流程分为几步发现服务器从配置文件读取、启动服务器子进程、握手、调用工具、关闭。我用的是官方的 Python SDK核心代码如下from mcp import ClientSession, StdioServerParameters async def connect_to_server(server_command, args): server_params StdioServerParameters(commandserver_command, argsargs) async with ClientSession(server_params) as session: await session.initialize() tools await session.list_tools() # tools 里包含所有工具名和描述 result await session.call_tool(read_file, {path: main.cpp}) return result配置文件我使用 JSON放在代理可执行文件的同目录下用户随时可以加新的工具{ mcpServers: { filesystem: { command: npx, args: [-y, modelcontextprotocol/server-filesystem, /path/to/projects] }, sqlite: { command: python, args: [-m, mcp_sqlite_server, mydb.sqlite] } } }启动时代理逐个读取这些配置拉起对应的子进程。调用时大模型会根据工具描述自动选择调用哪个工具同时把参数填充好。整个调用对用户是透明的用户在界面上看到的只是代理在“查找文件”、“执行 SQL”、“读取网页”这些步骤。还有一个细节MCP 的流式输出。有的工具返回内容特别大比如整个文件内容或者长日志直接塞进上下文会让模型变傻。我的做法是开启采样功能对大文件按行截断只返回关键部分比如前 200 行和最后 50 行。另外工具返回结果会缓存在本地临时文件里模型如果需要完整内容可以再通过“读取文件”的 MCP 工具按偏移量读取。这套逻辑和 CherryStudio 这类工具里的“流式输出到文件”理念一致目的都是不撑爆上下文窗口。4. 整体架构与单文件运行的技术细节4.1 模块划分与数据流整个代理分成五个核心模块交互输入层、任务调度层、工具执行层、模型推理层、资源管理层。交互输入层负责接收用户的自然语言指令可以通过命令行、HTTP 接口或者自带的简易聊天窗口任务调度层做的事情是把复杂任务分解成多个子任务比如“先把项目编译通过然后打开配置界面设置参数”调度层会规划成两个子任务并按顺序执行工具执行层包括 GUI 操控模块和 MCP 客户端真正做到“动手”模型推理层则负责所有的大模型调用我使用了 OpenAI 兼容接口这样用户可以自由选择本地模型、云端模型只要模型支持工具调用function calling即可。数据流是典型的“感知-决策-执行-反馈”循环截图或者 MCP 返回的数据进入感知缓存模型基于缓存生成决策决策变成具体动作执行结果再反馈回感知层。在这个循环中我特别设计了一个“短期记忆”机制保存最近 20 个步骤的关键信息比如点击了哪里、看到了什么文本这样大模型能知道自己已经做了什么不会在一个循环里反复尝试相同的错误操作。4.2 编译打包单文件的方法打包单文件首选工具是 PyInstaller。基本命令如下pyinstaller --onefile \ --add-data ocr_model:ocr_model \ --hidden-import pydirectinput \ --hidden-import mcp.client.streams \ main.py--onefile生成单个可执行文件--add-data把模型目录打包进去--hidden-import确保一些动态导入的模块被包含。为了减小体积我用了--exclude-module排除掉不用的大库比如把 torch 的 GPU 版相关模块排除只保留 CPU 推理需要的部分。打包后要注意路径问题。在 PyInstaller 单文件模式下资源文件会被解压到临时目录路径由sys._MEIPASS指向。代码里必须通过这个变量来获取资源路径import sys, os def resource_path(relative): if hasattr(sys, _MEIPASS): return os.path.join(sys._MEIPASS, relative) return os.path.join(os.path.dirname(__file__), relative)然后把模型路径传给 OCR 初始化函数。4.3 运行时资源解压与临时目录处理单文件运行时PyInstaller 把所有内容解压到%TEMP%\_MEIxxxxxx目录程序结束后自动清除。这里面有两个坑一是杀毒软件可能会拦截这个动作导致启动失败。我遇到过 Windows Defender 提示“可疑行为”解决办法是在启动程序时延迟几秒并尝试用高权限运行或者干脆给程序加数字签名免费的自签名证书也行但会被拦截。二是临时目录空间不足如果模型文件多在部分内存盘环境中解压会失败。检测到解压失败时程序会给出明确提示而不是闪退。另外单文件模式下如果程序需要写日志或者缓存不能直接写到可执行文件所在目录可能没有写权限也不能每次写入临时目录因为临时目录会被随机分配。我在主程序启动时先检测执行文件所在目录是否可写不可写则改用用户目录下的.ai_code_agent文件夹日志和配置文件都放到那里。这样既保证了单文件的可移植性又避免了权限问题。5. 实操从零到一构建一个可用的编码代理5.1 环境准备和依赖安装想复现这个项目你需要安装 Python 3.10 以上版本并准备好以下依赖pip install mss numpy pillow paddleocr pyautogui pydirectinput mcp openai如果你的机器没有 Nvidia 显卡PaddleOCR 会使用 CPU 推理速度略慢但可用。注意 PaddleOCR 的版本和 PaddlePaddle 有兼容关系建议直接安装最新的 CPU 版pip install paddlepaddle2.6.0安装大模型的 SDK我用了 OpenAI 兼容的openai包这样无论连线上服务还是本地跑 Ollama都可以用同一个接口。本地模型我推荐使用支持 function calling 的 Qwen 2.5 系列部署在 Ollama 中只需要设置环境变量export OPENAI_BASE_URLhttp://localhost:11434/v1 export OPENAI_API_KEYollama5.2 编写核心代码让代理能“听懂”指令并操作 GUI我们需要一个把大模型输出转换成动作的执行器。大模型的 prompt 必须包含屏幕 UI 元素列表和可用的动作类型。下面是我的 prompt 模板核心部分你是一个GUI操作专家。当前屏幕上有以下可交互元素 - 元素ID: 101, 类型: 文本框, 文本: 路径, 中心点: (450, 320) - 元素ID: 102, 类型: 按钮, 文本: 确定, 中心点: (560, 420) 用户的目标是{task} 请输出你的下一步动作格式必须是JSON {action: click, element_id: 102} {action: input, element_id: 101, text: C:\\project} {action: done, message: 已完成}那么主循环可以这样写import json, time import openai client openai.OpenAI(api_keyos.getenv(OPENAI_API_KEY), base_urlos.getenv(OPENAI_BASE_URL)) def run_agent(task): for step in range(20): img capture_screen() elements get_text_elements(img) prompt build_prompt(task, elements) response client.chat.completions.create( modelqwen2.5:7b, messages[{role: user, content: prompt}], temperature0.1 ) action json.loads(response.choices[0].message.content) if action[action] done: return action[message] if action[action] click: elem find_element(elements, action[element_id]) click_at(elem[center][0], elem[center][1]) elif action[action] input: elem find_element(elements, action[element_id]) click_at(elem[center][0], elem[center][1]) pydirectinput.write(action[text], interval0.02) time.sleep(0.3)实际上真正的项目里还加了动作合法性校验、重复动作检测和 undo 机制但核心框架就是这么简单。有意思的是只要模型能力足够10 行代码就能让它自主操作界面。5.3 测试场景让代理帮你操作一个 GUI 工具我用一个具体的例子来演示假设我们要让代理打开“配置工具”窗口然后修改服务器地址然后点击“连接测试”。第一次运行时代理先截屏识别到桌面图标列表然后它应该找到“配置工具”图标。如果图标没有文字标签OCR 会失败这时我引入了目标检测模型识别出“齿轮”图标并输出“配置工具”这个语义标签。代理双击图标窗口打开再截屏识别到窗口里的标签和输入框。之后代理在“服务器地址”输入框里填入192.168.1.100然后点击“连接测试”按钮。如果连接成功界面上可能会出现提示弹窗OCR 识别到“连接成功”文本任务结束。这个过程中让我印象最深的是第一次测试的时候代理连续点错了三个地方因为窗口初始化有动画控件位置会慢慢移动。后来我加入了一个“等待界面稳定”的步骤两次截图如果差异小于某个阈值才认为界面稳定之后才开始识别元素。这个改进直接让成功率从 30% 提升到了 90% 以上。GUI 自动化的核心是“时机”模型决策固然重要但什么时候执行动作同样重要。6. 踩坑与排查实际使用中的问题6.1 常见问题速查表我整理了这段时间最常遇到的几个问题以及对应解法方便大家参考。问题现象解决方案启动后闪退双击 exe 后无任何提示直接退出在命令行模式下运行查看报错最常见是缺少 VC 运行库安装 Visual C RedistributableOCR 识别不到任何文字截图中明明有文字但元素列表为空检查 PaddleOCR 模型路径是否加载成功确认屏幕分辨率不是 200% 缩放缩放后需要做坐标换算但 OCR 的坐标仍是原分辨率MCP 服务器连接失败报错connection refused或spawn ENOENT检查命令路径是否正确npx是否在 PATH 中如果用 Windows建议用绝对路径比如C:\\Program Files\\nodejs\\npx.cmd无法点击某些区域的元素点击后无响应如下拉框、自定义控件试试改用pyautogui.click的_interval参数或者用pydirectinput的双击如果仍不行则手动按 Tab 键让焦点移动到目标后再敲回车模型调用超时大模型响应太慢导致任务循环卡住设置合理的超时时长如 30 秒并在超时后让模型提供“跳过”动作而不是直接报错程序体积过大80MB 的单文件分发麻烦换用 ONNX Runtime 替代 PyTorch 做 OCR 推理可以把体积压到 35MB 左右或者用 UPX 压缩可执行文件6.2 独家避坑技巧有几个坑是文档里不会写的我花了很多时间才摸清楚。第一个是 Windows 上pydirectinput.moveTo在部分机器上会失效尤其是当显示设置里启用了“指针轨迹”时。你们的代码最好在移动鼠标前先重置一下指针设置或者直接用ctypes调用SetCursorPos来绕过。实测下来后者更稳定但要注意系统 DPI 感知必须调用SetProcessDPIAware。第二个坑是 MCP 子进程的输出缓冲。如果 MCP 服务器打印大量日志到 stdout子进程的管道可能会因为缓冲区满而阻塞导致客户端等待结果超时。解决办法是在启动子进程时把 stdout 重定向到日志文件只保留 stderr但 MCP 协议本身用的是 stdout 传输不能动。正确做法是在服务器代码里禁用调试日志或通过环境变量调整日志级别。第三个坑是单文件打包后杀毒软件总是误报。因为 PyInstaller 打包的 exe 需要从临时目录加载代码这个行为和某些恶意软件很像。我试过两个办法一是给 exe 加上数字签名二是换用 Nuitka 打包。Nuitka 会把 Python 代码编译成 C生成的二进制更接近原生程序误报率低很多而且启动速度更快。如果你的项目对体积要求不高我很推荐用 Nuitka 替代 PyInstaller。命令大概是nuitka --onefile --include-data-dirocr_modelocr_model main.py第四个技巧是关于模型选择的。如果你用本地 7B 模型做 GUI 操作它的指令遵循能力可能不够稳定经常输出不符合 JSON 格式的内容。我的解决办法是两层校验第一层是格式校验用json.loads解析解析失败就把原始输出返回给大模型让它修正第二层是语义校验如果模型输出了列表里不存在的 element_id就拒绝执行并告知模型正确的候选列表。这个“纠错循环”最多循环三次超过三次就强制停止防止死循环。7. 实测用代理跑一个真实的编译任务为了验证这套方案的可靠性我设计了一个综合测试让代理完成一个嵌入式项目的编译和参数配置。测试环境是一台 Windows 10 机器分辨率为 1920x1080缩放 100%。代理需要完成两步操作第一步打开 STM32CubeMX 软件一个图形化配置工具在芯片型号选择界面输入“STM32F407”然后点击“OK”第二步修改项目属性里的优化等级为 O3保存然后执行编译。第一次测试结果惨不忍睹代理花了 12 分钟其中大部分时间是在识别界面元素上因为 CubeMX 的窗口标题栏和主界面有大量重复的文本标签OCR 返回了太多无关元素。我后来加了“过滤高频非交互文本”的规则比如把纯信息展示的文本过滤掉只保留按钮、输入框、选项列表等可交互控件的文本。同时修改了 prompt 中的格式要求让模型只关注包含特定关键词的元素比如“优化等级”旁边的下拉框。这样处理后第二次测试用时 4 分钟成功率大约 85%剩下的失败主要原因是 IAR 编译器偶尔弹出“路径过长”警告而代理没有把这个警告当作需要处理的弹窗。这个结果让我意识到纯视觉 GUI 自动化还有很多工程挑战但已经可以用来处理一些以前必须人肉重复的机械操作了。再迭代几轮我的目标是让代理的决策更鲁棒尤其是在弹窗处理和错误恢复方面。我现在正在做的调整包括把 GUI 操作模块单独抽成进程这样即使某个动作卡死也不会使整个代理崩溃增加操作回放功能让代理的每一步操作都能追溯和撤销还有把 MCP 工具的调用日志和 GUI 操作日志统一整合到一块方便复盘。如果你自己也想做类似的工具我的建议是不要一上来就仿照我整个架构可以先从“截图 - 大模型生成点击坐标 - 执行”的最小闭环开始跑通之后再逐步加入 OCR、MCP、单文件打包这些增强功能。毕竟能真正在真实环境里稳定工作的 AI 编码代理都是这样一点点磨出来的。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑