ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

MCP 的流程 和MCP Client的搭建和使用:用 TypeScript SDK 配 TaoToken 打通配置骨架

2026/9/27 15:22:57 拓冰建站 浏览量
MCP 的流程 和MCP Client的搭建和使用:用 TypeScript SDK 配 TaoToken 打通配置骨架 1. 从一次“工具列表拉不到”的报错说起如果你正在写一个本地 AI 工具想让大模型调用你机器上的脚本、查数据库、读文件那大概率会碰到MCPModel Context Protocol这个词。MCP 是一套让大模型和本地/远程工具对话的协议你可以把它理解成“AI 世界的 USB-C 接口”模型不关心工具内部怎么实现只要双方按协议握手、协商能力、暴露工具列表就能完成一次工具调用。而MCP Client就是你项目里负责跟 MCP Server 通信的那一端它把模型吐出来的tool_calls翻译成真正的函数执行再把结果塞回对话。这篇面向的是想用TypeScript SDK从零搭一个 MCP Client 的开发者尤其是本地 AI 工具需要统一 Key/API 通道接入模型服务的场景。我会把 MCP 从握手、能力协商到工具调用的完整流程讲清楚然后给出一份可复制的settings.json/config.toml骨架和 SDK 初始化代码最后跑通一次真实的工具调用。模型服务这一侧我用 TaoToken 作为统一入口这样 Client 里只需要维护一个 Key 和一个 Base URL不用为每个模型厂商改一遍鉴权逻辑。我试过把模型调用散落在各个工具函数里结果换一个模型就要改五六个文件后来统一收敛到 Client 的callModel方法维护成本才降下来。下面按“流程 → 前置 → 配置 → 验证 → 排障”的顺序走一遍。2. MCP 的完整流程握手、能力协商、工具调用在写代码之前先把协议流程捋顺不然调试时会分不清是传输层断了还是能力没协商上。2.1 传输层建立与初始化握手MCP Client 启动后第一件事是建立传输层连接。最常见的两种是StdioClientTransport把 Server 当子进程拉起走标准输入输出和 SSE/HTTP 传输连远程 Server。以 stdio 为例Client 会 spawn 一个子进程然后发送initialize请求里面带上自己的协议版本、客户端名称和版本号。Server 收到后返回它支持的协议版本和capabilities字段这一步就是握手。如果双方协议版本不兼容握手会直接失败后面的listTools根本不会执行。2.2 能力协商Server 到底能干什么握手响应里的capabilities是关键。它可能包含tools、resources、prompts、logging等字段。只有 Server 声明了tools能力Client 才能调用listTools()声明了resources才能listResources()。很多人第一次写 Client 时直接调listTools报Method not found就是因为没检查能力协商结果。正确的做法是握手后先读capabilities再决定后续调用哪些方法。2.3 工具列表拉取与 Schema 转换能力协商通过后Client 调用listTools()Server 返回一个工具数组每个工具包含name、description和inputSchemaJSON Schema 格式。这个inputSchema需要被转换成模型能理解的 function calling 格式也就是{ type: function, function: { name, description, parameters } }。转换时要注意parameters直接透传inputSchema即可不要自己重写字段否则模型生成的参数可能对不上。2.4 工具调用与结果回填用户提问后Client 把消息和工具定义一起发给模型。模型如果决定调用工具会在响应里返回tool_calls每个 call 带function.name和function.argumentsJSON 字符串。Client 解析参数在本地工具表里找到同名工具并执行拿到结果后再作为一条role: tool或role: user的消息追加进对话发起第二轮模型请求让模型基于工具结果生成最终回答。这个“模型 → 工具 → 模型”的循环就是 MCP 工具调用的核心。3. TaoToken 前置统一 Key 与 API 通道在写 Client 之前先把模型服务这一侧准备好。TaoToken 在这里扮演的是统一 API 通道的角色你只需要一个 Key 和一个 Base URL就能在 Client 里调用不同模型不用为每个厂商单独写鉴权。第一步去官网注册并登录https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。登录后在控制台创建 API Key建议按项目命名方便后续轮换。第二步拿到 Key 后在项目根目录建.env文件把 Key 写进去并确保.gitignore里有.env避免误提交。Base URL 统一用https://taotoken.net/api注意这个地址不加 UTM 参数直接作为 API 端点使用。第三步如果你用的是 Claude Code 这类编码工具可以在它的配置里指向 TaoToken 的 Anthropic 兼容端点如果是自己写 Client就按下面第 4 节的代码走。控制台里还能看到用量和调用记录调试阶段很有用。注意Key 只放在.env或环境变量里不要硬编码进index.ts也不要把.env提交到仓库。4. 可复制配置settings.json / config.toml 骨架下面这份配置骨架可以直接抄按你的项目路径改一下即可。settings.json适合 VS Code 系或 Claude Code 这类工具config.toml适合 Codex 风格的配置。4.1 settings.json 骨架{ mcpServers: { local-tools: { command: node, args: [/absolute/path/to/server/build/index.js], env: { TAOTOKEN_API_KEY: ${env:TAOTOKEN_API_KEY} } } }, model: { provider: taotoken, baseUrl: https://taotoken.net/api, apiKeyEnv: TAOTOKEN_API_KEY, modelName: your-model-name } }这里mcpServers定义的是 MCP Server 的启动方式model段是 Client 调用模型时的参数。baseUrl固定为 TaoToken 的 API 地址apiKeyEnv指向环境变量名避免明文。4.2 config.toml 骨架[mcp_servers.local_tools] command node args [/absolute/path/to/server/build/index.js] [model] provider taotoken base_url https://taotoken.net/api api_key_env TAOTOKEN_API_KEY model_name your-model-name4.3 项目初始化与依赖npm init -y npm install modelcontextprotocol/sdk axios dotenv npm install -D typescript types/nodepackage.json里把type: module加上tsconfig.json用Node16模块解析{ compilerOptions: { target: ES2022, module: Node16, moduleResolution: Node16, outDir: ./build, rootDir: ./, strict: true, esModuleInterop: true, skipLibCheck: true }, include: [index.ts], exclude: [node_modules] }5. TypeScript SDK 初始化与工具调用代码这一节是核心把 Client 类写出来。代码分三块导入与初始化、连接 Server 并拉取工具、处理查询与工具调用。5.1 导入与 Client 初始化import axios from axios; import readline from readline/promises; import dotenv from dotenv; import { Client } from modelcontextprotocol/sdk/client/index.js; import { StdioClientTransport } from modelcontextprotocol/sdk/client/stdio.js; dotenv.config(); const TAOTOKEN_KEY process.env.TAOTOKEN_API_KEY; const TAOTOKEN_BASE https://taotoken.net/api; if (!TAOTOKEN_KEY) throw new Error(请在 .env 中配置 TAOTOKEN_API_KEY); interface ToolCall { id: string; function: { name: string; arguments: string }; } interface ToolDefinition { name: string; description: string; parameters: Recordstring, any; execute: (args: any) Promisestring; }Client是 MCP 的核心类StdioClientTransport负责把 Server 当子进程拉起。TAOTOKEN_BASE就是统一 API 通道的入口。5.2 连接 Server 与能力协商class MCPClient { private model your-model-name; private tools: ToolDefinition[] []; private transport: StdioClientTransport | null null; private mcp new Client({ name: mcp-client-cli, version: 1.0.0 }); async connectToServer(serverScriptPath: string) { const command serverScriptPath.endsWith(.py) ? process.platform win32 ? python : python3 : process.execPath; this.transport new StdioClientTransport({ command, args: [serverScriptPath], }); await this.mcp.connect(this.transport); const capabilities this.mcp.getServerCapabilities(); if (!capabilities?.tools) { throw new Error(Server 未声明 tools 能力无法拉取工具列表); } const { tools: remoteTools } await this.mcp.listTools(); this.tools remoteTools.map((t) ({ name: t.name, description: t.description || , parameters: t.inputSchema, execute: async (args: any) { const result await this.mcp.callTool({ name: t.name, arguments: args }); return JSON.stringify(result.content); }, })); console.log(已连接 MCP Server工具, this.tools.map((t) t.name).join(, )); } }这里显式检查了capabilities.tools避免能力没协商上就调listTools。execute里把结果JSON.stringify一下方便后续塞进消息。5.3 模型调用与工具循环private convertTool(tool: ToolDefinition) { return { type: function, function: { name: tool.name, description: tool.description, parameters: tool.parameters, }, }; } private async callModel(payload: any) { const { data } await axios.post( ${TAOTOKEN_BASE}/v1/chat/completions, { ...payload, stream: false }, { headers: { Content-Type: application/json, Authorization: Bearer ${TAOTOKEN_KEY}, }, } ); return data; } async processQuery(query: string) { const messages: any[] [{ role: user, content: query }]; const res await this.callModel({ model: this.model, messages, tools: this.tools.map((t) this.convertTool(t)), tool_choice: auto, }); const choice res.choices[0]; const toolCalls choice.message.tool_calls as ToolCall[] | undefined; if (toolCalls?.length) { for (const call of toolCalls) { const tool this.tools.find((t) t.name call.function.name); if (!tool) continue; const args JSON.parse(call.function.arguments || {}); const result await tool.execute(args); messages.push({ role: tool, tool_call_id: call.id, content: result }); } const final await this.callModel({ model: this.model, messages }); return final.choices[0].message.content; } return choice.message.content; }注意工具结果回填时用了role: tool并带上tool_call_id这是 OpenAI 兼容格式的要求少了tool_call_id模型会报错。5.4 交互循环与入口async chatLoop() { const rl readline.createInterface({ input: process.stdin, output: process.stdout }); console.log(输入内容输入 quit 退出); while (true) { const query await rl.question(请输入 ); if (query.toLowerCase() quit) break; const ans await this.processQuery(query); console.log(\nAI, ans, \n); } rl.close(); } } (async () { const mcpClient new MCPClient(); const scriptArg process.argv[2]; if (scriptArg) await mcpClient.connectToServer(scriptArg); await mcpClient.chatLoop(); process.exit(0); })();6. 验证请求握手成功、工具列表、单次调用代码写完后按下面步骤验证每一步都有明确的成功标志。先构建 TypeScriptnpm run build然后启动 Server假设你的 Server 在server/build/index.jsnode server/build/index.js再启动 Client把 Server 路径作为参数传进去node build/index.js server/build/index.js如果握手成功你会看到已连接 MCP Server工具xxx, yyy这行输出说明传输层建立、能力协商通过、listTools拉取成功。接着在提示符下输入一个会触发工具调用的问题比如“帮我查一下当前目录下的文件列表”观察是否打印出工具调用日志以及最终 AI 回答里是否包含工具返回的真实结果。如果三步都通过说明整条链路跑通了。提示验证模型本身是否可用可以单独用模型对话页面发一条消息确认 Key 和 Base URL 没问题再回来调 Client。7. 本篇常见错排查报错一Method not found: tools/list原因通常是 Server 没声明tools能力或者你连的 Server 版本太旧。检查握手响应里的capabilities确认有tools字段。如果 Server 是你自己写的在初始化时把capabilities: { tools: {} }加上。报错二spawn node ENOENTStdioClientTransport的command找不到可执行文件。Windows 上process.execPath一般没问题但如果你传的是相对路径的脚本args要用绝对路径。建议统一用path.resolve()转成绝对路径再传。报错三工具调用后模型报tool_call_id缺失回填工具结果时只写了role: tool和content漏了tool_call_id。每个tool_call的id必须原样带回否则模型无法把结果和调用对应起来。报错四401 UnauthorizedKey 没读到或格式不对。检查.env里变量名和代码里process.env.XXX是否一致Authorization头是不是Bearer开头。另外确认 Base URL 用的是https://taotoken.net/api不要多加斜杠或路径。报错五工具参数解析失败call.function.arguments是 JSON 字符串模型偶尔会返回不合法 JSON。加一层try/catch解析失败时把原始字符串记日志方便定位是模型问题还是 Schema 描述不清。8. 下一步把 Client 接进你的工作流跑通一次工具调用后你可以把 Client 封装成命令行工具或者接进现有的 Agent 框架。如果后续要做长期编码、批量任务建议用 Coding Plan 把调用额度管起来如果只是验证模型和工具链路模型对话页面足够接入和排障相关的文档都在接入文档里。Key 的管理统一在 API Keys 页面轮换和权限控制都在那里操作。把settings.json里的 Server 路径换成你自己的就能开始接真实工具了。