# GUI Agent MCP Server — Task 任务文档 > **文档类型**: Task(任务文档)— 描述"具体执行",拆解后的最小行动单元 > > **基于**: `specs/gui-agent/gui-agent-plan.md` Plan 计划文档 > > **模块路径**: `crates/agent-gui-server/` --- ## Context 构建 GUI Agent MCP Server — 一个桌面自动化服务,文本 Agent(claude-code、qimingcode)通过 MCP 协议调用它,执行截图识别 + 键鼠模拟。项目基于 pi-mono Agent 循环引擎,采用 Streamable HTTP 主模式 + stdio 备选,Mutex 互斥锁保证同时只有一个 GUI 任务执行。 --- ## 约定 - **ID 格式**: `T{phase}.{seq}`(如 T1.1) - **`[P]`** = 可与同 Wave 内其他任务并行 - **depends** = 必须等待前置任务完成 - 所有文件路径相对于 `crates/agent-gui-server/` - 参考项目约定: `crates/qiming-mcp-stdio-proxy/`(ESM、Node≥22、tsc+esbuild、Vitest) --- ## Phase 1: 项目脚手架 + 原子操作 > **目标**: MCP Server 启动(Streamable HTTP),13 个原子操作可调用 ### Wave 1 — 项目初始化 #### T1.1 创建项目脚手架 **创建文件**: - `package.json` — `"type": "module"`, Node≥22, `"bin": { "agent-gui-server": "dist/index.js" }`, `"main": "./dist/lib.js"` - `tsconfig.json` — target ES2022, module/moduleResolution Node16, strict, declaration - `vitest.config.ts` — globals, node env, 15s timeout, v8 coverage - `scripts/build.mjs` — esbuild 单文件 bundle + shebang **依赖**: ``` dependencies: @modelcontextprotocol/sdk ^1.27.1 @nut-tree/nut-js ^4.2.0 sharp ^0.33.0 clipboardy ^4.0.0 devDependencies: typescript ^5.7.0 vitest ^2.1.8 @vitest/coverage-v8 ^2.1.9 esbuild ^0.27.3 @types/node ^22.0.0 ``` **参考**: `crates/qiming-mcp-stdio-proxy/package.json`, `tsconfig.json`, `vitest.config.ts`, `scripts/build.mjs` **验收**: `pnpm install` 成功解析新 workspace 成员 --- ### Wave 2 — 基础设施层(全部 [P] 并行) #### T1.2 `src/utils/errors.ts` [P] 5 个结构化错误类,继承 Error,readonly context 字段: - `ConfigError(message)` - `DesktopError(operation, cause)` - `CoordinateError(coordinateMode, rawX, rawY)` - `SafetyError(keys, reason)` - `TaskExecutionError(taskText, step, cause)` **参考**: `crates/qiming-mcp-stdio-proxy/src/errors.ts` #### T1.3 `src/utils/logger.ts` [P] - stderr 输出,格式: `[YYYY-MM-DD HH:mm:ss.SSS] [LEVEL] [gui-agent] message` - `GUI_AGENT_LOG_FILE` 环境变量启用文件日志,日轮转,保留 7 天 - 导出: `logInfo`, `logWarn`, `logError`, `logDebug` **参考**: `crates/qiming-mcp-stdio-proxy/src/logger.ts` #### T1.4 `src/utils/platform.ts` [P] - `getPlatform(): 'macos' | 'windows' | 'linux'` - `checkScreenRecordingPermission(): Promise` - `checkAccessibilityPermission(): Promise` - `getPlatformPasteKeys(): Key[]`(macOS: Cmd+V, 其他: Ctrl+V) --- ### Wave 3 — 配置 + 坐标 + 桌面操作(大部分 [P] 并行) #### T1.5 `src/config.ts` **depends**: T1.2, T1.3 - `GuiAgentConfig` 接口,解析所有 `GUI_AGENT_*` 环境变量 - Fail Fast: `API_KEY` 缺失直接 throw `ConfigError` - 数值范围校验: jpegQuality(1-100), maxSteps(1-200), port(1-65535), stepDelayMs(100-30000) - 默认值: provider=`anthropic`, model=`claude-sonnet-4-20250514`, port=60008, maxSteps=50, stepDelayMs=1500, stuckThreshold=3, jpegQuality=75, displayIndex=0, transport=`http` #### T1.6 `src/coordinates/modelProfiles.ts` [P] **depends**: T1.2 - 7 条正则匹配规则 → `ModelProfile { coordinateMode, coordinateOrder }` - Gemini → `{ normalized-999, yx }`,其余 → `xy` - fallback: `image-absolute` + `xy` - `getModelProfile(modelName, overrideMode?): ModelProfile` #### T1.7 `src/coordinates/resolver.ts` **depends**: T1.6 纯函数,零 I/O,四步转换: 1. 坐标顺序修正(yx swap for Gemini) 2. 归一化到 0~1 3. 逻辑坐标 = norm × logicalWidth/Height 4. 全局偏移 = local + display.origin 边界 clamp + warning 日志 #### T1.8 `src/desktop/display.ts` [P] **depends**: T1.2, T1.3 - `DisplayDescriptor { index, label, width, height, scaleFactor, isPrimary, origin }` - `listDisplays()`, `getDisplay(index)`, `getPrimaryDisplay()` #### T1.9 `src/desktop/screenshot.ts` **depends**: T1.8 截图管线: 1. nut.js `screen.capture(region)` 物理分辨率 2. sharp resize 到逻辑分辨率(吸收 scaleFactor) 3. 最长边 > 1920 则等比缩放到 1920 4. JPEG encode quality=75 5. 超限降 quality 重试 6. 返回 `ScreenshotResult`(base64 + imageWidth/Height + logicalWidth/Height + physicalWidth/Height + scaleFactor) #### T1.10 `src/desktop/mouse.ts` [P] **depends**: T1.2 click, doubleClick, moveTo, drag, scroll, getPosition — 包装 nut.js #### T1.11 `src/desktop/keyboard.ts` [P] **depends**: T1.2, T1.4 - `typeText(text)` — 非 ASCII 或 >50 字符 → clipboard.pasteText,否则 nut.js type - `pressKey(key)`, `hotkey(keys[])` #### T1.12 `src/desktop/clipboard.ts` [P] **depends**: T1.2, T1.4 - `pasteText(text)` — 备份剪贴板 → 写入 → Cmd/Ctrl+V → 100ms → 恢复(try-catch) - `readClipboard()`, `writeClipboard(text)` — 使用 clipboardy #### T1.13 `src/desktop/imageSearch.ts` [P] **depends**: T1.2 - `findImage(templateBase64, confidence?)` → `{ found, region?, confidence? }` - `waitForImage(templateBase64, timeout?, confidence?)` → `{ found, region?, elapsed }` --- ### Wave 4 — 安全层([P] 并行) #### T1.14 `src/safety/hotkeys.ts` [P] **depends**: T1.2, T1.4 - `validateHotkey(keys[]): { blocked, reason? }` - 平台黑名单: macOS(Cmd+Q, Cmd+W, Cmd+Opt+Esc), Windows(Alt+F4, Ctrl+Alt+Del), Linux(Ctrl+Alt+Del) #### T1.15 `src/safety/auditLog.ts` [P] **depends**: T1.2 - `AuditLog` class — 环形缓冲 1000 条 - `record(entry)`, `getEntries(count?)`, `clear()` --- ### Wave 5 — MCP 协议层 #### T1.16 `src/mcp/atomicTools.ts` **depends**: T1.7, T1.9, T1.10, T1.11, T1.12, T1.13, T1.14, T1.15 注册 13 个 MCP tool handler: `gui_screenshot`, `gui_click`, `gui_double_click`, `gui_move_mouse`, `gui_drag`, `gui_scroll`, `gui_type`, `gui_press_key`, `gui_hotkey`, `gui_cursor_position`, `gui_list_displays`, `gui_find_image`, `gui_wait_for_image` 每个 handler: 输入校验 → safety 检查 → 坐标解析 → desktop 操作 → audit 记录 → 返回结果 #### T1.17 `src/mcp/resources.ts` [P with T1.16] **depends**: T1.4, T1.15 3 个 MCP Resource: `gui://status`, `gui://permissions`, `gui://audit-log` #### T1.18 `src/mcp/server.ts` **depends**: T1.16, T1.17 双模式 MCP Server: - **HTTP 主模式**: `http.createServer()` 监听 `127.0.0.1:`,`/mcp` 路径,per-session `StreamableHTTPServerTransport` + `Server`,session 管理 + 定期清理 - **stdio 备选**: 单 `Server` + `StdioServerTransport` **参考**: `crates/qiming-mcp-stdio-proxy/src/bridge.ts`(PersistentMcpBridge 模式) #### T1.19 `src/index.ts` **depends**: T1.5, T1.18 CLI 入口: `--port `, `--transport `, SIGINT/SIGTERM graceful shutdown --- ### Wave 6 — Phase 1 验收 #### T1.20 冒烟测试 **depends**: T1.19 - `tools/list` 返回 13 个工具 - `gui_screenshot` 返回 base64 + 正确 metadata - `gui_list_displays` 返回显示器数组 - HTTP 模式支持多客户端并发连接 --- ## Phase 2: 坐标转换验证 + 多屏 > **目标**: 坐标转换准确,多屏正确 #### T2.1 `tests/coordinates/resolver.test.ts` [P] 覆盖: 4 种坐标模式 × Gemini yx swap × Retina/HiDPI × 多屏偏移 × 边界 clamp × Spec 3.4.6 示例 #### T2.2 `tests/coordinates/modelProfiles.test.ts` [P] 覆盖: 7 条模型匹配 + fallback + coordinateOrder + override #### T2.3 `tests/desktop/screenshot.test.ts` [P] mock nut.js + sharp,覆盖: Retina 2880×1800 → 1440×900、4K → 1920×1080、1080p 不缩放、quality 降级重试 #### T2.4 多屏端到端测试 **depends**: T2.1 mock 多显示器,验证主屏/副屏坐标偏移正确 --- ## Phase 3: Agent 循环 (gui_execute_task) > **目标**: gui_execute_task 能执行自然语言 GUI 任务 #### T3.1 添加 pi-mono 依赖 新增 dependencies: `@mariozechner/pi-ai`, `@mariozechner/pi-agent-core`, `@sinclair/typebox` #### T3.2 `src/agent/systemPrompt.ts` [P] **depends**: T3.1 `buildSystemPrompt(taskText, memoryText): string` — 角色描述、7 个内部工具说明、坐标指引、popup 处理、computer_done 使用指导、memory 注入点 #### T3.3 `src/agent/stuckDetector.ts` [P] `StuckDetector` class — 截图缩放 32×32 → 像素均值差异 < 5% 连续 N 次 → 判定卡死 #### T3.4 `src/agent/memoryManager.ts` **depends**: T3.1 三层记忆管理(参考 TuriX-CUA): ``` summaryMemory (budget 2000 chars) ← LLM 摘要压缩 recentMemory (budget 500 chars) ← finalizeStep 写入 pendingMemory (no budget) ← addPendingStep 写入 ``` 核心 API: - `addPendingStep(stepId, goal)` - `async finalizeStep(stepId, evaluation)` — pending→recent,超 budget 触发 `complete(memoryModel, context)` 摘要 - `compose(): string` — 组合三层文本 - `pruneScreenshots(messages): AgentMessage[]` — 保留最近 3 步截图,老截图替换为文字占位 记忆摘要通过独立 `complete()` 调用(不走 Agent 循环) #### T3.5 `src/agent/taskRunner.ts` **depends**: T3.2, T3.3, T3.4, desktop/*, safety/* **核心**: 配置 pi-mono `Agent` 实例,不手写循环 ```typescript const agent = new Agent({ initialState: { systemPrompt, model, thinkingLevel: 'off', tools: guiTools, messages: [] }, toolExecution: 'sequential', transformContext: (msgs) => memoryManager.pruneScreenshots(msgs), convertToLlm: (msgs) => msgs.filter(m => ['user','assistant','toolResult'].includes(m.role)), beforeToolCall: ({ toolCall, args }) => hotkeys 拦截, afterToolCall: ({ toolCall, args, result, isError }) => auditLog 记录, }); ``` 7 个 `AgentTool[]`: computer_screenshot, computer_click, computer_type, computer_scroll, computer_hotkey, computer_wait, computer_done 事件订阅: `turn_end` → stepCount++, finalizeStep, stuckDetector, 更新 systemPrompt(compose()), maxSteps 检查 返回 `TaskResult { success, result?, finalScreenshot?, steps[], error? }` #### T3.6 `src/mcp/taskTools.ts` **depends**: T3.5 - `gui_execute_task` MCP tool handler - Mutex 互斥锁(`let currentTask: Promise | null`) - handler: acquire mutex → taskRunner.run(taskText, extra.signal, progressCb) → release in finally - 进度: `extra.sendNotification({ method: 'notifications/progress', params: { progressToken, progress, total } })` - 取消: `extra.signal` → `agent.abort()` #### T3.7 更新 `src/mcp/server.ts` **depends**: T3.6 在 session Server 注册中添加 `registerTaskTools()` **验收**: `tools/list` 返回 14 个工具,`gui_execute_task("打开 Finder")` 自动完成并返回步骤日志 #### T3.8 Agent 单元测试 [P with T3.7] - `tests/agent/taskRunner.test.ts` — mock Agent/getModel/desktop,测试正常完成、abort、maxSteps - `tests/agent/memoryManager.test.ts` — budget 触发、摘要调用、compose 输出、pruneScreenshots - `tests/agent/stuckDetector.test.ts` — 相同/不同截图判定 --- ## Phase 4: SDK 导出 #### T4.1 `src/lib.ts` **depends**: T3.7 - `createGuiAgentMcpServer(config: Partial): { start(), stop() }` - Re-export: `GuiAgentConfig`, `ScreenshotResult`, `DisplayDescriptor`, `TaskResult` --- ## Phase 5: Electron 客户端集成 #### T5.1 类型定义 修改 `crates/agent-electron-client/src/shared/types/computerTypes.ts` — 新增 `GuiVisionModelConfig` #### T5.2 computerServer 路由 **depends**: T5.1 修改 `crates/agent-electron-client/src/main/services/computerServer.ts` — 4 个新路由: - `POST/GET /computer/gui-agent/vision-model` - `GET /computer/gui-agent/displays` - `POST /computer/gui-agent/display` #### T5.3 GUIAgentSettings 组件 [P with T5.2] 新建 `crates/agent-electron-client/src/renderer/components/GUIAgentSettings.tsx` — 显示器选择 + 视觉模型配置 #### T5.4 集成 SettingsPage **depends**: T5.3 修改 `crates/agent-electron-client/src/renderer/components/SettingsPage.tsx` — 添加 GUIAgentSettings 标签页 #### T5.5 workspace 集成 修改 `crates/agent-electron-client/package.json` — `"agent-gui-server": "workspace:*"` + extraResources --- ## Phase 6: 集成测试 + 文档 #### T6.1 MCP 集成测试 claude-code 通过 MCP URL 连接,list tools,调用 gui_screenshot #### T6.2 跨平台验证 macOS + Windows + Linux(X11) 基本功能验证 #### T6.3 README.md 安装、MCP 配置示例(HTTP + stdio)、环境变量说明、开发指南 --- ## 执行顺序总览 ``` Phase 1 (脚手架 + 原子操作): Wave 1: T1.1 Wave 2: T1.2 | T1.3 | T1.4 Wave 3: T1.5, T1.6 | T1.8 | T1.10 | T1.11 | T1.12 | T1.13 Wave 4: T1.7, T1.9, T1.14 | T1.15 Wave 5: T1.16 | T1.17 Wave 6: T1.18 Wave 7: T1.19 Wave 8: T1.20 Phase 2 (坐标验证): T2.1 | T2.2 | T2.3 → T2.4 Phase 3 (Agent 循环): T3.1 → T3.2 | T3.3 | T3.4 → T3.5 → T3.6 → T3.7 T3.8 (parallel) Phase 4: T4.1 Phase 5: T5.1 → T5.2 | T5.3 → T5.4, T5.5 Phase 6: T6.1 → T6.2 → T6.3 ``` --- ## 验证方式 1. **Phase 1 验收**: 启动 server → `curl POST /mcp` 调用 `tools/list` 返回 13 工具 → `gui_screenshot` 返回 base64 2. **Phase 2 验收**: `pnpm test` 全部通过,坐标覆盖率 >90% 3. **Phase 3 验收**: `gui_execute_task("打开 Finder")` → 自动截图分析+点击 → 返回 steps + finalScreenshot 4. **Phase 4 验收**: `import { createGuiAgentMcpServer } from 'agent-gui-server'` 可用 5. **Phase 5 验收**: Electron Settings UI 可配置视觉模型和显示器 6. **Phase 6 验收**: claude-code MCP 配置后能调用 gui_execute_task --- ## 关键参考文件 | 文件 | 用途 | |------|------| | `specs/gui-agent/gui-agent.md` | Spec 规范文档(权威需求源) | | `specs/gui-agent/gui-agent-plan.md` | Plan 计划文档(技术方案源) | | `crates/qiming-mcp-stdio-proxy/package.json` | 工程约定模板 | | `crates/qiming-mcp-stdio-proxy/scripts/build.mjs` | esbuild 构建脚本模板 | | `crates/qiming-mcp-stdio-proxy/src/bridge.ts` | Streamable HTTP + session 管理模式 | | `crates/qiming-mcp-stdio-proxy/src/logger.ts` | 日志模块模板 | | `crates/qiming-mcp-stdio-proxy/src/errors.ts` | 错误类模板 |