Skip to content

Computer Use 架构深度解析

深入解析 Computer Use 功能的底层实现:从 MCP 工具定义到 Python Bridge,从 9 层安全关卡到灰度控制绕过。

补丁环境 · 分层架构 · MCP 工具层 · 安全关卡 · Python Bridge · 交互闭环 · 源文件索引

Computer Use 整体架构


一、补丁环境总览

Claude Code 原版的 Computer Use 功能(内部代号 Chicago)依赖三个不可公开获取的组件:

组件作用获取方式
@ant/computer-use-swift屏幕截图、显示器枚举Anthropic 私有 npm 包
@ant/computer-use-input鼠标/键盘模拟Anthropic 私有 npm 包
GrowthBook 远程配置灰度控制、功能开关Anthropic 内部服务

我们的方案是:保留原始 MCP 工具定义和安全机制不变,仅替换底层执行层和灰度控制

补丁环境对比

我们做了什么

原始 Claude Code                         Claude Code Haha (补丁版)
─────────────────                        ─────────────────────────
@ant/computer-use-swift  ──替换为──→     Python Bridge (mac_helper.py)
@ant/computer-use-input  ──替换为──→     pyautogui + pyobjc
GrowthBook 灰度控制      ──绕过──→      gates.ts 硬编码 return true
订阅检查 (Max/Pro)       ──绕过──→      getChicagoEnabled() = true
编译宏 CHICAGO_MCP       ──替换为──→     true
isDefaultDisabledBuiltin ──修改──→      返回 false

我们没有改什么

  • MCP 工具定义(24 个工具的 schema 和参数校验)
  • 9 层安全关卡(TCC 权限、应用白名单、权限等级、像素验证等)
  • 应用分类系统(191 个 bundle ID 的分类和权限映射)
  • 会话上下文管理(全局锁、截图缓存、状态同步)
  • 键盘快捷键阻止列表(系统级危险操作拦截)

灰度绕过细节

原始代码通过三层门控限制 Computer Use 的访问:

typescript
// 原始代码(简化)
function getChicagoEnabled(): boolean {
  // 层1:GrowthBook 远程配置
  const config = getDynamicConfig('tengu_malort_pedway')
  // 层2:订阅检查
  const hasSubscription = hasRequiredSubscription() // Max/Pro
  // 层3:编译时宏
  return feature('CHICAGO_MCP') && config.enabled && hasSubscription
}

我们的处理:

typescript
// gates.ts — 我们的修改
export function getChicagoEnabled(): boolean {
  return true  // ← 三层门控全部绕过
}

注意:子开关(pixelValidation、mouseAnimation 等)仍然保留原始逻辑,可通过配置控制。


二、分层架构

Computer Use 采用 6 层架构,每层职责清晰、边界明确:

┌─────────────────────────────────────────────────────────────┐
│  Layer 1 — MCP 工具接口层                                    │
│  tools.ts: 24 个工具 schema + 参数校验                       │
│  buildComputerUseTools() → MCP Tool Definition              │
├─────────────────────────────────────────────────────────────┤
│  Layer 2 — 工具调度与安全控制层                               │
│  toolCalls.ts: handleToolCall() + 9 层安全关卡               │
│  deniedApps.ts: 191 个应用分类 + 权限等级                    │
├─────────────────────────────────────────────────────────────┤
│  Layer 3 — MCP 服务器绑定层                                  │
│  mcpServer.ts: 会话上下文 + 全局锁 + 截图缓存               │
│  bindSessionContext() → per-call overrides                  │
├─────────────────────────────────────────────────────────────┤
│  Layer 4 — CLI 集成层                                        │
│  wrapper.tsx: 权限对话框 + 状态读写                          │
│  setup.ts: MCP 配置初始化                                    │
│  gates.ts: 灰度控制(已绕过)                                │
├─────────────────────────────────────────────────────────────┤
│  Layer 5 — Python Bridge 进程通信层                  [补丁]  │
│  pythonBridge.ts: venv 管理 + JSON RPC + 错误处理           │
│  callPythonHelper<T>(command, payload) → T                  │
├─────────────────────────────────────────────────────────────┤
│  Layer 6 — Python 运行时执行层                       [补丁]  │
│  mac_helper.py: pyautogui + mss + pyobjc                    │
│  660 行 Python 实现所有系统交互                              │
└─────────────────────────────────────────────────────────────┘

[补丁] 的层是我们替换/新增的代码,其余层完全保留原始 Claude Code 的逻辑。

为什么这样分层?

来源可复用性
Layer 1-2vendor/computer-use-mcp/平台无关,可用于 Electron、Web 等宿主
Layer 3vendor/computer-use-mcp/平台无关,MCP 标准协议
Layer 4utils/computerUse/CLI 专用,绑定应用状态
Layer 5-6utils/computerUse/ + runtime/macOS 专用,Python 实现

三、MCP 工具层

24 个工具一览

Computer Use 通过 MCP(Model Context Protocol)暴露 24 个工具给模型:

类别工具权限等级
权限request_access, list_granted_applications无需权限
截屏screenshot, zoomread
鼠标点击left_click, double_click, triple_clickclick
鼠标高级right_click, middle_click, left_click_dragfull
鼠标移动mouse_move, cursor_position, scrollclick
鼠标底层left_mouse_down, left_mouse_upfull
键盘type, key, hold_keyfull
应用open_application, switch_displayfull
剪贴板read_clipboard, write_clipboardfull
批量computer_batch继承子操作
等待wait无需权限

坐标系统

模型通过两种坐标模式与屏幕交互:

pixels 模式(默认):
  模型看到截图尺寸 (1176 x 784)
  模型输出坐标 [588, 392]
  scaleCoord() 转换:
    x_logical = (588 * displayWidth / 1176) + originX
    y_logical = (392 * displayHeight / 784) + originY

normalized_0_100 模式:
  模型输出坐标 [50, 50](百分比)
  scaleCoord() 转换:
    x_logical = (50 / 100) * displayWidth + originX
    y_logical = (50 / 100) * displayHeight + originY

截图尺寸经过 imageResize.ts 计算,确保:

  • 长边 ≤ 1568 像素
  • Token 预算 ≤ 1568(视觉编码器 28px/token)
  • 保持宽高比

应用分类系统

deniedApps.ts 对 191 个应用进行精细分类:

浏览器类(55 个 bundle ID)→ 权限等级 read

Safari, Chrome, Firefox, Arc, Edge, Opera, Brave, Vivaldi...
理由:浏览器操作应使用 Chrome MCP,而非盲点击

终端类(102 个 bundle ID)→ 权限等级 click

Terminal, iTerm2, VS Code, Cursor, JetBrains 全家桶, Xcode...
理由:终端操作应使用 Bash Tool,限制为只能点击不能输入

交易类(34 个 bundle ID)→ 权限等级 read

Webull, Fidelity, Interactive Brokers, Binance, Kraken...
理由:金融操作风险极高,仅允许截图查看

完全禁止(策略拒绝名单):

Netflix, Spotify, Apple Music, Kindle...
理由:版权合规,不进入权限对话框直接拒绝

四、安全关卡体系

每个输入操作(点击、键盘、拖拽)在执行前需通过 9 层安全关卡

安全关卡流程

关卡详解

Gate 1:Kill Switch

typescript
if (adapter.isDisabled()) return errorResult("Computer Use is disabled")

读取 getChicagoEnabled() — 在补丁版中永远返回 true

Gate 2:TCC 权限检查

typescript
await adapter.ensureOsPermissions()
// → Python: check_permissions
//   → Accessibility: osascript "tell System Events..."
//   → Screen Recording: CGDisplayCaptureDisplay()

如果缺少 macOS Accessibility 或 Screen Recording 权限,直接报错。

Gate 3:全局互斥锁

typescript
await tryAcquireComputerUseLock(sessionId)
// 文件锁: ~/.claude/computer-use.lock
// JSON: { sessionId, pid, acquiredAt }

确保同一时间只有一个 Claude 会话可以控制电脑。支持 stale PID 恢复。

Gate 4:隐藏非白名单应用

typescript
await executor.prepareForAction(allowlistBundleIds)
// 隐藏所有不在白名单中的应用窗口
// 确保截图中只出现授权应用

Gate 5:前台应用检查

typescript
const frontmost = await executor.getFrontmostApp()
if (!allowlist.includes(frontmost.bundleId)) {
  return errorResult("Application not authorized")
}

即使通过了白名单,还需确认当前前台应用是已授权的。

Gate 6:权限等级检查

三级权限模型:

Tier允许的操作禁止的操作
read截图查看任何输入操作
click左键点击、滚动右键、拖拽、键盘输入
full全部操作无限制
typescript
function tierSatisfies(tier: CuAppPermTier, required: ActionKind): boolean {
  const order = { read: 0, click: 1, full: 2 }
  return order[tier] >= order[required]
}

反绕过机制:如果权限不足,响应中会附加 TIER_ANTI_SUBVERSION 提示,防止模型通过 AppleScript 或 System Events 绕过限制。

Gate 7:剪贴板防护

威胁模型

1. Agent 调用 write_clipboard("rm -rf /")
2. 切换到 Terminal(click-tier 允许点击)
3. 模型点击 Terminal 的粘贴按钮
4. 恶意命令被执行

防护机制

当 click-tier 应用成为前台时:
  → 保存当前剪贴板内容(stash)
  → 清空剪贴板
  → 每次操作后再次清空

当非 click-tier 应用成为前台时:
  → 恢复原始剪贴板内容

Gate 8:像素验证(Staleness Guard)

上次截图                  当前实际屏幕
┌────────────┐           ┌────────────┐
│    按钮A    │           │   对话框    │  ← UI 已变化
│   [756,342] │           │    确认?    │
└────────────┘           └────────────┘

像素验证:在 [756,342] 取 9×9 网格
  → 对比上次截图 vs 实时截图的像素
  → 不同 → 拒绝点击 + 提示重新截图
  → 相同 → 允许点击

注意:补丁版中 pixelValidation 默认关闭(hostAdapter.cropRawPatch() 返回 null)。

Gate 9:系统快捷键拦截

keyBlocklist.ts 阻止危险快捷键:

快捷键危险操作
⌘Q退出应用
⇧⌘Q登出系统
⌥⌘⎋强制退出对话框
⌘Tab应用切换器
⌘SpaceSpotlight
⌃⌘Q锁屏

五、Python Bridge 机制

Python Bridge 通信机制

架构设计

原始 Claude Code 使用编译好的 Swift 原生模块(.node NAPI 插件)直接调用 macOS API。我们用 Python 子进程 + JSON RPC 替代了这一层:

TypeScript (Bun 运行时)                    Python (venv)
┌────────────────────┐                   ┌────────────────────┐
│  executor.ts       │                   │  mac_helper.py     │
│                    │   execFile()      │                    │
│  callPythonHelper  │ ──────────────→   │  main()            │
│  ('click',         │   command +       │    ├─ 解析 argv    │
│   {x:756,y:342})   │   --payload JSON  │    ├─ dispatch()   │
│                    │                   │    └─ click()      │
│  ← JSON.parse ──── │ ←──────────────   │       pyautogui    │
│  {ok:true,         │   stdout JSON     │                    │
│   result:true}     │                   │  json_output(...)  │
└────────────────────┘                   └────────────────────┘

启动引导流程

首次调用 callPythonHelper() 时自动完成环境初始化:

ensureBootstrapped()

  ├─ 检查 .runtime/venv/bin/python3 是否存在
  │   └─ 不存在 → python3 -m venv .runtime/venv/

  ├─ 检查 pip 是否可用
  │   └─ 不可用 → python3 -m ensurepip --upgrade

  ├─ 计算 runtime/requirements.txt 的 SHA256
  │   └─ 与 .runtime/requirements.sha256 对比
  │       └─ 不同 → pip install -r requirements.txt
  │                 写入新的 SHA256 哈希
  │       └─ 相同 → 跳过安装

  └─ 就绪,返回 venv Python 路径

依赖清单runtime/requirements.txt):

用途
mss高性能屏幕截图
PillowJPEG 编码和图像处理
pyautogui鼠标点击、键盘输入
pyobjc-coremacOS Objective-C 桥接
pyobjc-framework-CocoaNSWorkspace(应用管理)、NSPasteboard(剪贴板)
pyobjc-framework-QuartzCGDisplay(显示器)、CGWindow(窗口列表)

命令映射表

mac_helper.py(660 行)实现了以下命令:

命令Python 实现返回值
screenshotmss.grab() + PIL.Image JPEG 编码{base64, width, height, displayWidth, displayHeight}
zoommss.grab(region) 区域截图{base64, width, height}
clickpyautogui.moveTo() + pyautogui.click()true
keypyautogui.hotkey() / pyautogui.press()true
typepyautogui.write(interval=0.008)true
dragpyautogui.dragTo(duration=0.2)true
scrollpyautogui.scroll() / pyautogui.hscroll()true
hold_keypyautogui.keyDown() + sleep + pyautogui.keyUp()true
frontmost_appNSWorkspace.frontmostApplication(){bundleId, displayName}
list_displaysCGGetActiveDisplayList() + CGDisplayBounds()[DisplayGeometry...]
find_window_displaysCGWindowListCopyWindowInfo() + 交集计算[{bundleId, displayIds}...]
list_installed_apps递归扫描 /Applications + plistlib[InstalledApp...]
list_running_appsNSWorkspace.runningApplications()[RunningApp...]
open_appNSWorkspace.launchApplicationAtURL_options_void
read_clipboardNSPasteboard.stringForType_()string
write_clipboardNSPasteboard.setString_forType_()void
check_permissionsosascript + CGDisplayCaptureDisplay{accessibility, screenRecording}

错误处理

python
# mac_helper.py 的统一错误处理
def main():
    try:
        result = dispatch(command, payload)
        json_output({"ok": True, "result": result})
    except Exception as e:
        error_output({"ok": False, "error": {"message": str(e)}})

TypeScript 端:

typescript
// pythonBridge.ts
const parsed = JSON.parse(stdout)
if (!parsed.ok) {
  throw new Error(parsed.error.message)  // → MCP tool error
}
return parsed.result

六、截图-分析-操作闭环

一个完整的 Computer Use 交互由多个 截图-分析-操作 循环组成:

典型交互流程

用户: "帮我打开网易云音乐,搜索一首歌"

┌─ 循环 1:发现并打开应用 ─────────────────────────────────┐
│                                                          │
│  Step 1: request_access                                  │
│    → 弹出权限对话框,用户授权允许操作的应用               │
│    → 设置 allowedApps, grantFlags                        │
│                                                          │
│  Step 2: screenshot                                      │
│    → 截取全屏 → JPEG 编码 → base64                       │
│    → 缓存截图尺寸 (lastScreenshotDims)                   │
│    → 返回给模型                                          │
│                                                          │
│  Step 3: 模型分析截图                                    │
│    → "桌面上没有网易云音乐,需要打开它"                   │
│    → 决定调用 open_application                           │
│                                                          │
│  Step 4: open_application("com.netease.163music")        │
│    → Gate 1-9 全部通过                                   │
│    → Python: NSWorkspace.launchApplicationAtURL_()       │
│                                                          │
└──────────────────────────────────────────────────────────┘

┌─ 循环 2:定位搜索框 ────────────────────────────────────┐
│                                                          │
│  Step 5: screenshot                                      │
│    → 截取全屏(网易云已打开)                             │
│    → 更新 lastScreenshotDims                             │
│                                                          │
│  Step 6: 模型分析截图                                    │
│    → 视觉识别搜索框位置 → (756, 342)                     │
│    → 决定点击搜索框                                      │
│                                                          │
│  Step 7: left_click({coordinate: [756, 342]})            │
│    → Gate 4: 隐藏非白名单应用                            │
│    → Gate 5: 前台是网易云 ✓                              │
│    → Gate 6: tier=full ≥ click ✓                         │
│    → Gate 7: 非 click-tier 应用,跳过                    │
│    → scaleCoord(756, 342) 转换为屏幕坐标                 │
│    → Python: pyautogui.click(x_logical, y_logical)       │
│                                                          │
└──────────────────────────────────────────────────────────┘

┌─ 循环 3:输入搜索词 ────────────────────────────────────┐
│                                                          │
│  Step 8: type({text: "喜欢你"})                          │
│    → Gate 6: tier=full ≥ keyboard ✓                      │
│    → Python: pyautogui.write("喜欢你", interval=0.008)   │
│                                                          │
│  Step 9: screenshot                                      │
│    → 确认搜索结果已出现                                  │
│    → 模型分析:"搜索结果列表中第一个就是目标歌曲"         │
│                                                          │
│  Step 10: left_click({coordinate: [...]})                │
│    → 点击目标歌曲                                        │
│                                                          │
└──────────────────────────────────────────────────────────┘

坐标转换的关键

模型看到的屏幕实际的是不同的坐标空间:

原始屏幕 (2560 x 1600, Retina 2x)
  ├─ 逻辑尺寸: 1280 x 800
  └─ 物理像素: 2560 x 1600

imageResize 计算后:
  ├─ 缩放后尺寸: 1176 x 735 (≤1568px 预算)
  └─ 这就是模型"看到"的截图尺寸

模型输出坐标: [588, 368](图像空间的像素位置)

scaleCoord 转换:
  x_logical = (588 / 1176) * 1280 + originX = 640 + 0 = 640
  y_logical = (368 / 735) * 800 + originY = 400 + 0 = 400

Python 执行:
  pyautogui.moveTo(640, 400)  ← 逻辑坐标(macOS 自动处理 Retina)

截图缓存与状态同步

bindSessionContext 闭包

  ├─ lastScreenshot (内存)
  │   ├─ base64: JPEG 数据(用于 pixel validation)
  │   ├─ width/height: 模型看到的尺寸
  │   └─ displayWidth/displayHeight/originX/originY: 显示器几何

  └─ AppState.computerUseMcpState (持久化)
      ├─ allowedApps: AppGrant[]          — 已授权应用列表
      ├─ grantFlags: {...}                — 剪贴板/系统快捷键权限
      ├─ selectedDisplayId?: number       — 选定的显示器
      ├─ lastScreenshotDims?: {...}       — 截图几何(跨重启恢复)
      └─ hiddenDuringTurn?: Set<string>   — 本轮隐藏的应用

七、关键源文件索引

vendor/computer-use-mcp/(原始代码层)

文件行数职责
types.ts622权限模型、会话上下文、全部类型定义
tools.ts70724 个 MCP 工具的 schema 和参数校验
toolCalls.ts1600+核心:工具派发、9 层安全关卡、权限流程
deniedApps.ts554191 个应用的分类(browser/terminal/trading)和权限映射
sentinelApps.ts44敏感应用预警标签(shell/filesystem/system_settings)
mcpServer.ts314MCP 服务器工厂、会话上下文绑定、全局锁
pixelCompare.ts172点击目标像素验证(staleness guard)
imageResize.ts109截图尺寸计算(API 图像转码算法)
keyBlocklist.ts154系统快捷键拦截(⌘Q、⌘Tab 等)
executor.ts101ComputerExecutor 接口定义
subGates.ts20灰度子开关预设组合

utils/computerUse/(CLI 适配层)

文件行数职责补丁?
executor.ts231ComputerExecutor 的 Python bridge 实现✅ 重写
pythonBridge.ts111Python 子进程管理、venv 引导、JSON RPC✅ 新增
hostAdapter.ts54HostAdapter 实现(权限检查、灰度读取)部分修改
gates.ts51GrowthBook 灰度控制(getChicagoEnabled 绕过)✅ 修改
wrapper.tsx300+会话上下文构建、权限对话框、锁管理未修改
setup.ts54MCP 配置初始化未修改
computerUseLock.ts216全局文件锁(~/.claude/computer-use.lock未修改
common.ts62常量定义(server name、bundle ID)未修改
cleanup.tsturn-end 清理(应用恢复、剪贴板恢复)未修改
toolRendering.tsx工具结果 UI 渲染未修改

runtime/(Python 运行时)

文件行数职责补丁?
mac_helper.py660所有系统交互的 Python 实现✅ 新增
requirements.txt6Python 依赖声明✅ 新增

八、设计权衡

为什么选择 Python Bridge?

维度原生 Swift (.node)Python Bridge
性能~0ms(进程内调用)~50-100ms(子进程启动)
可读性编译后不可读660 行清晰的 Python
可修改性需要 Swift 编译环境直接编辑 .py 文件
依赖特定 Bun 版本的 NAPI任意 Python 3.8+
跨平台仅 macOSpyautogui/mss 天然跨平台
用户体验无感知无感知(模型思考时间秒级)

结论:50-100ms 的额外延迟在 Computer Use 的场景下完全可以忽略——模型分析截图和决策的时间通常是 2-5 秒,用户不会注意到底层操作多了 100ms。

我们尝试过但放弃的方案

方案一:提取原生 .node 模块

  • 从 Claude Code 二进制中成功提取了 computer-use-swift.node(ARM64 424KB)
  • 同步方法正常工作,但 Swift 异步方法的 continuation 永远不会 resume
  • 根因:.node 文件针对 Claude Code 内置 Bun 编译,与用户 Bun 版本不兼容

方案二:空 Stub 包

  • 代码能编译但所有操作报错——没有实际执行能力

相关文档

Released under the MIT License.