MCP是如何工作的?
LLM 在调用外部工具(Tool-calling / Function-calling)时,确实经历了“口语化输入 → 结构化解析 → 运行时执行 → 结果回灌”这一闭环。MCP(Model-Calling-Protocol,或更广为人知的“Model-Context-Protocol”)正是把这一闭环标准化、可插拔化之后的产物。
从人话到函数,再从函数回到人话
—— 用 MCP 的视角拆解 LLM 工具调用
想象一下:用户说“今天北京热吗?”
你家的 AI 居然自己打开了天气 API,把温度读出来,还用中文回答:“34℃,挺热的,记得防晒。”
这背后发生了什么?为什么 AI 知道要调哪个函数、填哪些参数?
答案就藏在 MCP 的三次握手 里。
1. 口语 → 结构化:第一次握手(解析)
用户输入:
“今天北京热吗?”
在 MCP 里,这行文本首先被送进 解析器(Parser) 。解析器并不关心天气,它只干一件事:
把自然语言映射成“函数签名 + 参数” 。
// 解析器输出(LLM 第一次返回)
{"name": "get_weather","arguments": {"city": "北京","date": "2025-07-29"}
}
关键点
- 解析器可以就是 LLM 自己(通过 prompt/fine-tune),也可以是独立 NLU 模型。
- MCP 要求函数声明(JSON Schema)提前注册,LLM 像“查字典”一样匹配最符合意图的函数。
- 对可选参数、枚举值、数组等复杂 schema,MCP 会给出完整描述,减少幻觉。
2. 结构化 → 运行时:第二次握手(执行)
解析器把 JSON 交给 MCP 运行时(Runtime) 。
运行时的职责是“零信任”地执行函数,并捕获三种结果:
结果类型 | 示例 | 如何处理 |
---|---|---|
正常值 | {"temp": 34, "unit": "℃"} |
直接返回 |
业务错误 | {"error": "城市不存在"} |
包装成 message 回传 |
系统异常 | timeout、网络不通 | 转成统一异常格式,避免 LLM 看到堆栈 |
// 运行时返回
{"tool_call_id": "call_1","content": {"temp": 34,"unit": "℃"}
}
MCP 的精妙之处
- 统一错误码,让 LLM 可以“优雅地道歉”或“自动重试”。
- 支持异步调用、批量调用(parallel tool calls),LLM 一次可请求 3~N 个函数。
3. 运行时 → 口语:第三次握手(生成)
现在 LLM 拿到了原始数据,但它不会把 JSON 甩给用户。
它要进行 第二次推理,把结构化结果再翻译成人话:
34℃,体感偏热,紫外线强,出门建议涂 SPF50。
至此,MCP 的三次握手 完成闭环:
- 人话 → 结构化(意图解析)
- 结构化 → 执行(函数调用)
- 执行结果 → 人话(总结回答)
4. 把“握手”做成协议:MCP 架构全景
为了让不同团队、不同语言的模型和工具都能互通,MCP 把上述流程固化为一套协议:
┌────────────┐ MCP Schema ┌───────────┐
│ LLM Core │◄──────────────────►│ Registry │
└────┬───────┘ └────┬──────┘│ 1. 结构化请求 │ 2. 函数清单
┌────┴───────┐ JSON-RPC ┌────┴───────┐
│ Runtime │◄───────────────►│ Tool A/B │
└────────────┘ └────────────┘
- Registry:函数清单 + JSON Schema,可热更新。
- Runtime:沙箱、鉴权、限流、重试、日志。
- LLM Core:只关心“我要调什么、结果长啥样”,其余全部交给协议层。
5. 开发者如何落地 MCP?
-
定义函数
用 JSON Schema 写一个get_weather.json
,放进 registry。 -
接入 Runtime
Python/Java/Go 都有官方 SDK,三行代码起一个本地 server。 -
调教 LLM
在 prompt 里加一句:你只能用以下函数:get_weather, book_restaurant...
或者 fine-tune 一个小模型专门做解析器。
6. 小结:记住一句话
MCP 把“人话 ↔ 函数”的黑盒拆成了可观测、可插拔的三次握手。
开发者只需写好函数,剩下的事交给协议。
7. 彩蛋:如果用户问“帮我订今晚 7 点海底捞”?
-
解析器输出:
{"name": "book_restaurant","arguments": {"brand": "海底捞","date": "2025-07-29","time": "19:00","people": 2} }
-
运行时返回:
{"order_id": "HN12345", "wait_time": 30}
-
LLM 回复:
已为您预订今晚 7 点海底捞,预计等位 30 分钟,订单号 HN12345。
附 Python 代码
工具函数代码
# tools.py
def get_current_weather(location):"""获取指定城市的实时天气信息注意:实际项目中应接入真实天气 API,这里仅为示例"""# 模拟返回天气数据(实际项目中应调用高德、OpenWeatherMap 等)weather_data = {"location": location,"temperature": "26°C","condition": "多云","humidity": "65%"}return weather_datadef calculate_temperature(num1, num2, operation):"""支持加减乘除四种运算"""if operation == "add":return num1 + num2elif operation == "subtract":return num1 - num2elif operation == "multiply":return num1 * num2elif operation == "divide":if num2 == 0:raise ValueError("除数不能为零")return num1 / num2else:raise ValueError(f"未知运算类型: {operation}")if __name__ == "__main__":print(calculate_temperature(966, 69, "add"))
调用模型代码
# qwq_mcp.py
import os
import json
from openai import OpenAI
from tools import get_current_weather, calculate_temperature# 初始化客户端
client = OpenAI(api_key="sk-0f0dac238000000000000000000000", # 替换为你的实际 API Keybase_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)# 定义可用工具
tools = [{"type": "function","function": {"name": "get_current_weather","description": "获取指定城市的实时天气信息","parameters": {"type": "object","properties": {"location": {"type": "string", "description": "城市名称,如杭州市"}},"required": ["location"]}}},{"type": "function","function": {"name": "get_current_time","description": "获取当前时间","parameters": {}}},{"type": "function","function": {"name": "calculate","description": "执行两个数字的加减乘除运算","parameters": {"type": "object","properties": {"num1": {"type": "number", "description": "第一个操作数"},"num2": {"type": "number", "description": "第二个操作数"},"operation": {"type": "string","description": "运算类型:'add', 'subtract', 'multiply', 'divide'","enum": ["add", "subtract", "multiply", "divide"]}},"required": ["num1", "num2", "operation"]}}}
]# 用户提问
messages = [{"role": "user", "content": "588+222 等于多少?"}]# 第一次请求:让模型判断是否需要调用工具
completion = client.chat.completions.create(model="qwen3-235b-a22b-instruct-2507",messages=messages,tools=tools,tool_choice='auto' # 可选:auto, none, 或指定工具
)response_message = completion.choices[0].message # 获取模型回复
print(" 模型回复:", response_message)# 检查是否需要调用工具
if response_message.tool_calls:# 添加工具调用结果到消息历史messages.append(response_message)# 遍历所有 tool_callsfor tool_call in response_message.tool_calls:function_name = tool_call.function.namefunction_args = json.loads(tool_call.function.arguments)# 执行对应的工具函数if function_name == "get_current_weather":# 这里可以接入真实天气 APIlocation = function_args.get("location", "未知城市")# 模拟返回天气数据(实际项目中应调用高德、OpenWeatherMap 等)weather_data = get_current_weather(location)tool_response_content = json.dumps(weather_data, ensure_ascii=False)elif function_name == "get_current_time":from datetime import datetimecurrent_time = datetime.now().strftime("北京时间 %Y年%m月%d日 %H:%M:%S")tool_response_content = json.dumps({"time": current_time}, ensure_ascii=False)elif function_name == "calculate_temperature":num1= function_args.get("num1", 0)num2 = function_args.get("num2", 0)operation = function_args.get("operation", "add")result = calculate_temperature(num1, num2, operation)tool_response_content = json.dumps(result, ensure_ascii=False)else:tool_response_content = json.dumps({"error": "未知工具"}, ensure_ascii=False)# 将工具执行结果以 role="tool" 的形式加入对话messages.append({"role": "tool","content": tool_response_content,"tool_call_id": tool_call.id # 必须匹配})# 第二次请求:将工具结果传回模型,生成最终回答final_response = client.chat.completions.create(model="qwen3-235b-a22b-instruct-2507",messages=messages)print("✅ 最终回答:")print(final_response.choices[0].message.content)
else:# 如果模型没有调用工具,直接输出其回复print(" 模型直接回复:")print(response_message.content)
执行后的返回如下
(mcp-test) zy@ztg2:~/mycode/MCP_test$ python qwq_mcp.py 模型回复: ChatCompletionMessage(content='', refusal=None, role='assistant', annotations=None, audio=None, function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_2ba108ca775e465aa1ac0c', function=Function(arguments='{"num1": 588, "num2": 222, "operation": "add"}', name='calculate'), type='function', index=0)])
✅ 最终回答:
588 + 222 = 810。
可以看到第一次请求大模型后,它就把用户的口语完整地解析成工具所需的函数参数格式;运行时(qwq_mcp.py)执行函数后,把结果再次回传给大模型,最终由大模型生成回答。