Android/iOS 控制

把 Android 手机(和 iOS 设备)接入 MCP:读取屏幕树/截图,并下发点击、输入、滚动、打开应用等命令。Agent 通过授权的 device_id 操控你配对的真实手机。

解决的核心问题Agent 只能生成文本或调用简单 API,要操作手机上的 App 就得自己接 ADB / WebDriverAgent、处理连接保活与凭据。device-control 把"读屏 + 下发操控命令"做成内置 MCP,配对一次即可被 Agent 调用,凭据与吊销都走平台治理。

特色:配对码换 token,10 分钟单次

  1. 创建设备实例:在用户侧「我的工具」新建设备控制实例,点「生成配对码」。配对码 10 分钟内有效且只能使用一次,存 Redis(SET NX EX + GETDEL)以支持多 worker。
  2. 手机端配对:在 Android App 中输入服务器地址和配对码。地址可填域名(https://host)或完整路径(https://host/mcp/device-control),App 会先试原地址,不通再补 /mcp/device-control
  3. 授权给 Agent:把该实例授权给需要用手机的 MCP principal / Agent。Agent 通过资源授权获得 device_id
  4. 解除配对:清除服务端 token_hash 并停用设备;在线设备收到 close 4003,App 擦除凭据并停止重连。
设备 token ≠ MCP 访问 token手机端只用配对后返回的设备 token(连 WebSocket 时鉴权);Agent 调用 MCP 用的是MCP 访问 token,二者是两回事——和 CDP 的两类 token 同构。设备 token 只存 sha256,泄库也无法还原。

特色:15 个操控命令

服务端不下发设备未在 capabilities 里声明的命令——真下发了设备会回 unsupported。命令词汇表(spec §8):

命令行为
get_screen_state读取屏幕树/当前状态(只读,可并发)
tap / long_press / double_tap点击 / 长按 / 双击坐标或节点
swipe / scroll / scroll_to_node滑动 / 滚动 / 滚到指定节点
type_text / set_text输入文本 / 直接设置输入框文本
press_key按键:enter/tab/delete/backspace/escape/space/dpad_*
dismiss_keyboard收起键盘
press_back / press_home / press_recents系统返回 / 主页 / 最近任务键
open_app / list_apps打开应用 / 列出已安装应用(list_apps 只读可并发)
串行与并发设备必须按收到顺序串行执行会改 UI 状态的命令(服务端同样不并发下发,避免"两个 tap 交错");get_screen_statelist_apps 是只读例外,可并发。v0 故意排除音量/电源键。

特色:WebSocket v0 协议

设备打开 WS 到 /mcp/device-control/ws/device,握手双方带 protocol_version,服务端权威——不匹配直接 close 4004,绝不"假定兼容"。帧类型(spec §3):

  • 设备 → 服务端:register(登记设备与 capabilities)、heartbeatcall-response(命令结果)、event(capabilities-changed / control-revoked)。
  • 服务端 → 设备:registered(ack 与协商参数)、call(下发命令)、call-cancel(建议性取消,设备仍须回 call-response)。

关键常量(spec)

常量含义
心跳间隔 / 超时15s / 60s任意帧都刷新活性,超时收割(close 4010)
单设备在途上限8第 9 条提前挡掉,设备会回 overloaded
call 超时默认 15s / 上限 60s设备侧可 clamp 到 60s,服务端预算 = timeout + 5s
最大帧4 MiB超过 close 4002
register 超时10sWS 打开后 10s 内必须发 register,否则 close 4008

特色:新连接踢旧,半开不再锁死

安卓半开连接远比 Chrome 频繁——doze、蜂窝/WiFi 切换、进程被后台回收都不发 TCP FIN。协议用新连接踢旧(last-writer-wins,旧连接 close 4009),而不是拒绝式,避免"服务端以为还连着、设备怎么都连不上"。空闲超时保留,但只是兜底清理,不再是设备能否重连的关键路径。

吊销即断用户在前端删掉/轮换设备凭据后,活连接必须当场断(close 4003),不能等下次心跳超时——与 CDP driver 的 apply_clients 吊销 diff 同一思路。WS 读循环每帧复查 token 是否仍在授权快照里。

iOS:node-ios 独立二进制

iOS 走 node-ios 独立 Go 二进制(nodes/ios),用 go-ios + WebDriverAgent 驱动一台或多台 iPhone(USB/LAN),对服务端在线上与 Android App 不可区分:

  • node-ios pair --server URL --code CODE --udid X 兑换配对码(纯 HTTP,无需连真机),写 0600 凭据并登记设备。
  • node-ios run 按配置为每台设备起一个连接循环;真机链路(go-ios discovery + WDA 启动/端口转发)在 device-control/ios driver,无设备时记 "no iOS device found" 并保活。
  • 不共用节点协议栈:device-control 用自己的配对码 → 长连接 token 鉴权与 WebSocket,不复用 execution 节点的 NodeConnect / TOTP 栈——所以没有把 go-ios 拖进每个节点。
两套栈不要混淆「节点」(cap-node) 主动连 NodeConnect、TOTP 鉴权、跑 CLI/终端/代理;「Android/iOS 控制」(本页) 主动连 device-control WS、配对码换 token、读屏下发操控命令。两者协议、鉴权、用途都不同,是两套独立栈。

特色:与 CDP 桥接的结构差异

CDP 浏览器桥接device-control 手机
结构层级client → user → pages(一浏览器扇出多标签)device_id → DeviceContext(一台手机一个端点,无第三层)
结果等待同步轮询asyncio.Future 按 request_id 配对
重连模型already_connected 拒绝式新连接踢旧(close 4009)
用途操作已登录网页操作手机 App 与系统

device token / MCP token 隔离治理 → · 浏览器插件(CDP)详解 →