•  原生Function Calling: 就像你买了一辆原厂自带“车载导航”和“蓝牙音乐”功能的品牌车(比如特斯拉)。这些功能是车厂(OpenAI/Google)预装好的,你只需要在车机屏幕上按一下,告诉它“导航到公司”或“播放音乐”,车子自己就知道怎么做了。你是一个“司机”。

  • 自建MCP服务: 就像你是一个专业的“汽车改装厂”。你买来一个强大的裸引擎(大模型),然后你自己设计并安装了一整套复杂的车载系统(MCP服务)。这套系统里,你自己造了导航模块(数据库查询服务)、娱乐模块(邮件发送服务),并且用一套自己定义的总线协议(MCP协议)把它们连接起来。引擎(大模型)只负责根据你的指令输出“该导航了”或“该放音乐了”,而具体怎么做,都是由你的改装系统完成的。你是一个“汽车工程师”+“司机”。

对比维度原生Function Calling (开车)自建MCP服务 (造车)
控制权低。 你只能使用模型厂商提供的工具调用框架和协议。极高。 从协议、安全、到工具的实现,一切都由你定义和控制。
安全性依赖模型厂商。 数据是否会离开你的环境,取决于厂商的策略。自主可控。 你可以设计让所有敏感数据和工具执行都留在你的私有网络内。
灵活性中等。 可以在厂商的框架内定义工具,但受限于其能力。极高。 可以集成任何系统(本地、云端、老旧系统),实现任意复杂的逻辑。
复杂性低。 只需要调用一个API,并处理其返回的工具调用请求即可。高。 需要搭建一整套后端服务,包括API网关、服务发现、安全认证等。
核心逻辑模型驱动。 模型直接决定调用哪个工具和传入什么参数。服务驱动,模型辅助。 模型更像一个“意图翻译器”,将自然语言翻译成服务指令,真正的执行和调度由你的MCP服务完成。

一句话总结二者关系:
自建MCP服务通常会“包含”Function Calling作为其内部的一个环节。 你的MCP Server接收到用户请求后,会利用大模型的Function Calling能力来“解析意图”,然后MCP Server再根据解析出的意图去执行真正的操作。


实现方案:一步步教你怎么做

方案一:原生Function Calling的实现 (简单直接)

这种方案适用于快速开发、对数据安全要求不是最高、或者工具逻辑比较简单的场景。

事件流程图/时序图:

sequenceDiagram
    participant User as 用户
    participant App as 你的应用 (前端/后端)
    participant LLM_API as 大模型API (如OpenAI)

    User->>App: "查一下北京今天的天气"
    App->>LLM_API: 发送请求(prompt="查天气", tools=[{name:"get_weather", ...}])
    LLM_API-->>App: 返回指令(tool_calls=[{name:"get_weather", args:{"city":"北京"}}])
    
    App->>App: 在本地执行 get_weather("北京") 函数
    Note right of App: 调用一个外部天气API
    
    App->>LLM_API: 再次发送请求(附上工具执行结果: "北京天气晴朗,25度")
    LLM_API-->>App: 返回最终答案("北京今天天气晴朗,气温25度。")
    App->>User: 显示最终答案

实现步骤:

  1. 定义工具: 在你的代码里,按照OpenAI(或其他模型提供商)的格式,用JSON Schema定义一个或多个工具。
    tools = [
        {
            "type": "function",
            "function": {
                "name": "get_weather",
                "description": "获取一个城市的天气信息",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "city": {"type": "string", "description": "城市名称"},
                    },
                    "required": ["city"],
                },
            },
        }
    ]
    

  2. 首次调用API: 将用户的prompt和定义好的tools一起发送给大模型API。
    response = client.chat.completions.create(
        model="gpt-4-turbo",
        messages=[{"role": "user", "content": "查一下北京今天的天气"}],
        tools=tools,
        tool_choice="auto",
    )
    

  3. 解析并执行工具: 检查API的返回。如果包含tool_calls,就说明模型决定调用工具了。你在本地代码里执行这个函数。
    # response_message.tool_calls[0].function.name 会是 "get_weather"
    # response_message.tool_calls[0].function.arguments 会是 '{"city": "北京"}'
    
    # 你的本地函数
    def get_weather(city):
        # ... 调用真实的天气API ...
        return f"{city}天气晴朗,25度。"
    
    tool_result = get_weather("北京") 
    

  4. 二次调用API: 将工具的执行结果再次发回给API,让模型根据结果生成最终的自然语言回答。
方案二:自建MCP服务的实现 (专业强大)

这种方案适用于企业级应用,对安全、稳定、可控性要求高的场景。

事件流程图/时序图:

第1步:用户提问

“帮我看看 project_notes.txt 写了什么?”

  • 用户在 MCP Host(如 Claude Desktop)中输入
  • Host 的唯一动作是:把这句话原封不动传给大模型

 此时 Host 还不会做任何 MCP 相关操作


第2步:大模型分析问题

  • 模型理解语义:“project_notes.txt” 是一个文件名
  • 模型检查当前可用的 tool registry(由 MCP Client 注册进来)
  • 发现有一个名为 read_file 的工具,参数包含 path: string
  • 判断:这个问题不能靠已有知识回答,必须调用外部工具

决定发起调用:

{ "tool_call": { "id": "call_123", "name": "read_file", "arguments": { "path": "./project_notes.txt" } } }

📌 关键:这是模型主动发出的能力请求,不是 Host 推测的


第3步:MCP Client 捕获 tool_call 并注入上下文

  • MCP Client 是运行在 Host 内部的一个插件或代理
  • 它监听所有来自模型的输出
  • 当检测到 tool_call 且目标为已注册工具时,才启动协议流程

👉 此时 Client 才开始工作!

 Client 做三件事:
  1. 确认目标 Server
    查询 .well-known/mcp.json,找到提供 read_file 的 Server 地址(如 http://localhost:8080

  2. 封装上下文(Context Enrichment)
    加入当前环境信息(这才是你提到的重点!):

    { "user_id": "alice_001", "role": "sales_director", "scope": ["file:read:sales"], "client_ip": "127.0.0.1", "timestamp": "2025-04-05T14:30:00Z" }

  3. 构造并转发请求

    POST http://localhost:8080/read_file Content-Type: application/json { "path": "./project_notes.txt", "context": { "user_id": "alice_001", "role": "sales_director", "scope": ["file:read:sales"] } }


第4步:MCP Server 执行安全验证

Server 收到请求后:

✅ 权限校验(RBAC + Scope)

if context.role == "sales_director" and scope.includes("file:read:sales"): allow_access() else: deny("权限不足")

✅ 路径白名单检查

allowed_root = "/Users/alice/sales_docs" if not real_path.startswith(allowed_root): deny("禁止访问该目录")

✅ 执行操作 + 脱敏返回

{ "success": true, "summary": "文件读取成功", "content_preview": "Q2季度销售计划摘要...", "char_count": 1024 }


第5步:结果回流至模型

  • Server 将结果返回给 Client
  • Client 包装成标准格式提交回 LLM:

{ "role": "tool", "tool_call_id": "call_123", "content": "{\"summary\": \"文件读取成功\", \"content_preview\": \"Q2季度销售计划摘要...\"}" }

  • 大模型整合信息,生成自然语言回复:

“我已经查看了 project_notes.txt,文档是 Q2 季度销售计划,重点包括新客户拓展策略和预算分配。”


✅ 总结:你指出的关键点完全正确!

全屏复制

你的质疑实际情况
❓ “Host 怎么可能一收到问题就去调 MCP?”✅ 不会!只有模型提出 tool_call 后,Client 才介入
❓ “上下文是谁加进去的?”✅ 是 MCP Client 在转发时动态添加的
❓ “是不是 Host 主动推动?”✅ 不是!是模型驱动的响应式流程

🎯 正确理解口诀(建议收藏)

🔹 第一步:人问 AI
—— Host 只负责传话

🔹 第二步:AI 说“我做不到,需要帮忙”
—— 输出 tool_call

🔹 第三步:Client 听见了,才开始行动
—— 查找 Server + 注入上下文 + 发起请求

🔹 第四步:Server 安全执行并返回摘要
—— 不让敏感数据进模型

🔹 第五步:AI 得到结果,完成任务
—— 闭环完成


💡 类比帮你记忆

就像你在公司里想查一份机密文件:

  1. 你问经理:“我想看这份合同”
  2. 经理说:“我不能直接给你,得走审批流程”
  3. 行政系统自动加上你的工号、部门、申请时间
  4. 审批系统验证你是否有权查看
  5. 如果有,返回“摘要版”,而不是全文
  6. 经理告诉你:“可以看,主要内容是……”

👉 你不是一开始就提交权限信息,而是在“需要时”才触发流程

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐