MCP vs Function Calling:两个总被搞混的概念,一次说清楚

很多人把 MCP 和 Function Calling 当成竞争关系,其实它们解决的是完全不同层次的问题。这篇文章用一个实际的聊天机器人项目,把两者的关系彻底讲清楚。


先说结论

Function Calling 是 LLM 与工具交互的语言,MCP 是工具注册和调用的标准协议。

两者不是替代关系,而是不同层次的解决方案:

javascript 复制代码
用户 → Host(Cline/Claude Desktop)→ LLM(决策层)
                                            ↓ Function Calling / XML(Host自定义格式)
Host ←────────────────────────────→ MCP Server(工具层)
                                         MCP 协议(JSON-RPC 2.0)

Function Calling 是什么

Function Calling 是 OpenAI 在 2023 年引入的功能,让 LLM 能够以结构化的方式"请求调用"外部函数。

本质上,它是一种约定格式:你告诉 LLM "有这些函数可以用",LLM 在认为需要的时候,输出一个符合格式的 JSON,宿主代码解析后执行对应函数。

工作流程

python 复制代码
import anthropic

client = anthropic.Anthropic()

# Step 1: 定义工具
tools = [
    {
        "name": "get_weather",
        "description": "获取指定城市的实时天气",
        "input_schema": {
            "type": "object",
            "properties": {
                "city": {
                    "type": "string",
                    "description": "城市名称,如'北京'、'上海'"
                }
            },
            "required": ["city"]
        }
    }
]

# Step 2: 发送请求(带工具定义)
response = client.messages.create(
    model="claude-3-5-sonnet-20241022",
    max_tokens=1024,
    tools=tools,
    messages=[{"role": "user", "content": "北京今天天气怎么样?"}]
)

# Step 3: 检查 LLM 是否请求工具调用
if response.stop_reason == "tool_use":
    tool_use = next(b for b in response.content if b.type == "tool_use")
    print(f"LLM 请求调用工具:{tool_use.name}")
    print(f"参数:{tool_use.input}")
    # → 工具名:get_weather
    # → 参数:{"city": "北京"}
    
    # Step 4: 执行工具(你自己的代码)
    weather_result = "北京今天晴,气温 22°C"
    
    # Step 5: 把结果返回给 LLM
    final_response = client.messages.create(
        model="claude-3-5-sonnet-20241022",
        max_tokens=1024,
        tools=tools,
        messages=[
            {"role": "user", "content": "北京今天天气怎么样?"},
            {"role": "assistant", "content": response.content},
            {
                "role": "user",
                "content": [{"type": "tool_result", "tool_use_id": tool_use.id, "content": weather_result}]
            }
        ]
    )
    print(final_response.content[0].text)

关键点:工具的定义、调用、结果处理,全部在你的代码里。LLM 只负责"决策"------它说"我要调用 get_weather,参数是 Beijing",但真正的调用是你的代码执行的。


用一个聊天机器人项目说清楚

马克在这期视频里用了一个叫 "MarkChat" 的项目来演示 MCP 和 Function Calling 的关系。

bash 复制代码
MarkChat 项目结构:
MCP 与 Function Calling 到底什么关系/
└── MarkChat/
    ├── app.py          # 主程序(MCP Host 角色)
    ├── tools.py        # 工具定义(通过 Function Calling 暴露给 LLM)
    └── mcp_client.py   # MCP 客户端(连接 MCP Server)

场景:MarkChat 想使用一个天气 MCP Server

python 复制代码
# mcp_client.py:连接 MCP Server,发现工具

class MCPClient:
    def __init__(self, server_config: dict):
        self.server_config = server_config
        self.available_tools = []
    
    async def connect(self):
        """连接 MCP Server 并获取工具列表"""
        # 启动 MCP Server 进程
        self.process = await asyncio.create_subprocess_exec(
            *self.server_config["command"],
            stdin=asyncio.subprocess.PIPE,
            stdout=asyncio.subprocess.PIPE
        )
        
        # 初始化握手
        await self._send({"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {...}})
        await self._recv()
        
        # 获取工具列表
        await self._send({"jsonrpc": "2.0", "id": 2, "method": "tools/list"})
        result = await self._recv()
        
        # 把 MCP 工具格式转换成 Function Calling 格式
        self.available_tools = [
            self._mcp_tool_to_fc_format(tool) 
            for tool in result["result"]["tools"]
        ]
    
    def _mcp_tool_to_fc_format(self, mcp_tool: dict) -> dict:
        """把 MCP 工具格式转换成 Claude Function Calling 格式"""
        return {
            "name": f"mcp__{mcp_tool['name']}",  # 加前缀区分
            "description": mcp_tool["description"],
            "input_schema": mcp_tool["inputSchema"]
        }
    
    async def call_tool(self, tool_name: str, arguments: dict) -> str:
        """调用 MCP Server 上的工具"""
        real_name = tool_name.replace("mcp__", "")
        await self._send({
            "jsonrpc": "2.0",
            "id": 3,
            "method": "tools/call",
            "params": {"name": real_name, "arguments": arguments}
        })
        result = await self._recv()
        return result["result"]["content"][0]["text"]
python 复制代码
# app.py:主程序,把 MCP 工具通过 Function Calling 暴露给 LLM

class MarkChat:
    def __init__(self):
        self.mcp_client = MCPClient({
            "command": ["uv", "run", "weather.py"]
        })
        self.claude = anthropic.Anthropic()
    
    async def setup(self):
        await self.mcp_client.connect()
        # mcp_client.available_tools 现在包含了天气工具,格式是 Function Calling 格式
    
    async def chat(self, user_message: str) -> str:
        # 把 MCP 工具列表作为 Function Calling 工具传给 LLM
        all_tools = self.mcp_client.available_tools
        
        messages = [{"role": "user", "content": user_message}]
        
        while True:
            response = self.claude.messages.create(
                model="claude-3-5-sonnet-20241022",
                max_tokens=1024,
                tools=all_tools,
                messages=messages
            )
            
            if response.stop_reason == "end_turn":
                return response.content[0].text
            
            if response.stop_reason == "tool_use":
                # LLM 要调用工具
                for block in response.content:
                    if block.type == "tool_use":
                        # 通过 MCP 协议实际执行工具
                        result = await self.mcp_client.call_tool(
                            block.name, 
                            block.input
                        )
                        # 把工具结果返回给 LLM
                        messages.append({"role": "assistant", "content": response.content})
                        messages.append({
                            "role": "user",
                            "content": [{"type": "tool_result", "tool_use_id": block.id, "content": result}]
                        })

关系图:两者如何协同工作

scss 复制代码
┌─────────────────────────────────────────────────────┐
│                    MarkChat (Host)                    │
│                                                       │
│  ┌─────────────┐      Function Calling     ┌───────┐ │
│  │             │ ←──────────────────────→  │       │ │
│  │ MCP Client  │   (工具定义 + 调用请求)    │  LLM  │ │
│  │             │                           │       │ │
│  └──────┬──────┘                           └───────┘ │
│         │ MCP 协议 (JSON-RPC 2.0)                     │
└─────────┼───────────────────────────────────────────┘
          │
          ↓
   ┌──────────────┐
   │  MCP Server  │
   │ (weather.py) │
   └──────────────┘

Function Calling 发生在 Host(MarkChat)和 LLM 之间:LLM 用 Function Calling 格式表达"我要调用这个工具"。

MCP 协议 发生在 Host(MarkChat)和 MCP Server 之间:Host 用 JSON-RPC 2.0 实际执行工具调用。


为什么不直接用 Function Calling 而要 MCP?

维度 纯 Function Calling MCP + Function Calling
工具复用 每个项目重新实现 写一次 MCP Server,所有支持 MCP 的 Host 都能用
工具发现 硬编码在代码里 动态发现,新工具自动可用
跨 Host 兼容 不兼容 兼容所有 MCP Host
进程隔离 工具和 Host 同进程 独立进程,崩溃不影响 Host
开发分工 工具和 Host 必须同一个团队 工具提供方和 Host 开发者可以分离

总结

一句话分清两者

  • Function Calling :LLM 说"我想调用 X 工具"的语言格式(在 Host 和 LLM 之间)
  • MCP :工具如何被发现、注册和调用的标准化协议(在 Host 和 Server 之间)

实际生产系统里,两者经常同时使用:MCP 负责工具的标准化接入,Function Calling 负责 LLM 与工具的交互接口。

理解了这个关系,你就能看懂为什么 Cursor、Cline、Claude Desktop 都在往 MCP 上迁移------不是为了替代 Function Calling,而是为了让工具生态标准化。


相关推荐
黄粱梦醒2 小时前
OpenClaw-window安装教程以及通用常用命令
人工智能·llm
2501_918126912 小时前
学习所有python写服务器的语句
服务器·人工智能·python·学习·个人开发
永霖光电_UVLED2 小时前
Hensoldt 与 UMS 签署 GaN 供应协议
人工智能·神经网络·生成对抗网络
ofoxcoding2 小时前
怎么用 API 搭一个 AI 客服机器人?从零到上线的完整方案 [特殊字符]
人工智能·ai·机器人
技术小甜甜2 小时前
[AI架构] 云模型 vs 本地模型:企业AI部署的架构选择
人工智能·ai·架构·创业创新
K姐研究社2 小时前
讯飞AstronClaw实测 – 零门槛一键部署安全版 OpenClaw
人工智能·aigc
极光代码工作室2 小时前
基于机器学习的房价预测系统设计与实现
人工智能·python·深度学习·机器学习
weixin_668898642 小时前
分词、词嵌入
人工智能·深度学习
李子琪。2 小时前
基于“产业-空间-社会”三重网络的传统工业城市现代化转型路径研究——以广西柳州市典型
java·人工智能·经验分享