把 Android 控制能力交给 AI 前,先收紧网络与权限:Android Remote Control MCP 的设备端安全边界
让 AI Agent 操作手机,最容易被误解成“再接一个 MCP 服务”。但 Android Remote Control MCP 的关键区别是:MCP 服务和控制能力都在 Android 设备或模拟器上运行。它借助 Accessibility Service 读取界面结构、执行点击和输入,并通过 HTTP 的 Streamable HTTP 端点把这些能力交给兼容 MCP 的客户端。
这很适合开发阶段的设备回归、真机排障和可访问性流程验证:模型可以先读取当前界面的结构化节点,再决定是否需要截图,而不是每一步都把整张屏幕送进上下文。不过,把“读 UI”与“点按钮、输文本、读通知、访问文件”放到同一个远程接口后,真正需要设计的已不再是提示词,而是设备的权限面、网络面和人工确认点。
本文以项目当前公开文档为依据,拆解一套适合测试设备的最小暴露方案。它不应被直接套用到装有生产账号、支付工具或个人隐私数据的主力手机上。
先理解它控制的是什么
项目把 Android 应用本身作为 MCP Server:服务端使用 Ktor/Netty,MCP 请求发送到 POST /mcp,采用 JSON-RPC 2.0 的 Streamable HTTP 传输。公开工具参考列出屏幕状态、手势、节点操作、文本输入、剪贴板、文件、应用、相机、通知、位置和分享等多类能力。
其中更值得关注的是 android_get_screen_state。它会返回压缩的界面节点数据,并尝试通过 AccessibilityService 的窗口枚举看到系统对话框、权限弹窗或输入法窗口。截图不是默认结果;只有调用时明确要求,才会附带经过尺寸与质量限制的截图。这种“结构优先、图像按需”的接口,能减少无谓的视觉上下文,却不意味着返回文本就是安全内容。
屏幕节点、剪贴板、通知、文件名和应用文本都来自外部环境。它们可能包含敏感信息,也可能包含诱导模型继续执行的恶意文本。项目在工具结果前加入不可信数据提示,但文档也明确承认:文字提示无法消除图片中的对抗内容。因此,客户端仍应把设备返回值当作数据解析,而不是当作可执行指令;高风险动作必须另设批准步骤。
第一条边界:把服务留在设备回环地址
默认绑定是手机自己的 127.0.0.1:8080。这里的“本地”是 Android 设备本身,不是开发电脑。因此,若希望电脑上的 MCP 客户端连接,最稳妥的开发方式不是把服务改为 0.0.0.0,而是经 USB 使用 ADB 端口转发:
adb forward tcp:8080 tcp:8080
这样电脑只访问自己的 localhost:8080,而手机服务仍不暴露给同一 Wi-Fi 中的其他设备。随后可先完成 MCP 握手;令牌只从本机环境变量读取,不应进入仓库、Shell 历史或文章截图:
curl -sS -D - -X POST http://localhost:8080/mcp \
-H "Authorization: Bearer ${ARC_MCP_TOKEN}" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"local-readonly-check","version":"1.0.0"}}}'
从响应头保存 mcp-session-id 后,再进行只读的屏幕状态检查,是验证链路的合理起点。不要一连通就让 Agent 执行点击、安装、文件写入或发送动作。先确认它能识别测试应用的预期节点,再逐项开启需要的工具范围,能把调试失败缩小到网络、鉴权、权限或 UI 定位中的某一层。
改成 0.0.0.0 意味着同网段设备都可能访问该端口。项目文档只建议在受信任私网中这样做;HTTPS 默认也并未开启。即使随后使用 Cloudflare 或 ngrok 隧道拿到公网 HTTPS 地址,也不应把“有 TLS”误解为“适合公开控制手机”:隧道只是改变入口位置,不会降低 Accessibility 控制本身的影响。
第二条边界:认证要保持 fail-closed
服务可接受静态 Bearer token,也可接受它签发的 OAuth 访问令牌;两种认证开关默认启用。只有两者都被关闭时,服务才接受未认证请求。值得注意的是,Bearer 认证启用但 token 值为空时,项目按拒绝访问处理,而不是悄悄降级为免认证。
这正是设备控制服务应有的默认失败方式。部署检查不应只问“能不能连上”,还应分别验证:错误 token 是否得到 401、重启后旧会话是否失效、隧道关闭后是否不可达、OAuth client 是否可以撤销。若接入 Claude Desktop、Claude Code 或其他 HTTP MCP 客户端,配置中的 URL、header 和 token 应存放在用户级配置或密钥管理工具中;不要把真实 token 写入团队共享的 MCP JSON。
项目为远程连接器给出的连接形式类似下面的结构,实际地址与令牌应由设备端显示的值替换:
{
"mcpServers": {
"android-test-phone": {
"type": "http",
"url": "http://DEVICE_IP:PORT/mcp",
"headers": {
"Authorization": "Bearer YOUR_TOKEN"
}
}
}
}
如果服务仍保持默认回环绑定,这个 LAN 地址并不能直接从电脑访问;应继续使用 ADB 转发,或在经过风险评审后使用受控隧道。把这条限制写进团队运行手册,能避免为了“先跑通”而不经思考地打开全网监听。
第三条边界:权限不是一次性开关
Accessibility Service 是该项目读取 UI、执行操作和截图的核心权限。相机、麦克风、位置、通知、媒体读取则服务于对应功能;文件访问还依赖用户通过 Android 系统文件选择器授予的 Storage Access Framework 目录。它们的风险和用途并不相同,不该打包成“给 Agent 全权限”。
建议为自动化准备一台专用测试机或模拟器:使用独立测试账号,关闭不需要的通知来源,只授权测试目录,不存放个人照片、密码管理器或生产凭据。每次扩大能力前,先明确要验证的场景与可接受的副作用。例如 UI 回归只需屏幕读取和少量测试应用操作,不需要相机、位置或任意文件访问;验证完成后应回收临时授权和端口转发。
项目的 Privacy Mode 可以在数据离开设备前尝试假名化或遮蔽邮箱、电话、卡号、凭据等模式,但文档将其定义为 best-effort,并不保证不遗漏。尤其是姓名检测的公开自测结果并不适合作为安全承诺。它可以作为减少误传的辅助层,不能替代测试账号、最小权限、网络隔离和人工审核。
把它放进什么流程才合适
Android Remote Control MCP 更适合“受控设备上的开发和测试基础设施”,而不是无人值守的通用手机代理。一个可审计的流程可以是:测试人员先用 ADB 建立本地转发;客户端用 token 初始化会话;Agent 只读取 UI 树并提出下一步;涉及提交表单、下载文件、发送消息、修改设置或访问非测试应用时,停在人工批准点;结束后关闭服务、撤销隧道和 OAuth client,并复查测试机状态。
这种分层会牺牲一点自动化速度,却能避免把便利误当作授权。MCP 让模型更容易调用设备能力,不会自动给出风险边界。对 Android 这类高度个人化、权限密集的终端来说,最值得自动化的往往不是“让 Agent 什么都能做”,而是让每一次能力扩张都有明确的设备、网络、凭据和人类责任人。
失败时如何收敛,而不是扩大权限
设备自动化经常卡在权限弹窗、界面版本变化、测试账号过期或节点定位不到。最糟的应对是让 Agent 为了绕过失败而反复点击、改用更大的网络暴露范围,或者索取与场景无关的权限。更可靠的做法是把失败分成可诊断的几类:连接失败先检查 ADB 转发与监听地址;返回 401 时检查 token 和认证开关;看不到目标控件时先采集只读 UI 状态并由人确认当前页面;需要系统级授权时回到设备端手动确认。
还应为每次测试设置停止条件。比如只允许访问指定包名、限定在测试环境的账号和时间窗口,并把会产生外部副作用的动作列为必须确认项。测试结束后,关闭应用内服务或转发规则,撤销临时连接器与不再使用的 OAuth client,再检查通知、下载目录和应用登录状态。这样即使模型判断错误,影响也能被限制在可重新部署的测试设备和可回溯的短会话中。