本文完全从消息流转的角度,把“决策”和“执行”彻底剥离开,让你看清数据包是怎么在网络和内存中跑来跑去的。

我将整个流程拆解成 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. 顾客(用户):“我要一份宫保鸡丁。”(消息1)

  2. 扫码点餐系统(应用+MCP):把菜单(工具清单)翻译成大脑能看的格式(消息2-3)。

  3. 服务员大脑(大模型 FC):“收到,决定下单宫保鸡丁,备注微辣。”(消息4,返回 tool_calls

  4. 服务员把单子撕给后厨(MCP 反向翻译 + 执行):后厨(真实函数)开始炒菜。(消息5-7)

  5. 后厨炒好了(返回结果),服务员不能直接端给顾客,得先向大脑汇报:“菜做好了。”(消息8)

  6. 大脑说:“好的,告诉顾客菜好了。”(消息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 执行链路。

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐