副标题一个真实排障日志。当你确认工具已经列出来、模型也选了它、参数也对但调用就是失败——问题往往不在你的代码逻辑而在协议栈的某一层。一、故障现场上周四晚上你的桌面应用的 MCP server 上线前夜。7 个工具里 6 个在 Claude Desktop 里调得顺风顺水get_account_quota、search_contacts、list_recent_chats……唯独send_message怎么都调不通。现象很诡异tools/list能列出来模型也知道该调它模型生成的arguments完全正确account_id、contact_id、text 都在但一调用就报Method not found或者干脆超时Claude Desktop 里显示工具调用失败请重试。其他 6 个工具同一个 Server、同一套装饰器凭什么就它不行我赌了半包烟在肯定是 send 的异步实现写错了结果打脸。二、排查树从表象往根因走我把排障分成四层从外到内逐层排除Host 层是 Claude Desktop 的问题还是所有 Host 都调不通换 Cursor 试协议层JSON-RPC 的method名、id、传输是否对Server 层mcp.tool()注册成功了吗tools/list返回里有没有它业务层工具函数内部是不是抛异常了第一斧换 Host定位是不是客户端问题我先在 Cursor 里试了同一个send_message结果一样失败。说明不是 Claude Desktop 的锅是 Server 侧。第二斧抓 JSON-RPC 报文在 MCP endpoint127.0.0.1:53716前面挂了一层 middleware 打印所有进出报文。发现一个关键线索{jsonrpc:2.0,id:42,method:tools/call,params:{name:send_message,arguments:{account_id:acct_main_01,contact_id:c_8842,text:Hi}}}请求长得没问题。但 Server 回的却是{jsonrpc:2.0,id:42,error:{code:-32601,message:Method not found}}-32601 Method not found——意思是 Server 根本不认识tools/call这个 method。可其他 6 个工具明明能调说明tools/call这个入口是存在的。矛盾点就在这里同一个tools/call换个别的工具名就行换成send_message就报 method not found第三斧看 tools/list 返回我直接curl了tools/list把返回的 tools 数组打印出来。真相浮出水面——列表里压根没有send_message这个工具。模型之所以知道要调它是因为我的instructions里写了可用工具涵盖消息发送但tools/list实际注册的工具名是send_message早期命名。模型从 instructions 推测出名字实际注册名对不上于是tools/call带着错误的name进来Server 当然回Method not found。三、为什么只有它出问题其他 6 个工具名都和 instructions 描述一致模型不会搞错唯独send_message在重构时改过名instructions 没同步。模型不是瞎调是被我过时的 instructions 误导了。这正好印证了上一篇说的instructions字段的权重极高但它的内容必须和tools/list真实注册名严格一致。一份过期的 instructions比没有 instructions 更危险——它制造了模型以为能调、实际调不到的幻觉。四、顺手修掉的另外两个暗雷排查过程中还顺手发现两个隐患记下来暗雷 1streamable HTTP 的 idle 超时。我用的传输是 streamable HTTPServer 侧给 SSE 连接设了 30s idle 超时。长文本发送200 字产品介绍偶尔耗时超过 30s连接被服务端掐断表现为调用超时。修复把 idle 超时调到 120s并在客户端加指数退避重试。暗雷 2async 事件循环嵌套。send_message内部调了 你的桌面应用业务后端的异步 SDK而我最初图省事在同步工具函数里asyncio.run()包了一层。FastMCP 本身就是 async 跑的嵌套事件循环直接抛RuntimeError: This event loop is already running。改成mcp.tool()装饰 async 函数 await后解决。五、踩坑清单这一篇的精华按我真踩过的顺序tools/list返回的工具名是唯一真相来源。模型调工具用的是params.name它必须和tools/list里注册的name一字不差。instructions/ 文档 / 你脑子里的名字都不算数。改了工具名三处注册、instructions、调用方必须一起改。-32601 Method not found不等于函数没写。它只代表带着这个 name 的 tools/call 进来了但我这没有。先curl tools/list看真实注册名比翻代码快十倍。instructions过期比缺失更坑。它会被注入每个 Host 的上下文模型高度信任。写完后每次改工具名/参数把 instructions 当代码一样走 review。streamable HTTP 的 idle 超时要按最长工具耗时设。不要用默认 30s。长耗时工具发消息、跑批单独评估必要时拆成提交→轮询两步。MCP 工具函数一律async def。只要内部有 IOHTTP、DB、消息队列就await别在同步函数里asyncio.run()。FastMCP 已经在事件循环里了。排障要从协议层抓报文不要靠猜。在 endpoint 前挂一层 middleware 打印 JSON-RPC 进出90% 的调不通在报文里一目了然。这是性价比最高的调试习惯。换 Host 复现先排除客户端。一个 Host 调不通立刻换另一个Cursor / WorkBuddy / Cline。如果都失败问题 100% 在 Server如果只有某一个失败是 Host 的 MCP 实现差异。六、下一篇预告第 4 篇讲多租户路由设计一个 你的桌面应用ID 怎么管多个 应用账号account_id怎么在 MCP 工具层做路由隔离避免调 A 账号却发了 B 账号消息的事故。系列目录第 1 篇为什么选 MCP、怎么和 HTTP API/CLI/WS 对比第 2 篇从 0 暴露 7 个工具的完整代码第 3 篇本篇MCP 调用失败的排查实录第 4 篇多租户路由设计…… 共 10 篇每日更新如果你也在接 MCP建议现在就给 endpoint 加一层报文日志——等调不通的那天你会感谢今天的自己。