MCP 和 Function Calling 不是一回事

一个常被混用的概念:Function Calling 是模型本身的能力,MCP 是连接模型和工具的协议。这篇讲清楚它们在工具发现、安全边界、可移植性上的真实差别,以及 Spring AI 是怎么统一这两者的。

MCP 和 Function Calling 不是一回事

这两个词经常被人当成同义词用,但它们在一个 AI 应用里处于完全不同的层级。

混用会直接导致架构选错——比如纠结”要不要为了用 MCP 换掉 Function Calling”,其实这两者根本不冲突。

一句话区分

  • Function Calling:模型本身具备的能力(what)——模型能在生成回复时判断”我需要调个外部函数”
  • MCP:连接模型和工具的协议(how)——定义工具怎么被发现、怎么调用、怎么传参

打个比方:Function Calling 像是编程语言里的函数调用机制,MCP 像是 HTTP 协议。你会问”用 HTTP 还是用函数调用”吗?不会,因为它们解决的是不同层面的问题。

定位对比:

维度Function CallingMCP
本质模型 API 的特性应用层通信协议
定义者模型厂商(OpenAI、Anthropic 等)Anthropic 开源标准
作用域单个模型与其定义的工具任何 AI 应用与任何工具
标准化各厂商实现不一致统一标准,模型无关
工具管理每次请求都要带上工具定义工具独立部署,动态发现

四个真正的差别

1. 工具发现机制

Function Calling:每次请求都要把完整的工具定义塞进去。

response = openai.ChatCompletion.create(
    model="gpt-4",
    messages=[{"role": "user", "content": "北京今天天气怎么样?"}],
    functions=functions,          # 每次都得传
    function_call="auto"
)

工具少的时候无所谓。当你有五十个工具的时候,每次请求都要带上五十个 schema,token 消耗和延迟都会明显上去。

MCP:工具是独立部署的服务,客户端连上之后自己拉取工具列表。工具定义不占对话的上下文,还能动态增删——加一个新工具,所有连着的客户端立刻能用,不用改任何代码。

2. 安全边界

这是我认为最容易被低估的一条。

Function Calling 的安全完全取决于应用自己怎么实现。模型说”调用 read_file('/etc/passwd')”,你的代码要不要执行、有没有权限控制,全是应用层的责任。

MCP 把安全边界做进了协议:MCP Server 自己声明能访问什么资源,客户端在连接时就能看到并授权。用 MCP 接本地文件系统,权限是 Server 侧控制的,模型没法靠 prompt 绕过。

配合前面那篇讲沙箱的文章看会更清楚——MCP 解决的是”工具该被允许访问什么”,沙箱解决的是”代码执行时怎么隔离”,两者是互补的。

3. 可移植性

Function Calling 最烦的实际问题是各厂商格式不统一。同一个工具,你得维护三份定义:

// OpenAI 的格式
{ "type": "function", "function": { "name": "get_weather", "description": "...", "parameters": {...} } }

// Anthropic Claude 的格式
{ "name": "get_weather", "description": "...", "input_schema": {...} }

// Google Gemini 的格式
{ "function_declarations": [{ "name": "get_weather", "description": "...", "parameters": {...} }] }

想让你的工具同时支持三家模型,就得写三套。MCP 是模型无关的,写一次,任何支持 MCP 的客户端都能用。

4. 资源管理

Function Calling 是无状态的,每次调用独立。

MCP Server 可以持有状态(数据库连接、文件句柄、会话上下文),这意味着工具之间能共享资源、能互相调用形成工作流,而不是每次都从零开始。

Spring AI 怎么统一这两者

如果你用 Java 技术栈,Spring AI 已经把这层差异封装掉了——这也正好说明两者是互补而非互斥的关系:

@Service
public class ToolService {

    // 1. 定义工具(同时支持 Function Calling 和 MCP)
    @Tool(name = "get_weather", description = "获取天气信息")
    public String getWeather(@ToolParam(description = "城市名称") String city) {
        return weatherService.getWeather(city);
    }

    // 2. 配置 MCP 连接
    @Bean
    public McpClient mcpClient() {
        return McpClient.builder()
            .serverUrl("http://localhost:8080/mcp")
            .build();
    }

    // 3. 使用统一 API
    public String chatWithTools(String query) {
        return chatClient.prompt()
            .user(query)
            .tools("get_weather")   // 统一引用,不区分底层是哪种机制
            .call()
            .content();
    }
}

注意最后那个 .tools("get_weather")——你不需要关心这个工具是本地的 Function Calling 还是远程的 MCP Server,框架帮你处理了。

这就是”两者不冲突”的最好证明:在同一个应用里,你可以本地工具走 Function Calling,远程工具走 MCP,用同一套代码调用。

什么时候用哪个

我的判断标准:

用 Function Calling 就够了的情况:

  • 工具数量在十个以内
  • 工具是应用私有的,不需要给别人用
  • 不需要跨会话保持状态
  • 只对接一家模型厂商

应该上 MCP 的情况:

  • 工具要给别人用(这是 MCP 最核心的价值——你写的工具,别人和别人的 AI 都能直接接)
  • 工具数量多,每次全量传定义已经影响成本和延迟
  • 需要严格的权限边界(比如访问本地文件、数据库)
  • 要对接多家模型
  • 工具之间有依赖关系,需要形成工作流

一个具体场景

假设你要让 AI 访问本地文件系统。

纯 Function Calling 的做法:定义 read_filewrite_file 两个函数,你的代码里接收路径参数,然后直接读写。风险是——模型完全可能被诱导去读 /etc/passwd 或者你的 SSH 私钥,而你的实现里如果没做路径白名单,就直接裸奔了。

MCP 的做法:起一个 filesystem MCP Server,在 Server 配置里声明只允许访问 ~/Documents/workspace。无论模型怎么被诱导,协议层就把它挡在外面了。

差别不在”能不能实现”——Function Calling 里你也能手写白名单。差别在于MCP 把这件事变成了标准配置,而不是每个开发者各自实现一遍

小结

  • Function Calling 是模型能力,MCP 是连接协议,不在一个层级
  • 大方向:应用内部、少量工具 → Function Calling;要对外开放、工具多、要权限控制 → MCP
  • Java 栈直接用 Spring AI 的 @Tool + McpClient,两套机制统一调用

前面那篇《写一个 MCP 服务》讲的是怎么把你的工具做成 MCP Server,这篇讲的是它和 Function Calling 的关系,两篇连着看会清楚一些。

奇妙感 本文采用署名-非商业性使用-相同方式共享协议,转载请注明出处与作者。

目录