MCP 握手后模型没收到工具结果?TaoToken 通道的 base_url 这样填。
2026/9/20 13:09:59 网站建设 项目流程

1. 握手成功却拿不到工具结果,问题往往不在 get_weather

你大概率遇到过这种场景:终端里 MCP 的 initialize 握手日志一切正常,list_tools 也返回了 get_weather 的描述,DeepSeek 那边甚至已经吐出了 tool_calls,可最后模型就是收不到天气数据,回复停在“正在为你查询”或者干脆答非所问。这时候很多人第一反应是去翻 get_weather 的逻辑,怀疑字典写错、城市名对不上、async 没加 await。

我试过把排查顺序倒过来,先看 stdout 干不干净,再看 base_url 填得对不对,八成问题当场现形。MCP 的客户端与服务器之间走的是标准输入输出上的 JSON-RPC,stdout 是协议数据的专用通道,任何一句调试用的 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_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=

登录后在控制台生成 API Key,形如 sk- 开头的一串字符。这里要区分两个地址,很多排障卡住就是混用了它们:

用途地址说明
浏览器访问、注册、看文档https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_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_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=

如果你后面要长期跑编码类 Agent、反复调试 MCP 工具链,可以了解下 Coding Plan,按套餐走比单次调用更省心:

Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=

3. 可复制配置: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_key=os.environ.get("TAOTOKEN_API_KEY", "sk-你的Key"), base_url="https://taotoken.net/api" # 注意:不带 /v1,也不是官网落地页 ) async def main(): server_params = StdioServerParameters( command="py", 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( model="deepseek-chat", messages=messages, tools=tools, tool_choice="auto" ) 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( model="deepseek-chat", messages=messages ) print("AI助手最终回复:", final_response.choices[0].message.content) if __name__ == "__main__": asyncio.run(main())

第二处确认在 weather_server.py,print 重定向必须放在所有业务代码之前,且不能被后续 import 覆盖:

import sys import builtins # 核心修复:把 print 全部导向 stderr,保证 stdout 只跑 JSON-RPC _orig_print = builtins.print builtins.print = lambda *a, **k: _orig_print(*a, file=sys.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_KEY="sk-你的Key" python main.py # macOS / Linux export TAOTOKEN_API_KEY="sk-你的Key" python main.py

正常的话你会看到类似输出:

AI助手最终回复: 今天杭州是晴天,气温 28℃,微风,非常适合散步。

如果只看到握手成功、list_tools 也返回了工具,但最终回复里没有天气数据,可以加一段临时日志确认工具结果有没有被塞回 messages。在messages.append那段后面加一行打印到 stderr 的调试:

import sys print("工具返回:", result.content[0].text, file=sys.stderr)

注意这里显式指定 file=sys.stderr,不要用裸 print,否则又污染 stdout。看到这行说明工具执行成功,问题就落在模型补全请求上,回去检查 base_url 和 Key。看不到这行说明 tool_calls 为空,模型没决定调用工具,可以检查 tools 描述是否完整传给了请求。

想单独验证模型通道是否通,可以到模型对话页面发一条普通消息,确认 Key 和接口地址没问题:

模型对话:https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=

5. 本篇常见错排查

报错一:握手正常,模型回复“我无法获取实时天气”。这是最典型的症状。先确认 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_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=

6. 排障收尾:把两处确认固化成习惯

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 和接入文档,比盲目改业务代码快得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询