# MCP 手机控制(Mobile Control MCP Server)设计文档 > 状态:**设计稿 / 演进记录**——凡与实现不符处,以 [MCP.md](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 协议支持 `image` content 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 追加),image `python: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](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_tasks` > - `de_type_text` 实际签名 `(serial, text)`,走 u2 `EditText.set_text`(非设计稿的 ClipInject+paste 优先) > - `de_tap` 坐标语义已是**截图坐标系、server 按比例换算原生**(非设计稿「原生像素、调用方自换算」) > - `de_open_app` 是 **adb monkey 直启**(非 u2 `app_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](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: , width, height}` ## 7. 图像链路细节 1. 平台截图(`/api/screen/thumb`)→ JPEG 字节 2. MCP Server 校验尺寸 → 等比缩放到 `MCP_SCREENSHOT_WIDTH`(用 Pillow,不重压缩已够质量则跳过) 3. 组装 MCP `image` content block(`{"type":"image","data":,"mimeType":"image/jpeg"}`) 4. 客户端(Claude)原生把 image 传入视觉上下文 性能预算:540px JPEG ~40-80KB/张;模型一次多步操作 5-15 张 ≈ 1MB 以内,可接受。 ## 8. 认证与安全 1. **平台会话**:MCP Server 启动时用 `MCP_PLATFORM_USER/PASS` 登录平台拿会话(Cookie),会话失效自动重登;不向客户端暴露平台凭据 2. **写操作门控**:`MCP_ALLOW_WRITE=0`(默认)时 L2/L3 操作全部拒绝(`write_disabled`)——先部署只读,验证感知链路后再开写 3. **设备白名单**:`MCP_ALLOWED_SERIALS` 指定;为空时自动限制为**设备池 enabled 设备**(平台口径)——**此为设计目标,当前代码未实现**:实现只校验 serial 非空,空名单不按设备池过滤(如需收紧请配置白名单) 4. **占用互斥**:写操作前查设备 `worker_status`——running/connecting 中拒绝(`device_busy`),避免 MCP 与任务打架(已实现,只读工具不受限) 5. **审计**:每次调用(含只读)写审计日志:`ts|tool|serial|args摘要|result`;落盘 `MCP_AUDIT_FILE` 6. **传输安全**:内网部署(host 网络 8033)默认无 TLS;若外网暴露需前置 TLS/网络隔离(平台红线:设备控制接口不进公网) 7. **无状态**: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](MCP.md) 与 DEPLOY §2.4。 ```yaml 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): ```json // .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. 风险与开放问题 1. **AI 误操作**:模型点击错位/误触关键按钮(如删除)——缓解:L3 定位优先于裸坐标、写门控先行、审计可追溯;**不做**二次确认(破坏自动化体验),靠白名单+占用互斥兜底 2. **截图成本**:长会话多截图 → token/延迟成本——缓解:尺寸/质量可调、截图上限、`de_ui_tree` 作为低成本替代(文本树比图像便宜) 3. **横竖屏/分辨率异构**:设备多样 → 坐标换算依赖截图 1:1;`de_find_and_tap`(语义层)不受分辨率影响 4. **图像 content block 兼容性**:Claude 系原生支持;其他多模态客户端需验证(M0 用 Claude Code 验证) 5. **是否接任务系统**:二期决策——`de_run_task` 语义与"单设备操作"不同(任务面向多设备调度),倾向二期用独立命名空间 6. **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 工具层统一把关,绝不越权触碰平台红线。