Files
butubb 7ce4ae9016 feat(账号): 账号台账(web 页 + 任务取号 + 设备端身份页);fix(剪贴板): 注入通道改走设备端 Agent
一、账号台账(新表 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 会读到上一次的内容 → 误报"写入可能被拒"(实测内容已写入却回失败)
2026-09-24 16:12:58 +08:00

11 KiB
Raw Permalink Blame History

MCP 手机控制手册(MCP.md)

多模态 AI(DeepSeek / Claude 等)通过 MCP(Model Context Protocol) 操作 Android 手机的统一出口。平台自带的「AI 控制台」(mcp_agent/)与外部 MCP 客户端使用的都是这一批工具。

本文 = 使用手册(工具清单 / 用法 / 接入)。设计取舍与演进见 MCP_DESIGN.md;AI 控制台的会话/经验/动作机制见 AI_CONSOLE.md。


1. 架构与边界

AI 控制台 / 外部 MCP 客户端
        │  Streamable HTTP(http://<host>:8033/mcp)
        ▼
MCP Server(mcp_server/mcp_server.py)        ← 20 个 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 §9)

2. 运行与配置

生产跑在 220 的 python-app 容器内(scripts/start.sh 自动拉起),端点 http://192.168.20.220:8033/mcp。

# 本机手动启动
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)
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. 工具清单(20 个)

统一返回约定:

{"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, name, model, online, task_job, worker_status, foreground_app}]。开局第一步;name 是设备在平台里的名称(身份),向用户汇报时用名称(同型号多台靠它区分),调工具仍用 serial
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_snapshot serial, limit(120,1-300) 截图 + 元素树一次取齐(推荐用它代替 de_screenshot+de_ui_tree):平台侧同一个 u2 连接背靠背取 + 双截图校验。返回 {image, width, height, native_size, screen_state, unstable, cost_ms, count, elements};unstable=true 表示抓取期间界面在变化(此时元素坐标不可信,让设备静下来再取)。同样会建立 de_tap/de_swipe 的坐标空间
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 写入设备剪贴板(设备端 Agent 通道 + 读回验证)

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. 客户端接入示例

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。


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(doc 同步红线)。