Function Calling 和 MCP 解决的是同一个问题——让 LLM 拿到外部能力——但走的路径完全不同。前者是”在请求里塞工具定义、模型返回结构化参数、应用自己执行”,后者是”模型通过标准协议与外部 Server 双向通信、由协议层负责发现与执行”。在 Agent 系统从单工具原型走向多工具、多模型、跨终端时,这两条路径的工程代价会迅速拉开。下面把它们的设计、适用面、代价与落地边界拆开讲清。
一、问题起源:为什么会有两条路径
LLM 本身只能生成文本,要让模型”干活”就必须给它一个能调用外部能力的口子。OpenAI 在 2023 年 6 月推出 Function Calling,把工具调用规范化为”JSON Schema 声明 → 模型选择 → 应用执行”三步,这件事是 Agent 工程化的一块基石;但每接一个工具,都要为它写一份 Schema,并与应用紧耦合地绑死,跨模型迁移时往往要重写。Anthropic 在 2024 年 11 月推出 Model Context Protocol(MCP),把”工具发现与调用”抽象成一个客户端—服务器协议,任何支持 MCP 的客户端都能用同一套 Server,这把”N 个工具 × M 个模型”的重写矩阵压缩成”N+M”。两条路径解决的是同一问题,但抽象层次不同。
下面用一张表把两者的接口契约、发现方式、调用路径与适用场景做对照:
| 维度 | Function Calling | MCP |
|---|---|---|
| 接口契约 | 每次请求附带 JSON Schema 列表 | 协议数据模型(tools / resources / prompts) |
| 发现方式 | 静态列表,改工具要改请求 | 动态发现,通过 tools/list 拉取 |
| 调用路径 | 模型选函数 → 应用执行 | 客户端经 JSON-RPC 调 tools/call |
| 传输 | 一般随 LLM API 内联 | stdio 或 HTTP(SSE) |
| 可移植性 | 绑定特定 LLM 厂商 | 跨 Host / 跨模型复用 |
| 适合场景 | 单应用、少量工具、延迟敏感 | 多工具、跨终端、长期治理 |
| 主要代价 | 工具多了请求会膨胀,跨模型重写 | 要起 Server、配 Host 策略、运维 JSON-RPC |
理解这张表是后面所有判断的基础:Function Calling 是”应用内的紧耦合控制流”,MCP 是”应用外的标准化服务流”。
二、Function Calling 的工程细节
Function Calling 的核心是”声明—选择—执行”三步:
- 声明:把可用工具的 JSON Schema 放进 LLM 请求的
tools字段; - 选择:模型基于用户 query 决定调哪个工具,并返回结构化参数(
tool_call); - 执行:应用侧拿到参数,自行执行真正的业务调用(查数据库、HTTP 请求、查文件),把结果回填给模型。
这个流程的好处是极低延迟、零额外进程,适合”工具就两三个、要快”的场景。但它的代价也来自同一个设计:工具定义与请求绑定,工具增加时请求体变胖,长尾工具的 token 成本不可忽略;工具 Schema 是厂商方言(OpenAI、Anthropic、Google 在字段命名、必填项、嵌套风格上都有差异),跨模型迁移往往要重写。下面给出一段最小化调用示例,展示声明—选择—执行的完整链路:
# function_calling.py
import json
from openai import OpenAI
client = OpenAI()
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市的当前天气",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string", "description": "城市名,如 Paris"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
},
"required": ["location"],
},
},
}
]
resp = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Paris 现在多少度? 用摄氏度。"}],
tools=tools,
)
call = resp.choices[0].message.tool_calls[0]
args = json.loads(call.function.arguments)
# 应用侧真正去查天气 API
weather = call_external_weather_api(args["location"], args.get("unit"))
print(weather)
这段代码的价值不在”调通一个天气接口”,而在于看清边界:模型只负责”选函数 + 填参数”,真正执行仍由应用代码完成;它不会绕过你直接打外部 API,这就是为什么 Function Calling 仍是”应用内紧耦合控制流”。
三、MCP 的工程细节
MCP 把这件事的”声明”和”执行”彻底拆开:MCP Server 负责暴露工具、资源、提示词,Host(如 Claude Desktop、IDE、Agent 框架)内的 Client 通过 JSON-RPC 与 Server 通信。模型本身不直接见 Server,它见到的是 Host 在请求时动态注入的工具描述;真正调用时,Host 内的 Client 把请求转发到对应 Server,Server 执行后把结果回给 Host,再回到模型的下一轮上下文。
这段关系决定了 MCP 的几个工程属性:
- Server 可独立开发、测试、部署,跨 Host 复用——同一份 GitHub MCP Server 可以在 Claude Desktop、Cursor、Semantic Kernel 之间共享;
- 工具热插拔——Host 启动时发现,运行时可注册 / 卸载,不需要改应用代码;
- 协议层支持授权与审计——Host 可以按”允许的 Server、白名单工具、用户授权提示”策略管理风险;
- 代价是必须维护 Server 进程——这是与 Function Calling 零额外进程的根本差别。
# weather_server.py —— 最小化 MCP Server
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("weather")
@mcp.tool(description="查询指定城市的当前天气")
def get_weather(location: str, unit: str = "celsius") -> dict:
data = call_external_weather_api(location, unit)
return {"location": location, "unit": unit, "data": data}
if __name__ == "__main__":
mcp.run(transport="stdio")
启动后,任何支持 MCP 的 Host(Claude Desktop、IDE 插件、Agent 运行时)都能通过 stdio 或 HTTP(SSE) 把它挂进自己的工具集。效果上,工具逻辑和应用逻辑彻底解耦,Server 改一个版本不需要发新版 Agent;注意点是 Host 必须实现会话生命周期与权限策略,默认不能让任意 Server 任意工具都通,否则会出”恶意 Server 越权”问题。
四、优缺点对照:何时选谁
把两者的取舍摊开看,适用面其实非常清晰:
Function Calling 更适合
- 单应用内少量工具(2~5 个)、调用稳定;
- 延迟极敏感场景(高频调用、嵌入式 Agent);
- 团队规模小、不希望维护额外 Server 进程;
- 工具逻辑只服务于当前应用,不需要跨产品共享。
MCP 更适合
- 工具集多且持续增长(10+ 工具,且要跨项目复用);
- 跨 Host / 跨模型复用(同一份 Server 给 Claude Desktop、Cursor、Semantic Kernel、IDE 插件共用);
- 需要平台级治理(白名单 Server、按工具细粒度授权、操作审计);
- 团队开始出现”工具集市”诉求,需要把工具 Server 作为独立产品运营。
MCP 的工程代价主要在 3 处:Server 进程的运维(部署、监控、版本管理)、Host 侧的策略实现(允许谁、禁止谁、谁能调什么)、会话生命周期的实现(连接保活、断开重连、并发隔离)。Function Calling 的代价则是”工具集越大,跨模型迁移越痛”——这在企业里很常见,一旦把 OpenAI 换成本地模型,Schema 适配工作会被重新激活。
五、二者协同的混合模式
实践中,Function Calling 与 MCP 并非互斥,而是上下游关系。一种常见组合是:模型先用 Function Calling 解析用户意图、把”调哪个工具、调什么参数”做成结构化决策;再由应用的”工具网关”把这次调用桥接到 MCP 协议,分发给真正的 Server。这种”FC 做解析 + MCP 做执行”的好处是,既能保留 Function Calling 的低延迟意图解析,又能拿到 MCP 的工具生态扩展性——OpenAI 等厂商已经在做”Function Calling 转 MCP 网关”的能力,工程上不再是两套体系二选一。
落地这一混合模式时,关键约束是:网关层要做严格的白名单与参数校验,不能把 Function Calling 的输出直接透传到任意 MCP Server,否则一旦模型产生幻觉,网关会变成”全网风险入口”。
六、选型决策表与落地步骤
| 你的现状 | 建议方案 |
|---|---|
| 单一应用、2~5 个工具、追求最低延迟 | Function Calling |
| 多工具、跨项目复用、需要长期治理 | MCP |
| 已有大量 HTTP 服务、需要正式契约与安全方案 | OpenAPI 工具,经 MCP 暴露 |
| 跨厂商模型迁移频繁、工具集市已成型 | MCP + OpenAPI 混合 |
| 金融 / 政企场景,需要细粒度审计 | MCP + Function Calling 双层,MCP 负责审计,FC 做解析 |
落地步骤
- 画工具边界:把工具按”应用内 / 跨应用 / 跨厂商”分桶;
- 试点一工具:挑 1 个跨应用工具做成 MCP Server,在 Claude Desktop 验证;
- 接入 Host 策略:实现白名单、用户授权、审计日志;
- 评估迁移成本:对剩余应用内工具,继续保留 Function Calling,直到需要共享时再迁;
- 治理与度量:用 Langfuse / OpenTelemetry 监控每次调用的 trace、token、错误率,设硬上限。
到这里,Function Calling 与 MCP 的边界、代价与混合模式就完整了。选哪条路,不取决于”哪个更新”,而取决于工具集的规模、跨模型与跨终端的复用需求,以及平台治理的成熟度。把这三条对齐,就不会在”接一个工具”这种小事上反复推倒重来。
常见问题(FAQ)
Q1:MCP 会取代 Function Calling 吗?
不会。Function Calling 仍是应用内的低延迟控制流,MCP 更适合跨工具复用,二者是互补关系。
Q2:什么时候该把工具迁到 MCP?
工具数超过 5 个并需要跨项目、跨模型复用时,迁 MCP 收益明显。
Q3:Function Calling 跨模型迁移怎么降低工作量?
把工具声明抽成独立 schema 文件,各模型做一层薄适配,而不是把 Schema 散落在代码里。