1. 移动端自动化的痛点与 mobile-mcp 的破局思路
做过移动端自动化的人都有一个共同的感受:环境搭建比写用例还累。Appium 要装 Node、配 JDK、下 Android SDK、搞 WebDriverAgent 签名,一套流程走下来,半天时间没了,代码一行还没写。更别提跨平台了,Android 和 iOS 两套完全不同的工具链,维护成本直接翻倍。而 mobile-mcp 这个项目的出现,本质上是想用 MCP(Model Context Protocol)这套协议,把移动端设备的能力抽象成标准化的工具接口,让 AI Agent 或者自动化脚本通过统一的协议去操控手机,不再关心底层是 ADB 还是 WebDriverAgent。
先说清楚 mobile-mcp 是什么。它是一个基于 MCP 协议实现的移动端控制服务,核心思路是把 iOS 和 Android 设备的常见操作——点击、滑动、输入、截图、获取 UI 树、启动 App、安装应用等——封装成 MCP 工具,然后通过标准输入输出或者 SSE 的方式暴露给上层调用方。这意味着任何支持 MCP 协议的客户端,比如 Claude Desktop、各种 AI IDE、甚至你自己写的 Agent 框架,都能直接调用这些工具来控制真机或模拟器。
它解决的核心问题有三个。第一是协议统一,以前 Android 用 ADB、iOS 用 WebDriverAgent,接口风格完全不同,现在都收敛到 MCP 的工具调用格式。第二是AI 友好,MCP 本身就是为 LLM 设计的协议,工具描述、参数 schema 都是结构化的,AI 能直接理解每个工具干什么、需要什么参数。第三是降低门槛,你不需要精通 Appium 的 caps 配置,也不需要手动处理 WDA 的 session 管理,mobile-mcp 把这些脏活累活都包了。
适合谁来用?如果你在做移动端自动化测试、想用 AI Agent 操控手机做任务、或者需要批量管理多台设备做群控类操作,这个项目值得花时间研究。前端开发者想快速验证移动端页面的交互逻辑,也可以用它在真机上跑一些简单的自动化脚本。但如果你只是偶尔手动点几下手机,那确实没必要上这套工具。
2. 核心架构拆解:MCP 协议如何桥接移动设备
2.1 MCP 协议的本质:给 AI 用的 USB 接口
MCP 全称 Model Context Protocol,你可以把它理解成“AI 世界的 USB 接口标准”。以前每个 AI 应用要调用外部工具,都得自己写一套适配层,A 工具用 JSON-RPC,B 工具用 REST,C 工具用 gRPC,乱得不行。MCP 做的事情就是定义一套统一的通信规范,包括工具发现(tools/list)、工具调用(tools/call)、资源读取(resources/read)等标准方法,让 AI 客户端和工具服务端能即插即用。
mobile-mcp 作为 MCP 服务端,启动后会向客户端注册一系列工具。客户端连接上来第一件事就是发tools/list请求,拿到所有可用工具的名称、描述和参数 schema。比如mobile_click工具的描述可能是“在指定坐标点击屏幕”,参数是{x: number, y: number}。AI 看到这个描述就知道什么时候该调用它、怎么传参。这种设计的好处是工具是自描述的,新增一个工具不需要改客户端代码,AI 自动就能发现并使用。
传输层方面,mobile-mcp 通常支持两种模式:stdio 和 SSE。stdio 模式适合本地进程间通信,客户端把 mobile-mcp 当子进程启动,通过标准输入输出交换 JSON-RPC 消息。SSE 模式适合远程调用,mobile-mcp 跑在一台机器上,客户端通过网络连接过来。两种模式各有适用场景,本地开发调试用 stdio 更方便,多设备集中管理用 SSE 更合理。
2.2 双平台适配层:Android 走 ADB,iOS 走 WDA
mobile-mcp 内部对 Android 和 iOS 做了差异化处理,但对外暴露的工具接口是一致的。Android 侧主要依赖 ADB(Android Debug Bridge),通过adb shell input tap、adb shell input swipe、adb shell uiautomator dump等命令实现点击、滑动、UI 树获取。截图用adb exec-out screencap -p,安装应用用adb install,启动 App 用adb shell am start。这些都是 ADB 的标准能力,稳定性和兼容性经过多年验证。
iOS 侧就复杂一些,核心依赖 WebDriverAgent(WDA)。WDA 是 Facebook 开源的一个 WebDriver 服务,跑在 iOS 设备上,通过 HTTP 接口接收指令并调用 XCUITest 框架执行。mobile-mcp 需要先确保 WDA 已经安装并运行在目标设备上,然后通过 HTTP 请求与 WDA 通信。点击操作对应 WDA 的/session/:id/wda/touch/perform接口,截图对应/screenshot,UI 树对应/source。WDA 的痛点在于签名和部署,免费开发者账号每 7 天要重新签名,这是绕不过去的坑。
注意:iOS 真机使用 WDA 需要开启开发者模式,并且设备要信任对应的开发者证书。模拟器相对简单,不需要签名,但模拟器无法覆盖所有真机场景,比如某些传感器、摄像头、生物识别相关的功能。
2.3 工具集设计:从原子操作到组合能力
mobile-mcp 的工具集设计遵循“原子操作优先”的原则,每个工具只做一件事,复杂流程由上层组合。目前核心工具大概分几类:
| 工具类别 | 代表工具 | 功能说明 |
|---|---|---|
| 设备管理 | list_devices | 列出当前连接的设备 |
| 应用管理 | install_app、launch_app、terminate_app | 安装、启动、终止应用 |
| 交互操作 | click、swipe、input_text、press_key | 点击、滑动、输入、按键 |
| 信息获取 | screenshot、get_ui_tree、get_screen_size | 截图、UI 树、屏幕尺寸 |
| 系统操作 | press_home、press_back、open_url | Home 键、返回键、打开链接 |
这种设计的优势在于可组合性。比如你要实现“打开微信,搜索一个联系人,发一条消息”,可以拆解成:launch_app(wechat)→click(搜索框坐标)→input_text(联系人名)→click(搜索结果)→input_text(消息内容)→click(发送按钮)。每一步都是独立的工具调用,AI 可以根据 UI 树的反馈动态调整下一步操作,而不是写死一个固定流程。
3. 环境搭建实操:从零到跑通第一条指令
3.1 Android 侧环境准备
Android 的环境相对简单,核心就是 ADB 和 USB 驱动。先确认你的电脑上装了 ADB,命令行执行adb version能看到版本号就行。如果没有,去 Android 开发者官网下载 platform-tools 包,解压后把路径加到系统环境变量里。Windows 用户注意 USB 驱动,大部分国产手机需要单独装驱动,或者去厂商开发者官网下载。Mac 和 Linux 通常免驱,插上就能识别。
手机端要开启开发者选项和 USB 调试。开发者选项的开启方式一般是连续点击“关于手机”里的版本号 7 次,不同厂商略有差异。开启后在开发者选项里找到“USB 调试”并打开。插上电脑后手机会弹出一个“允许 USB 调试吗”的对话框,勾选“始终允许”并确认。这时候在电脑上执行adb devices,应该能看到设备列表里有一台设备,状态是device。如果显示unauthorized,说明手机上还没确认授权;如果显示offline,试试重新插拔或者adb kill-server && adb start-server。
实操心得:有些国产手机即使开了 USB 调试,ADB 连接也不稳定,经常断连。我试过在开发者选项里额外打开“USB 调试(安全设置)”和“禁止权限监控”,断连问题会好很多。另外,用原装数据线,别用那种只能充电的劣质线,这是很多人踩过的坑。
3.2 iOS 侧环境准备
iOS 侧的门槛明显高一个档次。首先你需要一台 Mac,Windows 和 Linux 没法跑 WDA。然后装 Xcode,从 App Store 下载就行,安装完成后打开一次,同意许可协议,让它自动安装必要的组件。接着装 Appium 或者直接装 WDA,如果你用 Appium,npm install -g appium然后appium driver install xcuitest就行。如果直接用 WDA,去 GitHub 克隆 WebDriverAgent 仓库,用 Xcode 打开项目,配置签名后部署到设备上。
签名是 iOS 自动化最大的坑。免费开发者账号每 7 天要重新签名一次,而且最多只能同时签名 3 个 App。付费开发者账号(每年 99 美元)可以一年签一次,适合长期使用。签名配置在 Xcode 的 Signing & Capabilities 里,选择你的 Team,修改 Bundle Identifier 为一个唯一的字符串,然后点 Build 部署到设备。部署成功后,手机上会多一个 WebDriverAgentRunner 的 App,点开它,如果屏幕上显示一个 IP 地址和端口号,说明 WDA 跑起来了。
注意:iOS 16 以后,真机使用 WDA 需要在“设置 → 隐私与安全性 → 开发者模式”里开启开发者模式,并且重启设备。这个选项默认是隐藏的,需要先用 Xcode 部署一次任意 App 才会出现。
3.3 mobile-mcp 安装与配置
mobile-mcp 本身通常是一个 Node.js 项目或者 Python 项目,安装方式取决于具体实现。假设是 Node.js 版本,npm install -g mobile-mcp或者从源码git clone后npm install && npm run build。安装完成后,你需要配置 MCP 客户端来连接它。以 Claude Desktop 为例,编辑配置文件claude_desktop_config.json,在mcpServers里加一段:
{ "mcpServers": { "mobile-mcp": { "command": "npx", "args": ["-y", "mobile-mcp"], "env": { "ANDROID_HOME": "/path/to/android/sdk", "DEVICE_ID": "your-device-id" } } } }配置完成后重启客户端,如果一切正常,你会在工具列表里看到 mobile-mcp 注册的那些工具。这时候可以试着调用list_devices,看看能不能正确返回设备列表。如果返回空列表,检查 ADB 是否能识别设备;如果报错连接失败,检查环境变量路径是否正确。
4. 核心工具实战:点击、滑动、输入与 UI 树解析
4.1 坐标点击与元素定位的取舍
mobile-mcp 的点击工具通常接受坐标参数,而不是元素选择器。这是有意为之的设计选择。坐标点击的好处是跨平台一致,Android 和 iOS 都支持,不依赖特定框架的元素定位能力。坏处是脆弱,屏幕分辨率一变、布局一改,坐标就失效了。所以实际使用中,坐标通常不是硬编码的,而是通过 UI 树动态计算出来的。
UI 树是 mobile-mcp 最核心的能力之一。Android 侧通过uiautomator dump获取当前界面的 XML 结构,iOS 侧通过 WDA 的/source接口获取。UI 树里包含了每个元素的类型、文本、坐标、尺寸、可点击性等属性。你可以写一个简单的查找逻辑:遍历 UI 树,找到文本等于“登录”的按钮,取它的中心坐标,然后调用click。这样即使布局变了,只要按钮文本没变,脚本依然能跑通。
# 伪代码示例:通过 UI 树查找元素并点击 def find_and_click(ui_tree, text): for node in ui_tree.iter(): if node.text == text and node.clickable: x = node.bounds.center_x y = node.bounds.center_y mobile_mcp.click(x, y) return True return False实操心得:Android 的 UI 树有时候会漏掉一些自定义 View 的文本,因为开发者没有设置 contentDescription。这种情况下只能靠坐标或者图像识别兜底。iOS 的 UI 树相对完整,但 WebView 内部的元素需要额外开启
webview相关的配置才能拿到。
4.2 滑动操作的参数计算
滑动工具一般接受起点坐标、终点坐标和持续时间三个参数。持续时间很关键,太短会被系统识别成 fling(快速滑动),太长又会影响执行效率。经验值:普通滚动列表用 300 到 500 毫秒,翻页用 200 到 300 毫秒,下拉刷新用 500 到 800 毫秒。滑动距离一般取屏幕高度的 1/3 到 1/2,太短了没效果,太长了容易滑过头。
Android 的滑动命令是adb shell input swipe x1 y1 x2 y2 duration,iOS 对应 WDA 的/wda/touch/perform接口,传一个 actions 数组描述按下、移动、抬起的完整过程。mobile-mcp 把这两个接口统一成了swipe工具,参数格式一致,底层自动适配。实际使用中,我习惯先调get_screen_size拿到屏幕宽高,然后按比例计算滑动坐标,这样换一台分辨率不同的设备也不用改代码。
4.3 文本输入的坑与绕行方案
文本输入看起来简单,实际上坑最多。Android 的adb shell input text不支持中文和特殊字符,输入中文需要借助 ADBKeyboard 这样的第三方输入法,或者用adb shell am broadcast发送广播。iOS 的 WDA 输入中文也有问题,需要设置typeText的编码方式,或者用粘贴板的方式绕过。
mobile-mcp 通常会对这些差异做封装,但底层限制依然存在。我的建议是:如果目标 App 支持粘贴板输入,优先用粘贴板。流程是先把文本写到系统粘贴板,然后模拟长按输入框、点击粘贴。Android 用adb shell am broadcast -a clipper.set -e text "内容",iOS 用pbcopy配合 WDA 的粘贴操作。这种方式兼容性最好,中文、emoji、特殊符号都能搞定。
注意:部分 App 会禁用粘贴功能,或者对粘贴板内容做校验。这种情况下只能老老实实用输入法方案,或者让 AI 生成拼音再逐个选字,但效率很低,不推荐。
5. 多设备管理与批量操作实战
5.1 设备发现与连接池设计
当你有十几台甚至几十台设备需要同时操作时,单设备的管理方式就不够用了。mobile-mcp 支持通过list_devices获取所有已连接设备,然后每个工具调用都可以指定device_id参数来路由到特定设备。这为批量操作提供了基础。
实际落地时,我建议做一个简单的连接池。启动时扫描所有设备,为每台设备建立一个会话上下文,记录设备状态(在线、离线、忙碌、空闲)。任务调度器从队列里取任务,分配给空闲设备,执行完成后释放。这样能最大化设备利用率,避免某台设备卡死导致整个队列阻塞。
# 伪代码:简单的设备连接池 class DevicePool: def __init__(self): self.devices = {} for d in mobile_mcp.list_devices(): self.devices[d.id] = {"status": "idle", "session": None} def acquire(self): for did, info in self.devices.items(): if info["status"] == "idle": info["status"] = "busy" return did return None # 没有空闲设备 def release(self, device_id): self.devices[device_id]["status"] = "idle"5.2 并行任务编排与异常隔离
多设备并行执行时,最大的风险是异常扩散。一台设备上的脚本崩溃了,不能影响其他设备。所以每个设备的执行上下文要完全隔离,异常要捕获并记录,不能往上抛导致整个调度器挂掉。我通常用 Python 的concurrent.futures.ThreadPoolExecutor或者asyncio来做并发,每个任务包一层 try-except,失败的任务重新入队或者标记为失败。
另一个问题是设备漂移。长时间运行后,某些设备可能会因为内存不足、系统更新、USB 断连等原因掉线。调度器需要定期健康检查,发现掉线的设备及时从池子里移除,并尝试重连。重连失败就告警,让人工介入。这套机制听起来复杂,但用几十行代码就能实现,比事后手动排查划算得多。
5.3 批量安装与配置下发
批量操作里最常见的场景是给所有设备安装同一个 App,或者下发同一份配置文件。Android 用adb -s <device_id> install app.apk,iOS 用xcrun simctl install或者 WDA 的安装接口。mobile-mcp 的install_app工具封装了这些差异,你只需要传 APK 或 IPA 的路径和目标设备 ID。
配置下发稍微麻烦一点。Android 可以通过adb push把文件推到/sdcard/目录,然后 App 自己去读。iOS 因为沙盒机制,不能直接推文件到 App 的沙盒里,只能通过 App 自身的导入功能或者 iCloud 同步。如果 App 支持 URL Scheme 导入,可以用open_url工具触发。如果不支持,那就只能走 UI 自动化,模拟用户手动导入的流程。
实操心得:批量安装时,Android 的
adb install默认是阻塞的,一台装完才装下一台。加-r参数可以覆盖安装,加-d可以降级安装。如果要并行安装,用adb -s <id> install配合多线程,但注意 USB 带宽有限,同时装太多台会互相拖慢,一般 4 到 6 台并行比较合适。
6. 常见问题排查与避坑指南
6.1 连接类问题速查
| 现象 | 可能原因 | 排查步骤 |
|---|---|---|
adb devices列表为空 | USB 驱动未装、线缆问题、调试未开 | 换线、换口、重装驱动、确认开发者选项 |
设备显示unauthorized | 手机未确认授权弹窗 | 拔插重试,勾选“始终允许” |
设备显示offline | ADB 服务异常 | adb kill-server && adb start-server |
| WDA 启动失败 | 签名过期、开发者模式未开 | 重新签名、开启开发者模式并重启 |
| MCP 客户端连不上 | 配置路径错误、端口占用 | 检查配置文件、确认进程启动、看日志 |
6.2 执行类问题与绕行方案
点击无效是最常见的问题。原因可能是坐标算错了、元素被遮挡、或者 App 做了防自动化检测。排查方法:先截图看当前界面,确认目标元素确实在预期位置;然后检查 UI 树里该元素的clickable属性,如果是 false,可能需要点击它的父节点;如果都正常但还是点不中,试试加一点随机偏移,有些 App 会检测完全一致的点击坐标。
输入框无法输入,除了前面说的中文问题,还有一种情况是输入框没有获取焦点。解决方法是先点击输入框,等 200 到 300 毫秒,再执行输入。有些 App 的输入框点击后会弹出一个自定义键盘,这时候input_text可能不生效,需要改用模拟按键的方式逐个字符输入。
截图返回黑屏,Android 上通常是因为 App 设置了FLAG_SECURE,禁止截屏。这是安全机制,没法绕过。iOS 上如果 WDA 截图黑屏,检查是不是设备锁屏了,或者 WDA 的 session 断了。重新连接 session 通常能解决。
6.3 稳定性优化的几个关键点
第一,加等待。不要假设点击后界面立刻响应,每次操作后加一个 500 毫秒到 1 秒的等待,或者用轮询的方式检查目标元素是否出现。轮询比固定等待更高效,但实现起来复杂一点。
第二,重试机制。网络波动、设备卡顿都可能导致单次操作失败,关键步骤加 2 到 3 次重试,每次重试前先截图确认当前状态,避免在错误的状态下继续操作。
第三,日志留痕。每一步操作都记录时间戳、设备 ID、操作类型、参数、结果。出问题的时候,日志是唯一的线索。我习惯把截图也存下来,按时间戳命名,排查的时候一目了然。
第四,定期重启。长时间运行的设备,ADB 连接和 WDA session 都可能变得不稳定。我的做法是每跑完 50 到 100 个任务,主动断开重连一次,清理一下状态。这个操作看起来多余,但能避免很多莫名其妙的偶发失败。
7. 与 AI Agent 结合的进阶玩法
mobile-mcp 最大的想象空间在于和 AI Agent 的结合。传统的移动端自动化是“写死流程”,每一步都预先定义好。而 AI Agent 可以根据当前屏幕内容动态决策,遇到没见过的界面也能尝试操作。比如你说“帮我在外卖 App 上点一份黄焖鸡米饭”,Agent 会自己打开 App、搜索、选店、加购物车、下单,全程不需要你写一行定位代码。
实现路径是:Agent 拿到用户指令后,先调get_ui_tree和screenshot了解当前界面,然后推理下一步该调什么工具。比如看到搜索框,就调click点击搜索框,再调input_text输入关键词。每一步执行完再获取一次 UI 树,确认操作结果,然后继续推理。这个循环直到任务完成或者 Agent 判断无法继续为止。
这种模式对 UI 树的依赖很重,UI 树的质量直接决定 Agent 的成功率。所以实际部署时,建议对目标 App 做一定的适配,比如给关键元素加上contentDescription或者accessibilityLabel,让 UI 树更完整。另外,Agent 的推理速度受限于 LLM 的响应时间,复杂任务可能需要几十秒甚至几分钟,适合对时效性要求不高的场景。
实操心得:AI Agent 操控手机目前还处于早期阶段,成功率远不如传统脚本。我的建议是混合使用:固定流程用脚本,异常处理用 Agent。比如正常下单流程用脚本跑,遇到弹窗、验证码、网络异常这些脚本处理不了的情况,再唤起 Agent 来决策。这样兼顾了效率和灵活性。
8. 性能调优与规模化部署建议
当设备数量从个位数涨到几十台,性能问题就会暴露出来。首先是ADB 连接数限制,一台电脑同时连接的 ADB 设备太多,USB 控制器会扛不住,表现为设备频繁掉线、命令响应变慢。解决方案是分机器部署,每台电脑管 8 到 12 台设备,通过中心调度器统一分发任务。中心调度器可以用 Redis 做任务队列,每台工作机跑一个 Worker 进程,从队列里取任务执行。
其次是截图和 UI 树的传输开销。截图是 PNG 格式,一张 1080P 的截图大概 1 到 2 MB,UI 树的 XML 也有几十到几百 KB。如果每步操作都传一次,网络带宽和存储都会吃紧。优化方法是:截图降分辨率或者转 JPEG 压缩,UI 树只传关键属性,不需要的节点裁剪掉。如果 Agent 和 mobile-mcp 在同一台机器上,用 stdio 模式比 SSE 模式快很多,省去了网络传输的开销。
最后是任务调度策略。简单的轮询调度在设备性能不一致的时候效率很低,快的设备早早跑完闲着,慢的设备还在慢慢磨。更好的做法是根据设备的历史执行时间做加权调度,快的设备多分配任务,慢的设备少分配。另外,任务要分优先级,紧急任务插队执行,普通任务排队等待。这套调度逻辑用 Python 的heapq或者celery都能实现,关键是把设备状态和任务队列管理好。
规模化部署还有一个容易被忽视的点:设备散热。几十台手机同时跑自动化,发热量很大,尤其是充电的时候。温度过高会导致 CPU 降频、电池鼓包、甚至设备关机。我的做法是设备不充电的时候跑任务,电量低于 20% 就暂停任务去充电,充到 80% 再继续。另外搞个小风扇对着吹,成本不高但效果明显。