MCP 服务使用

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.modelContext Chrome 内置 AI(Gemini Nano)等页面内 Agent 🧪 实验性(Chrome 149+ Origin Trial)

目录


架构概览

┌──────────────────────────────────────────────────────────────────────┐
│                        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下载

目录结构

mcp-server/
├── src/server.ts       ← McpServer + WebSocket 中继实现
├── dist/server.js      ← 编译产物(MCP 客户端配置指向此文件)
├── package.json        ← name: phone-agent-mcp
└── tsconfig.json

工作原理

  1. MCP 协议层:使用 @modelcontextprotocol/sdk 的 McpServer + StdioServerTransport,以 stdio JSON-RPC 2.0 与 MCP 客户端通信,Server 名称为 phone-agent
  2. WebSocket 中继层:内嵌 WebSocketServer(默认端口 7788),浏览器页面的 McpRelayClient 主动连入
  3. 调用转发:收到 tools/call 后通过 callBrowser() 将 {id, tool, args} 发给浏览器,等待浏览器回传 {id, result} 后返回给 MCP 客户端
  4. 连接自检:浏览器接入 3 秒后 Server 会自动调用一次 get_device_status 验证链路
  5. 单连接模型:同一时刻只支持一个浏览器连接,新连接会顶替旧连接

启动步骤

cd mcp-server
npm install       # 首次运行安装依赖
npm run build     # 编译 TypeScript → dist/server.js
npm start         # 启动(stdio + WebSocket :7788)
# 开发模式(免编译热执行):
npm run dev       # tsx src/server.ts

然后:

  1. 打开 Mobile AI Use 页面(如 https://mobile-ai-use.com/mobileAi),页面加载后 McpRelayClient 自动连接中继
  2. 确认 AI 对话面板顶部 MCP Relay 徽章为绿色
  3. 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 已弃用,代码仅作回退)

启用步骤

  1. 开启 WebMCP Flag:地址栏输入 chrome://flags/#enable-webmcp-testing,设为 Enabled 后重启浏览器
  2. 验证 API 可用:
    // DevTools Console
    console.log(typeof document.modelContext);              // "object"
    console.log(typeof document.modelContext.registerTool); // "function"
    
  3. 打开 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 设备是否已连接

关键实现细节

  1. execute 属性名(通道 B):WebMCP 规范要求工具处理函数必须命名为 execute(非 handler)
  2. document.modelContext:API 挂在 document 上。早期版本挂在 navigator 上,Chrome 仍保留但访问时会输出 navigator.modelContext is deprecated 警告;WebMcpService.getModelContext() 优先取 document,只在缺失时用 in 探测后回退到 navigator(直接读属性就会触发警告)
  3. 并发保护:run_agent_task 执行前检查 isRunning(),同一时间只允许一个 Agent 任务
  4. 超时控制:浏览器端通过 AbortController + setTimeout 实现;通道 A 的 Server 侧另有 timeoutMs + 5s 的兜底超时,超时返回 isError: true 而非挂死
  5. 断线重连:McpRelayClient 断开后每 30 秒重试;Server 侧浏览器重复连接时旧连接被顶替
  6. 请求配对:中继层用唯一 id 在 pending Map 中配对请求/响应,浏览器断连不影响其他挂起请求的超时处理

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 未连上中继。

排查:

  1. 确认 mcp-server 已启动(终端应输出 WebSocket relay on ws://localhost:7788)
  2. 打开 WebADB 页面并确认 Relay 徽章为绿色
  3. 若刚启动 Server,等待最多 30 秒(浏览器自动重连间隔)或刷新页面

Q: Relay 徽章一直灰色

排查:

  1. 本地 MCP Server 是否在运行:cd mcp-server && npm start
  2. 端口是否被修改:PHONE_AGENT_WS_PORT 与浏览器端连接 URL(默认 ws://localhost:7788)必须一致
  3. 端口被占用时改用其他端口并同步更新 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

排查步骤:

  1. 确认 Chrome 版本 ≥ 149(chrome://version)
  2. 确认已开启 chrome://flags/#enable-webmcp-testing
  3. 确认页面通过 HTTPS 或 localhost 访问
  4. 重启浏览器后重试

Q: 外部 Agent 看不到已注册的 Tools

可能原因:

  • 页面未处于活跃标签页(Chrome 可能暂停后台标签的 MCP 服务)
  • 注册时 isAvailable 返回 false(检查控制台 warn 日志)
  • Origin 不匹配(外部 Agent 配置的 origin 需与页面 URL 一致)

通用

Q: run_agent_task 返回 "Agent is already running"

原因: 同一时间只允许一个 Agent 任务运行。

解决: 先调用 abort_task 中断当前任务,再重新下发。

Q: 截图返回空白或报错

排查:

  1. 确认设备已通过 USB 连接且投屏正常
  2. 确认 get_device_status 返回 connected: true
  3. 检查 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;
}