首页 / 资讯中心 / 文章详情

用 MCP 与 Claude 在 Node.js 里搭聊天应用:把 settings 改到 TaoToken 的完整配置

用 MCP 与 Claude 在 Node.js 里搭聊天应用:把 settings 改到 TaoToken 的完整配置 ★ FEATURED ARTICLE
1. Node.js 聊天应用接入 MCP 与 Claude 的真实场景如果你正在搜「Node.js MCP Claude 聊天应用 配置」大概率已经卡在同一个地方模型能聊天但一让它调用本地工具就报错或者流式输出到一半断了再或者settings.json里改了半天 Base URL 还是走默认通道。我试过把 MCP Server、Claude 模型和 Express 聊天界面串起来中间踩的坑基本都集中在「配置通道」和「工具注册」这两块。先说清楚这套东西是什么。MCPModel Context Protocol模型上下文协议是让 Claude 这类模型用统一格式去调用外部工具的协议层。它解决的核心问题是以前你写一个查天气的函数得手动拼 prompt 告诉模型「你有这个能力」模型返回的调用意图你还得自己解析。MCP 把这套流程标准化成tools/list、tools/call这样的接口模型侧和工具侧各说各话中间由 MCP Client 翻译。它能做什么你可以让聊天应用里的 Claude 去查数据库、读本地文件、调内部 API、算复杂公式而不用把逻辑全塞进 prompt。适合谁适合已经会用 Node.js 写 Express 服务、想让 AI 真正「动手」而不是只「动嘴」的开发者。整条链路是浏览器发消息 → Express 收请求 → MCP Client 带上工具定义请求模型 → 模型返回tool_use→ Client 调 MCP Server 执行 → 结果回传模型 → 流式吐回浏览器。这里有个关键前提模型请求得走一条稳定、可配置的 API 通道。很多教程默认你直连官方但实际项目里你需要能改 Base URL、能换 Key、能指定 Model ID。下面我会用 TaoToken 作为 API 通道来演示因为它把这三件套Base URL Key Model ID都暴露成可配置项改起来直观。官网入口在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 根地址是 https://taotoken.net/api 注意 API 地址不带 UTM 参数。我先把整体架构拆成三层你对照自己的项目看缺哪层层文件职责常见故障Web 层server.js收 HTTP 请求、管会话、SSE 推流流式断连、CORS协议层mcpClient.js连模型 连 MCP Server、转发 tool_use401、tool 未注册工具层mcpServer.js定义工具、执行、返回结果schema 不匹配、超时三层里最容易出问题的是协议层因为它同时握着模型通道和工具通道两把钥匙。模型通道配错报 401 或local proxy failed工具通道配错报tool not found或reading choices。接下来我按「先配通道、再写工具、最后验证」的顺序走一遍每一步都给可复制的片段。2. TaoToken 前置配置Base URL、Key 与 Model ID 三件套在写任何 MCP 代码之前先把模型通道打通。这一步不做后面所有报错你都会误以为是 MCP 的问题。TaoToken 的接入信息就三样Base URL、API Key、Model ID。Base URL 固定是https://taotoken.net/apiKey 去控制台生成Model ID 按你实际要用的 Claude 型号填。先去控制台拿 Key。打开 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 登录后在 API Keys 页面创建一个新 Key。创建时给它起个能认出来的名字比如mcp-chat-dev方便后面区分环境。复制出来的 Key 一般以sk-开头只显示一次丢了就重新建。拿到 Key 之后别急着写进代码先写进环境变量。项目根目录建一个.env文件# .env TAOTOKEN_API_KEYsk-你的实际key TAOTOKEN_BASE_URLhttps://taotoken.net/api TAOTOKEN_MODELclaude-3-5-sonnet-20241022注意.env一定要进.gitignoreKey 泄露是新手最常见的翻车点。然后在package.json里确认依赖MCP 相关的 SDK 和 Anthropic SDK 都要装npm init -y npm install express dotenv anthropic-ai/sdk modelcontextprotocol/sdk这里有个细节anthropic-ai/sdk默认会读ANTHROPIC_API_KEY和ANTHROPIC_BASE_URL这两个环境变量。如果你不想改代码里的变量名可以直接用官方变量名省得在初始化时手动传# .env 的另一种写法直接用官方变量名 ANTHROPIC_API_KEYsk-你的实际key ANTHROPIC_BASE_URLhttps://taotoken.net/api两种写法都行我建议用官方变量名因为 SDK 会自动接管少写几行初始化代码。但如果你项目里同时接了多个通道用自定义变量名更清晰初始化时显式传参即可。如果你用的是 Claude Code 这类带settings.json的工具配置结构是这样的路径按你系统实际位置macOS/Linux 一般在~/.claude/settings.jsonWindows 在%USERPROFILE%\.claude\settings.json{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的实际key, ANTHROPIC_MODEL: claude-3-5-sonnet-20241022 } }这个 JSON 片段就是「把 settings 改到 TaoToken」的核心。三个字段缺一不可Base URL 决定请求打到哪Key 决定能不能过鉴权Model ID 决定用哪个模型。很多人只改了 Base URL 忘了 Model ID结果请求发出去了但模型名对不上报model not found。如果你用的是 Cline 或带 MCP 配置的编辑器MCP Server 的注册通常写在单独的配置文件里格式类似{ mcpServers: { local-tools: { command: node, args: [/绝对路径/mcpServer.js], env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的实际key } } } }这里command和args指向你的 MCP Server 启动方式env把通道信息透传给子进程。注意路径要用绝对路径相对路径在不同工作目录下会找不到文件这是MCP server failed to start的高频原因。配完这些先别写业务逻辑用一条 curl 验证通道是否通curl https://taotoken.net/api/v1/messages \ -H x-api-key: $ANTHROPIC_API_KEY \ -H anthropic-version: 2023-06-01 \ -H content-type: application/json \ -d { model: claude-3-5-sonnet-20241022, max_tokens: 64, messages: [{role: user, content: ping}] }返回里带content数组且里面有文本说明通道通了。如果返回 401检查 Key 有没有多余空格如果返回local proxy failed检查 Base URL 是不是写成了带路径的完整地址应该是https://taotoken.net/api不要自己加/v1SDK 会拼。这一步过了再往下写 MCP。3. 可复制配置MCP Server 注册与 Claude 客户端初始化通道验证通过后开始写三层代码。先写工具层mcpServer.js它定义 Claude 能调用的工具。MCP SDK 的 Server 类负责注册工具和启动 stdio 传输// mcpServer.js import { Server } from modelcontextprotocol/sdk/server/index.js; import { StdioServerTransport } from modelcontextprotocol/sdk/server/stdio.js; const server new Server( { name: local-tools, version: 1.0.0 }, { capabilities: { tools: {} } } ); // 注册工具列表 server.setRequestHandler(tools/list, async () ({ tools: [ { name: getCurrentWeather, description: 获取指定城市的当前天气, inputSchema: { type: object, properties: { location: { type: string, description: 城市名如 Beijing }, unit: { type: string, enum: [celsius, fahrenheit] } }, required: [location] } } ] })); // 处理工具调用 server.setRequestHandler(tools/call, async (request) { const { name, arguments: args } request.params; if (name getCurrentWeather) { const { location, unit celsius } args; // 真实项目这里换成天气 API 调用 return { content: [ { type: text, text: JSON.stringify({ location, temperature: unit celsius ? 22 : 72, conditions: Sunny, humidity: 45% }) } ] }; } throw new Error(Unknown tool: ${name}); }); const transport new StdioServerTransport(); await server.connect(transport);注意这里用的是setRequestHandler而不是老教程里的defineToolSDK 版本更新后 API 变了用旧写法会报server.defineTool is not a function。工具定义里inputSchema必须是合法 JSON Schemarequired数组别漏否则模型可能不传参数。接着写协议层mcpClient.js它同时连模型和 MCP Server// mcpClient.js import Anthropic from anthropic-ai/sdk; import { Client } from modelcontextprotocol/sdk/client/index.js; import { StdioClientTransport } from modelcontextprotocol/sdk/client/stdio.js; export class McpChatClient { constructor() { this.anthropic new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY, baseURL: process.env.ANTHROPIC_BASE_URL }); this.model process.env.ANTHROPIC_MODEL || claude-3-5-sonnet-20241022; this.chatHistory []; this.mcp null; this.tools []; } async init() { const transport new StdioClientTransport({ command: node, args: [./mcpServer.js] }); this.mcp new Client({ name: chat-client, version: 1.0.0 }); await this.mcp.connect(transport); const { tools } await this.mcp.listTools(); this.tools tools.map((t) ({ name: t.name, description: t.description, input_schema: t.inputSchema })); console.log(已注册工具:, this.tools.map((t) t.name).join(, )); } async processQuery(query) { this.chatHistory.push({ role: user, content: query }); let response await this.anthropic.messages.create({ model: this.model, max_tokens: 1024, messages: this.chatHistory, tools: this.tools }); // 循环处理工具调用直到模型不再请求工具 while (response.stop_reason tool_use) { const toolUse response.content.find((c) c.type tool_use); const result await this.mcp.callTool({ name: toolUse.name, arguments: toolUse.input }); this.chatHistory.push({ role: assistant, content: response.content }); this.chatHistory.push({ role: user, content: [ { type: tool_result, tool_use_id: toolUse.id, content: result.content } ] }); response await this.anthropic.messages.create({ model: this.model, max_tokens: 1024, messages: this.chatHistory, tools: this.tools }); } const text response.content .filter((c) c.type text) .map((c) c.text) .join(); this.chatHistory.push({ role: assistant, content: text }); return text; } }这里有几个关键点。第一tools字段的格式是input_schema下划线不是inputSchema从 MCP 的listTools拿到的字段名要转换写错模型会忽略工具。第二工具调用要用while循环因为模型可能连续调多个工具只处理一次会漏。第三tool_result的tool_use_id必须和tool_use的id对上对不上报tool_use_id mismatch。最后写 Web 层server.js用 SSE 做流式返回// server.js import dotenv/config; import express from express; import { McpChatClient } from ./mcpClient.js; const app express(); app.use(express.json()); app.use(express.static(public)); const client new McpChatClient(); await client.init(); app.post(/api/chat, async (req, res) { const { message } req.body; res.setHeader(Content-Type, text/event-stream); res.setHeader(Cache-Control, no-cache); res.setHeader(Connection, keep-alive); try { const reply await client.processQuery(message); // 按字符分块推送模拟流式 for (const char of reply) { res.write(data: ${JSON.stringify({ delta: char })}\n\n); await new Promise((r) setTimeout(r, 10)); } res.write(data: [DONE]\n\n); } catch (err) { res.write(data: ${JSON.stringify({ error: err.message })}\n\n); } res.end(); }); app.listen(3000, () console.log(聊天服务已启动: http://localhost:3000));public/index.html里放一个简单的输入框和消息区用EventSource或fetch读 SSE 流即可。到这里三层就齐了配置片段全部可复制。注意server.js里await client.init()用了顶层 awaitNode 版本要 14.8低版本会报语法错误。4. 端到端验证一次带工具调用的完整对话代码写完启动服务验证。先单独跑 MCP Server 确认工具能列出来node mcpServer.js这个进程会挂在 stdio 上等输入不报错就说明 Server 本身没问题。然后启动 Web 服务node server.js控制台应该打印已注册工具: getCurrentWeather和聊天服务已启动。如果只打印了启动信息没有工具列表说明client.init()里的listTools没拿到数据回去检查mcpServer.js的tools/listhandler 有没有写对。打开浏览器访问http://localhost:3000输入一句会触发工具的话比如「北京现在天气怎么样」。正常链路是这样的浏览器 POST 到/api/chatprocessQuery把消息和工具定义发给模型模型返回stop_reason: tool_usecontent里有tool_use块name是getCurrentWeatherinput是{location: 北京}Client 调mcp.callToolMCP Server 返回天气 JSON结果作为tool_result回传模型模型生成自然语言回复比如「北京当前晴气温 22 摄氏度湿度 45%」回复按字符 SSE 推回浏览器用 curl 也能验证直接打接口看 SSE 流curl -N -X POST http://localhost:3000/api/chat \ -H Content-Type: application/json \ -d {message: 北京现在天气怎么样}返回应该是一串data: {delta:...}逐字推送最后data: [DONE]。如果返回里直接是data: {error:...}看错误信息定位。我实测下来第一次跑最容易卡在工具没被调用上——模型直接用自己的知识回答了天气没走工具。这通常是因为工具description写得太模糊模型判断不需要调用。把描述写具体比如「获取指定城市的实时天气数据包括温度、湿度、天气状况」模型就更愿意调。再验证一个多轮场景先问天气再追问「那适合穿外套吗」。第二轮请求会带上完整chatHistory模型能看到上一轮的工具结果直接基于 22 度给建议不会重复调工具。如果第二轮又调了一次工具说明chatHistory没正确累积检查processQuery里 push 的顺序。验证成功的标志有三个控制台打印了工具列表、浏览器能看到逐字输出、回复内容里包含工具返回的真实数据22 度、Sunny 这些。三个都满足说明模型通道、MCP 通道、流式链路全通了。5. 常见报错排查401、local proxy failed、reading choices这一节按真实报错对照排查每条都给定位方法和修复动作。401 Unauthorized / invalid api key。这是鉴权失败九成是 Key 的问题。先确认.env里ANTHROPIC_API_KEY没有引号、没有多余空格、没有换行。然后确认代码里初始化 SDK 时传的变量名和.env一致。如果你用的是settings.json确认 JSON 里没有尾逗号JSON 不允许尾逗号有的话整个配置解析失败Key 等于没配。还有一种情况是 Key 被复制时带了前后空格肉眼看不出来用echo $ANTHROPIC_API_KEY | cat -A看行尾有没有$之外的字符。local proxy failed / connection refused。这个报错说明请求根本没发出去卡在本地网络层。检查ANTHROPIC_BASE_URL是不是写成了https://taotoken.net/api/末尾多了斜杠SDK 拼接时会变成//v1/messages部分环境会拒绝。正确写法是https://taotoken.net/api不带末尾斜杠。另外确认你的运行环境能正常访问外网 HTTPS公司内网如果有出口限制需要走允许的通道。Cannot read properties of undefined (reading choices)。这个报错来自 SDK 解析响应时说明返回体结构不是预期的 OpenAI 兼容格式。常见原因是 Base URL 指错了地方比如指到了某个只支持原生 Anthropic 格式的端点但 SDK 按 OpenAI 格式解析。确认ANTHROPIC_BASE_URL是https://taotoken.net/api并且请求路径由 SDK 自动拼成/v1/messages。如果你手动拼了 URL去掉手动拼接交给 SDK 处理。tool_use_id mismatch / tool not found。工具链路问题。tool not found说明模型请求的工具名不在tools列表里检查mcpClient.js里listTools的结果有没有正确映射成input_schema格式。tool_use_id mismatch说明回传tool_result时tool_use_id和请求的id对不上检查while循环里有没有用错变量确保toolUse.id原样传回。MCP server failed to start / spawn node ENOENT。MCP Client 启动子进程失败。检查StdioClientTransport里的command是不是nodeargs里的路径是不是绝对路径。相对路径./mcpServer.js在 Web 服务的工作目录和 MCP 子进程的工作目录不一致时会找不到。改成path.resolve(__dirname, mcpServer.js)最稳。Windows 上如果node不在 PATH用process.execPath代替node。流式输出中断 / SSE 只收到一半。检查server.js里有没有在res.end()之前就返回了或者中间件里有没有对响应做缓冲。Express 默认不缓冲 SSE但如果你前面挂了压缩中间件compression它会把流式响应攒起来再发导致看起来像一次性返回。SSE 路由上禁用压缩即可。模型不调用工具直接回答。不是报错但很常见。三个调整方向把工具description写具体说明什么时候该用在系统提示里加一句「涉及实时数据时优先调用工具」确认tools数组非空空数组模型无从调用。我踩过的坑是工具描述写成了「获取天气」模型觉得它自己知道天气就不调了改成「获取指定城市的实时天气数据」后调用率明显上升。排查顺序建议固定成先 curl 验通道 → 再单跑 MCP Server 验工具 → 最后跑 Web 验链路。这样每层独立验证报错不会互相干扰。6. 继续往下走从能跑到好用链路跑通只是起点。真实项目里你还需要处理几件事工具执行加超时避免某个 API 卡住拖垮整个对话chatHistory加长度上限不然多轮之后 token 爆掉工具返回结果做截断大 JSON 直接塞回去会撑爆上下文。这些都是在mcpClient.js的processQuery里加几行判断的事。如果你要把这套东西用到长期编码或 Agent 场景建议把模型通道单独抽成配置方便在不同环境切换。TaoToken 的 Coding Plan 适合这种需要持续调用、频繁切换模型的场景入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。API Key 管理在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 遇到 SDK 层面的细节问题可以先翻文档。想直接验证模型对话效果用 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 快速试一句。最后留一个实用技巧把 MCP Server 的工具定义和 Web 层解耦工具单独一个仓库、单独版本号Client 通过配置读工具路径。这样你加新工具不用动聊天代码改完mcpServer.js重启子进程就行。工具多了之后按领域拆成多个 MCP ServerClient 同时连多个tools列表合并去重模型侧感知不到差异。这套结构撑到十几个工具没问题再往上就要考虑工具检索了那是另一个话题。
阅读完成 · 觉得有帮助?
咨询建站