14 KiB
GUI Agent MCP Server — Task 任务文档
文档类型: Task(任务文档)— 描述"具体执行",拆解后的最小行动单元
基于:
specs/gui-agent/gui-agent-plan.mdPlan 计划文档模块路径:
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, declarationvitest.config.ts— globals, node env, 15s timeout, v8 coveragescripts/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<boolean>checkAccessibilityPermission(): Promise<boolean>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缺失直接 throwConfigError - 数值范围校验: 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,四步转换:
- 坐标顺序修正(yx swap for Gemini)
- 归一化到 0~1
- 逻辑坐标 = norm × logicalWidth/Height
- 全局偏移 = 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
截图管线:
- nut.js
screen.capture(region)物理分辨率 - sharp resize 到逻辑分辨率(吸收 scaleFactor)
- 最长边 > 1920 则等比缩放到 1920
- JPEG encode quality=75
- 超限降 quality 重试
- 返回
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 typepressKey(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
AuditLogclass — 环形缓冲 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:<port>,/mcp路径,per-sessionStreamableHTTPServerTransport+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 <number>, --transport <http|stdio>, SIGINT/SIGTERM graceful shutdown
Wave 6 — Phase 1 验收
T1.20 冒烟测试
depends: T1.19
tools/list返回 13 个工具gui_screenshot返回 base64 + 正确 metadatagui_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 实例,不手写循环
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_taskMCP 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、maxStepstests/agent/memoryManager.test.ts— budget 触发、摘要调用、compose 输出、pruneScreenshotstests/agent/stuckDetector.test.ts— 相同/不同截图判定
Phase 4: SDK 导出
T4.1 src/lib.ts
depends: T3.7
createGuiAgentMcpServer(config: Partial<GuiAgentConfig>): { 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-modelGET /computer/gui-agent/displaysPOST /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
验证方式
- Phase 1 验收: 启动 server →
curl POST /mcp调用tools/list返回 13 工具 →gui_screenshot返回 base64 - Phase 2 验收:
pnpm test全部通过,坐标覆盖率 >90% - Phase 3 验收:
gui_execute_task("打开 Finder")→ 自动截图分析+点击 → 返回 steps + finalScreenshot - Phase 4 验收:
import { createGuiAgentMcpServer } from 'agent-gui-server'可用 - Phase 5 验收: Electron Settings UI 可配置视觉模型和显示器
- 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 |
错误类模板 |