MCP 接入
如果你在做 AI agent,不必自己封装 REST。本平台提供 MCP(Model Context Protocol)端点,把云手机的查询与操作暴露成一组工具,支持 MCP 的客户端配好地址 和密钥就能直接调用。
端点
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为准,别假设名下所有设备一样。