一、账号台账(新表 device_account,schema v8) - core/ledger.py:CRUD、Excel 粘贴解析(Tab 分隔 / 表头乱序缺列 / 是·否布尔 / 逐行留痕)、 按范围取号(本机台账 / 全部 / 按设备分组)、设备端账号块、dry_run 预览 - 新「账号」顶级 Tab(权限 devices):列表 + 搜索 + 设备筛选 + 增删改 + 粘贴导入(默认先预览再写,逐行显示 新增/覆盖/跳过/失败 + 留痕) - 设备池加「账号 N」列(0 个显示"未登记"),点开看该设备账号明细 - 任务「条件判断」新增 cmp_source / cmp_group:候选值可直接来自台账, 与手填值**合并(OR)**;取不到号时回落手填值,并把原因写进步骤明细与日志 (否则"永远走 else 分支"而任务照样显示成功,最难查) - 设备端契约:身份页多推 accounts_b64(base64 JSON;默认不含手机号; 超预算按整条丢且绝不算字节切),见 doc/DEVICE_AGENT.md §5.2.1 - 备份/文档红线:TABLE_LABELS 加「账号台账」;DATA_MODEL/API/ARCHITECTURE/DEPLOY/ TASK_DEV/DEVICE_AGENT/DEVELOPMENT/README 同步;顺手补上 DATA_MODEL 漏列的 done_mark ⚠ 台账里的抖音号是**纯号**,只当"比对用的候选值":绝不能拿去填「去重」的身份元素 (身份是元素原文逐字算 key,格式不同会让去重静默失效)。代码与文档都写明了。 二、剪贴板注入通道(修:平台还在调早期的独立 APK) - 改为按顺序尝试:设备端 Agent(com.example.deviceagent/.ClipActivity)→ 旧版独立 ClipInject 兜底;两个都没有时报"需要设备端 Agent"(不再只说 ClipInject) - 读回验证改成**轮询到 3 秒**:透明 Activity 要等窗口拿到焦点才写, 原来只睡 0.5s 会读到上一次的内容 → 误报"写入可能被拒"(实测内容已写入却回失败)
18 KiB
MCP 手机控制(Mobile Control MCP Server)设计文档
状态:设计稿 / 演进记录——凡与实现不符处,以 MCP.md(使用手册,含 20 个工具的权威清单)与
mcp_server/代码为准。 本文保留设计取舍与里程碑,便于回溯"为什么这么做";文中的"演进备选"均未落地。 最后核对:2026-09-10(对照 dev 现状)。
1. 背景与目标
平台(auto_control,生产 220:18050)已具备完整的手机远程控制与自动化能力:设备池管理、u2 控制(tap/swipe/key/输入/剪贴板)、minicap 看屏(截图/流)、uiautodev 元素树、RapidOCR、任务调度与动作编辑器。
本设计的目标:新增一个 MCP Server,把平台能力以 MCP 协议暴露给多模态 AI(Claude 等),使 AI 能像人一样"看到手机屏幕 → 理解 → 操作 → 再看到"地闭环控制手机。
核心判断:平台能力全部已有,MCP Server 是薄封装层——主要工作在:工具规格设计、多模态图像链路、认证与安全、部署。
2. 术语
| 术语 | 说明 |
|---|---|
| MCP | Model Context Protocol,模型上下文协议(客户端-服务器,工具调用) |
| Tools | MCP 暴露的可调用能力(本设计按 L1/L2/L3 分层) |
| serial | 设备标识,如 192.168.20.66:5555(设备池唯一主键) |
| 平台 | 指 auto_control web 服务(18050)及其 core 层能力 |
| 宿主 | MCP Server 运行进程/容器 |
3. 总体架构
┌──────────────┐ MCP (Streamable HTTP / stdio) ┌──────────────────┐
│ 多模态 AI │ ────────────────────────────────▶ │ MCP Server │
│ Claude Desktop│ tools + image content block │ (与平台同容器) │
│ Claude Code │ ◀──────────────────────────────── │ mcp_server/ 包 │
└──────────────┘ 截图图像 / JSON 结果 └────────┬─────────┘
│ 内部调用
▼
┌──────────────────┐
│ auto_control │
│ 18050 REST API │
│ (登录会话 token)│
├──────────────────┤
│ core 层能力: │
│ u2 / minicap / │
│ uiautodev / OCR /│
│ 任务调度 / 设备池 │
└──────────────────┘
- 图像方向:MCP 协议支持
imagecontent block(base64),截图以此返回,多模态模型原生可读——这是"AI 看到手机"的关键链路。 - 控制方向:AI 返回结构化工具调用(tap/swipe/type…),MCP Server 转发平台执行。
- 循环:截图 → 模型视觉推理 → 工具调用 → 平台执行 → 截图验证。
4. 平台现有能力复用清单
| 平台能力 | 现有入口 | MCP 复用方式 |
|---|---|---|
| 设备列表/状态 | GET /api/status、/api/devices/pool |
MCP server 内部 HTTP 调用(带会话) |
| 截图 | GET /api/screen/thumb?serial=(单帧 JPEG) |
直接转发为 image block(亦可直连 core u2 screenshot) |
| 看屏流 | /api/screen/stream(MJPEG) |
M0-M2 不需要;M3 可选(视频理解场景) |
| 点击 | POST /api/screen/tap {serial,x,y} |
封装 tool tap |
| 滑动 | POST /api/screen/swipe |
封装 swipe |
| 按键 | POST /api/screen/key |
封装 press_key |
| 输入文字 | 设备端 Agent 通道(core.clipboard_helper)+ u2 input | 封装 type_text |
| 剪贴板 | POST /api/tools/clipboard/set(设备端 Agent 通道) |
封装 set_clipboard |
| 元素树 | GET /api/uiauto/...(uiautodev) |
封装 get_ui_tree |
| OCR | core.ocr(RapidOCR) | 封装 ocr_screen |
| 打开 App | u2 app_start(经任务层/直连) |
封装 open_app(走 core device 直连) |
| 设备在线状态/亮熄屏 | /api/device/screen_all、dumpsys |
封装 screen_state/wake |
决策:MCP Server 优先走 HTTP API(薄封装,认证简单、与平台解耦、平台安全逻辑全复用)。对延迟敏感且 HTTP 无入口的能力(u2 直连截图/输入),经平台 core 模块进程内调用或新增少量只读端点,不绕过平台安全层。
5. MCP Server 设计
5.1 技术栈
- 语言/运行时:Python 3.11(与平台一致)
- 框架:FastMCP(
fastmcp,官方 SDK 之上,装饰器式 tools,自带 Streamable HTTP/stdio 双传输) - 依赖:
fastmcp(requirements.txt 已含,>=2.0)、httpx、Pillow(图像处理)、mcp[cli](设计依赖,未落地——当前 requirements 未含,仅 fastmcp) - 日志:logging → 平台同款格式(时间/级别/模块)
5.2 进程与容器
实现现状:当前 MCP 与 web_server 同容器,由
scripts/start.sh后台拉起(MCP_ENABLED=1默认,=0 可关),监听 8033。下方独立容器方案为演进备选。
- 独立容器
mcp-server(220 docker-compose 追加),imagepython:3.11-slim(演进备选) - 挂载:无数据挂载(无状态,配置走环境变量);网络 host 或独立端口(候选:8033,避免与 18050/18051 冲突)
- 独立于 auto_control 重启,互不阻塞;MCP Server 崩溃不影响平台,平台不可用时 MCP tools 返回明确错误
5.3 配置(环境变量)
| 变量 | 默认 | 说明 |
|---|---|---|
MCP_PLATFORM_URL |
http://127.0.0.1:18050 |
平台地址 |
MCP_PLATFORM_USER / MCP_PLATFORM_PASS |
空 | 平台登录账号(admin) |
MCP_ALLOW_WRITE |
0 |
写操作总开关(0=只读感知,1=可操作) |
MCP_ALLOWED_SERIALS |
空=全部 | 设备白名单(逗号分隔;为空时自动=设备池内设备)——设计目标,代码未实现(实现仅校验 serial 非空,见 §8.3 注) |
MCP_HTTP_HOST |
0.0.0.0 |
HTTP 监听地址 |
MCP_HTTP_PORT |
8033 |
HTTP 传输端口 |
MCP_PLATFORM_TIMEOUT |
30 |
平台请求超时(秒) |
MCP_SCREENSHOT_WIDTH |
540 |
截图宽度(等比缩放,控图像 token 成本) |
MCP_JPEG_QUALITY |
70 |
截图 JPEG 质量 |
MCP_AUDIT_FILE |
/var/log/mcp/audit.log |
审计日志路径 |
MCP_ENABLED由scripts/start.sh消费(默认 1),非 MCP 进程内配置;MCP_PLATFORM_PASS在 start.sh 兜底admin123,改 admin 密码须同步(见 doc/MCP.md)。
6. Tools 规格
命名空间 de_(device)前缀避免与常见工具冲突。全部工具对未配置白名单/离线设备返回明确错误,不做静默跳过。
实现现状对照(本节以下是设计规格;实际实现以
mcp_server/与 doc/MCP.md 为准,主要差异):
de_screen_state未独立实现:并入de_screenshot返回的screen_state字段- 实际另有(设计稿未列):
de_tap_text/de_tap_element/de_stop_app/de_read_clipboard/de_foreground_app/de_list_apps/de_list_tasksde_type_text实际签名(serial, text),走 u2EditText.set_text(非设计稿的 ClipInject+paste 优先)de_tap坐标语义已是截图坐标系、server 按比例换算原生(非设计稿「原生像素、调用方自换算」)de_open_app是 adb monkey 直启(非 u2app_start)de_find_and_tap/de_wait_until/de_get_task_state未实现;语义点击由de_tap_text/de_tap_element承担- 平台级工具层未实现:任务 CRUD/提交、分组/设备池/自定义动作/APK/备份等 REST 只给前端用,未 MCP 化;补齐规划见 doc/AI_TASK_GEN.md §9——P0 只补
list_task_types/list_groups/list_pool+submit_task(校验不落库),不暴露写 CRUD,维持「AI 提案 → 人工确认」
L1 感知层
de_list_devices
- 描述:列出可控制设备及其状态(在线/任务/型号/前台 App)
- 返回:
[{serial, model, online, task_job, worker_status, foreground_app, screen_state}] - 错误:平台不可达 →
platform_unavailable
de_screenshot(serial: str) -> image
- 描述:截取设备当前屏幕(JPEG),返回 image content block;同时返回
width/height/serial元信息 - 实现:平台
/api/screen/thumb(X-Screen-State 头复用判断亮熄屏);失败(离线/超时)→ 明确错误 - 图像规格:宽 ≤
MCP_SCREENSHOT_WIDTH(默认 540),质量MCP_JPEG_QUALITY;需保证图像方向正确(设备可能横竖屏,含 EXIF 或由调用方按截图尺寸推断) - 限制:截图频率 ≥ 1s/次(防 AI 疯狂截图),可配置(未落地:现状无频率限制)
de_ui_tree(serial: str) -> str
- 描述:获取当前界面元素树(扁平 JSON:resource-id/text/content-desc/class/bounds),供定位
- 返回:文本 JSON(大树截断至
MCP_UI_TREE_MAX字符,默认 20000,防 context 爆炸) - 空界面/无元素 → 明确错误
de_ocr(serial: str) -> str
- 描述:OCR 识别当前屏幕文字,返回
[{text, score, box}] - 场景:图片/画布/WebView 渲染文字(UI 树里没有的)
de_screen_state(serial: str) -> str
- 描述:亮屏/熄屏/未知
L2 操作层(受 MCP_ALLOW_WRITE=1 门控)
de_tap(serial, x: int, y: int)
- 坐标:设备原生分辨率像素(与截图 1:1 换算——调用方用截图尺寸按比例换算,server 不做猜测)
de_swipe(serial, x1,y1,x2,y2, duration: float=0.2)
de_press_key(serial, key: str)
- key ∈ back/home/recent/menu/power/enter/delete…
de_type_text(serial, text: str, clear_first: bool=True)
- 实现:优先设备端 Agent 通道(
core/clipboard_helper.inject_clipboard语义:写入剪贴板 + paste 到焦点输入框),失败回退 u2 input - 中文/emoji 全支持(Agent 通道实测通过)
de_set_clipboard(serial, text: str)
- 写入设备剪贴板(设备端 Agent 的 am start 通道),读回验证
de_open_app(serial, package: str)
- 打开指定 App(u2 app_start);
package需在已知清单或完全限定名
de_wake(serial) / de_sleep(serial)
L3 语义层(M2 里程碑)
de_find_and_tap(serial, target: str, by: "text"|"id"|"ocr"|"desc"=...)
- UI 树/OCR 定位含 target 的元素 → 自动点击其中心;多命中返回候选列表让 AI 选择
- 复用平台元素选择器策略(属性唯一/重复序号消歧)
de_wait_until(serial, condition: str, timeout: int=15)
- 轮询 OCR/UI 树直到出现条件文本(或消失),返回命中的坐标/文本
de_get_task_state(serial)(若二期接任务系统)
- 设备当前任务状态;可选
de_run_task(task_id)需单独授权
工具返回约定
- 全部工具返回结构化 JSON:
{ok: true, data: ...}或{ok: false, error: {code, message}} - 错误码:
platform_unavailable/device_offline/device_not_allowed/invalid_param/write_disabled/device_busy(设备被任务占用,running/connecting 拒写) /text_not_found(de_tap_text 屏幕上无该文字) - image 返回:
{ok:true, image: <content block>, width, height}
7. 图像链路细节
- 平台截图(
/api/screen/thumb)→ JPEG 字节 - MCP Server 校验尺寸 → 等比缩放到
MCP_SCREENSHOT_WIDTH(用 Pillow,不重压缩已够质量则跳过) - 组装 MCP
imagecontent block({"type":"image","data":<base64>,"mimeType":"image/jpeg"}) - 客户端(Claude)原生把 image 传入视觉上下文
性能预算:540px JPEG ~40-80KB/张;模型一次多步操作 5-15 张 ≈ 1MB 以内,可接受。
8. 认证与安全
- 平台会话:MCP Server 启动时用
MCP_PLATFORM_USER/PASS登录平台拿会话(Cookie),会话失效自动重登;不向客户端暴露平台凭据 - 写操作门控:
MCP_ALLOW_WRITE=0(默认)时 L2/L3 操作全部拒绝(write_disabled)——先部署只读,验证感知链路后再开写 - 设备白名单:
MCP_ALLOWED_SERIALS指定;为空时自动限制为设备池 enabled 设备(平台口径)——此为设计目标,当前代码未实现:实现只校验 serial 非空,空名单不按设备池过滤(如需收紧请配置白名单) - 占用互斥:写操作前查设备
worker_status——running/connecting 中拒绝(device_busy),避免 MCP 与任务打架(已实现,只读工具不受限) - 审计:每次调用(含只读)写审计日志:
ts|tool|serial|args摘要|result;落盘MCP_AUDIT_FILE - 传输安全:内网部署(host 网络 8033)默认无 TLS;若外网暴露需前置 TLS/网络隔离(平台红线:设备控制接口不进公网)
- 无状态:MCP Server 不存设备数据,全量透传平台
9. 部署(220 docker-compose 追加)
演进备选:下方「独立 mcp-server 容器」为演进方案。当前实现=与 web_server 同容器,由
scripts/start.sh后台拉起(MCP_ENABLED=1默认、MCP_ALLOW_WRITE=1、MCP_PLATFORM_PASS兜底 admin123),见 doc/MCP.md 与 DEPLOY §2.4。
mcp-server:
container_name: mcp-server
image: dockerproxy.net/library/python:3.11-slim
restart: unless-stopped
working_dir: /app
volumes:
- "./mcp_server:/app"
command: sh -c "pip install --no-cache-dir -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt && python -m mcp_server.mcp_server"
environment:
- MCP_PLATFORM_URL=http://127.0.0.1:18050
- MCP_PLATFORM_USER=admin
- MCP_PLATFORM_PASS=${MCP_PLATFORM_PASS} # .env 提供
- MCP_ALLOW_WRITE=1
- MCP_ALLOWED_SERIALS=
network_mode: host
客户端接入(Claude Code / Desktop):
// .mcp.json / claude_desktop_config.json
{ "mcpServers": { "mobile": {
"url": "http://192.168.20.220:8033/mcp",
"transport": "streamable-http"
}}}
10. 里程碑与验收
| 里程碑 | 内容 | 验收标准 |
|---|---|---|
| M0 | Server 骨架 + de_list_devices + de_screenshot + de_tap/de_swipe(HTTP 传输、登录会话、审计骨架) |
Claude Code 连上后:能列设备、截图(AI 能描述屏幕内容)、点击指定坐标生效 |
| M1 | L1 完备(ui_tree/ocr/screen_state)+ L2 全量(key/type/clipboard/open_app/wake) | AI 完成「打开抖音搜索『奚学东』」多步操作 |
| M2 | L3 语义层(find_and_tap/wait_until)+ 图像尺寸/性能调优 | AI 用自然语言指令(含中文输入)完成跨 3 步以上操作且自适应页面变化 |
| M3 | 生产化:安全加固复核、审计看板、部署脚本、接入文档、任务系统互斥回归 | 日常稳定使用 1 周无异常 |
实现状态备注:M0/M1 已实现;M2 部分实现(
de_tap_text/de_tap_element已实现,de_find_and_tap/de_wait_until未实现);M3 未验收。
11. 风险与开放问题
- AI 误操作:模型点击错位/误触关键按钮(如删除)——缓解:L3 定位优先于裸坐标、写门控先行、审计可追溯;不做二次确认(破坏自动化体验),靠白名单+占用互斥兜底
- 截图成本:长会话多截图 → token/延迟成本——缓解:尺寸/质量可调、截图上限、
de_ui_tree作为低成本替代(文本树比图像便宜) - 横竖屏/分辨率异构:设备多样 → 坐标换算依赖截图 1:1;
de_find_and_tap(语义层)不受分辨率影响 - 图像 content block 兼容性:Claude 系原生支持;其他多模态客户端需验证(M0 用 Claude Code 验证)
- 是否接任务系统:二期决策——
de_run_task语义与"单设备操作"不同(任务面向多设备调度),倾向二期用独立命名空间 - clipboard/input 通道差异:个别 MIUI 需授权写入剪贴板的透明 Activity——M0 阶段在受控设备集验证,必要时 fallback 链已在 core 层(设备端 Agent → 旧版 ClipInject)
12. 附录:平台 API 映射(MCP → 平台)
| MCP tool | 实际平台调用(现状) |
|---|---|
| de_list_devices | GET /api/status |
| de_screenshot | GET /api/screen/thumb?serial=(X-Screen-State 头 → screen_state;另 GET /api/screen/size 取原生分辨率) |
| de_ui_tree | GET /api/uiauto/elements(uiautodev) |
| de_ocr | direct_ops.ocr:u2 截图 + core.ocr RapidOCR |
| de_tap | POST /api/screen/tap(snap=1 自动吸附) |
| de_swipe | POST /api/screen/swipe |
| de_press_key | POST /api/screen/key |
| de_tap_text | POST /api/screen/tap_text(UI 树子串匹配 → OCR 兜底) |
| de_tap_element | u2 元素直连(uiautomator2 d(by=value).click(),无平台端点) |
| de_type_text | u2 EditText.set_text(direct_ops.type_text,不走剪贴板通道) |
| de_set_clipboard | core.clipboard_helper 设备端 Agent 通道(inject_clipboard,读回验证) |
| de_read_clipboard | u2 d.clipboard |
| de_open_app | adb monkey 直启(direct_ops.open_app) |
| de_stop_app | adb am force-stop(direct_ops.stop_app) |
| de_foreground_app | adb dumpsys window(direct_ops.foreground_app,mCurrentFocus/mFocusedApp 兜底) |
| de_list_apps | adb pm list packages -3(direct_ops.list_apps) |
| de_wake / de_sleep | POST /api/device/screen_all(mode=on/off) |
| de_list_tasks | GET /api/jobs |
de_screen_state无独立工具,已并入de_screenshot的screen_state字段;设计稿中de_find_and_tap/de_wait_until/de_get_task_state未实现,不在此表。现状中 MCP 直连面(u2/adb)不经平台 REST,但安全(白名单/写门控/busy/审计)仍在 MCP 工具层统一把关,绝不越权触碰平台红线。