跳到主要内容

MCP 接入

如果你在做 AI agent,不必自己封装 REST。本平台提供 MCP(Model Context Protocol)端点,把云手机的查询与操作暴露成一组工具,支持 MCP 的客户端配好地址 和密钥就能直接调用。

想用传统 HTTP 调用的,看总览与接口清单。 两条路用的是同一套应用与权限,只是认证方式不同。

端点​

POST https://<你的域名>/mcp
  • 传输:Streamable HTTP,无状态,不需要维护会话
  • 协议版本:2025-03-26
  • 服务端标识:nodemobi-cloudphone

对这个地址发 GET 会返回 405,并提示改用 POST。这是正常的:本端点没有服务端 推送流,不是故障。实测主流客户端全程只发 POST,不会受影响。

认证​

按应用签发的密钥(key_id 与 secret)在开发者控制台创建。两种写法等价,挑你 的客户端支持的那一种:

两个请求头

X-Api-Key: ak_xxxxxxxxxxxx
X-Api-Secret: cps_xxxxxxxxxxxx

或者一个 bearer

Authorization: Bearer ak_xxxxxxxxxxxx:cps_xxxxxxxxxxxx

中间用冒号分隔。key_id 与 secret 本身都不含冒号,所以按第一个冒号切分是明确 的。提供 bearer 这种写法,是因为不少 MCP 客户端只能配置一个 token,配不了任 意请求头。

两种写法同时出现时,以显式的请求头为准。

警告

/mcp 与 /v1 的认证不一样。/v1 走 HMAC 签名(X-Api-Key + X-Timestamp + X-Nonce + X-Signature),把 MCP 的 bearer 拿去打 /v1 会 401。

第一次调用​

配客户端之前,先用一条命令确认密钥是通的。tools/list 是只读的,不会碰你的 设备。

curl -s -X POST https://<你的域名>/mcp \
-H "Authorization: Bearer $NODEMOBI_MCP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Windows 用户注意两点:

  • PowerShell 里的 curl 是 Invoke-WebRequest 的别名,不认 -H,会报 「无法绑定参数 Headers」。要用真 curl 就写 curl.exe。
  • 或者直接用 PowerShell 的写法:
$h = @{ Authorization = "Bearer $env:NODEMOBI_MCP_TOKEN"; 'Content-Type' = 'application/json' }
$r = Invoke-RestMethod -Method Post -Uri 'https://<你的域名>/mcp' -Headers $h `
-Body '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
$r.result.tools.name

返回里应当列出你的密钥能调的工具。列表比下面那张表短是正常的——tools/list 只返回这把密钥有权限调的工具。

客户端配置​

原生支持 Streamable HTTP 的客户端​

直接填地址,密钥放环境变量。以 Codex CLI 为例:

codex mcp add nodemobi \
--url https://<你的域名>/mcp \
--bearer-token-env-var NODEMOBI_MCP_TOKEN

然后把 NODEMOBI_MCP_TOKEN 设成 ak_xxx:cps_xxx。这样配置文件里只有变量名, 密钥不落盘。

只支持 stdio、或配不了自定义头的客户端​

用 mcp-remote 做一层本地桥。以 Claude Desktop 的配置为例:

{
"mcpServers": {
"nodemobi": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://<你的域名>/mcp",
"--header", "X-Api-Key:${NODEMOBI_KEY}",
"--header", "X-Api-Secret:${NODEMOBI_SECRET}",
"--transport", "http-only"],
"env": {
"NODEMOBI_KEY": "ak_xxxxxxxxxxxx",
"NODEMOBI_SECRET": "cps_xxxxxxxxxxxx"
}
}
}
}
注意

这种写法下密钥是明文存在配置文件里的。能用原生 Streamable HTTP 就别走这条 路;必须走的话,建议给这把密钥只授只读权限,并单独记录、随时可吊销。

工具与权限​

tools/list 只返回这把密钥调得动的工具。权限不够的工具不会出现在列表里—— 这样 agent 不会去调一个注定失败的接口。

工具作用需要的权限
list_devices列出设备及基础状态device_read
get_device查一台设备的状态、规格与能力device_read
get_task查任务状态device_read
create_session签发短期 H5 画面会话令牌stream
set_proxy设置出口代理device
clear_proxy清除代理,恢复直连device
set_power开机 / 关机 / 重启device

device 权限包含了 device_read,不必两个都勾。

不列出来只是提前告知,不是权限本身:直接按名字调一个没权限的工具,同样会被 拒绝。

备注

有些客户端会给工具名加前缀,比如 Codex 里实际是 mcp__nodemobi__list_devices。找不到工具时,先把客户端的完整工具清单打出来看 一眼,不要直接断定服务不存在。

每个工具的完整说明(参数、前置条件、失败处置)都写在 tools/list 返回的 description 里,你的客户端能直接读到,这里不再重复。

调用前先看 capabilities​

list_devices 和 get_device 返回的每台设备都带一个 capabilities:

{
"device_id": "1",
"state": "running",
"capabilities": { "proxy": true, "power": true, "session": true }
}

它表示本平台当前能否对这台设备执行该操作。调写工具之前先看它,为 false 就别调。

state 为 unknown 表示上游状态读不到,不代表设备不属于你、也不代表设备故 障。能不能操作以 capabilities 为准,不要从 state 推导。

写操作是异步的​

除 create_session 外的写工具都返回一个任务,而不是最终结果:

{ "task_id": "tsk_xxxx", "status": "dispatched", "terminal": false }

dispatched 只表示已派发,不表示已生效。用 get_task 轮询到 terminal: true 才知道结果,轮询间隔不要短于 15 秒。

五种终态​

它们含义不同、处置也不同,不要笼统当成成功或失败:

终态含义
success效果已确认生效(平台实地校验过)
success_unverified上游报成功,但这类操作平台没有校验手段。不等于成功,只等于「上游这么说」
failed明确失败:上游报错,或校验确认没生效且上游也说失败
failed_silent上游报成功、实测没生效。这是唯一会触发退款的终态,也是最值得报警的一种
timeout到时限仍读不到期望效果,且不能断定它没生效(可能只是设备一时不可达),因此不退款,也不应直接当成失败

举例:clear_proxy 没有可比对的出口 IP,所以它的正常终态通常是 success_unverified 或 timeout,而不是 success。

幂等​

写工具返回 idempotent_hit: true,表示这次请求与一条还在进行中的任务同幂等 键,返回的是那条既有任务,本次没有重复派发——这不是你的调用失败了。

上一条任务已经终态之后,同样的操作可以再次发起。

错误怎么处置​

失败响应带 retryable 字段,按它决定要不要重试:

{ "code": "not_supported", "message": "该设备不支持设置代理", "retryable": false }
  • retryable: false —— 终局错误,不要重试、不要退避。换设备或上报即可。
  • retryable: true —— 值得退避重试。

两个容易混的:

  • forbidden —— 这把密钥没有该工具所需的权限。去控制台给应用加权限。
  • not_supported —— 权限没问题,是这台设备不提供该能力。看 capabilities 就能提前避开。

限流​

默认 每分钟 300 次,按应用计(不按密钥计,同一应用的多把密钥共享额度)。 超限返回 rate_limited,retryable: true。

目前的限制​

写在前面,免得你按错误的假设设计:

  • 没有回调。任务完成不会通知你,只能用 get_task 轮询。
  • create_session 返回的 expires_at 可能是 null,表示上游没有给出有效 期。不要据此假设令牌长期有效,用到失败即重新签发。
  • 不同设备的能力可能不同,以 capabilities 为准,别假设名下所有设备一样。