1. 握手成功却拿不到工具结果问题往往不在 get_weather你大概率遇到过这种场景终端里 MCP 的 initialize 握手日志一切正常list_tools 也返回了 get_weather 的描述DeepSeek 那边甚至已经吐出了 tool_calls可最后模型就是收不到天气数据回复停在“正在为你查询”或者干脆答非所问。这时候很多人第一反应是去翻 get_weather 的逻辑怀疑字典写错、城市名对不上、async 没加 await。我试过把排查顺序倒过来先看 stdout 干不干净再看 base_url 填得对不对八成问题当场现形。MCP 的客户端与服务器之间走的是标准输入输出上的 JSON-RPCstdout 是协议数据的专用通道任何一句调试用的 print 混进去都会让客户端解析失败。weather_server.py 里专门把 print 重定向到 stderr就是在守这条底线。而 main.py 里 OpenAI 客户端的 base_url 如果填成官网落地页或者多带了 /v1模型对话补全的请求根本发不出去工具结果自然回不来。这篇就按排障视角把这两处最容易踩的坑一次讲清。2. 先把 TaoToken 通道准备好Key 和地址别混用MCP 本身只负责模型与工具之间的通信真正决定“模型能不能收到工具结果”的是模型对话补全那一段请求有没有成功往返。所以你需要一个稳定的模型通道把 main.py 里的 OpenAI 客户端指向它。到 TaoToken 官网创建 Key 的入口在这里官网落地页https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content登录后在控制台生成 API Key形如 sk- 开头的一串字符。这里要区分两个地址很多排障卡住就是混用了它们用途地址说明浏览器访问、注册、看文档https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content官网落地页带查询参数给人看的代码里 base_urlhttps://taotoken.net/api接口地址给 OpenAI 客户端用的注意接口地址后面不要带 /v1。OpenAI SDK 会自己在 base_url 后面拼 /chat/completions 这类路径你多写一层 /v1 就变成 /api/v1/chat/completions请求直接 404表现就是模型侧毫无响应工具调用链断在第一步。另外也别把官网落地页那一长串带 utm 的地址填进 base_url那是页面地址不是接口地址客户端会把它当成非法 URL 处理。Key 的管理和查看在控制台完成需要轮换或补额度时从这里进API Keys 管理https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content如果你后面要长期跑编码类 Agent、反复调试 MCP 工具链可以了解下 Coding Plan按套餐走比单次调用更省心Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content3. 可复制配置main.py 的 base_url 与 weather_server.py 的 stdout 双确认排障的核心就两处确认先把 main.py 里 OpenAI 客户端的初始化改对。下面这段是修正后的写法重点看 base_url 和 api_key 两行import os import json import asyncio from openai import OpenAI from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client # 从环境变量读取 Key避免硬编码进仓库 client OpenAI( api_keyos.environ.get(TAOTOKEN_API_KEY, sk-你的Key), base_urlhttps://taotoken.net/api # 注意不带 /v1也不是官网落地页 ) async def main(): server_params StdioServerParameters( commandpy, args[weather_server.py] ) async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() tools_response await session.list_tools() tools [ { type: function, function: { name: tool.name, description: tool.description, parameters: tool.inputSchema } } for tool in tools_response.tools ] messages [ {role: system, content: 你是一个贴心的天气助手请根据工具提供的数据回答用户。}, {role: user, content: 今天杭州天气如何} ] response client.chat.completions.create( modeldeepseek-chat, messagesmessages, toolstools, tool_choiceauto ) msg response.choices[0].message if msg.tool_calls: messages.append(msg) for tool_call in msg.tool_calls: result await session.call_tool( tool_call.function.name, json.loads(tool_call.function.arguments) ) messages.append({ role: tool, tool_call_id: tool_call.id, content: result.content[0].text }) final_response client.chat.completions.create( modeldeepseek-chat, messagesmessages ) print(AI助手最终回复:, final_response.choices[0].message.content) if __name__ __main__: asyncio.run(main())第二处确认在 weather_server.pyprint 重定向必须放在所有业务代码之前且不能被后续 import 覆盖import sys import builtins # 核心修复把 print 全部导向 stderr保证 stdout 只跑 JSON-RPC _orig_print builtins.print builtins.print lambda *a, **k: _orig_print(*a, filesys.stderr, **k) import asyncio from fastmcp import FastMCP mcp FastMCP(Weather-Server) mcp.tool() async def get_weather(city: str) - str: 获取指定城市的当前天气情况。 参数: city - 城市名称例如杭州、北京 fake_weather_data { 杭州: 晴天气温 28℃微风非常适合散步。, 北京: 多云气温 25℃空气质量良。, 上海: 小雨气温 22℃出门记得带伞。 } return fake_weather_data.get(city, f抱歉暂时无法获取 {city} 的天气信息。) if __name__ __main__: mcp.run()这两处改完stdout 里只剩协议帧模型侧请求也指向了正确接口工具结果才有机会回到对话里。消耗 Token 的正是最后那段模型对话补全前面握手和工具调用本身不产生补全消耗。4. 验证请求从握手日志到最终回复的完整链路配置改好后按顺序跑一遍验证。先装依赖pip install openai mcp fastmcp设置环境变量再运行避免 Key 写死在代码里# Windows PowerShell $env:TAOTOKEN_API_KEYsk-你的Key python main.py # macOS / Linux export TAOTOKEN_API_KEYsk-你的Key python main.py正常的话你会看到类似输出AI助手最终回复: 今天杭州是晴天气温 28℃微风非常适合散步。如果只看到握手成功、list_tools 也返回了工具但最终回复里没有天气数据可以加一段临时日志确认工具结果有没有被塞回 messages。在messages.append那段后面加一行打印到 stderr 的调试import sys print(工具返回:, result.content[0].text, filesys.stderr)注意这里显式指定 filesys.stderr不要用裸 print否则又污染 stdout。看到这行说明工具执行成功问题就落在模型补全请求上回去检查 base_url 和 Key。看不到这行说明 tool_calls 为空模型没决定调用工具可以检查 tools 描述是否完整传给了请求。想单独验证模型通道是否通可以到模型对话页面发一条普通消息确认 Key 和接口地址没问题模型对话https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content5. 本篇常见错排查报错一握手正常模型回复“我无法获取实时天气”。这是最典型的症状。先确认 weather_server.py 的 print 重定向是否真的生效有没有在重定向之前就 import 了会打印日志的库。再确认 main.py 的 base_url 是不是写成了官网落地页或者多带了 /v1。两者任一出错工具结果都回不到模型。报错二客户端解析 JSON 失败日志里出现 Unexpected token。说明 stdout 被污染了。检查 weather_server.py 里有没有漏网的 print包括第三方库在 import 时打的日志。重定向必须放在最顶部晚于任何可能输出的代码都不行。报错三请求返回 404 或连接被拒。九成是 base_url 写错。正确值是 https://taotoken.net/api不带 /v1不带查询参数。把官网落地页那串带 utm 的地址填进去也会报错那是页面地址不是接口地址。报错四tool_calls 为空模型直接回答。检查传给请求的 tools 列表是否为空list_tools 是否真的返回了 get_weather。另外 system 提示词里可以明确要求“涉及天气必须调用工具”提高模型调用意愿。报错五Key 无效或额度不足。到控制台确认 Key 状态和余额必要时重新生成。接入细节和参数说明可以对照接入文档接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content6. 排障收尾把两处确认固化成习惯MCP 工具链的排障顺序比努力重要。先看 stdout 干不干净再看 base_url 填得对不对最后才去怀疑 get_weather 的业务逻辑。weather_server.py 里那行 print 重定向不是可有可无的装饰它是保证 JSON-RPC 通道纯净的关键main.py 里 base_url 填 https://taotoken.net/api、不带 /v1、不填官网落地页是保证模型对话补全能往返的前提。两处都确认过模型才能拿到 get_weather 的返回并继续回答。如果你在跑更复杂的多工具 MCP 场景或者要把这套链路接进长期运行的编码 Agent建议把 Key 管理、接口地址、stdout 规范写成一份项目内的 checklist每次新增工具时过一遍。需要长期编码或 Agent 场景的可以从 Coding Plan 入手只是临时验证模型通道模型对话页面足够。排障时优先查 API Keys 和接入文档比盲目改业务代码快得多。