
1. 先看清 -32001 到底卡在哪一环MCP error -32001 (Request timed out) 是 Claude 这类 MCP 客户端在约定时间内没收到 Node.js MCP server 的 JSON-RPC 响应时抛出的错误。它不是一个网络断了的错误而是我发了请求但你没在窗口内回我。能做什么帮你把排查范围从玄学超时收敛到三个具体位置——初始化握手、工具 handler 阻塞、响应根本没返回。适合谁自己写了 Node.js MCP server、接进 Claude 后调工具就超时的人。我先把现象拆成两类因为它们的排查路径完全不同。第一类是 server 压根没收到请求。表现是 Claude 侧报 -32001但你 Node 进程的日志里连initialize都没打印。这种情况基本卡在启动阶段server 启动时做了重活加载大文件、连数据库、拉远程配置Claude 的握手超时窗口先到了。或者启动命令写错进程起来又立刻退出Claude 等了个寂寞。第二类是 server 收到了请求但没在窗口内回。表现是日志里能看到tools/call进来了然后就没有然后了。常见原因有三个handler 里写了同步阻塞操作fs.readFileSync读大文件、同步 HTTP 请求把 Node 的事件循环卡死handler 是 async 但漏了returnPromise resolve 成 undefined客户端永远等不到合法响应handler 抛了异常但没被捕获转成 JSON-RPC error请求悬空。MCP 基于 JSON-RPC 2.0客户端每发一个请求都期待一个响应。协议层有超时窗口server 在窗口内没回 response 或 progress客户端就主动判定 -32001。所以本质就一句话server 响应太慢或者根本没响应。这里有个容易误判的点本地用 inspector 或 mcp CLI 直连 server 时一切正常一接 Claude 就超时。原因是 inspector 往往不设严格超时或者你手动点的时候给了足够时间而 Claude 的超时窗口是固定的。所以本地能跑不能证明接 Claude 能跑得按客户端的超时标准来测。下面按先定位、再修配置、后验证的顺序走。我会给出可复制的 MCP server 配置片段、超时阈值调整示例以及一个最小请求来确认连接是否恢复。如果你在排查过程中需要确认模型侧的行为可以先用模型对话快速验证一次请求链路再回到本地配置。2. 把 Node.js MCP server 配置对齐到 TaoToken在动超时参数之前先把 MCP 客户端的 server 配置写对。很多 -32001 其实是配置里启动命令或环境变量不对导致 server 根本没正常起来Claude 在握手阶段就超时了。Claude Desktop 的 MCP 配置一般在claude_desktop_config.json路径按系统不同macOS 是~/Library/Application Support/Claude/claude_desktop_config.jsonWindows 是%APPDATA%\Claude\claude_desktop_config.json。这个文件里mcpServers字段决定 Claude 怎么拉起你的 Node server。一个能跑通的最小配置长这样{ mcpServers: { my-node-server: { command: node, args: [/absolute/path/to/your-server/build/index.js], env: { NODE_ENV: production, MCP_REQUEST_TIMEOUT_MS: 30000 } } } }三个字段必须写全缺一个都可能超时。command是启动可执行文件用node而不是npx能少一层解析开销args里务必用绝对路径相对路径在 Claude 的工作目录下经常找不到文件进程秒退env里可以塞超时相关的环境变量让你的 server 自己读。如果你用的是 Cline 或带 MCP 支持的编辑器配置结构类似但字段名可能是mcpServers下的command/args/env三件套。这里要强调一个高频坑Base URL、Key、Model ID 这三件套在 MCP 场景里同样要对齐。如果你的 Node server 内部要调用模型能力它需要知道往哪发请求、用什么凭证、用哪个模型。这三者任何一个缺失或写错server 在处理tools/call时就会卡在等待上游响应最终表现为 -32001。把这三件套落到配置里可以这样组织{ mcpServers: { my-node-server: { command: node, args: [/absolute/path/to/your-server/build/index.js], env: { BASE_URL: https://taotoken.net/api, API_KEY: sk-你的key, MODEL_ID: claude-sonnet-4-5, MCP_REQUEST_TIMEOUT_MS: 30000 } } } }Base URL 指向https://taotoken.net/apiKey 在控制台的 API Keys 页面生成Model ID 按你实际要用的模型填。这三件套写进env后server 启动时读环境变量就不会在运行时因为找不到配置而卡住。配置改完必须完全重启 Claude Desktop不是关窗口是退出进程再打开。MCP server 是在客户端启动时拉起的热改配置不生效。重启后可以在 Claude 里问一句让它列出可用工具如果工具列表能出来说明握手阶段过了-32001 至少不在初始化环节。这一步做完如果还超时就进入下一节调超时阈值和改 handler。3. 可复制的超时阈值与 handler 配置配置对齐后接下来处理server 收到了但回得慢。分两块客户端侧的超时阈值和 server 侧的 handler 写法。先看客户端侧。不同 MCP 客户端对超时的支持不一样有的暴露配置项有的写死。Claude Desktop 本身没有公开的超时配置字段但你的 server 可以通过环境变量控制自己的内部超时间接影响整体表现。把MCP_REQUEST_TIMEOUT_MS设成 3000030 秒是个稳妥起点慢操作多的话可以到 60000。server 侧才是重头戏。下面是一个修正后的 handler 写法把同步阻塞改成异步、确保一定 return、慢操作发 progressimport { readFile } from fs/promises; import { CallToolRequestSchema } from modelcontextprotocol/sdk/types.js; server.setRequestHandler(CallToolRequestSchema, async (req) { const { name, arguments: args } req.params; if (name read_large_file) { // 1. 用异步 API不阻塞事件循环 const data await readFile(args.path, utf8); // 2. 慢操作发进度通知重置客户端超时预期 if (req.meta?.progressToken) { await server.notification({ method: notifications/progress, params: { progressToken: req.meta.progressToken, progress: 1, total: 1, }, }); } // 3. 务必 return 含 content 的响应 return { content: [{ type: text, text: 文件长度 ${data.length} }], }; } // 未知工具也要返回不能悬空 return { content: [{ type: text, text: 未知工具: ${name} }], isError: true, }; });三个关键点readFile来自fs/promises而不是fs避免readFileSync卡事件循环progressToken存在时发进度通知让客户端知道你在干活每个分支都return包括未知工具的错误分支。如果你的 handler 里有 CPU 密集或同步阻塞的活用setImmediate或 worker 把它挪出主线程function heavySyncWork(input) { // 模拟同步阻塞 const start Date.now(); while (Date.now() - start 200) {} return input.toUpperCase(); } server.setRequestHandler(CallToolRequestSchema, async (req) { const result await new Promise((resolve) { setImmediate(() resolve(heavySyncWork(req.params.arguments.text))); }); return { content: [{ type: text, text: result }] }; });setImmediate把同步活推到事件循环的下一轮至少不会在同一个 tick 里把响应堵死。更彻底的做法是丢进 worker_threads但对大多数 MCP 工具来说setImmediate加异步 API 就够了。还有一个隐蔽的坑handler 里await了一个永远不会 resolve 的 Promise。比如等一个没设超时的外部请求或者等一个已经断开的连接。这种要在外部调用上加超时function withTimeout(promise, ms, label) { return Promise.race([ promise, new Promise((_, reject) setTimeout(() reject(new Error(${label} 超时 ${ms}ms)), ms) ), ]); } // 用法 const upstream await withTimeout(fetchUpstream(args), 8000, upstream);这样即使上游卡住你的 handler 也会在 8 秒内抛错并返回错误响应而不是让请求悬空到客户端超时。配置和 handler 都改完后重启 Claude再调一次工具。如果还超时进下一节用最小请求验证。4. 用一次最小请求验证连接是否恢复改完配置别急着上复杂工具先用一个最小请求确认链路通了。这一步的目的是把配置问题和业务逻辑问题分开。最小验证分两层。第一层在 server 侧用官方 inspector 或 mcp CLI 直连确认 server 本身能正常响应npx modelcontextprotocol/inspector node /absolute/path/to/your-server/build/index.jsinspector 起来后会给你一个本地地址在浏览器里打开能看到工具列表手动调一次tools/list和一次最简单的tools/call。如果这里就超时问题在 server 自身跟 Claude 无关回去查 handler。第二层在 Claude 侧重启后发一句最简单的指令比如列出你可用的工具。这一步走的是tools/list不涉及你的业务逻辑只验证握手和基础通信。工具列表能出来说明初始化握手和基础请求都通了。然后调一个最简单的工具最好是那种不依赖外部服务、纯计算的。比如一个echo工具server.setRequestHandler(CallToolRequestSchema, async (req) { if (req.params.name echo) { return { content: [{ type: text, text: req.params.arguments.text }], }; } // ... 其他工具 });在 Claude 里调echo传个字符串如果秒回说明整条链路通了-32001 已经解决。如果echo也超时那问题还在配置或启动阶段回到第 2 节检查路径和命令。验证通过后再逐个调你真正的业务工具。哪个工具超时就单独查那个 handler 的阻塞点和 return。这种先最小后全量的顺序能帮你快速定位是全局配置问题还是单个工具问题。如果你在验证过程中想确认模型侧对某个请求的响应是否符合预期可以用模型对话单独发一次同样的请求对比两边行为能更快判断是 server 逻辑问题还是客户端超时设置问题。5. 本篇常见报错对照排查这一节把实际会撞到的报错和对应动作列清楚方便你对着日志定位。MCP error -32001 (Request timed out)且 server 日志无initialize启动阶段就挂了。检查args路径是否为绝对路径、command是否在 PATH 里、Node 版本是否满足 server 要求。手动在终端跑一遍node /path/to/index.js看是否报错退出。MCP error -32001且日志有tools/call但无后续handler 阻塞或漏 return。搜 handler 里有没有readFileSync、同步execSync、没设超时的await。加日志在 handler 入口和 return 前确认执行到哪一步。local proxy failed或连接被拒MCP 客户端和 server 之间的本地通信断了。检查 server 进程是否还活着有没有因为未捕获异常退出。给 server 加全局错误处理process.on(uncaughtException, (err) { console.error(未捕获异常:, err); }); process.on(unhandledRejection, (err) { console.error(未处理的 Promise 拒绝:, err); });reading choices或类似字段读取错误通常是 server 内部调上游模型时响应结构不符合预期。检查 Base URL、Key、Model ID 三件套是否写对以及上游返回的 JSON 结构是否和你代码里解析的一致。Key 无效会返回 401Model ID 写错会返回模型不存在这些都会让 handler 抛错如果没捕获就变成悬空请求最终 -32001。401 UnauthorizedKey 没传或传错。确认env里的API_KEY真的被 server 读到了可以在 server 启动时打印一下process.env.API_KEY ? 已设置 : 缺失别打印完整 Key。OAuth相关报错如果你的 server 走的是需要 OAuth 的链路token 过期或 scope 不对会卡在鉴权。确认 token 有效期以及请求头里的Authorization格式是否正确。排查顺序建议固定成先看 server 有没有收到请求再看 handler 有没有 return再看有没有同步阻塞最后看客户端超时能不能调大。这个顺序能覆盖九成以上的 -32001。6. 把配置和验证固定成流程到这一步-32001 的排查路径已经走完配置对齐、超时调整、handler 修正、最小验证、报错对照。最后说几个把它固定成习惯的做法。第一把 MCP server 的启动命令和配置写进版本控制别只存在本地claude_desktop_config.json里。配置漂移是超时的常见来源今天能跑明天不行往往是有人改了路径或环境变量。第二给每个 handler 加必须返回的断言在 CI 里跑。一个简单的测试就能挡住漏 return 这类问题import assert from assert; async function testHandlerReturns(handler, req) { const res await handler(req); assert(res res.content, handler 必须返回含 content 的响应); }第三慢操作统一走带超时的封装别让任何一个await无限等。上面那个withTimeout函数可以直接复用。第四验证顺序固定成inspector 直连 → Claude 列工具 → 调 echo → 调业务工具。每次改完配置都按这个顺序过一遍能快速定位问题在哪一层。如果你需要长期跑编码类或 Agent 类任务把 MCP server 的稳定性和 Coding Plan 配合起来会更省心配置一次、长期复用。Key 和接入细节在 API Keys 和接入文档里都有照着填三件套即可。