Skip to main content

MCP 协议:AI 世界的 USB 接口

作者:程序员马丁

在线博客:https://nageoffer.com

note

Ragent AI —— 从 0 到 1 纯手工打造企业级 Agentic RAG,拒绝 Demo 玩具!AI 时代,助你拿个offer。

上一篇讲了 Function Call,你已经能让 RAG 系统从只能查知识库升级到能查数据、能调接口、能干活。模型自己判断什么时候该调工具,输出标准化的调用意图,你的代码执行函数——整个流程跑得很顺。

但文章最后留了一个尾巴:Function Call 很好用,工具一多就管不过来了。

假设你在一家公司做企业知识库助手,一开始只有两个工具:查年假、查订单。你手写两份 JSON Schema,代码里写两个 if-else 路由,没什么问题。但半年过去了,产品经理不断加需求:查考勤、查销售数据、查会议室、查报销进度、查项目排期、查库存、查物流、查合同……工具从 2 个变成了 20 个。

这时候你会发现:

  • 20 个工具 × 每个工具 5~10 个参数 = 几百行 JSON Schema 要手写和维护
  • Python 团队写了一个数据分析工具,你的 Java 系统调不了
  • 新来的同事问“系统里有哪些工具可用”,你翻了半天代码才找全
  • 某个工具的参数改了,JSON Schema 忘了同步更新,模型传了错误的参数,线上出了 bug

你需要的不是更多的 if-else,而是一个标准化的工具管理协议。这就是今天要讲的 MCP。

Function Call 的痛点:工具一多就管不过来

1. 回顾:Function Call 做对了什么

在展开痛点之前,先肯定一下 Function Call 的核心价值:

  • 让模型自己判断什么时候该调工具:不用写规则匹配,模型理解自然语言,“我还剩几天年假”和“假期余额还有多少”都能识别
  • 输出格式标准化:模型输出 JSON 格式的 tool_calls,易于解析,不会出现“模型在回答里夹了一段代码”的混乱情况
  • 多轮对话机制成熟:定义工具 → 模型输出调用意图 → 执行函数 → 返回结果 → 生成答案,流程清晰

这些能力本身没问题,Function Call 解决了让模型调工具的核心问题。但当工具规模化之后,Function Call 协议本身没有覆盖的那些“管理层面”的问题就暴露出来了。

2. 工具规模化之后的四大痛点

2.1 工具定义的维护噩梦

上一篇的代码你应该还有印象,定义一个工具要写多少 JSON Schema:

JsonObject tool1 = new JsonObject();
tool1.addProperty("type", "function");
JsonObject function1 = new JsonObject();
function1.addProperty("name", "getUserAnnualLeave");
function1.addProperty("description", "查询用户的年假余额,包括总天数、已使用天数、剩余天数");
JsonObject parameters1 = new JsonObject();
parameters1.addProperty("type", "object");
JsonObject properties1 = new JsonObject();
JsonObject userId1 = new JsonObject();
userId1.addProperty("type", "string");
userId1.addProperty("description", "用户 ID");
properties1.add("userId", userId1);
parameters1.add("properties", properties1);
JsonArray required1 = new JsonArray();
required1.add("userId");
parameters1.add("required", required1);
function1.add("parameters", parameters1);
tool1.add("function", function1);

这只是一个工具、一个参数。如果一个工具有 5 个参数,代码量翻 5 倍。20 个工具就是几百行纯粹的 JSON Schema 构建代码。

更要命的是维护问题:

  • 工具的参数改了(比如 getUserAnnualLeave 新增了一个 year 参数),你要同步修改 JSON Schema,忘了改就会出 bug
  • 没有工具文档,新同事不知道系统里有哪些工具可用,只能翻代码
  • 工具定义散落在代码各处,没有统一的注册中心

2.2 跨语言跨系统的集成困境

你的企业知识库助手需要调用的工具来自不同团队:

  • Java 团队写的 HR 工具(查年假、查考勤)
  • Python 团队写的数据分析工具(销售报表、用户画像)
  • 第三方 HTTP API(物流查询、天气查询)

三种不同的调用方式,你的代码里要写三套集成逻辑:

if ("getUserAnnualLeave".equals(functionName)) {
// 直接调用本地 Java 方法
return hrService.getAnnualLeave(userId);
} else if ("analyzeSalesData".equals(functionName)) {
// 调用 Python 服务的 HTTP API
return httpClient.post("http://python-service:8000/analyze", params);
} else if ("getWeather".equals(functionName)) {
// 调用第三方 API
return httpClient.get("https://api.weather.com/v1/current?city=" + city);
}

每接入一个新系统,就要写一套适配代码。而且每个系统的认证方式不同(有的用 Token,有的用 API Key,有的用 OAuth),错误处理方式也不同。

2.3 权限和安全的黑洞

Function Call 协议本身没有任何权限机制。所有的权限校验都要你自己写:

  • 用户 A 能不能查用户 B 的年假?
  • 实习生能不能调用删除订单这个工具?
  • 某个工具只允许管理员使用,怎么控制?

工具越多,权限逻辑越复杂。而且权限代码和业务代码混在一起,容易出漏洞。

2.4 可观测性的缺失

20 个工具在线上跑,你需要知道:

  • 每个工具被调用了多少次?
  • 平均耗时多少?哪个工具最慢?
  • 调用失败率是多少?失败的原因是什么?
  • 某次调用的完整链路:用户问了什么 → 模型选了哪个工具 → 传了什么参数 → 返回了什么结果 → 最终答案是什么?

Function Call 协议不管这些,全靠你自己埋点、写日志、搭监控。工具调用链路一长(调用工具 A → 工具 A 内部调用工具 B → 工具 B 查数据库),排查问题就像大海捞针。

3. 我们需要的是一个标准化的工具管理协议

总结一下,Function Call 解决了让模型调工具的问题,但没有解决怎么管理工具的问题:

维度Function Call 解决了吗
模型判断是否调用工具解决了
标准化的调用意图输出解决了
工具定义的自动化管理没有,手写 JSON Schema
工具的动态发现和注册没有,硬编码在代码里
跨语言跨系统的统一调用没有,每个系统写一套适配
权限和安全控制没有,自己实现
调用链路的可观测性没有,自己埋点

我们需要的是在 Function Call 之上,加一层标准化的工具管理框架——统一工具的定义、注册、发现、调用、权限控制。

这就是 MCP 要做的事。

MCP 是什么

1. MCP 的核心思想:大模型世界的 USB 接口

回想一下 USB 出现之前的世界:打印机用并口,鼠标用 PS/2 接口,手机充电有 Micro USB、Lightning、Type-C 好几种。每换一个设备,就要换一根线。USB 出来之后,统一了接口标准——不管你是键盘、鼠标、U 盘还是手机,只要有 USB 接口,插上就能用。

MCP 做的是同样的事,只不过它统一的不是硬件接口,而是大模型调用工具的接口。

MCP,全称 Model Context Protocol(模型上下文协议),由 Anthropic(Claude 的母公司)于 2024 年 11 月开源发布。它不是一个具体的产品或框架,而是一个开放的协议规范。核心思想用一句话概括:

任何工具只要实现了 MCP 协议,就能被任何支持 MCP 的客户端调用,不用关心对方是什么语言、什么平台。

打个比方:

  • 没有 MCP 之前:你的 Java 工具只能被你的 Java 代码调用,Python 团队的工具只能被 Python 代码调用,每个系统都是一座孤岛
  • 有了 MCP 之后:Java 工具、Python 工具、Node.js 工具都实现 MCP 协议,Claude Desktop、Cursor、你自己的企业助手都支持 MCP 协议,任意组合,即插即用

这就像 USB 接口一样——工具是“设备”,客户端是“电脑”,MCP 是“USB 标准”。

2. MCP 的三层架构:Host、Client、Server

MCP 的架构设计分三层:Host(宿主应用)、Client(MCP 客户端)、Server(MCP 服务端)。这三层的关系用一张图说明:

这张图最近在网上被广泛引用。不过坦白说,我刚开始学习时,看着它并没有完全理解,尤其是 MCP Server 和 Hosts 之间的关系,总觉得差点意思。

于是我重新画了一张图,结合我们自己的文章脉络重新拆解了一遍,希望能让结构更清晰,也方便大家建立完整认知。

2.1 Host(宿主应用)

Host 是用户直接交互的应用,比如:

  • Claude Desktop(Anthropic 的桌面客户端)
  • Cursor(AI 编程 IDE)
  • 你自己开发的企业知识库助手(比如咱们的 Ragent)

Host 的职责是:接收用户输入 → 调用大模型 → 根据模型的指令通过 MCP Client 调用工具 → 把结果返回给模型 → 展示最终答案给用户。

Host 内部包含一个或多个 MCP Client,每个 Client 负责和一个 MCP Server 通信。

2.2 Client(MCP 客户端)

Client 是 Host 内部的通信组件,负责和 MCP Server 建立连接、发送请求、接收响应。

关键点:一个 Client 只连接一个 Server,但一个 Host 可以有多个 Client,连接多个 Server。就像你的电脑有多个 USB 接口,每个接口插一个设备。

Client 的职责包括:

  • 和 Server 建立连接(通过 Stdio 或 HTTP)
  • 发现 Server 提供的工具列表
  • 调用 Server 的工具并获取结果
  • 管理连接的生命周期

2.3 Server(MCP 服务端)

Server 是提供工具的一方。每个 Server 暴露一组工具,Client 通过 MCP 协议调用这些工具。

Server 可以是:

  • 本地进程(通过 Stdio 通信,比如一个本地的文件操作工具)
  • 远程 HTTP 服务(通过 Streamable HTTP 通信,比如部署在服务器上的 HR 系统工具)

Server 的职责包括:

  • 声明自己提供哪些工具(工具名、描述、参数定义)
  • 接收 Client 的调用请求
  • 执行工具逻辑并返回结果

2.4 一个完整的调用流程

用户在 RAG Desktop 里问:查下我的年假,顺便看周五有没有会。完整的调用流程是这样的:

注意初始化阶段:Client 启动时会自动从 Server 获取工具列表(包括工具名、描述、参数定义)。这意味着你不需要在 Host 端手写 JSON Schema——Server 端定义好工具,Client 自动发现。这就解决了 Function Call 的第一个痛点:工具定义的维护。

3. MCP 的三大核心能力

MCP 协议定义了三大核心能力:Tools(工具调用)、Resources(资源访问)、Prompts(提示词模板)。

3.1 Tools(工具调用)

这是 MCP 最核心的能力,也是和 Function Call 直接对应的部分。

Server 定义工具,Client 发现并调用工具。和 Function Call 的区别在于:

  • Function Call:工具定义在客户端代码里(手写 JSON Schema),和模型一起发送

  • MCP:工具定义在 Server 端,Client 通过协议动态获取

注意:MCP 协议本身只规定工具定义在 Server 端,Client 通过协议动态获取工具列表和元数据。至于 Server 端用什么方式定义工具(注解、手写 JSON、配置文件),那是实现框架的选择。下面展示的 @Tool 注解是 Spring AI 框架提供的便利性封装。

举个例子,Function Call 里你要这样定义工具:

{
"type": "function",
"function": {
"name": "getUserAnnualLeave",
"description": "查询用户的年假余额",
"parameters": {
"type": "object",
"properties": {
"userId": { "type": "string", "description": "用户 ID" }
},
"required": ["userId"]
}
}
}

在 Spring AI 框架中,你可以用 @Tool 注解定义工具(框架会自动生成符合 MCP 协议的工具元数据):

@Tool(description = "查询用户的年假余额,包括总天数、已使用天数、剩余天数")
public String getUserAnnualLeave(@ToolParam(description = "用户 ID") String userId) {
// 查询 HR 系统
return "{\"remainingDays\": 5, \"totalDays\": 10, \"usedDays\": 5}";
}

Spring AI 框架会扫描 @Tool 注解,自动提取方法名作为工具名、description 作为工具描述、方法参数和 @ToolParam 注解作为参数定义,生成符合 MCP 协议的工具元数据(JSON Schema 格式)。Client 启动时通过 MCP 协议从 Server 获取这些元数据,不用在 Client 端手写 JSON Schema,也不用担心定义和实现不同步。

3.2 Resources(资源访问)

Server 可以暴露资源供 Client 读取,比如:

  • 文件内容(配置文件、日志文件)
  • 数据库记录
  • API 返回的数据

Resources 和 Tools 的区别是:Tools 是执行操作(查年假、下订单),Resources 是提供数据(读取一个文件的内容)。Resources 更像是给模型提供额外的上下文信息。

3.3 Prompts(提示词模板)

Server 可以提供预定义的提示词模板,Client 可以使用这些模板来构建和模型的交互。适合标准化的交互场景,比如“代码审查模板"”、“文档总结模板”。

本篇重点讲 Tools,Resources 和 Prompts 在实际项目中用得相对少一些,了解即可。

4. MCP vs Function Call:不是替代,是增强

很多人第一次听到 MCP 会问:MCP 是不是要替代 Function Call?

答案是:不是。MCP 底层仍然依赖模型的 Function Call 能力。模型判断是否调用工具、输出 tool_calls JSON——这个能力是 Function Call 提供的,MCP 不会重新发明这个轮子。

MCP 做的是在 Function Call 之上,提供一层标准化的管理框架。用一个类比:Function Call 是"发动机",MCP 是"整车"。发动机提供动力,但你还需要方向盘、刹车、仪表盘才能上路。

下面用一张对比图说明两者的关系:

用表格对比更直观:

对比维度Function CallMCP
本质模型的原生能力(输出调用意图)标准化的工具管理协议
工具定义手写 JSON Schema,和代码分离代码注解自动生成,定义和实现一体
工具发现没有,硬编码在请求里Client 启动时自动从 Server 获取
跨语言支持不支持,每个语言自己实现协议统一,Java / Python / TS 都能互通
传输方式依赖具体的 HTTP API标准化传输(Stdio / Streamable HTTP)
权限控制没有,自己实现协议层面支持(还在完善中)
可观测性没有,自己埋点协议层面支持日志和追踪
生态各家模型厂商各自实现开放协议,社区共建 MCP Server

5. 澄清:MCP 协议 vs 实现框架

看到这里,你可能会有个疑问:前面展示的 @Tool 注解、自动注册工具,这些不都是 Spring AI 框架提供的能力吗?MCP 协议本身到底解决了什么问题?

这个问题问得很好。我们需要区分两个层面:

MCP 协议层面(标准制定者):

  • 定义了工具元数据的标准格式(name、description、parameters 的 JSON Schema)
  • 定义了 Client 和 Server 的通信协议(JSON-RPC over Stdio/HTTP)
  • 定义了工具发现机制(Client 启动时从 Server 获取工具列表)
  • 解决的核心问题:工具定义从 Client 端转移到 Server 端,实现跨语言跨系统互通

Spring AI 框架层面(协议实现者):

  • 提供 @Tool 注解,让你用注解方式定义工具(而不是手写 JSON)
  • 提供自动扫描注解并生成符合 MCP 协议的工具元数据
  • 处理 MCP 协议的通信细节(JSON-RPC 请求解析、响应构建等)
  • 解决的核心问题:让 Java 开发者更方便地实现 MCP 协议

打个比方:MCP 协议就像 HTTP 协议,Spring AI 就像 Spring MVC 框架。HTTP 定义了请求响应格式,但你可以用任何语言、任何框架实现 HTTP 服务器。Spring MVC 提供了 @RestController 注解让你更方便地写 HTTP 接口,但这不是 HTTP 协议本身的能力。

即使不用 Spring AI,你也可以用纯 Java 手写 JSON 来实现 MCP Server,只是更麻烦。MCP 协议的价值在于:一旦你的 Server 实现了 MCP 协议,任何支持 MCP 的 Client(Claude Desktop、Cursor、Python 客户端等)都能调用你的工具,不需要为每个 Client 写适配代码

这就是为什么说 MCP 解决了 Function Call 的工具定义和维护问题——不是因为注解写起来更方便,而是因为工具定义的位置变了(从 Client 端移到 Server 端),发现机制变了(从硬编码变成动态获取),互通性变了(从各自实现变成统一协议)。

MCP 的传输机制

MCP Client 和 Server 之间怎么通信?MCP 协议定义了两种传输方式:Stdio(标准输入输出)和 Streamable HTTP(可流式 HTTP)。

1. Stdio(标准输入输出)

Stdio 是最简单的传输方式。MCP Server 作为本地子进程运行,Client 通过操作系统的标准输入(stdin)和标准输出(stdout)和 Server 通信。

打个比方:你在终端里运行 grep "error" log.txt,你敲的命令是输入(stdin),grep 打印出来的结果是输出(stdout)。MCP 的 Stdio 传输就是这个原理:Client 启动 Server 进程,往 Server 的 stdin 写请求(JSON 格式),Server 从 stdout 返回结果(也是 JSON 格式)。整个过程就像你在和一个命令行程序对话,只不过对话内容是结构化的 JSON 数据。

通信过程:

  1. Client 启动 Server 进程(比如 java -jar mcp-server.jar
  2. Client 往 Server 的 stdin 写入 JSON-RPC 请求
  3. Server 从 stdin 读取请求,执行工具,把结果以 JSON-RPC 格式写入 stdout
  4. Client 从 Server 的 stdout 读取响应

JSON-RPC 是一种轻量级的远程过程调用协议,用 JSON 格式传输请求和响应。你可以把它理解为用 JSON 格式约定好请求和响应的结构,和 RESTful API 类似,但更简单。

Stdio 的优点:

  • 简单,不需要网络配置,不暴露端口
  • 安全,通信只在本地进程之间,不经过网络
  • 适合本地工具(文件操作、本地数据库查询、命令行工具)

Stdio 的缺点:

  • 只能本地调用,不支持远程访问
  • Server 和 Client 必须在同一台机器上

2. Streamable HTTP(可流式 HTTP)

Streamable HTTP 是 MCP 协议在 2025 年 3 月引入的传输方式(替代了早期的 HTTP+SSE 方案)。MCP Server 作为 HTTP 服务运行,Client 通过 HTTP 请求调用。

通信过程:

  1. Server 启动 HTTP 服务,监听某个端口(比如 http://localhost:8080
  2. Client 向 Server 发送 HTTP POST 请求(JSON-RPC 格式)
  3. Server 处理请求,返回 HTTP 响应
  4. 如果需要流式返回(比如长时间运行的工具),Server 通过 SSE(Server-Sent Events)推送增量结果

Streamable HTTP 的优点:

  • 支持远程访问,Server 可以部署在任何地方
  • 支持团队共享,多个 Client 可以连接同一个 Server
  • 支持流式响应(SSE),适合长时间运行的工具
  • 可以利用 HTTP 生态的认证、负载均衡、监控等基础设施

Streamable HTTP 的缺点:

  • 需要网络配置(端口、防火墙、HTTPS)
  • 相比 Stdio 多了网络开销

3. 怎么选

用表格总结:

对比维度StdioStreamable HTTP
部署方式本地子进程HTTP 服务(本地或远程)
通信方式stdin / stdoutHTTP POST + SSE
网络要求不需要需要网络(至少 localhost)
多客户端不支持(一对一)支持(多对一)
适用场景本地开发、个人工具团队共享、生产环境
安全性天然安全(本地进程)需要配置认证和 HTTPS
典型用法Claude Desktop 本地插件企业内部工具平台

一句话总结:本地开发和个人工具用 Stdio,团队共享和生产环境用 Streamable HTTP。企业级项目基本上都是后者。

Java 实战:搭建一个 MCP Server

1. 场景设定

延续前几篇的企业知识库助手场景,我们用 Spring AI MCP Server 框架搭建一个 MCP Server,提供两个工具:

  • getUserAnnualLeave:查询用户年假余额
  • getOrderStatus:查询订单状态

搭建完成后,这个 MCP Server 可以被 Claude Desktop、Cursor 等任何支持 MCP 的客户端直接调用。

2. 技术选型

Spring AI 从 1.0 版本开始提供了 MCP Server 的 Boot Starter,开箱即用。选 Spring AI 的理由很简单:

  • Java 生态,和现有的 Spring Boot 项目无缝集成
  • @Tool 注解定义工具,不用手写 JSON Schema
  • 同时支持 Stdio 和 Streamable HTTP 两种传输方式
  • 社区活跃,文档完善

Spring AI MCP Server 提供了三个 Starter,根据传输方式选择:

Starter传输方式适用场景
spring-ai-starter-mcp-serverStdio本地工具,被 Claude Desktop / Cursor 调用
spring-ai-starter-mcp-server-webmvcStreamable HTTP(基于 Spring MVC)远程工具,团队共享
spring-ai-starter-mcp-server-webfluxStreamable HTTP(基于 WebFlux)远程工具,响应式编程

本篇先用 Stdio 方式演示(最简单,能直接被 Claude Desktop 调用),后面再介绍 HTTP 方式。

项目已经提交到 GitHub mcp-server-demo,代码在 v1.0 分支,可直接下载。

注意:SpringAI 1.0 版本注解是 @Tool,2.0 版本修改为 @McpTool,后续文章以 @McpTool 为准。

3. Maven 依赖

创建一个 Spring Boot 项目,pom.xml 如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.3</version>
<relativePath/>
</parent>

<groupId>com.nageoffer.ai</groupId>
<artifactId>mcp-server-demo</artifactId>
<version>1.0.0</version>
<name>MCP Server Demo</name>
<description>企业知识库助手 MCP Server</description>

<properties>
<java.version>17</java.version>
<spring-ai.version>1.0.0</spring-ai.version>
</properties>

<dependencies>
<!-- Spring AI MCP Server Starter(Stdio 传输) -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server</artifactId>
<version>${spring-ai.version}</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

注意:spring-ai-starter-mcp-server 是 Stdio 传输方式的 Starter。如果你需要 HTTP 传输,换成 spring-ai-starter-mcp-server-webmvc

4. 定义工具

这是 MCP 相比 Function Call 最爽的地方——用 @Tool 注解定义工具,参数用 @ToolParam 注解描述,框架自动生成工具的 JSON Schema。

创建一个工具类 EnterpriseTools.java

package com.nageoffer.ai.mcp.tools;

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Service;

/**
* 企业知识库助手工具集
* 每个 @Tool 方法会自动注册为一个 MCP 工具
*/
@Service
public class EnterpriseTools {

/**
* 查询用户年假余额
*/
@Tool(description = "查询用户的年假余额,包括总天数、已使用天数、剩余天数。当用户询问年假、假期余额、还有多少天假等问题时使用此工具。")
public String getUserAnnualLeave(
@ToolParam(description = "用户 ID,例如 user_12345") String userId) {

// 实际项目中这里调用 HR 系统 API
// 这里用 mock 数据演示
return String.format("""
{
"userId": "%s",
"remainingDays": 5,
"totalDays": 10,
"usedDays": 5,
"year": 2026
}
""", userId);
}

/**
* 查询订单状态
*/
@Tool(description = "查询订单的物流状态和详细信息。当用户询问订单状态、物流进度、快递到哪了等问题时使用此工具。")
public String getOrderStatus(
@ToolParam(description = "订单号,例如 ORD-12345") String orderId) {

// 实际项目中这里调用订单系统 API
return String.format("""
{
"orderId": "%s",
"status": "运输中",
"location": "北京市朝阳区分拨中心",
"estimatedDelivery": "2026-03-01",
"carrier": "顺丰速运",
"trackingNumber": "SF1234567890"
}
""", orderId);
}
}

对比一下 Function Call 里定义同样两个工具需要多少代码——上一篇的 callModelWithTools 方法里,光构建 JSON Schema 就写了 40 多行。MCP 里只需要两个方法加注解,清爽多了。

5. 注册工具到 MCP Server

创建配置类,把工具注册到 MCP Server:

package com.nageoffer.ai.mcp.config;

import com.nageoffer.ai.mcp.tools.EnterpriseTools;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class McpServerConfig {

/**
* 注册工具提供者
* MethodToolCallbackProvider 会扫描 EnterpriseTools 中所有 @Tool 注解的方法,
* 自动注册为 MCP 工具
*/
@Bean
public ToolCallbackProvider enterpriseToolProvider(EnterpriseTools enterpriseTools) {
return MethodToolCallbackProvider.builder()
.toolObjects(enterpriseTools)
.build();
}
}

MethodToolCallbackProvider 会自动扫描 EnterpriseTools 类中所有带 @Tool 注解的方法,提取方法名作为工具名、description 作为工具描述、方法参数和 @ToolParam 注解作为参数定义,自动生成完整的工具元数据。

6. 配置 MCP Server

application.yml 配置:

spring:
ai:
mcp:
server:
name: enterprise-assistant
version: 1.0.0
type: SYNC
main:
web-application-type: none
banner-mode: off

logging:
file:
name: ./logs/mcp-server.log
level:
root: OFF

几个关键配置说明:

  • spring.ai.mcp.server.name:Server 名称,Client 连接时会看到这个名字
  • spring.ai.mcp.server.version:Server 版本号
  • spring.ai.mcp.server.type:工具执行模式,SYNC(同步)或 ASYNC(异步)
  • spring.main.web-application-type: none:Stdio 模式不需要 Web 容器
  • spring.main.banner-mode: off:关闭 Spring Boot 启动 banner,避免 banner 输出到 stdout 干扰 MCP 通信

Stdio 模式下,stdout 是 MCP 协议的通信通道,任何非协议内容(banner、日志)输出到 stdout 都会导致通信失败。这是一个常见的坑。

7. 启动类

package com.nageoffer.ai.mcp;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class McpServerApplication {

public static void main(String[] args) {
SpringApplication.run(McpServerApplication.class, args);
}
}

项目结构:

mcp-server-demo/
├── pom.xml
└── src/main/
├── java/com/nageoffer/ai/mcp/
│ ├── McpServerApplication.java
│ ├── config/
│ │ └── McpServerConfig.java
│ └── tools/
│ └── EnterpriseTools.java
└── resources/
└── application.yml

先用 Maven 打包:

mvn clean package -DskipTests

打包完成后,在 target 目录下会生成 mcp-server-demo-1.0.0.jar

8. 测试:用 MCP 客户端调用

8.1 在 Cursor 中配置

打开 Cursor 的 MCP 配置(Settings → Tools & MCP),如下图所示:

添加:

{
"mcpServers": {
"enterprise-assistant": {
"command": "java",
"args": [
"-jar",
"/path/to/mcp-server-demo-1.0.0.jar"
]
}
}
}

/path/to/ 替换成你的 jar 包实际路径。

咱们 MCP Demo 项目用的 JDK17,如果你的电脑默认不是 JDK17,需要将 command 设置为 Java 的绝对路径。我的默认 JDK 就不是 17,所以需要调整下,比如:

{
"mcpServers": {
"enterprise-assistant": {
"command": "/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/bin/java",
"args": [
"-jar",
"/path/to/mcp-server-demo-1.0.0.jar"
],
"env": {
"JAVA_HOME": "/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home"
}
}
}
}

在对话框里进行问答,Cursor 会自动调用你的 MCP Server 查询年假余额和订单状态。

因为是个 Demo 的 MCP 示例,所以问题有点呆,需要用户提示词自己把账号和订单号带进去。常规上肯定是程序里自动带入,大家忽略即可。

8.2 使用 Streamable HTTP 方式

如果你想让 MCP Server 以 HTTP 服务的方式运行(支持远程访问、多客户端共用),只需要做两个改动:

第一步,把 Maven 依赖换成 WebMVC 版本:

<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
<version>${spring-ai.version}</version>
</dependency>

第二步,修改 application.yml

spring:
ai:
mcp:
server:
name: enterprise-assistant
version: 1.0.0
type: SYNC

server:
port: 8080

启动后,MCP Server 会在 http://localhost:8080 上监听。Client 配置改为:

{
"mcpServers": {
"enterprise-assistant": {
"url": "http://localhost:8080/mcp"
}
}
}

工具代码和配置类完全不用改,只是传输方式从 Stdio 变成了 HTTP。

9. 对比:Function Call vs MCP + Spring AI 的代码量

同样实现查年假和查订单两个工具,对比一下两种方式的代码量:

对比项Function Call(上一篇)MCP + Spring AI(本篇)
工具定义40+ 行 JSON Schema 构建代码2 个 @Tool 注解方法
工具路由手写 if-else 或策略模式框架自动路由
协议处理手写 HTTP 请求、解析 tool_calls、构建第二轮消息框架自动处理
新增工具加 JSON Schema + 加路由 + 改代码 + 重新部署加一个 @Tool 方法 + 重新部署
客户端适配只能被你自己的代码调用任何 MCP 客户端都能调用
总代码量~200 行(含 JSON Schema 构建)~60 行(含工具实现)

代码量减少了 70%,而且新增工具只需要加一个方法,不用改任何框架代码。

说明:这里对比的是纯 Function Call 实现 vs MCP 协议 + Spring AI 框架实现。代码量的减少主要来自 Spring AI 框架的便利性封装(注解、自动路由等)。MCP 协议本身带来的核心价值是最后一行客户端适配——一旦你的工具实现了 MCP 协议,任何支持 MCP 的客户端都能调用,不需要为每个客户端写适配代码。

MCP 在 RAG 系统中的应用

讲完了 MCP 的原理和实战,回到我们的主线——RAG 系统。MCP 在 RAG 系统中能发挥什么作用?

1. 知识检索作为 MCP 工具

上一篇讲 Function Call 在 RAG 中的应用时,我们定义了一个 searchKnowledgeBase 工具,让模型自动判断是查知识库还是调业务工具。用 MCP 实现同样的能力,只需要把知识检索封装成一个 MCP 工具:

@Tool(description = "在企业知识库中搜索相关文档。当用户询问公司制度、产品文档、操作指南等静态知识时使用此工具。")
public String searchKnowledgeBase(
@ToolParam(description = "搜索关键词,用自然语言描述") String query,
@ToolParam(description = "返回结果数量,默认 5") int topK) {

// 调用向量数据库检索
// 实际项目中这里执行 Embedding → Milvus 检索 → Reranking
List<Document> results = ragService.search(query, topK);
return formatResults(results);
}

这样做的好处是:你的知识检索能力不再局限于自己的系统,任何支持 MCP 的客户端都能调用你的知识库。比如团队成员在 Cursor 里写代码时,可以直接问公司的代码规范是什么,Cursor 通过 MCP 调用你的知识库检索工具,返回相关文档。

2. 多工具编排:知识检索 + 业务工具

MCP 的真正威力在于多个 Server 协同工作。假设用户问我的订单 #12345 能退货吗,这个问题需要两部分信息:

  1. 订单的当前状态(需要调用订单系统)
  2. 退货政策(需要检索知识库)

在 MCP 架构下,这两个能力可以由不同的 MCP Server 提供:

每个 MCP Server 专注做一件事(订单查询、知识检索、HR 查询……),Host 根据模型的判断,并行调用多个 Server,综合结果生成答案。这种架构的好处是:

  • 职责清晰:每个 Server 独立开发、独立部署、独立维护
  • 灵活组合:新增一个能力只需要部署一个新的 MCP Server,不用改现有代码
  • 团队协作:不同团队各自维护自己的 MCP Server,通过协议互通

3. 企业级场景:Ragent 中的 MCP 实践

在我们的 Ragent 项目(企业级 RAG 智能体平台)中,MCP 被用来管理所有的外部工具调用。简要介绍一下架构思路:

  • MCPToolRegistry:工具注册表,管理所有已注册的 MCP Server 和工具
  • MCPToolExecutor:工具执行器,负责根据模型的 tool_calls 路由到对应的 MCP Server 并执行
  • 自动发现机制:系统启动时自动连接配置的 MCP Server,获取工具列表,注册到工具注册表

这种架构让 Ragent 可以灵活接入各种外部工具,而不需要为每个工具写适配代码。具体的代码实现会在后续的 Ragent 项目实战文章中详细展开。

MCP 的生态现状

1. 支持 MCP 的客户端

MCP 协议发布一年多以来,已经有不少客户端支持:

客户端类型MCP 支持情况
Claude DesktopAI 对话客户端完整支持,Anthropic 官方出品
CursorAI 编程 IDE完整支持,MCP 工具可在编程中使用
WindsurfAI 编程 IDE支持 MCP
ContinueVS Code AI 插件支持 MCP
ClineVS Code AI 插件支持 MCP
Claude CodeCLI 工具完整支持
Spring AIJava AI 框架提供 MCP Client 和 Server Starter
KiroAI IDE支持 MCP

这意味着你开发一个 MCP Server,上面这些客户端都能直接调用,不需要为每个客户端写适配代码。

2. 社区 MCP Server

GitHub 上已经有大量社区开发的 MCP Server,覆盖常见场景:

  • 文件系统:读写本地文件、目录操作
  • 数据库:MySQL、PostgreSQL、SQLite 查询
  • Web 搜索:Brave Search、Google Search
  • 代码仓库:GitHub 操作(创建 Issue、提交 PR、查看代码)
  • 知识管理:Notion、Obsidian 集成
  • 通信工具:Slack、Discord 消息发送
  • 云服务:AWS、GCP 资源管理

你可以在 MCP Servers 目录 找到这些社区 Server,直接配置使用,不需要自己开发。

3. MCP 的局限性和未来

MCP 的方向是对的,但目前还有一些局限:

  • 协议还在快速演进:从 2024 年 11 月发布到现在,传输层已经从 HTTP+SSE 改为 Streamable HTTP,API 也在不断调整。早期开发的 MCP Server 可能需要适配新版本
  • 部分客户端支持不完整:有些客户端只支持 Stdio,不支持 HTTP;有些只支持 Tools,不支持 Resources 和 Prompts
  • 生产环境的稳定性:MCP 在本地开发场景下表现很好,但在高并发的生产环境中,稳定性和性能还需要更多验证
  • 权限模型还在完善:MCP 协议层面的权限控制还比较基础,企业级的细粒度权限(比如基于角色的工具访问控制)仍然需要自己实现
  • 认证标准化:远程 MCP Server 的认证方式(OAuth、API Key 等)还没有统一标准

不过,标准化是大趋势。就像 HTTP 协议从 0.9 到 1.0 到 1.1 到 2.0 到 3.0,MCP 也会不断完善。现在学习和使用 MCP,是一个好的时机。

小结

这篇从 Function Call 的四大痛点出发,引出了 MCP 协议——大模型世界的 USB 接口。

核心要点回顾:

  1. 为什么需要 MCP:Function Call 解决了让模型调工具的问题,但工具规模化后,定义维护、跨语言集成、权限控制、可观测性都成了问题。MCP 在 Function Call 之上提供了标准化的工具管理框架
  2. MCP 的三层架构:Host(宿主应用)→ Client(通信组件)→ Server(工具提供方),一个 Host 可以连接多个 Server
  3. 三大核心能力:Tools(工具调用,最核心)、Resources(资源访问)、Prompts(提示词模板)
  4. 两种传输方式:Stdio(本地进程通信,简单安全)和 Streamable HTTP(远程 HTTP 通信,支持团队共享)
  5. Java 实战:用 Spring AI MCP Server 框架,@Tool 注解定义工具,60 行代码搞定,比 Function Call 的 200 行减少 70%
  6. MCP 在 RAG 中的应用:知识检索封装为 MCP 工具、多 Server 协同工作、企业级工具管理

到这里,RAG 系列的核心环节已经讲完了:数据分块 → 元数据管理 → 向量化 → 向量数据库 → 检索策略 → 生成策略 → Function Call → MCP 协议。从数据准备到检索生成,再到工具调用和协议标准化,一条完整的链路。