MCP 服务使用文档
将 AI Agent 手机自动化能力通过 MCP(Model Context Protocol)暴露为标准工具,供 OpenClaw、Qoder、Claude Desktop 等外部 AI Agent 调用。
提供 两种接入通道:
通道 传输方式 适用客户端 状态 A. 本地 MCP Server(推荐) stdio JSON-RPC + WebSocket 中继 OpenClaw / Qoder / Claude Desktop 等任意标准 MCP 客户端 ✅ 稳定 B. WebMCP(浏览器原生) document.modelContextChrome 内置 AI(Gemini Nano)等页面内 Agent 🧪 实验性(Chrome 149+ Origin Trial)
目录
- 架构概览
- 流程图
- 通道 A:本地 MCP Server(推荐)
- 通道 B:WebMCP(浏览器原生)
- 工具能力一览
- 工具详细定义
- Skill 配置
- 集成代码说明
- UI 状态指示
- 常见问题
- 附录:AgentTask 数据结构
架构概览
┌──────────────────────────────────────────────────────────────────────┐
│ External AI Agent │
│ (OpenClaw / Qoder / Claude Desktop / Chrome AI) │
└───────────┬──────────────────────────────────────────┬───────────────┘
│ 通道 A:stdio JSON-RPC 2.0 │ 通道 B:
▼ │ document.modelContext
┌───────────────────────────────┐ │ .callTool()
│ mcp-server/dist/server.js │ ▼
│ (Node.js McpServer) │ ┌───────────────────────────────┐
│ 6 Tools 注册于 stdio 传输 │ │ Chrome WebMCP Runtime │
└───────────┬───────────────────┘ │ (#enable-webmcp-testing) │
│ WebSocket └───────────────┬───────────────┘
│ ws://localhost:7788 │ execute()
▼ ▼
┌──────────────────────────────────────────────────────────────────────┐
│ 浏览器页面(Mobile AI Use) │
│ McpRelayClient(通道 A) WebMcpService(通道 B) │
│ └────────────┬────────────┘ │
│ ▼ McpServiceContext(共用) │
│ Plan-and-Resolve Agent / ReAct Agent │
└──────────────────────────────┬───────────────────────────────────────┘
│ Web ADB
▼
┌──────────────────────────────────────────────────────────────────────┐
│ Android / iOS Device │
└──────────────────────────────────────────────────────────────────────┘
两条通道最终 复用同一套 McpServiceContext,工具语义完全一致;差异仅在传输层和个别返回格式(见工具详细定义)。
流程图
通道 A:本地 MCP Server 调用流程
sequenceDiagram
participant Agent as External Agent
(OpenClaw/Qoder)
participant MCP as mcp-server
(Node.js, stdio)
participant WS as WebSocket 中继
ws://localhost:7788
participant Relay as McpRelayClient
(浏览器)
participant Panel as AiChatPanel
participant Device as Phone Device
Note over MCP,Relay: 页面打开后 Relay 自动连接
(断线每 30s 重连)
Agent->>MCP: tools/call run_agent_task {goal}
MCP->>WS: {id, tool, args} (JSON)
WS->>Relay: 转发请求
Relay->>Relay: 检查设备连接 & Agent 空闲
Relay->>Panel: runAgentForMcp(goal, signal)
Panel->>Device: Plan-and-Resolve:截图→视觉分析→触控
Device-->>Panel: 操作结果
Panel-->>Relay: AgentTask (finished)
Relay->>WS: {id, result: Markdown 报告}
WS->>MCP: 响应匹配 pending 请求
MCP-->>Agent: CallToolResult通道 B:WebMCP 服务注册流程
flowchart TD
A[Start] --> B[Mobile AI Use]
B --> C{document.modelContext 存在?}
C -->|No| D[WebMCP 不注册
仅启动 Relay 客户端]
C -->|Yes| E[创建 WebMcpService 实例]
E --> F[register: 注册 6 个 Tools]
F --> G[外部页面内 Agent 可发现并调用]
D --> H[McpRelayClient.connect]
G --> H
H --> I{连接 ws://localhost:7788 成功?}
I -->|Yes| J[Relay 徽章绿色]
I -->|No| K[Relay 徽章灰色
30s 后自动重试]任务执行状态机
stateDiagram-v2
[*] --> idle: 初始化
idle --> planning: run_agent_task 触发
planning --> executing: Plan 完成
executing --> finished: 所有步骤成功 / 滚动提取完成
executing --> failed: 步骤执行失败
executing --> aborted: abort_task 调用 / 超时
finished --> [*]
failed --> [*]
aborted --> [*]💡 特殊终结条件:当 Agent 执行成功后,提取结果直接作为任务结论返回。
通道 A:本地 MCP Server(推荐)
项目下载
目录结构
mcp-server/
├── src/server.ts ← McpServer + WebSocket 中继实现
├── dist/server.js ← 编译产物(MCP 客户端配置指向此文件)
├── package.json ← name: phone-agent-mcp
└── tsconfig.json
工作原理
- MCP 协议层:使用
@modelcontextprotocol/sdk的McpServer+StdioServerTransport,以 stdio JSON-RPC 2.0 与 MCP 客户端通信,Server 名称为phone-agent - WebSocket 中继层:内嵌
WebSocketServer(默认端口 7788),浏览器页面的McpRelayClient主动连入 - 调用转发:收到
tools/call后通过callBrowser()将{id, tool, args}发给浏览器,等待浏览器回传{id, result}后返回给 MCP 客户端 - 连接自检:浏览器接入 3 秒后 Server 会自动调用一次
get_device_status验证链路 - 单连接模型:同一时刻只支持一个浏览器连接,新连接会顶替旧连接
启动步骤
cd mcp-server
npm install # 首次运行安装依赖
npm run build # 编译 TypeScript → dist/server.js
npm start # 启动(stdio + WebSocket :7788)
# 开发模式(免编译热执行):
npm run dev # tsx src/server.ts
然后:
- 打开 Mobile AI Use 页面(如
https://mobile-ai-use.com/mobileAi),页面加载后McpRelayClient自动连接中继 - 确认 AI 对话面板顶部 MCP Relay 徽章为绿色
- USB 连接 Android 手机并确认投屏正常
环境变量
| 变量 | 默认值 | 说明 |
|---|---|---|
PHONE_AGENT_WS_PORT |
7788 |
WebSocket 中继端口。修改后需同步更新浏览器端 McpRelayClient 的连接 URL |
MCP 客户端配置
OpenClaw(openclaw.json):
{
mcp: {
servers: {
"phone-agent": {
command: "node",
// 将 <PROJECT_ROOT> 替换为项目实际绝对路径
args: ["<PROJECT_ROOT>/mcp-server/dist/server.js"],
env: { PHONE_AGENT_WS_PORT: "7788" }
}
}
}
}
或 CLI 方式:
openclaw mcp set phone-agent --command node --args "<PROJECT_ROOT>/mcp-server/dist/server.js"
Qoder(.qoder/mcp.json 或全局 MCP 配置):
{
"mcpServers": {
"phone-agent": {
"command": "node",
"args": ["<PROJECT_ROOT>/mcp-server/dist/server.js"],
"env": { "PHONE_AGENT_WS_PORT": "7788" }
}
}
}
Claude Desktop(claude_desktop_config.json):
{
"mcpServers": {
"phone-agent": {
"command": "node",
"args": ["<PROJECT_ROOT>/mcp-server/dist/server.js"]
}
}
}
WebSocket 中继协议
// Server → 浏览器(请求)
interface WsRequest {
id: string; // 请求唯一 ID(时间戳 + 随机串)
tool: string; // 工具名,如 'run_agent_task'
args: Record<string, unknown>; // 工具参数
}
// 浏览器 → Server(响应)
interface WsResponse {
id: string; // 与请求 ID 对应
result: McpToolResult; // { content: [...], isError? }
}
// 浏览器 → Server(中间进度,仅 run_agent_task 会推送)
interface WsProgress {
id: string; // 与请求 ID 对应
type: 'progress'; // Server 靠该字段与最终响应区分
seq: number; // 进度序号,从 1 递增
message: string; // 人类可读的过程描述
}
同一个 id 上可以先出现任意多条 WsProgress,最后必定以一条 WsResponse 收尾。Server 收到进度帧时只转发、不结束请求也不重置超时。
Agent 执行过程实时上报
run_agent_task 执行期间,浏览器把 Agent 的思考与执行过程实时推给 Server,Server 再转成 MCP 通知发给外部客户端(OpenClaw / Qoder / Claude Desktop)展示,避免长任务期间外部客户端「假死」等待。
上报节点(对应 runAgent 的 Agent 生命周期回调):
| 节点 | 上报内容 |
|---|---|
onPlanned |
📋 已生成执行计划(共 N 步) + 步骤标题列表 |
onStepStart |
▶️ 步骤 N:标题 + 步骤描述 |
onStepText |
缓冲不直接上报(逐 token 触发,直接透传会刷爆通道) |
onToolCall |
先 flush 缓冲的思考文本(💭 ...),再上报 🔧 调用工具 name(args) |
onToolResult |
→ 结果(截断至 300 字符) |
onStepEnd |
flush 剩余思考 + ✅/❌/⏭️ 步骤 N 完成(status,耗时 Xs) |
onError |
❌ Agent 执行异常:... |
Server 侧转发策略(mcp-server/src/server.ts):
- 客户端在请求
_meta.progressToken中声明愿意接收进度 → 发notifications/progress(progress取seq,message取过程文本) - 未声明 progressToken → 降级为
notifications/message(logging,level: info,logger: phone-agent),保证过程仍可见 - 通知发送失败只记 stderr 日志,不中断 Agent 任务
通道 B(WebMCP)没有对应的通知机制,
onProgress参数为可选,WebMCP 侧不传。
浏览器端 McpRelayClient
文件: relay-client.ts
- 页面加载时自动
connect(),断线后 每 30 秒自动重连 - 复用与 WebMCP 完全一致的
McpServiceContext,工具分派逻辑集中在_dispatch() onStatusChange(connected)回调驱动 UI 徽章状态run_agent_task的返回值经_formatTaskMarkdown()格式化为 Markdown 可读报告(任务摘要 + 每步标题/状态/耗时/工具调用列表),便于外部 Agent 直接引用_sendProgress()按reqId维护递增序号,连接已断或发送异常时静默忽略(进度展示属于附加能力,不得影响主流程)
通道 B:WebMCP(浏览器原生)
🧪 实验性能力,依赖 Chrome Origin Trial,适合页面内 Agent(如 Chrome 内置 AI)调用。外部进程类客户端请使用通道 A。
浏览器要求
| 项目 | 要求 |
|---|---|
| 浏览器 | Chrome 149+ (Canary / Dev Channel) |
| Flag | chrome://flags/#enable-webmcp-testing → Enabled |
| 协议 | 页面需通过 HTTPS 或 localhost 访问 |
| API 入口 | document.modelContext(旧版 navigator.modelContext 已弃用,代码仅作回退) |
启用步骤
- 开启 WebMCP Flag:地址栏输入
chrome://flags/#enable-webmcp-testing,设为Enabled后重启浏览器 - 验证 API 可用:
// DevTools Console console.log(typeof document.modelContext); // "object" console.log(typeof document.modelContext.registerTool); // "function" - 打开 WebADB 页面并连接手机:页面加载时
WebMcpService.register()自动注册 6 个 Tools(控制台可见[WebMCP] 6 tools registered日志)
工具能力一览
两条通道注册的工具名称、参数完全一致:
| # | Tool Name | 类型 | 描述 | Server 侧超时(通道 A) |
|---|---|---|---|---|
| 1 | run_agent_task |
写入 | 在手机上执行 AI Agent 自动化任务 | timeoutMs + 5s(默认 20 min) |
| 2 | abort_task |
写入 | 中断当前正在执行的 Agent 任务 | 10s |
| 3 | get_task_result |
只读 | 按 taskId 查询任务详细结果 | 10s |
| 4 | get_latest_task |
只读 | 获取当前对话最新任务结果 | 10s |
| 5 | take_screenshot |
只读 | 手机屏幕截图(返回 PNG base64) | 30s |
| 6 | get_device_status |
只读 | 获取已连接设备信息 | 15s |
通道 B(WebMCP)中
run_agent_task带有requiresUserApproval: true注解,Chrome 会弹出用户确认框;通道 A 中对应destructiveHint: true注解,是否需要确认由 MCP 客户端策略决定。
工具详细定义
1. run_agent_task
在已连接的手机上运行 AI Agent 自动化任务。Agent 使用 Plan-and-Resolve 架构,通过屏幕视觉理解和触控操作完成目标。
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
goal |
string | ✅ | 自然语言任务目标,如 "打开微信给张三发消息:明天见" |
timeoutMs |
number | ❌ | 最长执行时间(毫秒)。通道 A 默认 1200000(20 分钟),通道 B 默认 120000(2 分钟) |
前置检查: 设备未连接返回 No phone device connected.;Agent 正在运行返回 Agent is already running. Please wait or call abort_task first.
返回格式(通道 A —— Markdown 报告):
## ✅ Agent 任务完成
**目标**:打开微信给张三发消息
**状态**:finished **步骤**:4/4 成功 **耗时**:32.5s
**结论**:已成功发送微信消息
---
### ✅ 步骤 1:打开微信 (5.2s)
- `tap({"x":540,"y":1200})` → tapped at (540,1200)
...
返回格式(通道 B —— JSON):
{
"content": [{
"type": "text",
"text": "{\"taskId\":\"abc123\",\"status\":\"finished\",\"conclusion\":\"已成功发送微信消息\",\"stepsCompleted\":4,\"totalSteps\":4,\"durationMs\":32500,\"error\":null}"
}]
}
💡 滚动内容提取任务:若 goal 涉及"提取列表内容/滚动采集",Agent 会调用
ExtractScrollingContent工具(自动滚动截图 → 拼接长图 → AI 分析),提取完成后任务自动结束,提取摘要作为conclusion返回。
2. abort_task
中断当前正在运行的 Agent 任务。无参数。
返回示例:
{ "content": [{ "type": "text", "text": "Task aborted." }] }
无任务运行时返回 No task is currently running.
3. get_task_result
按 taskId 查询已完成任务的详细结果(含每步执行记录)。
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
taskId |
string | ✅ | run_agent_task 返回的任务 ID |
includeSteps |
boolean | ❌ | 是否包含每步详情(默认 true) |
返回示例:
{
"content": [{
"type": "text",
"text": "{\"taskId\":\"abc123\",\"goal\":\"打开微信发消息\",\"status\":\"finished\",\"conclusion\":\"任务完成\",\"stepsCompleted\":3,\"totalSteps\":3,\"durationMs\":28000,\"steps\":[{\"index\":1,\"title\":\"打开微信\",\"status\":\"success\",\"durationMs\":5200,\"error\":null}]}"
}]
}
4. get_latest_task
获取当前对话中最近一次 Agent 任务的完整结果(含步骤)。无参数。返回格式同 get_task_result。
5. take_screenshot
对已连接手机屏幕进行截图,返回 PNG 图片(base64 编码)。无参数。
返回示例:
{
"content": [{
"type": "image",
"data": "iVBORw0KGgoAAAANSUhEUg...",
"mimeType": "image/png"
}]
}
6. get_device_status
获取当前已连接手机设备的基本信息。无参数。
返回示例:
{
"content": [{
"type": "text",
"text": "{\"connected\":true,\"model\":\"Pixel 7\",\"brand\":\"google\",\"release\":\"14\",\"sdk\":\"34\"}"
}]
}
设备未连接时返回 {"connected": false}。
Skill 配置
项目内置 Skill 定义文件:/skills/phone-agent/SKILL.md 下载,可直接被 OpenClaw / Qoder 识别加载。
---
name: phone-agent
description: 通过本地 MCP Server 调用 WebADB AI Agent,在已连接的手机上执行自动化任务
tools:
- mcp__phone-agent__run_agent_task
- mcp__phone-agent__abort_task
- mcp__phone-agent__take_screenshot
- mcp__phone-agent__get_device_status
- mcp__phone-agent__get_task_result
- mcp__phone-agent__get_latest_task
---
配置完成后,直接用自然语言描述任务即可:
- "打开微信,找到和张三的对话,发送一条'你好'"
- "截个屏看看手机当前界面是什么"
- "打开小红书搜索 deepseek,把前 10 条笔记的标题和作者提取出来"
- "中断正在执行的任务"
- "最近一次 Agent 任务的结果是什么"
典型工具调用序列:
get_device_status # 确认设备在线
run_agent_task {goal} # 下发任务(阻塞至完成,返回 Markdown 报告)
get_latest_task # 需要结构化数据时补查
集成代码说明
核心文件
mcp-server/src/server.ts ← 本地 MCP Server(通道 A 服务端)
skills/phone-agent/SKILL.md ← Skill 定义
McpServiceContext 接口
| 方法 | 返回类型 | 说明 |
|---|---|---|
runAgent(goal, signal) |
Promise<AgentTask | void> |
执行 Agent 任务 |
getTask(taskId) |
AgentTask | undefined |
按 ID 查询任务 |
getAllTasks() |
AgentTask[] |
当前对话所有任务 |
screenshot() |
Promise<string> |
截图(data URL) |
deviceInfo() |
Promise<DeviceInfo> |
设备信息 |
isRunning() |
boolean |
Agent 是否运行中 |
abort() |
void |
中断当前任务 |
isConnected() |
boolean |
设备是否已连接 |
关键实现细节
execute属性名(通道 B):WebMCP 规范要求工具处理函数必须命名为execute(非handler)document.modelContext:API 挂在document上。早期版本挂在navigator上,Chrome 仍保留但访问时会输出navigator.modelContext is deprecated警告;WebMcpService.getModelContext()优先取document,只在缺失时用in探测后回退到navigator(直接读属性就会触发警告)- 并发保护:
run_agent_task执行前检查isRunning(),同一时间只允许一个 Agent 任务 - 超时控制:浏览器端通过
AbortController+setTimeout实现;通道 A 的 Server 侧另有timeoutMs + 5s的兜底超时,超时返回isError: true而非挂死 - 断线重连:
McpRelayClient断开后每 30 秒重试;Server 侧浏览器重复连接时旧连接被顶替 - 请求配对:中继层用唯一
id在pendingMap 中配对请求/响应,浏览器断连不影响其他挂起请求的超时处理
UI 状态指示
AI Agent 聊天框顶部提供 MCP Relay 徽章,指示浏览器与本地 MCP Server 的连接状态(通道 A):
| 状态 | 样式 | 含义 |
|---|---|---|
| 🟢 绿色呼吸动画 | mcp-badge--ready |
Relay 已连接,OpenClaw / Qoder 可调用手机 Agent |
| ⚪ 灰色 | mcp-badge--off |
Relay 未连接(本地 MCP Server 未启动) |
Hover 提示:
- 已连接:
MCP Relay 已连接:OpenClaw / Qoder 可调用手机 Agent - 未连接:
MCP Relay 未连接:请启动本地 MCP Server(cd mcp-server && npm start)
通道 B(WebMCP)的注册结果通过控制台日志确认:成功时输出
[WebMCP] 6 tools registered: ...,环境不支持时输出 warn 提示。
常见问题
通道 A(本地 MCP Server)
Q: 工具调用返回 "Browser not connected. Open the WebADB page first."
原因: 浏览器页面未打开,或 McpRelayClient 未连上中继。
排查:
- 确认
mcp-server已启动(终端应输出WebSocket relay on ws://localhost:7788) - 打开 WebADB 页面并确认 Relay 徽章为绿色
- 若刚启动 Server,等待最多 30 秒(浏览器自动重连间隔)或刷新页面
Q: Relay 徽章一直灰色
排查:
- 本地 MCP Server 是否在运行:
cd mcp-server && npm start - 端口是否被修改:
PHONE_AGENT_WS_PORT与浏览器端连接 URL(默认ws://localhost:7788)必须一致 - 端口被占用时改用其他端口并同步更新 MCP 客户端配置中的
env
Q: 工具调用超时(Tool "run_agent_task" timed out after ...ms)
原因: Agent 任务执行时间超过 timeoutMs(Server 侧兜底为 timeoutMs + 5s)。
解决: 复杂任务显式传大一些的 timeoutMs;或调用 abort_task 中断后拆分任务重试。
Q: 打开了多个 Mobile-AI-Use 页面,调用行为异常
原因: 中继层同一时刻只支持一个浏览器连接,最后连入的页面会顶替之前的连接。
解决: 只保留一个 Mobile-AI-Use 页面标签。
通道 B(WebMCP)
Q: 报错 Failed to read the 'execute' property from 'ModelContextTool'
原因: 工具定义中使用了 handler 而非 execute 作为处理函数属性名。
mc.registerTool({
name: 'my_tool',
inputSchema: { ... },
execute: async (params) => { ... }, // ✅ 正确
// handler: async (params) => { ... }, // ❌ 错误
});
Q: document.modelContext 为 undefined
排查步骤:
- 确认 Chrome 版本 ≥ 149(
chrome://version) - 确认已开启
chrome://flags/#enable-webmcp-testing - 确认页面通过 HTTPS 或 localhost 访问
- 重启浏览器后重试
Q: 外部 Agent 看不到已注册的 Tools
可能原因:
- 页面未处于活跃标签页(Chrome 可能暂停后台标签的 MCP 服务)
- 注册时
isAvailable返回 false(检查控制台 warn 日志) - Origin 不匹配(外部 Agent 配置的 origin 需与页面 URL 一致)
通用
Q: run_agent_task 返回 "Agent is already running"
原因: 同一时间只允许一个 Agent 任务运行。
解决: 先调用 abort_task 中断当前任务,再重新下发。
Q: 截图返回空白或报错
排查:
- 确认设备已通过 USB 连接且投屏正常
- 确认
get_device_status返回connected: true - 检查 Scrcpy 服务是否正常运行
附录:AgentTask 数据结构
interface AgentTask {
id: string; // 任务唯一 ID
goal: string; // 用户目标(自然语言)
steps: AgentStep[]; // Plan 阶段产出的步骤列表
status: 'idle' | 'planning' | 'executing' | 'finished' | 'failed' | 'aborted';
conclusion?: string; // 最终结论(滚动提取任务为提取摘要)
error?: string; // 错误信息
createdAt: number; // 创建时间戳
startedAt?: number; // 开始执行时间戳
finishedAt?: number; // 完成时间戳
}
interface AgentStep {
id: string;
index: number; // 步骤序号(从 1 开始)
title: string; // 步骤标题
description: string; // 详细描述
summary?: string; // 执行总结(Markdown 报告中引用)
status: 'pending' | 'running' | 'success' | 'failed' | 'skipped';
toolCalls: ToolCallRecord[]; // 工具调用记录
beforeScreenshot?: string; // 执行前截图
afterScreenshot?: string; // 执行后截图
error?: string;
startedAt?: number;
finishedAt?: number;
}