Files
auto_control/doc/MCP.md
T
butubb 24d57d3b96 docs: doc/ 全量重整——按现状重写并建立文档索引;项目统一更名 auto_control
背景:文档长期落后于代码(Tab 数、任务类型、接口示例等多处与现状不符),
且信息分散重复。这次按当前代码状态逐篇重写,并建立统一的文档体系。

新增
- doc/README.md:文档总索引(文档地图 / 推荐阅读路径 / **文档维护约定**)
- doc/DATA_MODEL.md:数据模型(7 张模型表 + 5 张非模型表、迁移机制、app_meta 键、
  数据目录、备份覆盖清单与双向自检)
- doc/AI_CONSOLE.md:AI 控制台机制(会话与 SSE、经验库/动作库蒸馏与召回、巡检、
  Markdown 渲染、推理链、token 统计、故障排查)

重写(按现状,去掉过时与重复)
- README.md:7 个 Tab、18 种步骤、设备生命周期、调度/窗口语义、常见问题;修掉
  「6 个 Tab / 分组为顶级 Tab」等过时内容与损坏的目录树
- doc/ARCHITECTURE.md:补启动装配顺序(import 期副作用、A~G 七阶段)、线程与锁清单、
  设备状态机、调度全链路、前端结构与实时通道、设计决策、**已知缺陷与踩坑清单**、扩展点
- doc/API.md:按蓝图重建「接口总索引」(107 条路由含鉴权)+ 分域详细说明 +
  非 JSON 响应汇总 + 错误分支速查
- doc/TASK_DEV.md:18 种步骤全表(参数/默认值/语义)、容器与公共参数、
  选择器与 XPath 序号语义、抓取器建议规则、新增任务类型骨架
- doc/DEPLOY.md:容器入口 start.sh 三件事、发布流程与检查清单、备份覆盖红线、
  按现象分类的故障排查
- doc/DEVELOPMENT.md:流程/红线/本地开发/**测试与写测试的约定**/配置速查/文档同步
- doc/MCP.md:19 个工具的参数级清单、坐标空间、写门控三连、安全与审计
- doc/MCP_DESIGN.md、doc/AI_TASK_GEN.md:标注设计 vs 实现现状,补交叉链接
- doc/backlog/TODO.md:新增「已知缺陷」小节(含复现与影响)+ 已完成留档
- .env.example:按代码实际读取的键重写(补 USB/DISCOVERY/MCP/AGENT,删死配置)

其它
- 项目名统一 auto_control:README/文档/scripts/pack.py 产物名;代码内的
  doc 章节引用(templates/admin/monitor.html)同步更新
- 校验:16 篇文档 156 条相对链接全部可解析;文档中的关键数字与代码核对一致
  (19 个 MCP 工具 / 18 种步骤 / 12 张备份表 / 1 种任务类型)
2026-09-10 22:19:18 +08:00

204 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# MCP 手机控制手册(MCP.md)
多模态 AI(DeepSeek / Claude 等)通过 [MCP(Model Context Protocol)](https://modelcontextprotocol.io) 操作 Android 手机的统一出口。平台自带的「AI 控制台」(`mcp_agent/`)与外部 MCP 客户端使用的都是这一批工具。
> 本文 = **使用手册**(工具清单 / 用法 / 接入)。设计取舍与演进见 [MCP_DESIGN.md](MCP_DESIGN.md);AI 控制台的会话/经验/动作机制见 [AI_CONSOLE.md](AI_CONSOLE.md)。
---
## 1. 架构与边界
```
AI 控制台 / 外部 MCP 客户端
│ Streamable HTTP(http://<host>:8033/mcp)
▼
MCP Server(mcp_server/mcp_server.py) ← 19 个 de_* 工具
│ 平台 HTTP API(登录 + CSRF) 或 轻量 adb/u2 直连
▼
auto_control 平台(:18050) ← 设备池 / 任务 / 看屏
│ adb / uiautomator2 / uiautodev / OCR
▼
Android 设备(IP:5555)
```
- **轻量通道直连**(adb monkey 开 App、u2 输入、本地 OCR)不绕平台,省时间省 token
- **坐标换算、UI 吸附、剪贴板注入**等细节全部在 Server 层消化,模型只给"意图"
- **只暴露设备层能力**:平台级的任务增改/分组/设备池/APK/备份等 REST 只给 Web 前端用,未做成 MCP 工具(补齐规划见 [AI_TASK_GEN.md](AI_TASK_GEN.md) §9)
---
## 2. 运行与配置
生产跑在 220 的 `python-app` 容器内(`scripts/start.sh` 自动拉起),端点 `http://192.168.20.220:8033/mcp`。
```bash
# 本机手动启动
MCP_ALLOW_WRITE=1 MCP_PLATFORM_USER=admin MCP_PLATFORM_PASS=<平台密码> \
MCP_AUDIT_FILE=data/mcp_audit.log python -m mcp_server.mcp_server
```
| 环境变量 | 默认 | 说明 |
|---|---|---|
| `MCP_PLATFORM_URL` | `http://127.0.0.1:18050` | 平台地址 |
| `MCP_PLATFORM_USER` / `MCP_PLATFORM_PASS` | `admin` / 空(`start.sh` 兜底 `admin123`) | 平台登录凭据。**改过 admin 密码必须同步**,否则登录失败 |
| `MCP_ALLOW_WRITE` | `0`(`start.sh` 内强制 `1`) | **写门控**:=1 才允许点击/输入/开关 App(只读工具不受限) |
| `MCP_ALLOWED_SERIALS` | 空 | 设备白名单(逗号分隔)。非空 = 只允许列出的 serial;**为空时只校验 serial 非空,不按平台设备池过滤**(语义缺口见 [backlog](backlog/TODO.md)) |
| `MCP_HTTP_HOST` / `MCP_HTTP_PORT` | `0.0.0.0` / `8033` | 监听 |
| `MCP_SCREENSHOT_WIDTH` | `540` | 截图返回宽度上限(模型看到的坐标系) |
| `MCP_JPEG_QUALITY` | `70` | 截图 JPEG 质量 |
| `MCP_AUDIT_FILE` | `/var/log/mcp/audit.log`(容器兜底 `/tmp/mcp_audit.log`) | 审计日志路径 |
| `MCP_PLATFORM_TIMEOUT` | `30` | 平台请求超时(秒) |
| `MCP_ENABLED` | `1` | **只被 `scripts/start.sh` 消费**:=0 则不后台拉起 MCP |
> `mcp_server/config.py` **只读进程环境变量,不读 `.env`**;`scripts/supervise.sh` 不拉起 MCP(容器场景由 `start.sh` 负责)。
---
## 3. 坐标空间(重要)
- `de_screenshot` 返回 **≤540px 宽**的 JPEG(display 空间),同时给出 `native_size`(设备原生分辨率)与 `screen_state`
- `de_tap` / `de_swipe` 的坐标一律用 **`de_screenshot` 返回图像的坐标系**,Server 按比例换算成原生坐标
- **必须先截图再点击**:Server 需要最近一次截图来建立坐标空间,否则报 `invalid_param`「请先对该设备执行 de_screenshot」
- `de_tap` 带**自动吸附**:落点若在某个可点击元素内,实际点该元素中心 → 坐标**只需大致对准**;返回 `snapped` / `label` 便于核对
---
## 4. 工具清单(19 个)
统一返回约定:
```json
{"ok": true, "data": { … }}
{"ok": false, "error": {"code": "device_busy", "message": "设备正在执行任务…"}}
```
错误码:`invalid_param` · `device_not_allowed` · `write_disabled` · `device_busy` · `platform_unavailable` · `device_offline` · `text_not_found`。
### 4.1 设备与状态(只读)
| 工具 | 参数 | 返回 / 说明 |
|------|------|------------|
| `de_list_devices` | — | `[{serial, model, online, task_job, worker_status, foreground_app}]`。**开局第一步** |
| `de_foreground_app` | `serial` | `{foreground_app}` 当前前台包名(dumpsys,MIUI 焦点为空时兜底) |
| `de_list_tasks` | — | 平台任务计划 `[{id,name,task_type,enabled,schedule}]`(只读) |
### 4.2 观察屏幕(只读)
| 工具 | 参数 | 返回 / 说明 |
|------|------|------------|
| `de_screenshot` | `serial` | `{image:{type:"image",data:<base64>,mimeType:"image/jpeg"}, width, height, native_size, screen_state}`。多模态模型直接看图 |
| `de_ui_tree` | `serial`, `limit`(150,1-300) | `{count, elements:[{text,id,desc,class,clickable,bounds}]}`,**可点击元素排前**;`limit` 控 token |
| `de_ocr` | `serial` | `{count, texts:[{text,score}]}`(≤100 条)。UI 树拿不到的图片/WebView 文字用它 |
| `de_read_clipboard` | `serial` | `{clipboard}` |
| `de_list_apps` | `serial`, `keyword`("") | `{count, packages}` 第三方已装包名(`pm list packages -3`,≤200) |
### 4.3 点击与滑动(**写操作**)
| 工具 | 参数 | 说明 |
|------|------|------|
| `de_tap_text` | `serial`, `text`(≤100 字符) | **推荐**:按屏幕可见文字点击。原生控件走 UI 树,WebView/图片文字自动 **OCR 兜底**;找不到 → `text_not_found` |
| `de_tap_element` | `serial`, `by`(`text`/`id`/`desc`/`text_contains`/`desc_contains`), `value`, `index`(1) | 按元素属性点击,无需坐标;多命中用 `index` |
| `de_tap` | `serial`, `x`, `y` | 坐标点击(截图坐标系 + 自动吸附)。**纯图形目标才用** |
| `de_swipe` | `serial`, `x1,y1,x2,y2`, `duration`(0.2) | 滑动(长按 = 同点起止 + `duration≥1`) |
| `de_press_key` | `serial`, `key` | `back` / `home` / `recent` / `menu` / `power` / `volume_up` / `volume_down` / `enter` / `delete` / `search` / `camera` |
### 4.4 输入与剪贴板(**写操作**)
| 工具 | 参数 | 说明 |
|------|------|------|
| `de_type_text` | `serial`, `text` | 向当前输入框输入(支持中文,直设 EditText,不依赖剪贴板) |
| `de_set_clipboard` | `serial`, `text` | 写入设备剪贴板(ClipInject 通道 + 读回验证) |
### 4.5 App 管理(**写操作**,走 adb 直连不建 u2 会话)
| 工具 | 参数 | 说明 |
|------|------|------|
| `de_open_app` | `serial`, `package` | `adb monkey` 直启(最快路径,无需知道 activity) |
| `de_stop_app` | `serial`, `package` | `am force-stop` |
### 4.6 亮屏 / 熄屏(**写操作**)
| 工具 | 参数 | 说明 |
|------|------|------|
| `de_wake` | `serial` | 亮屏并解锁(熄屏时先调它再截图) |
| `de_sleep` | `serial` | 熄屏(**会中断正在运行的任务,慎用**) |
> 后两组分别经 `direct_ops`(adb)与平台 `POST /api/device/screen_all` 实现,不在 MCP 进程内建 u2 会话。
### 4.7 写操作门控(三连)
所有写工具在执行前依次检查:
1. `_check_write()` —— `MCP_ALLOW_WRITE=1`?否则 `write_disabled`
2. `_check_serial()` —— serial 非空 / 在白名单内?否则 `device_not_allowed`
3. `_ensure_device_free()` —— 设备 `worker_status` 不是 `running`/`connecting`?否则 `device_busy`(**AI 不与任务抢设备**)
> 设备状态查询有 **5 秒缓存**;查询本身异常时**放行**(不阻塞),由平台侧兜底。
---
## 5. 推荐使用模式
1. **`de_list_devices`** 确认目标设备在线
2. **`de_screenshot`** 看图理解当前界面(图像会在下一回合送达模型)
3. **点击定位优先级**:
- 目标有可见文字 → **`de_tap_text`**(一次调用完成"找到并点击",最可靠)
- 文字有歧义/多候选 → `de_ui_tree` 确认后用 `de_tap_element`(可用 `text_contains` 模糊匹配)
- 纯图形目标 → `de_tap` 坐标(**无需精算**,会自动吸附)
4. **输入文字**:先点中输入框,再 `de_type_text`
5. **每步验证**:关键操作后再 `de_screenshot`——界面变了 = 成功;没变 = 未命中 → 换 `de_tap_text`/`de_tap_element` 重新定位,**不要重复点同一坐标**
6. **收尾**:用中文总结做了什么、当前状态、注意事项
7. **效率**:界面未变时不重复截图/点击;连续 6 步无进展就停止并总结
---
## 6. 安全与审计
| 机制 | 说明 |
|------|------|
| **写门控** | `MCP_ALLOW_WRITE=0`(默认)时所有写操作被拒,只读可用 |
| **任务互斥** | 写操作前检查设备是否在跑任务,忙则 `device_busy` |
| **平台会话** | `platform_client` 内部登录(`MCP_PLATFORM_USER/PASS`)、会话失效自动重登、POST 自动带 `X-CSRF-Token`;**平台凭据不暴露给客户端** |
| **白名单** | `MCP_ALLOWED_SERIALS` 非空时限制可操作 serial |
| **审计** | 每次调用(**含只读**)写一行 JSON 到 `MCP_AUDIT_FILE`:`{ts, tool, serial, args 摘要, result 摘要}` |
| **红线** | 任何工具都不执行 `adb kill-server` / `adb disconnect` |
| **端点鉴权** | ⚠️ MCP HTTP 端点自身**没有** token/账号校验,只做出站登录 → **必须靠网络隔离**(同机/内网),不要直接暴露公网 |
---
## 7. 客户端接入示例
```python
import asyncio
from fastmcp import Client
async def main():
async with Client("http://192.168.20.220:8033/mcp", timeout=30) as c:
devs = await c.call_tool("de_list_devices", {})
serial = devs.data["data"][0]["serial"] # 第一台设备
shot = await c.call_tool("de_screenshot", {"serial": serial})
img = shot.data["data"]["image"] # base64 JPEG(给多模态模型看)
await c.call_tool("de_tap_text", {"serial": serial, "text": "搜索"})
asyncio.run(main())
```
> 给外部数字员工的使用约定与红线,另见 [staffdeck/KNOWLEDGE_BASE.md](staffdeck/KNOWLEDGE_BASE.md)。
---
## 8. AI 控制台(内置 Agent)
Web 的「AI 控制台」Tab 内建 Agent(`mcp_agent/`,OpenAI 兼容模型)用的就是本 Server 的同一批工具:
- 流式输出 + 每步 MCP 调用实时展示(含截图缩略)
- 多轮会话(同会话保留上下文)
- **自进化记忆**:经验库(任务配方)+ 动作库(命名动作),相似任务自动注入参考
- 模型与 Key 在控制台右上角 ⚙ 配置(存平台 `app_meta`)
> **依赖提示**:AI 控制台依赖本 MCP Server(默认 `http://127.0.0.1:8033/mcp`)。未启动时平台会把 SDK 的含糊报错映射成明确文案「**MCP server(8033) 不可达 …**」。
---
> **维护约定**:新增 / 修改 / 删除任何一个 MCP 工具或相关配置,必须**同步更新本文与 [MCP_DESIGN.md](MCP_DESIGN.md)**(doc 同步红线)。