承上 :上一篇我们给AI装了四个工具,它已经能自动调度查订单、查库存、查物流了。但有个问题让我夜不能寐------这四个工具全是直接用
@Tool注解写的,跟Spring AI深度绑定。万一哪天老板说"咱们改用LangChain了",所有工具代码都得重写。有没有一种标准,让工具写一次、到处能用?
1. 问题场景:Function Calling的"锁定效应"
先回顾我们现在的工具写法:
less
@Component
public class BusinessTools {
@Tool(description = "查询商品库存")
public String checkStock(
@ToolParam(description = "商品名称") String productName) {
return inventoryService.checkStock(productName);
}
}
这个写法很爽,但有几个问题:
| 问题 | 后果 |
|---|---|
绑定了Spring AI的 @Tool注解 |
切换到其他AI框架时,工具全部重写 |
| 工具定义和实现耦合 | 改描述要改代码、重新部署 |
| 无法被外部系统复用 | 其他AI应用想用你的工具,没门 |
| 测试困难 | 必须在Spring容器里跑 |
后端老鸟的直觉反应:这不就是没有接口标准的年代,每个厂商有自己的驱动吗?JDBC出来之前,连MySQL和Oracle的调用方式都不一样。
我们需要一个"工具的JDBC"------MCP(Model Context Protocol,模型上下文协议)。
2. 核心概念:MCP到底是什么?
MCP(Model Context Protocol,模型上下文协议) 本质上是一个工具调用的标准协议。它定义了AI应用(Client)和工具服务(Server)之间的通信规范。
2.1. 用USB类比
插过U盘吧?不管是金士顿的、闪迪的,还是杂牌的,只要符合USB协议,插上就能用。
MCP就是AI工具的"USB协议":
css
Function Calling时代(没有标准):
AI应用 ←→ 工具A(自己定义的协议)
AI应用 ←→ 工具B(另一个协议)
AI应用 ←→ 工具C(又一个协议)
换个AI应用 → 全部重写
MCP时代(统一标准):
AI应用 ←→ MCP协议 ←→ 工具A
←→ 工具B
←→ 工具C
换个AI应用 → 工具不用改,支持MCP就行
2.2. MCP的三大角色
arduino
┌─────────────┐ MCP协议 ┌─────────────┐
│ MCP Client │ ◄──────────────► │ MCP Server │
│ (Spring AI)│ │ (我们的工具) │
└─────────────┘ └─────────────┘
↑ ↑
消费工具的一方 提供工具的一方
| 角色 | 职责 | 类比 |
|---|---|---|
| MCP Server | 暴露工具,描述工具的Schema | USB设备 |
| MCP Client | 发现并调用工具 | USB主机(电脑) |
| MCP协议 | 定义通信标准 | USB协议 |
2.3. MCP vs @Tool
| 对比维度 | @Tool注解 | MCP |
|---|---|---|
| 复用性 | 只能在Spring AI里用 | 任何支持MCP的框架都能用 |
| 部署方式 | 和主应用打包在一起 | 可以独立部署,远程调用 |
| 生命周期 | 随Spring容器启动/停止 | 独立进程,随时启停 |
| 跨语言 | 必须用Java | 可以用任何语言实现Server |
| 服务发现 | 只能在同一ApplicationContext里 | 可以注册到服务注册中心 |
| 适用场景 | 快速开发、内部工具 | 企业级、多应用共享的工具平台 |
一句话总结 :
@Tool是快速开发用的,MCP是企业级工具管理用的。两者可以共存,不是互斥关系。
3. 实战:把天气查询工具从@Tool改成MCP Server
Spring AI支持三种MCP Server
| 方式 | 依赖 | 通信机制 | 适用场景 |
|---|---|---|---|
| STDIO | spring-ai-starter-mcp-server |
进程间stdin/stdout | 本地桌面客户端、IDE插件 |
| WebMVC SSE | spring-ai-starter-mcp-server-webmvc |
HTTP + SSE长链接 | 传统Spring Boot、同步业务 |
| WebFlux SSE | spring-ai-starter-mcp-server-webflux |
响应式非阻塞SSE | 高并发微服务、云原生 |
下面逐一实现,每种都包含Server端和Client端。
3.1. 方式一:STDIO 传输(本地进程通信)
3.1.1. 什么是STDIO?
不启动HTTP服务器,通过进程的标准输入输出流通信:
arduino
MCP Client 启动 Server 进程
→ Client 往 Server 的 stdin 写入 JSON 请求
→ Server 处理后往 stdout 输出 JSON 响应
→ Client 读取 stdout 获取结果
没有网络端口,没有HTTP,纯进程间通信。
3.1.2. Server端实现
依赖:
xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server</artifactId>
</dependency>
配置 application.yml:
yaml
spring:
main:
banner-mode: off
web-application-type: none
ai:
mcp:
server:
stdio: true
name: my-weather-server-stdio
version: 1.0.0
type: SYNC
logging:
level:
root: INFO
org.springframework.ai: DEBUG
org.springframework: INFO
工具定义(和之前完全一样):
java
package com.yunxi.ai.tools;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class WeatherTools {
@Tool(description = "查询指定城市在指定日期的天气情况")
public String getWeather(@ToolParam(description = "城市名称,例如:北京、上海、深圳") String city) {
// 模拟数据
Map<String, String> weatherMap = Map.of(
"北京", "晴,25°C,微风",
"上海", "多云转阴,28°C,东南风3级",
"深圳", "雷阵雨,30°C,湿度85%",
"成都", "阴天,22°C,空气质量优"
);
String weather = weatherMap.getOrDefault(city, "数据暂未覆盖该城市");
return city + " 天气:" + weather;
}
}
工具注入:
java
@Bean
public ToolCallbackProvider toolCallbackProvider(WeatherTools weatherTools) {
return MethodToolCallbackProvider.builder()
.toolObjects(weatherTools)
.build();
}
打包运行:
java
mvn clean package -DskipTests
java -jar target/chapter-07-mcp-server-stdio.jar
启动后不会监听任何端口,只等待stdin输入。
3.1.3. Client端实现
依赖:
xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
配置 application.yml:
yaml
spring:
ai:
mcp:
client:
stdio:
servers-configuration: classpath:mcp-servers-config.json
mcp-servers-config.json:
json
{
"mcpServers": {
"my-weather-server-stdio": {
"command": "java",
"args": [
"-Dspring.ai.mcp.server.stdio=true",
"-Dspring.main.web-application-type=none",
"-Dspring.main.banner-mode=off",
"-Dlogging.pattern.console=",
"-jar",
"/Users/yunxi/IdeaProjects/yunxi-spring-ai/chapter-07-mcp-server-stdio/target/chapter-07-mcp-server-stdio-0.0.1-SNAPSHOT.jar"
]
}
}
}
Client代码:
java
package com.yunxi.ai.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class ChatService {
private final ChatClient chatClient;
public ChatService(ChatClient.Builder builder, ToolCallbackProvider tools) {
this.chatClient = builder
.defaultToolCallbacks(tools)
.build();
}
public String chat(String message) {
return chatClient.prompt()
.system("你是一个专业的助手,可以回答用户的问题")
.user(message)
.call()
.content();
}
}
3.1.4. 测试结果
本地服务测试

Vibe Code工具测试(以Trae为例)
**
**
3.2. 方式二:WebMVC SSE 传输(Servlet同步HTTP-SSE)
3.2.1. 什么是WebMVC SSE?
基于传统Spring MVC的Servlet容器,通过HTTP + SSE长链接通信:
arduino
Client发起HTTP连接 → Server保持SSE长链接不关闭
→ Client通过这个长链接持续发送请求
→ Server通过这个长链接持续推送响应
3.2.2. Server端实现
依赖:
xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>
配置 application.yml:
yaml
server:
port: 8081
spring:
ai:
mcp:
server:
stdio: false
name: my-weather-server-web-sse
version: 1.0.0
type: SYNC
instructions: "本服务提供天气查询相关工具"
protocol: streamable
工具定义 :和STDIO完全一样,@Tool 注解,不重复贴代码。
3.2.3. Client端实现
依赖不变:
xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
配置 application.yml:
yaml
spring:
ai:
mcp:
client:
type: SYNC # 或 ASYNC,根据应用需求选择 [citation:4]
request-timeout: 60s
toolcallback:
enabled: true # 必须启用,以便将MCP工具自动注入ChatClient [citation:1][citation:2]
streamable-http:
connections:
my-weather-server: # 自定义连接名称
url: http://localhost:8081 # 基础地址
endpoint: /mcp
Client代码 :Client代码和STDIO方式一模一样。传输方式的变化对业务代码完全透明------这就是MCP协议的价值。
适用场景:传统Spring Boot项目、同步阻塞业务、需多客户端远程访问。
3.2.4. 测试结果
本地测试

Vibe Code工具测试(以Trae为例)
json
{
"mcpServers": {
"my-weather-server": {
"url": "http://localhost:8081/mcp",
"headers": {}
}
}
}

3.3. 方式三:WebFlux SSE 传输(Reactor异步非阻塞HTTP-SSE)
3.3.1. 什么是WebFlux SSE?
和WebMVC SSE功能一样,但底层用WebFlux响应式引擎 ,非阻塞I/O,更高的并发能力,WebFlux SSE 模式正是为实现异步、流式输出而设计的,不过它背后的含义比单纯的"流式输出"要更丰富一些。
| 对比 | WebMVC SSE | WebFlux SSE |
|---|---|---|
| 线程模型 | 一个连接一个线程 | 事件驱动,少量线程 |
| 并发能力 | 受限于线程池大小 | 轻松支撑万级连接 |
| 推荐模式 | SYNC | ASYNC |
根据 Spring AI 官方文档,spring-ai-starter-mcp-server-webflux 这个依赖本身支持多种协议,默认启用的是 SSE 协议。WebFlux 只是一个技术栈选项,它默认适配的 MCP 协议是 SSE,而不是 Streamable-HTTP。
3.3.2. Server端实现
依赖:
xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
</dependency>
配置 application.yml (SSE模式) :
yaml
server:
port: 8082
spring:
ai:
mcp:
server:
name: weather-mcp-server-webflux
version: 1.0.0
type: ASYNC # WebFlux推荐异步模式
instructions: "本服务提供天气查询相关工具"
stdio: false
配置 application.yml ( Streamable-HTTP 模式 ) :
yaml
server:
port: 8082
spring:
ai:
mcp:
server:
protocol: STREAMABLE # 添加这一行,显式指定协议
name: weather-mcp-server-webflux
version: 1.0.0
type: ASYNC
instructions: "本服务提供天气查询相关工具"
stdio: false
工具定义 :依然是一样的 @Tool 注解。
3.3.3. Client端实现
依赖 :同样用 spring-ai-starter-mcp-client。
配置 application.yml (SSE模式) :
yaml
spring:
ai:
mcp:
client:
sse:
connections:
my-webflux-server:
url: http://localhost:8082
配置 application.yml ( Streamable-HTTP 模式 ) :
yaml
spring:
ai:
mcp:
client:
type: SYNC # 或 ASYNC,根据应用需求选择 [citation:4]
request-timeout: 60s
toolcallback:
enabled: true # 必须启用,以便将MCP工具自动注入ChatClient [citation:1][citation:2]
streamable-http: # 使用 Streamable-HTTP 传输
connections:
my-webflux-server: # 自定义连接名
url: http://localhost:8082 # 服务端基础地址
# endpoint 默认就是 /mcp,可以省略
Client代码 :三套Client代码完全一样。
适用场景:高并发微服务、云原生部署、大量并发客户端。
3.3.4. 测试结果
本地测试

Vibe Code工具测试(以Trae为例)
json
{
"mcpServers": {
"my-webflux-weather-server": {
"url": "http://localhost:8082/sse"
}
}
}

3.3.5. /mcp vs /sse:一个核心区别,让你彻底分清
/mcp ****是 Streamable-HTTP 协议的默认端点, /sse ****是 SSE 协议的默认端点。两者是不同协议下的接口,而不是同一个协议的不同路径。
| 对比维度 | /sse |
/mcp |
|---|---|---|
| 所属协议 | SSE(Server-Sent Events) | Streamable-HTTP |
| 协议状态 | ❌ 已弃用(Deprecated) | ✅ 官方推荐 |
| 通信方式 | 先连 /sse,再通过 /mcp/message发请求 |
所有请求都走 /mcp |
| 所需端点数量 | 2 个(/sse+ /mcp/message) |
1 个(/mcp) |
| 配置方式 | 默认,或 protocol: SSE |
需显式配置 protocol: STREAMABLE |
为什么会有两个?
在 MCP 协议的早期版本中,SSE 是实现服务器推送的主要方式。它需要客户端先访问 /sse 建立长连接,再通过 /mcp/message 发送请求,响应走 SSE 推送回来。这种设计需要维护两个端点,会话管理也比较复杂。
后来 MCP 协议升级,推出了 Streamable-HTTP 模式。它把通信简化为一个 /mcp 端点,所有请求和响应都通过这个端点完成。因为更简单、更灵活,它已经成为官方推荐的方式。
所以现在大家看到的最多的是/mcp。
4. 本篇小结
这一篇我们完整实现了MCP的三种传输方式:
| 方式 | Server关键配置 | Client关键配置 |
|---|---|---|
| STDIO | stdio: true |
command+ args |
| WebMVC SSE | 引入webmvc依赖 + 配置端口 | url: http://host/mcp |
| WebFlux SSE | 引入webflux依赖 + 配置端口 + ASYNC |
url: http://host/mcp+ ASYNC |
核心心法:
- 工具代码零改动 :
@Tool注解一套代码,三种方式通用 - Client代码零改动 :注入
McpClient,配置决定传输方式 - 切换传输方式只需改配置:从本地开发(STDIO)到测试环境(WebMVC)到生产(WebFlux),只改yml
下一篇,我们要更进一步------自己写Server还不够爽,直接接入社区里现成的MCP Server,让AI能读文件、查数据库、甚至操作GitHub仓库。
本文与DeepSeek协作完成