1. 从手动调工具到模型自主规划:MCP + Function Calling 到底解决什么问题
如果你已经用过 MCP(Model Context Protocol)把本地或远程工具挂到模型上,大概率经历过这个阶段:工具能跑通,但每次都得自己判断"该调哪个、传什么参数、结果怎么塞回上下文"。MCP 负责把工具标准化暴露出来,Function Calling 负责让模型输出结构化的调用意图,这两者拼在一起,才是真正的"模型自主驱动工具链"。
这篇要做的场景很具体:让模型自己完成一条三步依赖链——先拿到本机公网出口 IP,再用这个 IP 反查城市和经纬度,最后用经纬度查实时天气。三步里每一步的返回值都是下一步的输入参数,中间不需要人工干预。用户只说一句话,模型自己规划顺序、自己填参数、自己判断什么时候停。
适合谁看:已经了解 MCP 基础用法(知道怎么注册一个 HTTP 工具、怎么手动 run 一次),但还没把工具清单交给模型去决策的开发者。如果你还在用"把工具结果拼进 Prompt 再问一遍"的土办法,这篇的循环结构会帮你省掉大量胶水代码。
核心检索词先摆出来:MCP 工具链、Function Calling schema、多步推理、ToolCall 循环、统一 Key 接入。这几个词贯穿全文,后面每一段配置和代码都围绕它们展开。
我试过把三个工具写死在代码里按顺序调,能跑,但一旦工具增减或顺序变化就得改代码。换成 Function Calling 之后,工具清单从配置文件读,模型自己决定调谁,扩展成本几乎为零。下面从接入通道开始,一步步把这条链路搭起来。
2. TaoToken 统一 Key 与 API 通道:MCP 工具链的前置接入配置
在写循环之前,先把模型通道固定下来。多步推理对模型的 Function Calling 支持要求比较高——它得能稳定输出结构化的 tool_calls 字段,而不是把调用意图混在自然语言里。TaoToken 在这里的作用是提供一个统一的 OpenAI 兼容入口,你换模型时不用改鉴权逻辑,Base URL 和 Key 保持不变,只换 Model ID 就行。
接入信息如下,建议直接抄:
- 官网入口:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=
- API 基址:https://taotoken.net/api
- 模型对话调试页:https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite
- API Keys 管理:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite
- 接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite
如果你用的是 Claude Code 这类编码 Agent,长期跑工具链建议走 Coding Plan,额度更稳:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite
拿到 Key 之后,先别急着写 Java 循环,用最简请求验证通道能不能正常返回 tool_calls。这一步很关键,很多人后面循环跑不通,其实是通道层就没开 Function Calling。用 curl 测一下:
curl https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.6-plus", "messages": [{"role": "user", "content": "现在几点"}], "tools": [{ "type": "function", "function": { "name": "get_current_time", "description": "获取当前时间", "parameters": {"type": "object", "properties": {}} } }], "tool_choice": "auto" }'返回体里如果出现choices[0].message.tool_calls,说明通道支持结构化调用;如果只有content字段,说明当前模型或参数没走 Function Calling 路径,需要换模型或检查tools字段格式。
注意:Base URL 填
https://taotoken.net/api,不要带/v1后缀再拼一次,OpenAI SDK 通常会自动补/v1/chat/completions。填错会出现 404 或路径重复。
统一 Key 的好处在这里体现得很明显:MCP 服务端配置、Function Calling 请求、日志排查用的都是同一套鉴权,不用在多个平台之间同步密钥。下面进入可复制的配置环节。
3. 可复制配置:MCP 服务端声明与 Function Calling schema 落地
这一节给两份可直接用的配置。第一份是 MCP 工具声明,第二份是模型侧需要的 Function Calling schema。两者字段要对齐,否则模型输出的工具名在 MCP 侧找不到。
先看 MCP 服务端配置mcp.config.json,三个工具都放在default分组里:
{ "default": { "tools": [ { "name": "get_export_ip", "description": "获取本机公网出口 IP 地址", "method": "GET", "url": "https://api.ipify.org?format=json", "parameters": { "type": "object", "properties": {}, "required": [] } }, { "name": "get_ip_location", "description": "根据 IP 查询城市、经纬度、ISP 信息", "method": "GET", "url": "https://ipapi.co/{ip}/json/", "parameters": { "type": "object", "properties": { "ip": {"type": "string", "description": "要查询的公网 IP"} }, "required": ["ip"] } }, { "name": "get_weather_open_meteo", "description": "根据经纬度查询实时天气", "method": "GET", "url": "https://api.open-meteo.com/v1/forecast?latitude={latitude}&longitude={longitude}¤t_weather={current_weather}", "parameters": { "type": "object", "properties": { "latitude": {"type": "number", "description": "纬度"}, "longitude": {"type": "number", "description": "经度"}, "current_weather": {"type": "string", "description": "是否返回实时天气,传 true"} }, "required": ["latitude", "longitude", "current_weather"] } } ] } }这份配置的关键点是parameters字段本身就是标准 JSON Schema,可以直接转成模型需要的 tools 数组。manifestForInput()做的就是这件事——把 MCP 声明翻译成 Function Calling schema,避免手写两份。
如果你用 Cline 或 Claude Code 的 MCP 配置,写法类似,但要注意三件套必须齐全:Base URL、Key、Model ID。缺任何一个都会在启动时报鉴权或模型不存在。以 Cline 的 MCP settings 为例:
{ "mcpServers": { "taotoken-tools": { "command": "npx", "args": ["-y", "@taotoken/mcp-server"], "env": { "TAOTOKEN_BASE_URL": "https://taotoken.net/api", "TAOTOKEN_API_KEY": "sk-你的Key", "TAOTOKEN_MODEL_ID": "qwen3.6-plus" } } } }Codex 用户如果走auth.json,结构是:
{ "base_url": "https://taotoken.net/api", "api_key": "sk-你的Key", "model": "qwen3.6-plus" }三件套对齐之后,模型侧拿到的 tools 数组长这样(由 manifest 自动生成,这里展示结构):
[ { "type": "function", "function": { "name": "get_export_ip", "description": "获取本机公网出口 IP 地址", "parameters": {"type": "object", "properties": {}, "required": []} } }, { "type": "function", "function": { "name": "get_ip_location", "description": "根据 IP 查询城市、经纬度、ISP 信息", "parameters": { "type": "object", "properties": {"ip": {"type": "string"}}, "required": ["ip"] } } } ]配置阶段最容易踩的坑是工具名不一致:MCP 里叫get_ip_location,schema 里写成getIpLocation,模型输出后执行器找不到对应工具,直接抛异常。建议用同一份配置生成两边,别手抄。
提示:
current_weather参数在 Open-Meteo 里是布尔语义,但 JSON Schema 里用 string 传 "true" 也能被接受。如果你追求严格类型,改成"type": "boolean"并在执行器里做转换。
配置就绪后,进入验证环节,看模型是否真的按顺序调了三次工具。
4. 验证请求与成功结果:多步推理链路的完整日志
验证的目标很明确:发一句话,看模型是否自主完成三次 ToolCall,且每次的参数来自上一步的返回值。请求体如下:
curl https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.6-plus", "temperature": 0, "messages": [ {"role": "system", "content": "你是能调用 MCP 工具的智能体。按顺序:1) 调 get_export_ip 拿公网 IP;2) 把 IP 传给 get_ip_location 拿城市和经纬度;3) 用经纬度调 get_weather_open_meteo 并设 current_weather=true;4) 只输出位置和天气结论。"}, {"role": "user", "content": "自动检测我的公网 IP,推断所在城市并告知当前天气。"} ], "tools": [ ...上节的 tools 数组... ], "tool_choice": "auto" }'第一轮返回会是tool_calls,包含get_export_ip,参数为空对象。执行器拿到后调 MCP,把结果以role: tool的消息追加回 messages,再发第二轮。第二轮模型看到 IP 后输出get_ip_location的调用,参数里带上真实 IP。第三轮同理输出天气查询。第四轮模型不再输出 tool_calls,直接给最终回答。
完整日志轨迹应该类似这样:
[MCP Function-Calling 链开始] [ToolCall] get_export_ip params -> {} [Observation] {"ip":"123.117.177.40"} [ToolCall] get_ip_location params -> {"ip":"123.117.177.40"} [Observation] {"city":"Dongcheng Qu","latitude":39.9117,"longitude":116.4097,"org":"AS4134 Chinanet"} [ToolCall] get_weather_open_meteo params -> {"latitude":39.9117,"longitude":116.4097,"current_weather":"true"} [Observation] {"current_weather":{"temperature":18.3,"windspeed":6.1,"weathercode":1}} [链执行完成] === 最终回答 === 你的公网 IP 位于中国北京市东城区,当前温度约 18°C,天气晴朗,风速 6.1 km/h。判断链路是否健康,看三个信号:一是 ToolCall 次数等于依赖步数(这里是 3),多了说明模型在重复调,少了说明它跳步;二是每次 Observation 都被写回 messages,否则下一轮模型看不到上一步结果;三是最终回答里没有暴露工具名,说明 System Prompt 的约束生效了。
如果模型在第二轮没有输出get_ip_location,而是直接编了一个城市,说明它没把 Observation 当回事。这时候检查两点:Observation 是否以标准role: tool消息追加,以及tool_call_id是否和上一轮tool_calls[].id对应。对不上,模型会认为这条结果不属于它的调用,直接忽略。
验证通过后,把循环封装成函数,maxIterations设 5 就够,防止模型陷入死循环。退出条件很简单:某一轮返回的 message 里没有tool_calls字段,就说明模型准备给最终答案了。
5. 本篇常见错误排查:401、local proxy failed 与 reading choices 报错
多步链路跑不通,报错通常集中在几个固定位置。下面按真实报错对照排查。
401 Unauthorized:Key 没带上或格式不对。检查Authorization: Bearer sk-xxx里 Bearer 后面有没有空格,Key 有没有被 shell 变量吞掉。用echo $TAOTOKEN_API_KEY确认变量非空。如果走 MCP 服务端,检查env里的TAOTOKEN_API_KEY是否传进了子进程。
local proxy failed / connection refused:本地 MCP 服务没起来,或者端口被占。先单独跑一次 MCP server 看能否启动,再确认客户端配置里的command和args路径正确。这类错误和网络通道无关,纯粹是本地进程问题。
reading choices 报错(cannot read property 'choices' of undefined):请求返回体不是预期的 OpenAI 格式,通常是 Base URL 拼错导致打到了别的路径,或者模型名不存在返回了错误对象。打印完整响应体,看error字段。Base URL 用https://taotoken.net/api,模型名从文档里抄,别自己拼。
OAuth 相关报错:多见于 Claude Code 或 Codex 的登录态配置。如果你用的是 API Key 模式,确保没有残留的 OAuth token 覆盖。检查auth.json或环境变量里是否同时存在两套鉴权,冲突时优先 API Key。
工具名找不到(tool not found):模型输出的function.name和 MCP 注册名不一致。打开日志对比两边字符串,注意大小写和下划线。建议在manifestForInput()之后打印一次工具名列表,和模型输出对照。
参数解析失败(invalid arguments):模型输出的arguments是 JSON 字符串,需要先JSON.parse再传给 MCP。如果直接当对象用,会报类型错误。另外注意模型有时会输出带换行的 JSON,解析前先 trim。
循环不退出:模型反复调同一个工具。检查 System Prompt 里有没有"每个工具最多执行一次"的约束,以及maxIterations是否生效。温度设 0 能显著降低这种抖动。
排查顺序建议从通道层往上:先用 curl 确认 API 通,再确认 MCP 单工具能跑,最后才看循环逻辑。跳步排查容易在错误层浪费时间。
6. 把工具清单交给模型:统一 Key 驱动工具链的落地建议
整条链路的核心只有一句话:把 MCP 工具清单转成 Function Calling schema 交给模型,模型决定调什么,执行器只负责执行和回写结果。开发者不再写"如果 A 则调 B"的分支,扩展工具时只改配置文件。
落地时有几个实用建议。第一,System Prompt 里明确调用顺序和次数上限,能大幅降低模型跳步和重复调用的概率,尤其在依赖链场景下。第二,Observation 一律以标准 tool 消息回写,带上tool_call_id,这是模型识别结果归属的唯一依据。第三,工具执行失败时不要把异常直接抛出,而是把错误信息作为 Observation 写回,让模型自己决定重试还是降级,这样单点失败不会炸掉整条链。
统一 Key 的价值在长期维护里更明显:MCP 配置、Function Calling 请求、日志排查共用一套鉴权和 Base URL,换模型只改 Model ID。如果你要长期跑编码类 Agent 或复杂工具链,走 Coding Plan 额度更稳;临时验证模型行为,用模型对话页最快。
最后留一个可复用的检查清单:Base URL 是否为https://taotoken.net/api、Key 是否有效、Model ID 是否支持 Function Calling、工具名两边是否一致、Observation 是否带 tool_call_id、maxIterations 是否设置。这六项过了,多步推理链路基本不会出问题。