
1. Qwen3 工具调用与 MCP 服务之间的衔接断层Qwen3 开启工具调用后为什么还要手写 Function call 逻辑才能用 MCP 服务这是很多开发者在接入 MCP 时遇到的第一个认知落差。Qwen3 本身支持工具调用能力模型可以在对话中声明我需要调用某个工具但 MCP 服务并不是一个简单的 HTTP 接口——它有自己的协议握手、能力协商、工具注册流程。模型输出的 tool_calls 只是一段结构化 JSON它不知道 MCP 服务端监听的端口、不知道 transport 类型是 stdio 还是 SSE、更不知道工具列表需要通过 initialize 请求动态获取。换句话说Qwen3 负责决定调用什么而 Function call 逻辑负责把决定翻译成 MCP 协议能理解的请求。这个断层体现在三个层面。第一层是协议层MCP 使用 JSON-RPC 2.0 作为消息格式需要先发送 initialize 请求完成能力协商再发送 tools/list 获取可用工具最后才是 tools/call 执行调用。Qwen3 的 tool_calls 输出只包含函数名和参数不包含这些握手步骤。第二层是 schema 映射层Qwen3 期望的 tools 参数格式是 OpenAI 风格的 function 定义而 MCP 服务端返回的工具描述是 MCP 自己的 schema两者字段名和嵌套结构不同需要做转换。第三层是执行层模型输出 tool_calls 后需要有人真正去连接 MCP 服务、发送 JSON-RPC 请求、把结果回传给模型继续对话这个胶水层就是 Function call 逻辑。我试过直接用 Qwen3 的 tool_calls 输出去拼 MCP 请求结果卡在 initialize 握手阶段——模型根本不知道要发这个请求。所以正确的做法是用 Function call 逻辑作为中间层把 Qwen3 的工具声明转换成 MCP 工具注册把模型的 tool_calls 输出转换成 MCP 的 tools/call 请求。下面我会用 TaoToken 统一 Key 通道作为接入点演示从 Qwen3 工具声明到 MCP 服务注册的完整链路包括可复制的 Function call JSON schema 模板、MCP 服务端配置片段以及用 curl 验证工具调用是否真正触发的具体命令。2. TaoToken 统一 Key 通道的前置配置在写 Function call 逻辑之前你需要先有一个能稳定调用 Qwen3 的通道。TaoToken 提供统一的 API Key 和 Base URL兼容 OpenAI 风格的接口格式这样你的 Function call 逻辑只需要对接一套认证体系不用为每个模型单独管理 Key。官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentAPI 端点是 https://taotoken.net/api注意 API 地址不带 UTM 参数。前置配置分三步。第一步是获取 API Key登录后进入控制台在 API Keys 页面创建一个新的 Key复制保存。这个 Key 会用于所有模型调用请求的 Authorization 头。第二步是确认模型 IDQwen3 在 TaoToken 上的模型 ID 通常是 qwen3 或 qwen3-235b-a22b 这类格式你可以在模型对话页面测试确认。第三步是准备 MCP 服务端MCP 服务可以是一个本地 stdio 进程也可以是一个 SSE 端点。本文以本地 stdio 为例因为这样最容易调试。你需要安装 MCP 的 Python SDK 或 Node SDK。以 Python 为例执行 pip install mcp 即可。然后创建一个简单的 MCP 服务端脚本暴露一个查询天气的工具。这个工具本身不重要重要的是它的 schema 结构因为后面要做 Qwen3 function 定义和 MCP tool schema 之间的映射。MCP 服务端的工具定义包含 name、description、inputSchema 三个核心字段其中 inputSchema 是 JSON Schema 格式描述参数类型和必填项。TaoToken 的统一 Key 通道在这里的作用是你的 Function call 逻辑调用 Qwen3 时用 TaoToken 的 Base URL 和 Key你的 MCP 服务端不需要关心模型认证它只负责接收 JSON-RPC 请求并返回结果。这样职责分离调试时也容易定位问题——是模型没输出 tool_calls还是 MCP 服务端没响应还是中间的转换逻辑写错了。如果你需要长期跑编码类 Agent 任务可以考虑 TaoToken 的 Coding Plan它针对高频调用场景做了额度优化。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite里面有各语言的调用示例。3. 可复制的 Function call JSON schema 与 MCP 配置片段这一节是核心我会给出完整的 Function call JSON schema 模板和 MCP 服务端配置片段。先看 Qwen3 侧的 tools 定义。当你调用 Qwen3 的 chat completions 接口时需要在请求体里传入 tools 数组每个工具是一个 function 对象。下面是一个查询天气的 function 定义{ type: function, function: { name: get_weather, description: 查询指定城市的当前天气, parameters: { type: object, properties: { city: { type: string, description: 城市名称例如北京 }, unit: { type: string, enum: [celsius, fahrenheit], description: 温度单位 } }, required: [city] } } }这个格式是 OpenAI 风格的Qwen3 兼容这个格式。但 MCP 服务端的工具定义格式不同它长这样{ name: get_weather, description: 查询指定城市的当前天气, inputSchema: { type: object, properties: { city: { type: string, description: 城市名称例如北京 }, unit: { type: string, enum: [celsius, fahrenheit], description: 温度单位 } }, required: [city] } }差异在于Qwen3 用 parameters 字段MCP 用 inputSchema 字段Qwen3 外层包了 type: function 和 function 对象MCP 是扁平的。你的 Function call 逻辑需要做这个映射。写一个转换函数把 MCP 的 tool 定义转成 Qwen3 的 tools 格式def mcp_tool_to_qwen_tool(mcp_tool): return { type: function, function: { name: mcp_tool[name], description: mcp_tool[description], parameters: mcp_tool[inputSchema] } }反过来当 Qwen3 返回 tool_calls 时你需要把 tool_call 的 function.arguments 解析成 JSON然后构造 MCP 的 tools/call 请求import json def qwen_tool_call_to_mcp_request(tool_call): return { jsonrpc: 2.0, id: 1, method: tools/call, params: { name: tool_call[function][name], arguments: json.loads(tool_call[function][arguments]) } }MCP 服务端的配置片段以 Python SDK 为例创建一个 server.pyfrom mcp.server import Server from mcp.server.stdio import stdio_server from mcp.types import Tool, TextContent app Server(weather-server) app.list_tools() async def list_tools(): return [ Tool( nameget_weather, description查询指定城市的当前天气, inputSchema{ type: object, properties: { city: {type: string, description: 城市名称}, unit: {type: string, enum: [celsius, fahrenheit]} }, required: [city] } ) ] app.call_tool() async def call_tool(name, arguments): if name get_weather: city arguments.get(city) unit arguments.get(unit, celsius) return [TextContent(typetext, textf{city} 当前温度 25°{unit[0].upper()})] raise ValueError(f未知工具: {name}) async def main(): async with stdio_server() as (read, write): await app.run(read, write, app.create_initialization_options()) if __name__ __main__: import asyncio asyncio.run(main())这个服务端启动后会通过 stdio 监听 JSON-RPC 请求。你的 Function call 逻辑需要先发送 initialize 请求再发送 tools/list最后发送 tools/call。注意 initialize 请求的 params 里要带 protocolVersion 和 capabilities{ jsonrpc: 2.0, id: 0, method: initialize, params: { protocolVersion: 2024-11-05, capabilities: {}, clientInfo: {name: qwen-mcp-bridge, version: 1.0.0} } }如果你用的是 Claude Code 或 Cline 这类工具它们的 MCP 配置通常写在 settings.json 或 cline_mcp_settings.json 里格式是{ mcpServers: { weather: { command: python, args: [/path/to/server.py], env: {} } } }这里的三件套是Base URL 用 https://taotoken.net/apiKey 用你在控制台创建的 API KeyModel ID 用 qwen3。如果你用 CC Switch 管理多个模型通道需要在配置里同时填好这三项否则会出现 401 或 model not found 错误。4. 用 curl 验证工具调用是否真正触发配置写完后不要急着跑完整对话先用 curl 单独验证 Qwen3 是否能正确输出 tool_calls。这一步能帮你排除模型侧的问题。请求如下curl -X POST https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer YOUR_TAOTOKEN_KEY \ -H Content-Type: application/json \ -d { model: qwen3, messages: [ {role: user, content: 北京今天天气怎么样} ], tools: [ { type: function, function: { name: get_weather, description: 查询指定城市的当前天气, parameters: { type: object, properties: { city: {type: string, description: 城市名称}, unit: {type: string, enum: [celsius, fahrenheit]} }, required: [city] } } } ], tool_choice: auto }预期返回的 JSON 里choices[0].message 应该包含 tool_calls 数组而不是普通的 content 文本。tool_calls[0].function.name 应该是 get_weatherarguments 应该是 {city: 北京} 这样的 JSON 字符串。如果返回的是普通文本回答说明模型没有触发工具调用检查 tool_choice 是否设为 auto或者 tools 数组格式是否正确。拿到 tool_calls 后下一步是验证 MCP 服务端能否正确响应。启动你的 server.py然后用 curl 模拟 JSON-RPC 请求。但 stdio 服务不能直接用 curl 发你需要用 echo 管道echo {jsonrpc:2.0,id:1,method:tools/list,params:{}} | python server.py预期返回包含 result.tools 数组里面有 get_weather 的定义。如果返回 error检查 server.py 的 list_tools 装饰器是否正确注册。最后验证完整的 tools/callecho {jsonrpc:2.0,id:2,method:tools/call,params:{name:get_weather,arguments:{city:北京}}} | python server.py预期返回 result.content[0].text 是 北京 当前温度 25°C。如果这一步成功说明 MCP 服务端没问题。接下来就是把 Qwen3 的 tool_calls 输出转成这个 JSON-RPC 请求再把返回结果作为 tool role 的消息回传给 Qwen3让它生成最终回答。整个链路是用户提问 → Qwen3 输出 tool_calls → 你的代码转成 MCP tools/call → MCP 服务端返回结果 → 你的代码把结果包成 tool 消息 → 再次调用 Qwen3 → Qwen3 生成自然语言回答。这个循环就是 Function call 逻辑的核心缺了它Qwen3 的工具调用能力就悬在空中落不了地。5. 本篇常见错误排查第一个常见错误是 401 Unauthorized。这通常是因为 Authorization 头里的 Key 不对或者 Key 前面少了 Bearer 前缀。检查你的 TaoToken Key 是否复制完整有没有多余空格。如果你用的是环境变量确认变量名和代码里读取的一致。第二个错误是 local proxy failed 或 connection refused。这出现在 MCP 服务端启动失败时。检查 server.py 的路径是否正确Python 依赖是否安装完整。如果你用的是 stdio transport确认没有其他进程占用标准输入输出。SSE transport 的话检查端口是否被防火墙拦截。第三个错误是 reading choices 时返回空数组或 null。这通常是因为请求体里 tools 格式不对Qwen3 无法解析。检查 tools 数组里每个元素是否有 type: function 和 function 对象function.parameters 是否是合法的 JSON Schema。另外确认 model 字段是 qwen3不是 qwen3-turbo 之类的变体。第四个错误是 OAuth 相关报错。如果你在 MCP 服务端配置了 OAuth 认证但客户端没有正确传递 token会返回 401 或 invalid_token。检查 MCP 配置里的 env 字段是否包含了必要的认证信息。对于本地 stdio 服务通常不需要 OAuth直接去掉相关配置即可。第五个错误是模型输出了 tool_calls 但 arguments 是空字符串或非法 JSON。这通常是因为 Qwen3 在生成参数时被截断或者 temperature 设得太高导致输出不稳定。把 temperature 降到 0.1 或 0并在 system prompt 里明确要求调用工具时必须输出完整 JSON 参数。第六个错误是 MCP 服务端返回了结果但模型没有继续生成回答。这通常是因为你在回传 tool 消息时没有正确设置 role 为 tool或者没有带上 tool_call_id。Qwen3 需要根据 tool_call_id 匹配是哪次调用的结果。检查你的消息数组里tool 消息的 tool_call_id 是否和之前 assistant 消息里的 tool_calls[0].id 一致。如果你在 Claude Code 里接入 MCP遇到 OAuth 报错检查 ~/.claude/settings.json 里的 mcpServers 配置确认 command 和 args 路径正确。Cline 的话检查 cline_mcp_settings.json确认没有多余的逗号或括号。Codex 的 auth.json 里需要填好 Base URL、Key 和 Model ID 三件套缺一不可。6. 从工具声明到 MCP 注册的完整链路回顾回到最初的问题Qwen3 开启了工具调用功能为什么还要写 Function call 逻辑才能用 MCP 服务因为模型的能力边界止于生成结构化意图而 MCP 服务需要的是完整的 JSON-RPC 协议交互。Function call 逻辑就是这两者之间的翻译层和驱动层。它做三件事把 MCP 工具定义转成 Qwen3 能理解的 tools 格式把 Qwen3 的 tool_calls 输出转成 MCP 的 tools/call 请求把 MCP 的返回结果转成 Qwen3 能继续对话的 tool 消息。这套逻辑不需要微调模型。微调能提升模型对特定业务参数的理解但解决不了协议握手和 schema 映射的问题。Function call 机制本身就是为这种场景设计的模型负责决策代码负责执行。你只需要把转换函数写对把 MCP 服务端跑起来用 curl 验证每一步就能把链路打通。如果你需要测试更多模型或对比不同模型的工具调用表现可以在模型对话页面直接切换。长期跑 Agent 任务的话Coding Plan 的额度更划算。接入文档里有完整的 API 参考和示例代码遇到问题可以先查文档再排查。