从消息流转的角度彻底看清 FC、工具与 MCP 的关系

本文完全从消息流转的角度,把“决策”和“执行”彻底剥离开,让你看清数据包是怎么在网络和内存中跑来跑去的。
我将整个流程拆解成 10 条核心消息,按时间线分为 4 个阶段。假设场景:用户问“北京天气”,对接的是 OpenAI 大模型,工具是 MCP 托管的 get_weather。
阶段零:准备阶段(系统启动时,无用户消息)
动作: MCP 服务器启动,扫描所有带 @mcp.tool 装饰器的函数,生成一份标准化的内部工具清单(MCP 协议格式)。
// MCP 服务器内部生成的标准工具清单
{
"tools": [
{
"name": "get_weather",
"description": "查询指定城市的实时天气",
"inputSchema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,如:北京、上海"
}
},
"required": ["city"]
}
}
]
}
阶段一:发出请求(正向翻译)
第 1 条消息(用户 → 你的应用):
{
"content": "北京今天天气怎么样?"
}
第 2 条消息(你的应用 → MCP 客户端):
你的代码拿着用户消息,去问 MCP 客户端:“咱们现在有哪些工具可以用?” MCP 客户端返回阶段零生成的标准工具清单。
第 3 条消息(MCP 客户端 → OpenAI API):
MCP 把内部标准清单翻译成 OpenAI 大模型能够识别的 Function Calling 格式,裹在 API 请求里发出去。
此时,MCP 完成了“正向翻译”:把“工具说明书”翻译成了 FC 必须的 JSON 格式。
// MCP 正向翻译后的结果 —— OpenAI Function Calling 格式
{
"model": "gpt-4",
"messages": [
{
"role": "user",
"content": "北京今天天气怎么样?"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市的实时天气",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,如:北京、上海"
}
},
"required": ["city"]
}
}
}
],
"tool_choice": "auto"
}
关键洞察: tools 数组里的每一个元素,就是 MCP 把“工具”翻译成“大模型能看懂的 FC 格式”后的产物。这个 JSON 结构是 OpenAI 官方规定的,MCP 只是负责把工具信息“填入”这个模板。
阶段二:大模型决策(FC 大脑工作)
第 4 条消息(OpenAI API → 你的应用):
大模型收到请求后,启动 Function Calling 决策能力,发现“北京”对应 get_weather。它没有给出最终回答,而是返回一条特殊的意图消息:
// 大模型的 FC 决策结果 —— 只输出意图,不执行任何代码
{
"id": "chatcmpl-xxx",
"object": "chat.completion",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\": \"北京\"}"
}
}
]
},
"finish_reason": "tool_calls"
}
],
"usage": {
"prompt_tokens": 50,
"completion_tokens": 20,
"total_tokens": 70
}
}
注意! 大模型只输出了 JSON 字符串,并没有真正去查天气。这就是 FC 的全部工作——输出意图(tool_calls)。
arguments是一个 JSON 字符串,需要你的代码去解析。
阶段三:执行工具(反向翻译)
第 5 条消息(你的应用 → MCP 客户端):
你的代码解析出 tool_calls,拿着函数名 get_weather 和参数 {"city": "北京"},去问 MCP:“请执行这个工具。”
// 你的应用发给 MCP 客户端的执行请求
{
"tool_name": "get_weather",
"arguments": {
"city": "北京"
}
}
第 6 条消息(MCP 客户端 → 真实 Python 函数):
MCP 收到指令,完成“反向翻译”——把 JSON 里的 get_weather 映射到你电脑里真实存在的函数 def get_weather(city): ...,并把参数传进去,真正执行代码(比如调天气 API)。
# MCP 反向翻译后的真实代码调用
# 输入: {"tool_name": "get_weather", "arguments": {"city": "北京"}}
# 输出: 真实执行 get_weather("北京")
此时执行的可能是:
def get_weather(city: str):
# 真正去调第三方天气 API
response = requests.get(f"https://api.weather.com?city={city}")
return response.json() # 返回 "北京今天晴天,28°C"
第 7 条消息(真实函数 → MCP 客户端 → 你的应用):
函数执行完毕,返回结果。MCP 把这个结果打包,给你的应用。
// 工具执行结果
{
"tool_call_id": "call_abc123",
"result": "北京今天晴天,28°C"
}
阶段四:回填结果并生成最终回答(关键一环)
很多初学者以为到这里就结束了,但并没有! 大模型目前只知道“我要调工具”,还不知道“工具调完后结果是啥”。你必须把结果回填给大模型,让它看着结果给你说人话。
第 8 条消息(你的应用 → OpenAI API):
你把第 4 步的 tool_calls 消息和 MCP 返回的结果,一起再次发给大模型:
// 回填结果 —— 第二次调用大模型 API
{
"model": "gpt-4",
"messages": [
{
"role": "user",
"content": "北京今天天气怎么样?"
},
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\": \"北京\"}"
}
}
]
},
{
"role": "tool",
"tool_call_id": "call_abc123",
"content": "北京今天晴天,28°C"
}
]
}
关键点:
-
role: "assistant"的消息携带了第 4 步返回的tool_calls(大模型自己的决策) -
role: "tool"的消息携带了第 7 步的执行结果(你真正执行工具后的返回值) -
tool_call_id必须匹配,让大模型知道这个结果是针对哪个调用的回复
第 9 条消息(OpenAI API → 你的应用):
大模型看到结果,终于生成最终的自然语言回复:
// 最终回答
{
"id": "chatcmpl-yyy",
"object": "chat.completion",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "北京今天天气很不错,晴天,气温28°C,适合出门活动。"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 80,
"completion_tokens": 18,
"total_tokens": 98
}
}
第 10 条消息(你的应用 → 用户):
把这句话显示给用户。
{
"content": "北京今天天气很不错,晴天,气温28°C,适合出门活动。"
}
消息流转全景图(餐厅比喻版)
为了让你记牢,把组件替换成餐厅比喻:
-
顾客(用户):“我要一份宫保鸡丁。”(消息1)
-
扫码点餐系统(应用+MCP):把菜单(工具清单)翻译成大脑能看的格式(消息2-3)。
-
服务员大脑(大模型 FC):“收到,决定下单宫保鸡丁,备注微辣。”(消息4,返回
tool_calls) -
服务员把单子撕给后厨(MCP 反向翻译 + 执行):后厨(真实函数)开始炒菜。(消息5-7)
-
后厨炒好了(返回结果),服务员不能直接端给顾客,得先向大脑汇报:“菜做好了。”(消息8)
-
大脑说:“好的,告诉顾客菜好了。”(消息9-10,生成自然语言回复)
你现在能彻底回答这 3 个问题了吗?
| 问题 | 答案 |
|---|---|
| FC 在哪一步? | 第 4 步。只负责输出 tool_calls 决策(函数名+参数),绝不执行任何代码 |
| MCP 干了什么? | 第 3 步(正向翻译成 FC 格式)+ 第 5-6 步(反向翻译成真实函数调用) |
| 工具是谁执行的? | 第 7 步。你电脑里的真实 Python 函数代码 |
核心铁律
想让大模型基于工具结果回复,必须发起两次 API 调用:
第 1 次:拿意图(
tool_calls)第 2 次:回填结果(
role: "tool")拿最终话术
三个概念的精确定义
| 概念 | 本质 | 在消息流中的位置 |
|---|---|---|
| 工具(Tools) | 真实干活的代码(Python 函数) | 第 7 步,真正执行 |
| Function Calling | 大模型自带的决策能力 | 第 4 步,输出 tool_calls 意图 |
| MCP | 双向翻译官 + 标准化协议 | 第 3 步(正向翻译)+ 第 5-6 步(反向翻译) |
一句话终极总结
工具是“手”(真实干活),Function Calling 是“大脑的运动皮层”(只决策不干活),MCP 是“神经系统”(负责双向传导信号——把决策翻译成动作,把结果翻译回认知)。三者各司其职,缺一不可,串联成完整的 AI Agent 执行链路。
更多推荐


所有评论(0)