
1. 为什么要在鸿蒙设备上跑 MCP从 JSON-RPC 到端侧 Agent 的完整链路MCP 协议Model Context Protocol是一套让 AI Agent 调用外部工具的标准化通信协议它基于 JSON-RPC 2.0把「工具注册、资源读取、提示模板」三件事统一成一套接口。适合谁适合正在做 OpenHarmony/鸿蒙设备接入、又想让大模型直接控制端侧硬件灯、空调、窗帘、传感器的开发者。你不需要为每个模型写一套适配代码只要把设备能力封装成一个 MCP ServerAgent 侧就能用同一套 JSON-RPC 消息完成调用。我在 RK3588 开发板 OpenHarmony 6.1 上实测过整条链路鸿蒙端通过分布式软总线暴露设备能力Python 侧写一个 MCP Server 把能力翻译成标准工具AI Agent 通过 stdio 与 Server 通信最终实现「说一句话 → 灯亮、空调调到 24 度」。整条链路的核心不是模型多强而是 JSON-RPC 消息能不能稳定往返。先看协议分层。MCP 采用经典 Client-Server 结构Host 层是 AI 应用比如你的 AgentClient 层与一个 Server 维持 1:1 连接Server 层暴露 Tools、Resources、Prompts 三类能力。传输层默认用 stdio标准输入输出也支持 SSE。为什么默认 stdio 而不是 HTTP因为 stdio 天然进程隔离Server 崩了不影响 Host部署时也不用开端口端侧设备上这点很关键。一次完整的工具调用流程是这样的Client 发initialize握手Server 返回 capabilities 声明Client 发tools/list发现可用工具Agent 决定调用哪个工具后Client 发tools/call执行Server 返回执行结果。整个过程都是 JSON-RPC 2.0 格式请求带id响应带result或error。这里有个容易忽略的点MCP 的initialize之后Client 必须再发一条notifications/initialized通知注意没有id是通知不是请求否则部分 Server 会认为握手未完成。我踩过这个坑Server 一直不响应tools/list排查半天才发现是漏了这条通知。理解了协议接下来就是把它落到鸿蒙设备上。下面我会先讲怎么拿到稳定的模型调用入口再给可复制的 Server 配置、鸿蒙侧 Bridge 代码骨架最后是端到端联调和排错。2. TaoToken 前置给 MCP Agent 准备一个稳定的模型调用入口MCP Server 负责工具执行但「决定调用哪个工具」这件事得靠大模型。所以整条链路里模型调用入口的稳定性直接决定 Agent 能不能跑通。我试过几种方式最后用 TaoToken 作为统一入口原因是它同时提供 OpenAI 兼容接口和 Claude Code 兼容接口MCP Agent 里换模型不用改工具层代码。TaoToken 是什么简单说它是一个聚合式的模型调用平台提供统一的 API 地址和 Key支持对话模型、编码模型等多种模型。能做什么你可以用它跑通 MCP Agent 的推理环节也可以用它做 Claude Code 的编码辅助。适合谁适合需要在一个项目里切换多个模型、又不想维护多套鉴权逻辑的开发者。接入前你需要准备三样东西我把它叫「三件套」配置项值说明Base URLhttps://taotoken.net/apiOpenAI 兼容接口地址注意不带 UTMAPI Key在控制台创建形如sk-xxxx只显示一次Model ID如claude-sonnet-4-5等按控制台模型列表填获取 Key 的路径访问控制台创建 API Key。如果你用的是 Claude Code 这类工具还需要配置 Anthropic 兼容入口文档在接入文档里有详细说明。这里要强调一点MCP Agent 里的模型调用和工具调用是两条独立的链路。模型调用走 TaoToken 的 HTTP 接口工具调用走 MCP 的 stdio。两者通过 Agent 的推理循环串起来——模型返回tool_callsAgent 解析后通过 MCP Client 执行再把结果塞回消息列表给模型。所以 Base URL 和 Key 配错表现是模型不返回工具调用MCP 配置错表现是工具执行失败。排错时要先分清是哪条链路。如果你只是想让模型对话验证一下 Key 是否可用可以直接用模型对话页面测试。长期做编码和 Agent 开发的话Coding Plan 会更划算具体在 Coding Plan 页面看。配好三件套后先别急着写 MCP用一段最小代码验证模型能正常返回from openai import OpenAI client OpenAI( base_urlhttps://taotoken.net/api, api_keysk-你的Key, ) resp client.chat.completions.create( modelclaude-sonnet-4-5, messages[{role: user, content: 只回复两个字收到}], ) print(resp.choices[0].message.content)跑通这段说明模型链路没问题可以进入 MCP Server 的配置环节。3. 可复制配置MCP Server 的 JSON 与鸿蒙侧 settings 片段这一节给可直接复制的配置。MCP 的配置分两块一块是 Host 侧声明怎么启动 Server通常是 JSON一块是鸿蒙侧的分布式配置settings/JSON5。路径和字段我都按实际项目结构写你照着改设备 ID 就行。先看 Host 侧的 MCP 配置。以 Claude Desktop 或类似 Host 为例配置文件里加一个mcpServers段{ mcpServers: { harmony-device-control: { command: python, args: [/opt/mcp/harmony_mcp_server.py], env: { MCP_LOG_LEVEL: INFO, HARMONY_BRIDGE_HOST: 127.0.0.1, HARMONY_BRIDGE_PORT: 8765 } } } }关键字段说明command是启动 Server 的可执行文件args是脚本路径env里可以传日志级别和 Bridge 地址。注意command用绝对路径的python别用相对路径否则 Host 的工作目录一变就找不到。再看鸿蒙侧的分布式配置。在 OpenHarmony 工程的entry/src/main/resources/base/profile/下建一个mcp_bridge_config.json{ mcpBridge: { storeId: mcp_harmony_bridge, bundleName: com.example.mcp.harmony, syncMode: SYNC_MODE_PUSH, securityLevel: S1, autoSync: true, deviceCollab: true, toolActionMap: { control_device: harmony.device.control, query_device_status: harmony.device.query, get_all_devices: harmony.device.list, activate_scene: harmony.scene.activate } } }syncMode设成SYNC_MODE_PUSH是重点默认的 PULL 模式在弱网下延迟能到 3 秒以上PUSH 模式实测能压到 0.3 秒。securityLevel用 S1 就够S2 以上需要额外权限申请。如果你用的是 Codex 这类工具鉴权信息放在auth.json里结构大致是{ base_url: https://taotoken.net/api, api_key: sk-你的Key, model: claude-sonnet-4-5 }三件套Base URL Key Model ID必须齐全缺一个都会在启动时报鉴权失败。Cline MCP 的配置类似在 MCP 设置里填 Server 启动命令即可工具列表会自动发现。配置写完后先本地手动跑一次 Server确认能启动python /opt/mcp/harmony_mcp_server.py # 正常输出到 stderr # Harmony MCP Server started (stdio mode)看到这行说明 Server 进程能起来接下来才是联调。4. 验证请求从 tools/list 到端到端控制一次设备配置就绪后验证要分三步走先验证 MCP 协议层再验证鸿蒙 Bridge最后端到端跑一次控制。第一步用一段最小 Client 代码验证tools/list能返回工具import json import subprocess proc subprocess.Popen( [python, /opt/mcp/harmony_mcp_server.py], stdinsubprocess.PIPE, stdoutsubprocess.PIPE, stderropen(/tmp/mcp_server.log, a), textTrue, bufsize1, ) def send(req): proc.stdin.write(json.dumps(req) \n) proc.stdin.flush() return json.loads(proc.stdout.readline()) # 握手 init send({ jsonrpc: 2.0, id: 1, method: initialize, params: {protocolVersion: 2024-11-05, capabilities: {}, clientInfo: {name: test, version: 1.0}} }) print(capabilities:, init[result][capabilities]) # 发 initialized 通知无 id proc.stdin.write(json.dumps({jsonrpc: 2.0, method: notifications/initialized}) \n) proc.stdin.flush() # 列工具 tools send({jsonrpc: 2.0, id: 2, method: tools/list, params: {}}) for t in tools[result][tools]: print(tool:, t[name])预期输出里能看到control_device、query_device_status、get_all_devices、activate_scene四个工具。如果这里报Method not found说明 Server 的方法路由没写全。第二步验证工具调用。接着上面的代码调一次control_deviceresult send({ jsonrpc: 2.0, id: 3, method: tools/call, params: {name: control_device, arguments: {device_id: light_living, action: on, value: 80}} }) print(result[result][content][0][text])预期返回类似{success: true, device_id: light_living, state: {online: true, status: on, brightness: 80}}第三步鸿蒙侧 Bridge 验证。在设备上跑一段 ArkTS确认分布式 KV 能写入并同步import distributedData from ohos.data.distributedData; async function testBridge() { const kvManager distributedData.createKVManager({ context: getContext(this), storeId: mcp_harmony_bridge, }); const kvStore await kvManager.getKVStore(mcp_harmony_bridge, { createIfMissing: true, autoSync: true, kvStoreType: distributedData.KVStoreType.DEVICE_COLLABORATION, securityLevel: distributedData.SecurityLevel.S1, }); await kvStore.put(mcp_cmd_test, JSON.stringify({ type: device_control, target: light_living })); const entries await kvStore.getEntries(mcp_cmd_test); console.info(bridge entries: JSON.stringify(entries)); }如果entries能读到刚写入的命令说明鸿蒙侧 Bridge 通了。端到端联调时把 Agent 的推理循环接上模型返回tool_calls后由 MCP Client 执行再把结果回传模型整条链路就闭环了。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth排错时先定位是哪条链路。模型链路报错通常是 401 或reading choicesMCP 链路报错通常是local proxy failed或方法找不到鉴权链路报错是 OAuth 相关。401 Unauthorized模型调用返回 401九成是 Key 错了或没带。检查api_key是不是sk-开头Base URL 是不是https://taotoken.net/api注意别多加/v1有些客户端会自动拼。如果 Key 是从控制台复制的确认没有多余空格。local proxy failed这个报错通常出现在 MCP Client 启动 Server 时Host 找不到command指定的可执行文件。检查command是不是绝对路径args里的脚本路径是否存在。另一个常见原因是 Server 启动后立刻退出Host 认为连接失败。手动跑一次 Server看 stderr 有没有报错。reading choices 报错形如NoneType object has no attribute choices或reading choices说明模型返回体结构不对。常见原因是 Base URL 配成了非 OpenAI 兼容格式或者 Model ID 填错导致返回了错误结构。用第 2 节的最小代码先验证模型能返回标准结构。OAuth 相关报错Claude Code 或某些 Host 会走 OAuth 流程如果报OAuth token expired或invalid_grant说明鉴权 token 过期。重新在控制台生成 Key或按接入文档重新走一遍授权。注意 OAuth 和 API Key 是两套机制别混用。stdio Buffer 死锁这个不报错但 Agent 会卡死。原因是 Server 的 stderr 用了 PIPE日志写满 64KB 后 stdout 的readline()阻塞。解决方法是把 stderr 重定向到文件或起一个线程实时读 stderrimport threading, sys def drain_stderr(proc): for line in proc.stderr: print(f[MCP] {line}, filesys.stderr) threading.Thread(targetdrain_stderr, args(proc,), daemonTrue).start()工具列表不刷新Server 更新了工具定义但 Client 还用旧缓存。MCP 的tools.listChanged: true只是声明实际刷新要 Client 自己实现。可以在 Client 里起一个轮询线程每 30 秒对比一次工具列表的 JSON 字符串。鸿蒙分布式 KV 命令丢失默认 PULL 模式在弱网下会丢命令。改成SYNC_MODE_PUSH并加一个结果确认机制——写入命令后轮询确认 key最多等 2 秒。排错的核心思路是分层先确认模型链路用最小代码再确认 MCP 协议层用 tools/list最后确认鸿蒙 Bridge用 KV 读写。哪层报错就查哪层别一上来就怀疑模型。6. 语义一致 CTA把这条链路用到你自己的鸿蒙设备上整条链路跑通后你会发现 MCP 的价值不在协议本身多复杂而在于它把「工具接入」这件事标准化了。以前接 3 个工具要写 3 套适配代码现在写一个 MCP Server 就能被所有支持 MCP 的 Host 复用。鸿蒙设备接入也是同理Bridge 层把分布式能力翻译成标准工具Agent 侧完全不用关心底层是软总线还是 KV。如果你要复现这套方案建议按这个顺序推进先用模型对话页面验证 Key 可用再按第 3 节的 JSON 配置起一个最小 MCP Server用第 4 节的代码验证 tools/list 和 tools/call最后接鸿蒙 Bridge。每一步都单独验证别一次性全接上否则报错时很难定位。长期做编码和 Agent 开发的话Coding Plan 能省不少调用成本。需要看完整接口说明的接入文档里有每个端点的参数和返回结构。控制台里可以管理 Key 和查看调用量API Keys 页面直接创建。最后留一个实操建议MCP Server 的工具描述description要写清楚模型靠它决定调不调用。我见过太多人工具描述写「控制设备」四个字结果模型根本不知道能控制什么。把设备类型、支持的动作、参数范围都写进 description工具调用准确率会明显提升。