
1. 从一个真实痛点说起为什么外部系统接 MCP 总是跑不通很多人第一次接触 MCPModel Context Protocol时脑子里想的都是这不就是个能调工具的协议吗然后兴冲冲地打开编辑器写一个call_api()函数把公司工单系统的 REST 接口原样包一层结果模型要么不调用要么乱传参数要么调用完拿到一堆看不懂的报错。问题不在 HTTP 请求怎么写而在于你没有把外部系统的能力切成模型能理解、能安全调用的接口。MCP Tool 的本质是接口设计。它要回答四个问题这个工具给谁用、输入长什么样、输出长什么样、出错时怎么表达。把这四件事想清楚再动手写 Server成功率会高很多。这篇内容聚焦一条完整链路从 Tool schema 定义、Server 端参数校验到 OpenClaw 侧调用验证最后用 TaoToken 统一 Key 通道管理鉴权。目标很明确——你照着配置就能跑通一个能被 MCP 客户端识别的外部工具。适合谁看已经理解 MCP 基本概念、想把公司内部系统工单、CRM、部署平台、知识库接进 Agent 的开发者。如果你还在纠结MCP 是什么建议先补一下基础再回来跟着做。下面所有步骤都可以直接复制我尽量把每个参数、每个报错都写清楚。2. 前置准备TaoToken 统一 Key 通道与 MCP Server 环境在写 schema 之前先把鉴权通道这件事解决掉。外部系统通常需要 token、OAuth 或服务账号如果每个 MCP Server 都自己管一套密钥很快就会乱。我的做法是所有出站请求统一走 TaoToken 的 API 通道MCP Server 只持有一个 TaoToken Key外部系统的真实凭证由通道侧管理。这样模型侧永远看不到真实密钥日志里也不会打印敏感信息。TaoToken 在这里扮演的是统一 Key/API 通道的角色。你可以在官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 了解整体能力实际接入时用 API 地址 https://taotoken.net/api注意这个地址不加 UTM 参数。它的价值在于一个 Key 打通多个模型和工具调用MCP Server 不需要为每个上游系统维护独立凭证。环境准备清单Node.js 18 或 Python 3.10本文用 Node.js 示例Python 思路一致一个可用的 TaoToken API Key在控制台创建见下方 deep linkOpenClaw 客户端用于最终调用验证一个你想接入的外部系统本文以内部订单系统为例创建 Key 的入口https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentmcp_tool_serverutm_campaignrewrite Key 管理页在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentmcp_tool_serverutm_campaignrewrite 。接入文档参考 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentmcp_tool_serverutm_campaignrewrite 。注意不要把 TaoToken Key 硬编码进 MCP Server 源码用环境变量注入。stdio server 的日志一律写 stderr写 stdout 会破坏 JSON-RPC 消息流这是新手最常踩的坑。初始化项目mkdir mcp-order-server cd mcp-order-server npm init -y npm install modelcontextprotocol/sdk zod装完这两个包基础环境就好了。modelcontextprotocol/sdk提供 Server 和 Transport 实现zod用来做参数校验和 schema 生成。3. 可复制配置Tool schema 设计与 Server 落地片段这一步是核心。先选一个窄场景不要把整个订单系统一次性接进来。我们只做三件事按订单号查询、查退款状态、添加内部备注。前两个是读操作第三个有副作用但风险低。真正的退款动作不做成无确认工具。3.1 Tool schema 模板坏 schema 长这样do_order_action(action, data)——模型根本不知道action能填什么data里该放什么。好 schema 要具体到字段类型、枚举值、必填项。{ name: order_lookup, description: 根据订单号查询订单基础信息只读操作不产生任何副作用。, inputSchema: { type: object, properties: { order_id: { type: string, description: 订单号格式为 ORD- 开头加 8 位数字例如 ORD-20240101 }, include_items: { type: boolean, description: 是否返回订单明细行默认 false, default: false } }, required: [order_id], additionalProperties: false } }三个关键点description写清楚工具做什么、是否只读order_id给出格式示例模型才知道怎么填additionalProperties: false防止模型塞入未定义字段。3.2 Server 端参数校验与实现用 zod 定义 schemaSDK 会自动生成 JSON Schema 并做运行时校验import { Server } from modelcontextprotocol/sdk/server/index.js; import { StdioServerTransport } from modelcontextprotocol/sdk/server/stdio.js; import { z } from zod; const TAOTOKEN_BASE https://taotoken.net/api; const TAOTOKEN_KEY process.env.TAOTOKEN_API_KEY; const OrderLookupInput z.object({ order_id: z.string().regex(/^ORD-\d{8}$/, 订单号格式应为 ORD- 加 8 位数字), include_items: z.boolean().optional().default(false), }); async function callUpstream(path, body) { const res await fetch(${TAOTOKEN_BASE}${path}, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${TAOTOKEN_KEY}, }, body: JSON.stringify(body), }); if (!res.ok) { return { ok: false, code: res.status 401 ? UPSTREAM_UNAUTHORIZED : UPSTREAM_ERROR, message: 上游返回 ${res.status}, retryable: res.status 500, }; } return { ok: true, data: await res.json() }; } const server new Server( { name: order-mcp-server, version: 1.0.0 }, { capabilities: { tools: {} } } ); server.setRequestHandler(tools/list, async () ({ tools: [ { name: order_lookup, description: 根据订单号查询订单基础信息只读操作。, inputSchema: { type: object, properties: { order_id: { type: string, description: 订单号如 ORD-20240101 }, include_items: { type: boolean, default: false }, }, required: [order_id], additionalProperties: false, }, }, ], })); server.setRequestHandler(tools/call, async (req) { if (req.params.name ! order_lookup) { return { content: [{ type: text, text: JSON.stringify({ ok: false, code: TOOL_NOT_FOUND, message: 未知工具 ${req.params.name}, retryable: false, })}], isError: true, }; } const parsed OrderLookupInput.safeParse(req.params.arguments); if (!parsed.success) { return { content: [{ type: text, text: JSON.stringify({ ok: false, code: INVALID_ARGUMENT, message: parsed.error.issues[0].message, retryable: false, })}], isError: true, }; } const result await callUpstream(/v1/order/lookup, parsed.data); return { content: [{ type: text, text: JSON.stringify(result) }], isError: !result.ok, }; }); const transport new StdioServerTransport(); await server.connect(transport); console.error(order-mcp-server started); // 必须写 stderr3.3 OpenClaw 侧 MCP Server 配置在 OpenClaw 的 MCP 配置里注册这个 Server。配置文件通常放在~/.openclaw/mcp.json路径以你本地实际为准{ mcpServers: { order: { command: node, args: [/absolute/path/to/mcp-order-server/index.js], env: { TAOTOKEN_API_KEY: sk-your-taotoken-key } } } }三件套必须齐全Base URLhttps://taotoken.net/api、Key环境变量注入、Model ID在 OpenClaw 模型配置里指定例如claude-sonnet-4或你账号下可用的模型标识。缺任何一个调用都会失败。4. 验证请求一次端到端调用与成功结果配置写完后先别急着让模型调用手动验证三步Server 能启动、tools/list能看到工具、tools/call能返回结果。启动 ServerTAOTOKEN_API_KEYsk-your-key node index.js如果终端输出order-mcp-server started且没有报错说明 stdio 通道正常。接着用 MCP Inspector 或直接发 JSON-RPC 消息验证echo {jsonrpc:2.0,id:1,method:tools/list,params:{}} | \ TAOTOKEN_API_KEYsk-your-key node index.js预期返回{ jsonrpc: 2.0, id: 1, result: { tools: [ { name: order_lookup, description: 根据订单号查询订单基础信息只读操作。, inputSchema: { type: object, properties: { ...: {} } } } ] } }再验证一次真实调用echo {jsonrpc:2.0,id:2,method:tools/call,params:{name:order_lookup,arguments:{order_id:ORD-20240101}}} | \ TAOTOKEN_API_KEYsk-your-key node index.js成功时返回{ok:true,data:{...}}失败时返回结构化错误。到这里Server 侧就通了。最后在 OpenClaw 里验证。重启 OpenClaw 让它加载新的 MCP 配置然后在对话里输入帮我查一下订单 ORD-20240101 的状态如果 OpenClaw 正确识别并调用了order_lookup你会看到工具调用记录和返回结果。这一步跑通说明整条链路——schema 定义、Server 校验、TaoToken 通道鉴权、OpenClaw 消费——全部打通。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth接入过程中最常见的四类报错我按真实日志对照给你排查思路。401 Unauthorized / UPSTREAM_UNAUTHORIZEDTaoToken Key 没注入或写错。检查env里的TAOTOKEN_API_KEY是否和 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentmcp_tool_serverutm_campaignrewrite 里创建的一致。注意 Key 不要带多余空格环境变量名大小写要匹配。local proxy failed / connection refusedServer 进程没起来或者 OpenClaw 配置里的command/args路径不对。用绝对路径别用相对路径。先手动node index.js确认能启动再让 OpenClaw 拉起。Error reading choices / unexpected token通常是 stdout 被污染了。检查代码里有没有console.logstdio server 的所有日志必须走console.error。JSON-RPC 消息流里混入普通文本客户端解析就会失败。OAuth / token expired外部系统的 OAuth token 过期。如果你走 TaoToken 通道检查通道侧凭证是否有效如果是直连外部系统需要实现 token 刷新逻辑。建议把刷新逻辑放在通道侧MCP Server 只关心业务参数。提示每次改完配置先手动跑一遍tools/list确认 Server 本身没问题再去 OpenClaw 里测。这样能把Server 问题和客户端配置问题分开定位。6. 继续深入从单工具到多工具与 Coding Plan跑通一个工具后下一步是扩展。按同样的方法把order_refund_status读和order_add_internal_note低风险写加进来。注意写操作要有副作用标记高风险动作如真实退款不要做成无确认工具而是拆成生成退款候选清单这种只读工具让人工确认后再执行。如果你要长期做 Agent 开发、频繁调用模型和工具可以考虑 TaoToken 的 Coding Plan适合持续编码和 Agent 场景https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentmcp_tool_serverutm_campaignrewrite 。想先验证模型对话效果用 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmcp_tool_serverutm_campaignrewrite 。Claude Code 相关接入参考 https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentmcp_tool_serverutm_campaignrewrite 。最后留一个练习选一个你手边的外部系统列出三个适合暴露的能力判断它们分别是 Tool、Resource 还是 Prompt为其中一个写出输入 schema 和结构化错误格式。写完对照本文的order_lookup模板改一遍你会发现大部分坑在动手前就能避开。