MCP是如何工作的?

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 的三次握手 完成闭环:

  1. 人话 → 结构化(意图解析)
  2. 结构化 → 执行(函数调用)
  3. 执行结果 → 人话(总结回答)

4. 把“握手”做成协议:MCP 架构全景

为了让不同团队、不同语言的模型和工具都能互通,MCP 把上述流程固化为一套协议:

┌────────────┐     MCP Schema     ┌───────────┐
│  LLM Core  │◄──────────────────►│ Registry  │
└────┬───────┘                    └────┬──────┘
     │ 1. 结构化请求                   │ 2. 函数清单
┌────┴───────┐     JSON-RPC    ┌────┴───────┐
│  Runtime   │◄───────────────►│  Tool A/B  │
└────────────┘                 └────────────┘
  • Registry:函数清单 + JSON Schema,可热更新。
  • Runtime:沙箱、鉴权、限流、重试、日志。
  • LLM Core:只关心“我要调什么、结果长啥样”,其余全部交给协议层。

5. 开发者如何落地 MCP?

  1. 定义函数
    用 JSON Schema 写一个 get_weather.json​,放进 registry。

  2. 接入 Runtime
    Python/Java/Go 都有官方 SDK,三行代码起一个本地 server。

  3. 调教 LLM
    在 prompt 里加一句:

    你只能用以下函数:get_weather, book_restaurant...
    

    或者 fine-tune 一个小模型专门做解析器。


6. 小结:记住一句话

MCP 把“人话 ↔ 函数”的黑盒拆成了可观测、可插拔的三次握手。
开发者只需写好函数,剩下的事交给协议。


7. 彩蛋:如果用户问“帮我订今晚 7 点海底捞”?

  1. 解析器输出:

    {
      "name": "book_restaurant",
      "arguments": {
        "brand": "海底捞",
        "date": "2025-07-29",
        "time": "19:00",
        "people": 2
      }
    }
    
  2. 运行时返回:

    {"order_id": "HN12345", "wait_time": 30}
    
  3. 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_data


def calculate_temperature(num1, num2, operation):
    """
    支持加减乘除四种运算
    """
    if operation == "add":
        return num1 + num2
    elif operation == "subtract":
        return num1 - num2
    elif operation == "multiply":
        return num1 * num2
    elif operation == "divide":
        if num2 == 0:
            raise ValueError("除数不能为零")
        return num1 / num2
    else:
        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 Key
    base_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_calls
    for tool_call in response_message.tool_calls:
        function_name = tool_call.function.name
        function_args = json.loads(tool_call.function.arguments)

        # 执行对应的工具函数
        if function_name == "get_current_weather":
            # 这里可以接入真实天气 API
            location = 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 datetime
            current_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)执行函数后,把结果再次回传给大模型,最终由大模型生成回答。

posted @ 2025-07-29 17:55  zart2007  阅读(285)  评论(0)    收藏  举报