1. 为什么要在 OpenClaw 里把 MCP 链路跑通
OpenClaw 是一个开源的 AI Agent 操作系统,当前版本 v2.7.9,它把 MCP(Model Context Protocol)作为连接外部工具的标准通道。你可以把它理解成:Agent 是大脑,MCP 是神经,工具是手脚。大脑再聪明,如果神经信号传不过去,手脚就不会动。很多人在本地装好 OpenClaw 之后,卡在“工具明明注册了却调不动”“tools/list 返回空”“tools/call 报 -32602”这类问题上,本质都是 MCP 协议交互链路没打通。
这篇是 OpenClaw 工程实战系列的第二篇,聚焦 MCP 协议交互与工具调用的工程落地。我会把 JSON-RPC 消息格式、Schema 定义、握手流程讲清楚,然后交付一份可复制的 TaoToken 统一 Key/API 通道配置骨架,最后给出完整的验证动作和排错清单。适合已经在本地跑起 OpenClaw、想真正把工具调用链路复现出来的开发者。读完你应该能自己抓包看到 initialize → tools/list → tools/call 的完整报文,并且知道每一段出错该往哪查。
需要说明的是,MCP 的传输层支持 stdio 和 Streamable HTTP 两种。本地调试用 stdio 最直观,因为消息就是子进程 stdin/stdout 上的换行分隔 JSON;远程服务用 HTTP 更方便。两种我都会给配置。
2. TaoToken 统一 Key 前置准备
在动手配 MCP 之前,先把模型通道准备好。OpenClaw 的 Agent 决策依赖 LLM,而工具调用的参数生成、工具选择都靠模型输出。如果你用多个厂商的模型,Key 管理会很乱。TaoToken 提供统一 Key 和统一 API 通道,一个 Key 走多个模型,省去在 settings.json 里塞一堆 base_url 的麻烦。
官网入口:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=
API 基地址:https://taotoken.net/api
你需要先拿到 API Key。登录后进入控制台,在 API Keys 页面创建一个新 Key。建议按用途分 Key,比如 openclaw-dev 一个、openclaw-prod 一个,方便后面排查是哪个环境出的问题。
创建 Key 的入口在这里:https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite
拿到 Key 之后,先别急着写进 OpenClaw 配置。用一条 curl 验证通道是否通:
curl -s https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "claude-sonnet-4-20250514", "messages": [{"role": "user", "content": "reply with ok"}], "max_tokens": 16 }'如果返回里有choices字段,说明通道正常。这一步很重要,因为后面 MCP 工具调用失败时,你要能快速区分是模型通道问题还是协议问题。如果这条 curl 就报 401,那后面所有排错都是白费功夫。
想先在网页上确认模型可用性,可以用模型对话页面直接发一条消息:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite
如果你打算长期跑编码类 Agent,比如让 OpenClaw 自动改代码、跑测试,那 Coding Plan 更划算,额度模型和按量计费不一样:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite
3. 可复制的 OpenClaw MCP 配置骨架
OpenClaw 的配置分两块:模型通道配置和 MCP Server 配置。模型通道走 TaoToken,MCP Server 按你的工具来源配。下面给一份 settings.json 骨架,你可以直接改。
3.1 settings.json 模型通道部分
{ "llm": { "provider": "openai-compatible", "baseUrl": "https://taotoken.net/api", "apiKey": "${TAOTOKEN_API_KEY}", "model": "claude-sonnet-4-20250514", "maxTokens": 8192, "temperature": 0.2 }, "agent": { "name": "openclaw-agent", "version": "2.7.9", "mcpEnabled": true, "toolCallTimeoutMs": 60000, "handshakeTimeoutMs": 30000 } }这里apiKey用环境变量引用,别硬编码。toolCallTimeoutMs和handshakeTimeoutMs对应 MCP 的请求超时和握手超时,后面排错会用到。
3.2 MCP Server 配置:stdio 方式
stdio 方式适合本地工具,Server 作为子进程启动,消息走 stdin/stdout。
{ "mcpServers": { "local-fs": { "transport": "stdio", "command": "node", "args": ["./mcp-servers/fs-server.js"], "env": { "MCP_AUTH_MODE": "token", "MCP_WORKSPACE": "/tmp/openclaw-workspace" } } } }注意env里不要塞模型 Key,MCP Server 只负责工具执行,模型调用是 Agent Core 的事。职责分离,排错时才能定位。
3.3 MCP Server 配置:Streamable HTTP 方式
远程工具用 HTTP 方式,认证走 Header。
{ "mcpServers": { "remote-tools": { "transport": "streamable-http", "url": "https://your-mcp-server.example.com/mcp", "headers": { "Authorization": "Bearer ${MCP_SERVER_TOKEN}", "MCP-Protocol-Version": "2025-06-18" }, "timeoutMs": 60000 } } }MCP-Protocol-Version这个 Header 很关键,它参与版本协商。如果你服务端只支持 2024-11-05,这里写 2025-06-18 会触发降级或拒绝,具体看服务端实现。
3.4 config.toml 等价写法
如果你用 TOML 风格配置,等价骨架如下:
[llm] provider = "openai-compatible" base_url = "https://taotoken.net/api" api_key = "${TAOTOKEN_API_KEY}" model = "claude-sonnet-4-20250514" max_tokens = 8192 [agent] mcp_enabled = true tool_call_timeout_ms = 60000 handshake_timeout_ms = 30000 [mcp_servers.local-fs] transport = "stdio" command = "node" args = ["./mcp-servers/fs-server.js"] [mcp_servers.local-fs.env] MCP_AUTH_MODE = "token" MCP_WORKSPACE = "/tmp/openclaw-workspace"两种格式选一种,别混用。OpenClaw 启动时会读其中一个,混用会导致配置覆盖,出现“改了没生效”的假象。
4. JSON-RPC 链路验证:从握手到工具调用
配置写完,接下来是验证。MCP 的交互本质是 JSON-RPC 2.0 消息序列,我按时间顺序拆开讲,每一步都给报文和验证方法。
4.1 第一步:initialize 握手
客户端发的第一条消息必须是 initialize,且必须带 id。
{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2025-06-18", "capabilities": { "roots": {"listChanged": true}, "sampling": {} }, "clientInfo": { "name": "openclaw-agent", "version": "2.7.9" } } }服务端响应:
{ "jsonrpc": "2.0", "id": 1, "result": { "protocolVersion": "2025-06-18", "capabilities": { "tools": {"listChanged": true}, "resources": {"subscribe": true, "listChanged": true} }, "serverInfo": { "name": "local-fs-server", "version": "1.0.0" } } }验证要点:result.protocolVersion必须在你客户端支持的版本列表里。如果服务端返回一个你不认识的版本,握手应该失败并断开,而不是硬着头皮继续。
4.2 第二步:initialized 通知
握手响应收到后,客户端发一条通知,注意没有 id。
{ "jsonrpc": "2.0", "method": "notifications/initialized" }这条消息发出去,握手才算完成,进入操作态。很多“tools/list 返回空”的问题,就是漏了这条通知,服务端还在等握手确认,自然不响应工具枚举。
4.3 第三步:tools/list 枚举工具
{ "jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {} }响应里每个工具带 inputSchema:
{ "jsonrpc": "2.0", "id": 2, "result": { "tools": [ { "name": "read_file", "description": "读取工作区内的文件内容", "inputSchema": { "type": "object", "properties": { "path": {"type": "string", "description": "相对工作区路径"}, "maxBytes": {"type": "integer", "default": 65536} }, "required": ["path"] }, "annotations": { "title": "读取文件", "readOnlyHint": true, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false } } ], "nextCursor": null } }验证要点:inputSchema.required里的字段,调用时必须提供。annotations.readOnlyHint为 true 的工具,OpenClaw 可以走缓存;destructiveHint为 true 的,应该触发用户确认。
4.4 第四步:tools/call 调用工具
{ "jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": { "name": "read_file", "arguments": { "path": "notes/todo.md", "maxBytes": 4096 } } }成功响应:
{ "jsonrpc": "2.0", "id": 3, "result": { "content": [ {"type": "text", "text": "# TODO\n- 验证 MCP 链路\n"} ], "isError": false } }注意区分两种错误:result.isError: true是工具执行层面的错误(比如文件不存在),error对象是协议层面的错误(比如方法不存在、参数无效)。排错时先看是哪一层。
4.5 用脚本抓完整链路
想亲眼看到这些报文,可以写个最小 stdio 客户端。下面这段 Python 直接和 MCP Server 子进程对话:
import json import subprocess import sys proc = subprocess.Popen( ["node", "./mcp-servers/fs-server.js"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1, ) def send(msg): proc.stdin.write(json.dumps(msg) + "\n") proc.stdin.flush() def recv(): line = proc.stdout.readline() return json.loads(line) if line else None send({ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2025-06-18", "capabilities": {}, "clientInfo": {"name": "probe", "version": "0.1"} } }) print("initialize ->", recv()) send({"jsonrpc": "2.0", "method": "notifications/initialized"}) send({"jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {}}) print("tools/list ->", recv()) send({ "jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": {"name": "read_file", "arguments": {"path": "notes/todo.md"}} }) print("tools/call ->", recv()) proc.stdin.close() proc.wait(timeout=5)跑通这个脚本,你就把 MCP 链路完整复现了一遍。后面 OpenClaw 里出的问题,都可以拿这个脚本对照。
5. 本篇常见错误排查清单
下面这些是我在本地复现时踩过的坑,按出现频率排序。
5.1 tools/list 返回空数组
最常见的原因是漏发notifications/initialized。服务端在收到这条通知前,处于“握手中”状态,不会响应工具枚举。检查你的客户端实现,确认 initialize 响应处理后立刻发了通知。
第二个原因是 Server 的 capabilities 里没声明 tools。如果 initialize 响应里capabilities.tools缺失,说明这个 Server 根本没注册工具,tools/list 返回空是正常的。去 Server 端确认工具注册逻辑。
5.2 tools/call 报 -32602 Invalid params
这是参数校验失败。MCP 的 inputSchema 是 JSON Schema,必填字段缺失、类型不匹配、枚举值越界都会触发。排查步骤:
先看错误对象的data字段,好的实现会告诉你哪个参数出问题:
{ "jsonrpc": "2.0", "id": 3, "error": { "code": -32602, "message": "参数验证失败", "data": {"field": "path", "issue": "必填参数缺失"} } }如果data是空的,就手动对照 tools/list 返回的 inputSchema,逐个检查 arguments 的字段名、类型、必填项。注意 JSON Schema 里integer和number是区分的,传了浮点数给 integer 字段会失败。
5.3 握手超时
handshakeTimeoutMs默认 30 秒。超时通常是 Server 启动慢,或者 stdio 子进程没正确输出。检查:
Server 的启动命令能不能手动跑起来。比如node ./mcp-servers/fs-server.js直接执行,看有没有报错。如果手动跑就崩,OpenClaw 里当然也起不来。
stdio 方式下,Server 的日志如果写到 stdout,会污染 JSON-RPC 消息流。日志必须走 stderr。这是 stdio 传输的硬性约束,很多人栽在这。
5.4 版本不兼容
客户端发 2025-06-18,服务端只支持 2024-11-05。规范的做法是服务端返回自己支持的最高版本,客户端判断是否兼容。如果客户端不支持服务端版本,应该断开。
排查时看 initialize 响应里的protocolVersion,和你配置里写的对不对得上。对不上就改配置,或者升级 Server。
5.5 工具调用结果 isError 为 true
这不是协议错误,是工具自己执行失败。比如读文件时路径不存在、调外部 API 时被限流。看content里的文本,通常有具体原因。
这类错误 OpenClaw 会传给 Agent,Agent 可能换个工具重试,或者告诉用户。你排查时关注工具本身的逻辑,而不是 MCP 协议。
5.6 连接建立后立刻断开
stdio 方式下,如果 Server 进程启动后立刻退出,连接就断了。常见原因是环境变量缺失,Server 启动时校验失败直接 exit。检查配置里的env字段,把 Server 需要的变量都补上。
HTTP 方式下,检查AuthorizationHeader 格式,Bearer 后面有没有多余空格,Token 有没有过期。
6. 把链路固化下来
MCP 链路验证通过后,建议做两件事固化成果。
第一,把第 4.5 节的探测脚本存进仓库,作为回归测试。每次改配置或升级 Server,先跑一遍脚本,确认 initialize、tools/list、tools/call 三段都正常,再启动 OpenClaw。这样能把协议层问题和 Agent 层问题分开。
第二,给每个 MCP Server 单独配超时。默认的 60 秒对本地文件操作太长,对远程 API 可能又太短。在 settings.json 的 mcpServers 里按 Server 覆盖timeoutMs,比全局改更精准。
如果你要接的模型比较多,或者想让 Agent 在编码任务里稳定跑长链路,TaoToken 的统一 Key 通道能省掉不少切换成本。接入文档在这里,里面有各语言 SDK 的调用示例:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite
Claude Code 这类编码 Agent 的接入配置也有专门说明:https://taotoken.net/claude-code-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=claude_code&utm_campaign=rewrite
链路跑通只是开始,真正难的是让 Agent 在工具选择上稳定。下一篇我会讲工具路由和 Schema 设计对模型决策的影响,那才是决定 Agent 好不好用的关键。