1. 从 Prompt 到 MCP:大模型交互链路到底在解决什么问题
如果你刚开始接触大模型应用开发,大概率会经历这样一个过程:先学会写 Prompt,然后发现模型不会查天气、不会读数据库,于是去了解 Function Calling;再往后工具越来越多,每个 Agent 都要重复对接一遍,于是又听说了 MCP 协议。这条链路不是厂商为了造概念硬堆出来的,而是每一步都踩到了真实的痛点。
先把几个核心概念用一句话说清楚。Prompt 是你对模型说的话,分 System Prompt(人设、规则、背景)和 User Prompt(具体问题)。Function Calling 是让模型用结构化 JSON 告诉你"我要调用哪个函数、传什么参数",而不是让模型直接执行。MCP(Model Context Protocol)则是把工具、资源、提示词模板统一封装成服务,让任何 Agent 都能用同一套协议去调用,不用每换一个客户端就重写一遍对接代码。
适合谁看这篇?如果你已经能跑通基础的 Chat Completions 请求,但一遇到"模型不调用工具""返回格式解析失败""换个客户端工具就全废了"这类问题就卡住,那这篇就是写给你的。我会用 TaoToken 作为统一的 API 通道,把 Prompt 模板、Function Calling 参数、MCP 服务端配置串成一条可复制的链路,每一步都给命令和验证方法。
为什么用统一通道?因为在实际开发里,你可能会同时用 Claude、GPT、Gemini 做对比测试,如果每家都单独申请 Key、单独改 Base URL,光是环境变量就能把你搞晕。TaoToken 提供的是 OpenAI 兼容的统一入口,一个 Key 走多家模型,Base URL 固定,切换模型只改 model 字段。这样你在调试 MCP 和 Function Calling 的时候,变量能少一个是一个。
下面这张表先帮你建立整体认知,后面每一节都会展开。
| 技术层 | 解决的问题 | 典型产物 | 调试难点 |
|---|---|---|---|
| Prompt | 让模型理解角色和任务 | System/User 消息 | 人设漂移、指令冲突 |
| Function Calling | 让模型结构化地请求工具 | tools 参数 + tool_calls | 模型不调用、参数错 |
| MCP | 让工具服务可复用、可托管 | MCP Server/Client | 连接失败、协议版本 |
理解了这三层的关系,你再看后面那些报错就不会慌:401 是 Key 的问题,local proxy failed 是网络或端口的问题,reading choices 是响应结构没对上,OAuth 是 MCP 远程服务的鉴权问题。每一类我都在第 5 节给了对照排查。
2. TaoToken 前置准备:统一 Key 与 API 通道配置
在动手写 Function Calling 和 MCP 之前,得先把 API 通道打通。这一步看起来简单,但后面 80% 的"模型不响应""工具调用失败"其实都跟这里的环境没配对有关。我试过在三个不同项目里反复配 Key,最后发现统一到一个通道最省心。
2.1 获取 Key 与确认 Base URL
先到 TaoToken 控制台创建一个 API Key。地址是 https://taotoken.net/api-keys ,登录后点创建,复制出来的字符串就是你的 Key,格式通常以 sk- 开头。注意这个 Key 只在创建时完整显示一次,丢了就重新建一个。
Base URL 统一用 https://taotoken.net/api ,不要在后面加 /v1 之外的路径,OpenAI 兼容客户端一般会自动补 /v1/chat/completions。如果你用的是原生 OpenAI SDK,把 base_url 设成这个地址即可。
注意:Key 不要硬编码进前端代码或提交到 Git。用环境变量或 .env 文件管理,后面配置片段里我都会用占位符。
2.2 环境变量与最小验证
先把 Key 写进环境变量,Linux/macOS 用 export,Windows 用 set 或系统设置。下面以 macOS/Linux 为例:
export TAOTOKEN_API_KEY="sk-你的Key" export TAOTOKEN_BASE_URL="https://taotoken.net/api"然后写一个最小的 Python 脚本验证通道是否通。这里用 openai 官方 SDK,因为它对 OpenAI 兼容接口支持最好:
import os from openai import OpenAI client = OpenAI( api_key=os.environ["TAOTOKEN_API_KEY"], base_url=os.environ["TAOTOKEN_BASE_URL"], ) resp = client.chat.completions.create( model="claude-sonnet-4-20250514", messages=[ {"role": "system", "content": "你是一个简洁的助手,回答不超过两句话。"}, {"role": "user", "content": "用一句话说明什么是 MCP 协议。"}, ], ) print(resp.choices[0].message.content)跑通之后你会看到模型返回的一句话解释。如果这里就报 401,说明 Key 错了或没读到环境变量;如果报连接超时,检查 Base URL 有没有写错。这一步过了,再往下做 Function Calling 才有意义。
2.3 模型 ID 怎么选
TaoToken 支持多家模型,model 字段填对应的 ID 即可。做 Function Calling 和 Agent 场景时,建议优先选工具调用能力强的模型,比如 Claude 系列和 GPT 系列。你可以在模型对话页面 https://taotoken.net/chat 先手动试几个模型,看哪个在你任务上表现稳,再写进代码。
提示:不同模型对 tools 参数的支持程度不一样。有些开源模型虽然兼容 OpenAI 格式,但工具调用是"假装"的,返回的 JSON 可能不合法。调试阶段先用官方主力模型,跑通链路后再换。
前置准备到这里就够了。核心就三样:Key、Base URL、Model ID。这三样在后面 MCP 配置和 Function Calling 里会反复出现,记住它们的位置。
3. 可复制配置:Prompt 模板、Function Calling 参数与 MCP 服务端
这一节是全文的技术核心,我会给三份可以直接抄的配置:一份 System Prompt 模板、一份 Function Calling 的 tools 定义、一份 MCP 服务端的 settings 片段。每一份都说明字段含义和常见坑。
3.1 System Prompt 模板:把规则和人设分开写
很多人写 Prompt 喜欢把所有要求塞进 User 消息里,结果模型一会儿记得一会儿忘。正确做法是把稳定不变的部分放 System,把每次变化的问题放 User。下面这个模板适合 Agent 场景,你可以直接改:
你是「运维助手」,负责帮用户查询服务器状态并给出操作建议。 规则: 1. 需要实时数据时,必须调用提供的工具,不要凭记忆回答。 2. 调用工具前,先用一句话说明你要做什么。 3. 工具返回结果后,用中文总结,不要直接粘贴原始 JSON。 4. 如果工具报错,把错误原因翻译成用户能懂的话,并给出下一步建议。 输出格式: - 先给结论 - 再给依据 - 最后给建议(如果有)这个模板的关键在于"必须调用工具"这条硬规则。实测下来,如果不写这句,模型在它"觉得知道答案"的时候会跳过工具直接编,这在 Agent 场景里是致命的。
3.2 Function Calling 参数:tools 数组怎么写
Function Calling 的核心是把工具描述从 System Prompt 里剥离出来,放进独立的 tools 字段,用 JSON Schema 规范参数。下面是一个查询服务器状态的工具定义:
tools = [ { "type": "function", "function": { "name": "get_server_status", "description": "查询指定服务器的 CPU、内存和磁盘使用率", "parameters": { "type": "object", "properties": { "hostname": { "type": "string", "description": "服务器主机名,例如 web-01", }, "metric": { "type": "string", "enum": ["cpu", "memory", "disk", "all"], "description": "要查询的指标,默认 all", }, }, "required": ["hostname"], }, }, } ]几个容易踩的坑:description 一定要写清楚,模型是靠它判断什么时候调用的;enum 能限制取值范围,减少模型乱填参数;required 只放真正必须的字段,放多了模型会纠结。
调用时把 tools 传进去,并设置 tool_choice="auto":
resp = client.chat.completions.create( model="claude-sonnet-4-20250514", messages=messages, tools=tools, tool_choice="auto", ) msg = resp.choices[0].message if msg.tool_calls: for call in msg.tool_calls: print(call.function.name, call.function.arguments)如果模型决定调用工具,msg.tool_calls 里就会有内容,arguments 是 JSON 字符串。你解析后执行真实函数,再把结果以 role="tool" 的消息追加回去,让模型生成最终回答。
3.3 MCP 服务端配置:settings 片段
MCP 的价值在于把上面这种"每个 Agent 自己定义 tools"的模式,变成"工具作为服务统一托管"。MCP Server 提供 Tool、Resource、Prompt 三类能力,MCP Client(也就是 Agent)通过标准协议去发现和调用。
下面是一个 MCP 服务端的配置片段,以常见的 JSON 配置为例(不同客户端路径可能不同,字段名保持一致):
{ "mcpServers": { "ops-tools": { "command": "npx", "args": ["-y", "@your-scope/ops-mcp-server"], "env": { "TAOTOKEN_API_KEY": "sk-你的Key", "TAOTOKEN_BASE_URL": "https://taotoken.net/api", "MODEL_ID": "claude-sonnet-4-20250514" } } } }这里三件套必须齐全:Base URL、Key、Model ID。MCP Server 内部如果要调用大模型(比如做结果总结),就用这三个值去请求 TaoToken 的统一通道。command 和 args 根据你用的 MCP Server 实现来填,本地 stdio 模式用 npx 启动,远程 HTTP 模式则填 URL。
注意:MCP Server 跑在本地时通过标准输入输出通信,不要在里面打印无关日志,否则会污染协议流导致解析失败。这是新手最常踩的坑之一。
配置写完后,重启你的 MCP Client(比如 Claude Code、Cline 等),它会在启动时读取这份配置并连接 Server。连接成功后,Client 就能列出 Server 提供的所有工具。
4. 验证请求:从 Prompt 到工具调用的完整链路
配置写完不算完,得跑一遍完整链路确认每一环都通。这一节我按"Prompt 直答 → Function Calling → MCP 工具调用"三步走,每步都给预期结果。
4.1 第一步:纯 Prompt 验证
先用第 2 节的脚本发一条普通消息,确认模型能正常回复。这一步排除 Key 和网络问题。预期结果是模型返回一段中文文本,没有报错。
4.2 第二步:Function Calling 验证
用 3.2 的 tools 定义,发一条会触发工具调用的消息:
messages = [ {"role": "system", "content": "你是运维助手,需要实时数据时必须调用工具。"}, {"role": "user", "content": "帮我看下 web-01 的 CPU 使用率"}, ] resp = client.chat.completions.create( model="claude-sonnet-4-20250514", messages=messages, tools=tools, tool_choice="auto", ) msg = resp.choices[0].message print("tool_calls:", msg.tool_calls)预期结果是 msg.tool_calls 不为空,里面包含 get_server_status,arguments 是 {"hostname": "web-01", "metric": "cpu"} 这样的 JSON。如果 tool_calls 是空的,说明模型没触发调用,检查 System Prompt 里有没有"必须调用工具"的硬规则,以及 tool_choice 是不是 auto。
拿到 tool_calls 后,模拟执行函数并把结果回传:
import json tool_result = {"cpu": "23%", "status": "healthy"} messages.append(msg) messages.append({ "role": "tool", "tool_call_id": msg.tool_calls[0].id, "content": json.dumps(tool_result, ensure_ascii=False), }) final = client.chat.completions.create( model="claude-sonnet-4-20250514", messages=messages, tools=tools, ) print(final.choices[0].message.content)预期结果是模型基于工具返回的数据,用中文总结出"web-01 的 CPU 使用率是 23%,状态健康"这类回答。注意 role="tool" 的消息必须带 tool_call_id,且要和前面 tool_calls 里的 id 对上,否则会报参数错误。
4.3 第三步:MCP 工具调用验证
MCP 的验证依赖具体 Client。以支持 MCP 的编码工具为例,配置好 3.3 的 settings 后,在对话里问一个需要工具的问题,比如"用 ops-tools 查一下 web-01 的状态"。Client 会先通过 MCP 协议向 Server 请求工具列表,拿到 get_server_status 的定义,再转成 Function Calling 格式发给模型,模型返回调用请求后,Client 通过 MCP 调用 Server 执行,最后把结果回传模型。
你可以在 Client 的日志里看到这条链路:列出工具 → 模型返回 tool_calls → 调用 MCP Server → 返回结果 → 模型总结。如果中间断了,日志会停在某一步,对照第 5 节排查。
提示:验证 MCP 时先用最简单的工具(比如返回固定字符串的 echo 工具),确认协议通了再上真实业务工具。这样能把"协议问题"和"业务逻辑问题"分开。
5. 常见错误排查:401、local proxy failed、reading choices、OAuth
这一节按真实报错来组织,每条都给现象、原因、解决。这些是我在调试过程中实际遇到过的,不是从文档里抄的。
5.1 401 Unauthorized
现象:请求直接返回 401,提示 invalid api key 或 missing authentication。
原因基本就三类:Key 写错或过期、环境变量没读到、Base URL 和 Key 不匹配(比如把别家的 Key 配到了 TaoToken 的地址上)。
排查顺序:先 echo $TAOTOKEN_API_KEY 确认环境变量有值;再确认代码里读的是同一个变量名;最后确认 base_url 是 https://taotoken.net/api 。如果都对还报 401,去控制台重新生成一个 Key 试试。
5.2 local proxy failed
现象:MCP Client 启动时报 local proxy failed 或 connection refused。
原因通常是 MCP Server 没起来,或者端口被占用,或者 command/args 写错导致进程启动就退出。stdio 模式下如果 Server 往 stdout 打印了日志,也会让 Client 解析失败,报成 proxy 错误。
排查:先在终端手动跑一遍 command 和 args,看 Server 能不能正常启动;检查有没有多余 print;如果是 HTTP 模式,确认端口没被占用,防火墙没拦。
5.3 reading choices 报错
现象:解析响应时报 KeyError: 'choices' 或 reading 'choices' of undefined。
原因是响应结构和你预期的不一样。常见于:Base URL 写错导致返回了 HTML 错误页;模型 ID 不存在导致返回了错误 JSON;或者你用了流式但按非流式解析。
排查:先把原始响应 print 出来看结构。如果是 HTML,说明地址错了;如果是 {"error": ...},看 error.message 里的具体原因。确认 model 字段是 TaoToken 支持的 ID。
5.4 OAuth 鉴权失败
现象:连接远程 MCP Server 时报 OAuth token invalid 或 401 on MCP endpoint。
原因是远程 MCP Server 需要鉴权,而你的 Client 没带有效 token,或者 token 过期了。本地 stdio 模式一般不需要 OAuth,远程 HTTP 模式才涉及。
排查:确认 Server 的鉴权方式(Bearer Token 还是 OAuth 流程);检查配置里的 env 或 header 有没有正确带上凭证;如果是 OAuth,重新走一遍授权流程拿新 token。
5.5 模型不调用工具
这个不算报错,但比报错更让人头疼。现象是模型直接回答了,tool_calls 为空。
原因:System Prompt 没强调必须调用;工具 description 写得太模糊,模型不知道什么时候用;或者问题本身模型觉得它能直接答。
解决:在 System Prompt 里加硬规则;把 description 写具体,包含"什么时候用";必要时把 tool_choice 设成 {"type": "function", "function": {"name": "get_server_status"}} 强制调用指定工具来验证链路。
| 报错 | 最可能原因 | 第一步动作 |
|---|---|---|
| 401 | Key 错/没读到 | echo 环境变量 |
| local proxy failed | Server 没起/日志污染 | 手动跑 command |
| reading choices | 响应非预期结构 | print 原始响应 |
| OAuth | 远程鉴权缺失 | 检查 token/header |
6. 把链路用起来:从调试到长期编码的通道选择
链路跑通之后,接下来就是怎么把它用在实际工作里。这里有个选择:如果你只是偶尔调试几个请求,用按量计费的 API Key 就够了;如果你要长期跑 Agent、做编码辅助、频繁调用工具,那用 Coding Plan 会更划算,额度更稳定,不用担心每次调试都烧 token。
具体怎么选,看你的使用频率。调试阶段我建议先用 API Key,因为灵活,随时换模型。等你的 MCP Server 和 Agent 流程稳定了,再考虑 Coding Plan 做长期运行。接入文档在 https://taotoken.net/doc ,里面有各语言的示例和 MCP 相关的说明,遇到协议细节可以查。
最后给一个实用技巧:把 Base URL、Key、Model ID 这三件套写进一个统一的配置文件或环境变量模板,所有项目都从这里读。这样你换模型、换 Key 只改一处,不会出现"这个项目能跑那个项目报 401"的情况。MCP Server 的 env 里也引用同一套变量,保证 Agent 和 Server 用的是同一个通道。
调试 Function Calling 的时候,养成先 print 原始响应的习惯。很多"模型不听话"的问题,其实是响应结构没对上,看一眼原始 JSON 就明白了。工具调用返回的 arguments 是字符串不是对象,记得 json.loads 一下再用,这个坑我踩过不止一次。