“如果说 MCP 是 AI 世界的 USB 接口,那 Spring AI 就是帮你焊好接口、包好外壳、还贴心地附赠了说明书的那个工具人。”


目录


写在前面:MCP 是个啥?

MCP (Model Context Protocol) 是 Anthropic 提出的一套开放协议,让 AI 模型能够像调用 API 一样调用外部工具。你可以把它理解为:

1
2
传统 Web 开发:浏览器 <--HTTP--> 服务器
MCP 世界: AI 模型 <--MCP--> 你的工具服务

以前你想让 AI 查个天气、算个数,得靠 prompt 里硬塞一堆上下文。现在有了 MCP,AI 可以主动说:”嘿,我需要查一下北京的天气”,然后你的 MCP Server 就屁颠屁颠地把结果递过去了。

一句话总结:MCP 让 AI 从”只会动嘴”进化成了”能动手”。


Spring AI 版本演进史

Spring AI 的发展速度堪比坐上了火箭——从最初的”实验品”到现在的”生产级框架”,每个版本都在疯狂堆料。

官网版本全景图 (截至 2026 年 4 月)

打开 Spring AI 官网,你会看到这样一幅”版本大家族合影”:

版本 标签 状态说明
1.1.4 CURRENT 1.1.x 系列最新稳定版,本项目正在使用
1.0.5 CURRENT 1.0.x 系列最新稳定版,长期维护分支
2.0.0-SNAPSHOT SNAPSHOT 2.0 开发快照,每日构建,喜欢刺激的可以试试
2.0.0-M4 PRE 2.0 第四个里程碑预览版,功能逐步冻结中
1.1.5-SNAPSHOT SNAPSHOT 1.1.x 的下一个补丁版快照

小科普: CURRENT = 生产可用的稳定版;PRE = 预览版,功能基本成型但还在打磨;SNAPSHOT = 每日构建的开发版,随时可能翻天覆地,慎用于生产。

看到没?Spring AI 同时维护着 两条稳定线 (1.0.x 和 1.1.x),外加一条正在孵化的 2.0 线。这在 Spring 生态里很常见——老用户有安全网,新用户有最新功能,冒险家有尝鲜渠道。各取所需,和谐共处。

版本演进时间线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
0.8.x          1.0.0-M1~M6        1.0.0 GA         1.0.5           未来
| | | | |
v v v v v
[拓荒时代] --> [里程碑打磨] --> [首个正式版!] --> [稳定维护] --> [持续修补]
|
| (分支)
v
1.1.0 -------> 1.1.4 -------> 1.1.5
[鸟枪换炮] [当前推荐] [快照开发中]
|
| (分支)
v
2.0.0-M1~M4 --> 2.0.0
[下一代预览] [未来可期]

各版本核心特性详解

1.0.x 系列 —— “稳如老狗”

最新稳定版:1.0.5 CURRENT

Spring AI 1.0.x 是第一个正式发布的版本系列。如果你的项目已经在用 1.0.x 且跑得好好的,没必要急着升级——官方还在持续维护呢。

核心能力:

  • @Tool 注解:终于不用手写 JSON Schema 了!一个注解搞定工具声明
  • @ToolParam 注解:给参数加上描述,让 AI 知道该传什么
  • SSE 传输协议:基于 Server-Sent Events 的实时通信,支持远程部署
  • stdio 模式:通过标准输入输出通信,适合本地工具
  • MethodToolCallbackProvider:把普通 Java 方法变成 MCP 工具的魔法转换器
1
2
3
4
5
// 1.0.x 时代的写法(至今仍然适用)
@Tool(description = "Get the current time")
public String getCurrentTime() {
return LocalDateTime.now().toString();
}

1.0.x 的不足:SSE 协议在复杂网络环境下(代理、负载均衡)容易出问题,像是一个只能在实验室里跑的原型。也正因为这个痛点,才有了 1.1.x 的诞生。

1.1.x 系列 —— “好用且能打”

最新稳定版:1.1.4 CURRENT | 开发中:1.1.5-SNAPSHOT

1.1.0 是一次质的飞跃,也是目前新项目的最佳选择。三个重磅更新:

1. Streamable HTTP 协议

告别 SSE 的各种水土不服!Streamable HTTP 使用标准的 HTTP 请求/响应模型,天然兼容各种代理、网关和负载均衡器。

1
2
# 一行配置,从此告别 SSE
spring.ai.mcp.server.protocol: STREAMABLE

2. 注解扫描器 (Annotation Scanner)

1.1.0 引入了自动扫描机制——只要你的类是 Spring Bean 且方法标了 @Tool,框架就能自动发现并注册。不再需要手动配置 ToolCallbackProvider Bean(当然手动配置依然支持,看你心情)。

1
2
# 默认就是 true,写不写都行,但写出来显得专业
spring.ai.mcp.server.annotation-scanner.enabled: true

3. MCP SDK 持续升级

从 0.8.x 一路升到 0.17.0,每个版本都在完善协议实现。Spring AI BOM 帮你管好了版本,不用操心依赖冲突。

2.0.0 系列 —— “未来已来”

里程碑版:2.0.0-M4 PRE | 开发快照:2.0.0-SNAPSHOT

2.0 是 Spring AI 的下一个大版本,目前已经推进到第四个里程碑 (M4)。虽然官方还没正式公布完整的 changelog,但从里程碑节奏来看,2.0 GA 正在加速靠近。

从 M4 的标签可以推测,2.0 大概率会带来:

  • API 层面的重大重构和优化(大版本号升级的惯例)
  • 更完善的 MCP 协议支持
  • 可能的 breaking changes(所以才需要大版本号)

尝鲜提醒: 2.0.0-M4 (PRE) 可以用于技术预研和 POC,但别拿来上生产——除非你喜欢半夜被 oncall 电话叫醒的感觉。2.0.0-SNAPSHOT 就更刺激了,每天一个惊喜(或惊吓),适合 Spring AI 贡献者和极限运动爱好者。

版本选择建议

1
2
3
4
5
6
7
    你是什么类型的开发者?
/ | \
稳健型 进取型 冒险型
| | |
1.0.5 1.1.4 2.0.0-M4
(老项目 (新项目 (技术预研
维护) 首选!) 尝鲜)
你的情况 推荐版本 理由
新项目,从零开始 1.1.4 最新稳定版,Streamable HTTP 是正确答案
老项目,已在用 1.0.x 1.0.5 先升到 1.0.5 修补漏洞,再择机迁移到 1.1.x
想提前适配 2.0 2.0.0-M4 开个分支试试水,但别合进 main
想给 Spring AI 贡献代码 2.0.0-SNAPSHOT 欢迎入坑,社区需要你

本项目的选择:1.1.4——稳定、功能全、Streamable HTTP 加持,没毛病。


从零搭建一个 MCP Server

第一步:创建项目

最低配 pom.xml,简洁得像一首俳句:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.4</version>
</parent>

<properties>
<java.version>21</java.version>
<spring-ai.version>1.1.4</spring-ai.version>
</properties>

<dependencies>
<!-- 就这一个依赖,没了 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>
</dependencies>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

你没看错,核心依赖只有一个。Spring AI BOM 会帮你搞定 MCP SDK、Jackson、Reactor 等一票传递依赖。这就是 Spring Boot “约定优于配置” 哲学的魅力——它替你做了 90% 的决定,而且大部分时候做得还不错。

Starter 选择指南

Spring AI 提供了多个 MCP Server Starter,别选错了:

Starter 传输方式 适用场景
spring-ai-starter-mcp-server stdio 本地工具,客户端管理进程生命周期
spring-ai-starter-mcp-server-webmvc HTTP/SSE (同步) Web 部署,本项目用的就是这个
spring-ai-starter-mcp-server-webflux HTTP/SSE (响应式) 高并发场景,需要 WebFlux 技术栈

第二步:写启动类

1
2
3
4
5
6
@SpringBootApplication
public class McpWebServerDemo {
public static void main(String[] args) {
SpringApplication.run(McpWebServerDemo.class, args);
}
}

对,就这么朴实无华。Spring Boot 的启动类从来不需要花里胡哨。

第三步:写工具类

这是整个项目最有趣的部分——定义 AI 可以调用的工具:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Service
public class DemoTools {

@Tool(description = "Get the current date and time")
public String getCurrentTime() {
return LocalDateTime.now()
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}

@Tool(description = "Perform basic arithmetic calculations (+, -, *, /)")
public String calculate(
@ToolParam(description = "First operand") double a,
@ToolParam(description = "Arithmetic operator: +, -, *, /") String operator,
@ToolParam(description = "Second operand") double b) {

double result = switch (operator) {
case "+" -> a + b;
case "-" -> a - b;
case "*" -> a * b;
case "/" -> b != 0 ? a / b : Double.NaN;
default -> throw new IllegalArgumentException("Unknown operator: " + operator);
};
return String.format("%s %s %s = %s", a, operator, b, result);
}

@Tool(description = "Query weather information for a city (mock data for demo)")
public String queryWeather(
@ToolParam(description = "City name, e.g. Beijing, Shanghai") String city) {
// 实际项目中这里可以调用真实的天气 API
Map<String, String> mockWeather = Map.of(
"Beijing", "Sunny, 25°C, humidity 40%",
"Shanghai", "Cloudy, 22°C, humidity 65%"
);
return String.format("Weather in %s: %s", city,
mockWeather.getOrDefault(city, "Clear, 23°C (default mock data)"));
}
}

注意看,每个方法上面的 @Tool(description = "...") 就是告诉 AI:”嘿,我能干这个事,描述如下。” AI 在需要的时候就会来调用你。

第四步:注册工具(可选)

1
2
3
4
5
6
7
8
9
10
@Configuration
public class McpToolConfig {

@Bean
public ToolCallbackProvider tools(DemoTools demoTools) {
return MethodToolCallbackProvider.builder()
.toolObjects(demoTools)
.build();
}
}

为什么说”可选”? 因为从 Spring AI 1.1.0 开始,注解扫描器默认开启,它会自动发现所有标注了 @Tool 的 Spring Bean 方法。但手动注册的好处是:你可以精确控制哪些工具暴露出去。在生产环境中,”能干”和”该干”是两码事。

第五步:配置文件

1
2
3
4
5
6
7
8
9
10
server:
port: 8080

spring:
ai:
mcp:
server:
name: mcp-web-demo
version: 1.0.0
protocol: STREAMABLE

启动! ./mvnw spring-boot:run,然后你就拥有了一个运行在 http://localhost:8080/mcp 的 MCP 服务器。

就这?就这。五步搞定,比泡一碗方便面还简单。


核心注解详解

@Tool —— 工具的身份证

@Tool 注解标记一个方法为 MCP 工具,AI 可以通过 MCP 协议调用它。

1
2
@Tool(description = "这里写工具的功能描述,AI 靠这段话决定什么时候调用你")
public String myTool() { ... }
属性 类型 必填 说明
description String 工具功能描述,越清晰越好。这是 AI 理解你工具用途的唯一依据
name String 工具名称,默认使用方法名。建议用 camelCase
returnDirect boolean 是否将结果直接返回给用户而非 AI。默认 false

description 写法的艺术:

1
2
3
4
5
6
// 反面教材 —— AI:这啥?能干啥?我要不要用?算了不用了
@Tool(description = "process data")

// 正面教材 —— AI:哦!查天气用这个!
@Tool(description = "Query real-time weather information for a given city name, "
+ "returns temperature, humidity, and weather conditions")

description 是你和 AI 之间的”契约”。写得越清楚,AI 越知道什么时候该用、什么时候不该用。把它当成 Javadoc 来写就对了——只不过你的读者从人类变成了 AI。

@ToolParam —— 参数的说明书

@ToolParam 为方法参数添加描述,帮助 AI 理解该传什么值。

1
2
3
4
5
public String calculate(
@ToolParam(description = "First operand") double a,
@ToolParam(description = "Arithmetic operator: +, -, *, /") String operator,
@ToolParam(description = "Second operand") double b
) { ... }
属性 类型 必填 说明
description String 推荐 参数含义描述
required boolean 是否必填,默认 true

小贴士: @ToolParam 不是必须的——不加的话,AI 只能看到参数名和类型,靠猜。但你想让 AI 猜你的意思吗?

支持的参数类型

Spring AI 会自动将参数转换为 JSON Schema,以下类型开箱即用:

Java 类型 JSON Schema 类型 示例
String string "hello"
int / Integer / long / Long integer 42
double / Double / float / Float number 3.14
boolean / Boolean boolean true
List<T> array [1, 2, 3]
Map<String, T> object {"key": "value"}
POJO object (展开所有字段) {"name": "test", "age": 18}
enum string (带 enum 约束) "MONDAY"

返回值约定

方法可以返回任意类型,Spring AI 会自动序列化为 JSON 字符串发给 AI。但有几个最佳实践:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 推荐:返回 String,格式可控
@Tool(description = "...")
public String myTool() {
return "结果清晰明了";
}

// 也行:返回对象,自动 JSON 序列化
@Tool(description = "...")
public UserInfo getUser(Long id) {
return userService.findById(id); // 会被序列化为 {"name": "...", "age": ...}
}

// 不推荐:返回 void —— AI 收到空响应会很困惑
@Tool(description = "...")
public void doSomething() { ... } // AI:???干完了没?结果呢?

配置参数大全

这是本文最”枯燥”但最有用的部分。泡杯茶,我们一个一个说。

最小化配置 —— 三行就能跑

先上结论,一个 MCP Server 真正需要配的只有三行

1
2
3
4
5
6
7
spring:
ai:
mcp:
server:
name: mcp-web-demo # 你是谁
version: 1.0.0 # 你是哪个版本
protocol: STREAMABLE # 你用什么协议说话

没了?没了。其余全有默认值。

  • 端口?默认 8080
  • 端点路径?默认 /mcp
  • Tool 能力?默认开启
  • 注解扫描?默认开启
  • 服务器类型?默认同步

Spring Boot 的哲学:能不让你写的,绝不让你多写一个字。

对应的最小 pom.xml 依赖也只有一个:

1
2
3
4
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>

就这个组合,mvn spring-boot:run 一敲,http://localhost:8080/mcp 就活了。

如果你想更完整地了解”还能配什么”,请继续往下看。


完整配置树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
spring:
ai:
mcp:
server:
# =============================================
# 基础配置 —— 你的 MCP 服务器的"名片"
# =============================================
name: mcp-web-demo # [必填] 服务名称
version: 1.0.0 # [必填] 服务版本
type: SYNC # 服务器类型,默认 SYNC
protocol: STREAMABLE # 传输协议,默认 SSE(但推荐 STREAMABLE)

# =============================================
# 能力声明 —— 告诉客户端 "我能干什么"
# =============================================
capabilities:
tool: true # 工具能力 (默认 true)
resource: true # 资源能力 (默认 true)
prompt: true # 提示词能力 (默认 true)
completion: true # 补全能力 (默认 true)

# =============================================
# 变更通知 —— 当服务端内容变化时主动推送
# =============================================
tool-change-notification: true
resource-change-notification: true
prompt-change-notification: true

# =============================================
# Streamable HTTP 配置
# =============================================
streamable-http:
mcp-endpoint: /mcp # MCP 端点路径 (默认 /mcp)
disallow-delete: false # 禁止客户端 DELETE 请求 (默认 false)
keep-alive-interval: 30s # 长连接保活间隔 (默认无)

# =============================================
# SSE 配置(已废弃,不推荐使用)
# =============================================
sse-message-endpoint: /mcp/message
base-url:

# =============================================
# 注解扫描器
# =============================================
annotation-scanner:
enabled: true # 自动扫描 @Tool 注解 (默认 true)

逐项解析

name & version

1
2
name: mcp-web-demo
version: 1.0.0

这两个是你 MCP 服务器的”身份证”。客户端连接时能看到这些信息,方便调试和管理。

建议 version 跟着你项目的实际版本走,别一直写 1.0.0 然后功能已经迭代到第 37 版了。(别问我怎么知道的。)

type —— 同步 vs 异步

1
type: SYNC  # 或 ASYNC
类型 底层实现 适用场景
SYNC McpSyncServer 大多数场景。工具执行快、逻辑简单
ASYNC McpAsyncServer 工具执行耗时长(如调用外部 API、数据库大查询)

选择困难症患者请看这里:SYNC。如果你不确定要不要用 ASYNC,那你就不需要用 ASYNC。当你的工具开始因为超时被 AI 抱怨的时候,再切过去也来得及。

protocol —— 传输协议

1
protocol: STREAMABLE  # 或 SSE

这是 1.1.0 最重要的新配置项。详见下一章 传输协议的前世今生

capabilities —— 能力声明

1
2
3
4
5
capabilities:
tool: true # Tool:让 AI 调用你的方法
resource: true # Resource:向 AI 暴露数据(文件、数据库内容等)
prompt: true # Prompt:提供预定义的 prompt 模板
completion: true # Completion:提供自动补全建议

MCP 协议定义了四种能力。对于大多数项目,你只用到 tool。但框架默认全开,因为开着又不收费。

如果你确定不用某个能力,关掉它可以减少握手时的信息交换量(虽然这点优化约等于没有):

1
2
3
4
5
capabilities:
tool: true
resource: false # 不提供资源
prompt: false # 不提供 prompt 模板
completion: false # 不提供补全

变更通知

1
2
3
tool-change-notification: true
resource-change-notification: true
prompt-change-notification: true

当服务端的 Tool/Resource/Prompt 发生变化时(比如热更新了一个新工具),主动通知客户端刷新。在开发阶段很有用,生产环境中如果你的工具列表不会动态变化,可以关掉。

streamable-http 配置块

1
2
3
4
streamable-http:
mcp-endpoint: /mcp # 端点路径
disallow-delete: false # 是否禁止 DELETE
keep-alive-interval: 30s # 保活间隔

mcp-endpoint:MCP 服务的 URL 路径。改成 /api/mcp 也行,客户端连接时对应改就好。

disallow-delete:MCP 协议中,客户端可以发送 DELETE 请求来关闭会话。如果你出于安全考虑不想让客户端主动断开,设为 true

keep-alive-interval:对于长时间运行的 SSE 连接,定期发送心跳包防止连接被代理或防火墙切断。支持 Duration 格式:30s1mPT30S 都行。

annotation-scanner

1
2
annotation-scanner:
enabled: true

这个配置决定了 Spring AI 是否自动扫描所有 Spring Bean 中的 @Tool 方法并注册为 MCP 工具。

设为 false 的话,你必须手动通过 ToolCallbackProvider Bean 注册工具。适合那些对安全性要求极高、想要精确控制暴露面的场景。


传输协议的前世今生

MCP 支持三种传输方式,每种都有自己的”人设”:

stdio —— 本地青梅竹马

1
[AI 客户端] --stdin/stdout--> [你的 Java 进程]
  • 通过标准输入/输出通信
  • 客户端负责启动和管理你的进程
  • 优点:简单、零网络配置
  • 缺点:只能本地用,无法远程共享
  • 人话:AI 直接把你的程序跑起来,然后俩人通过管道悄悄话
1
2
# Claude Code 配置 stdio 模式
claude mcp add my-tool -- java -jar my-tool.jar

SSE (Server-Sent Events) —— 曾经的浪漫

1
[AI 客户端] --HTTP POST--> [服务器] --SSE stream--> [AI 客户端]
  • 客户端用 POST 发请求,服务器用 SSE 流式返回
  • 优点:支持远程部署、支持流式响应
  • 缺点:单向流、代理兼容性差、连接容易断

SSE 就像短信时代的异地恋——能通信但很不稳定,动不动就断线。

Spring AI 1.1.0 起,SSE 已被标记为 deprecated。 它还能用,但官方不推荐了。

Streamable HTTP —— 当代最优解

1
[AI 客户端] <--HTTP--> [服务器]
  • 使用标准 HTTP 协议,支持请求/响应和流式两种模式
  • 优点:兼容所有 HTTP 基础设施(代理、CDN、负载均衡、API 网关)
  • 缺点:想了半天,没有

Streamable HTTP 就像 4G 网络时代的视频通话——稳定、双向、随时在线。

这就是为什么本项目选择了 protocol: STREAMABLE

三种协议对比总结

特性 stdio SSE Streamable HTTP
部署方式 本地进程 远程服务 远程服务
网络要求 需要 HTTP 需要 HTTP
代理/CDN 兼容 N/A
双向通信 半双工
流式响应
Spring AI 支持版本 1.0.0+ 1.0.0+ (deprecated) 1.1.0+
推荐度 本地开发用 请迁移 生产推荐

进阶玩法

1. 注入 Spring 生态的一切

@Tool 方法所在的类就是普通的 Spring Bean,这意味着你可以注入任何 Spring 管理的组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Service
public class DatabaseTools {

private final JdbcTemplate jdbcTemplate;
private final RedisTemplate<String, String> redis;
private final UserRepository userRepo;

public DatabaseTools(JdbcTemplate jdbcTemplate,
RedisTemplate<String, String> redis,
UserRepository userRepo) {
this.jdbcTemplate = jdbcTemplate;
this.redis = redis;
this.userRepo = userRepo;
}

@Tool(description = "Execute a read-only SQL query against the database")
public String queryDatabase(
@ToolParam(description = "SQL SELECT statement") String sql) {
// 注意:生产环境一定要做 SQL 注入防护!
List<Map<String, Object>> results = jdbcTemplate.queryForList(sql);
return results.toString();
}

@Tool(description = "Look up user information by username")
public String findUser(
@ToolParam(description = "Username to search for") String username) {
return userRepo.findByUsername(username)
.map(User::toString)
.orElse("User not found: " + username);
}
}

Spring 全家桶(JPA、MyBatis、Redis、Kafka、Elasticsearch……)都是你的弹药库。MCP 工具只是个入口,背后可以连接整个企业级后端。

2. 多工具类组织

当工具多了,别都塞在一个类里:

1
2
3
4
5
// 按领域拆分
@Service public class TimeTools { ... } // 时间相关
@Service public class WeatherTools { ... } // 天气相关
@Service public class DatabaseTools { ... } // 数据库操作
@Service public class FileTools { ... } // 文件操作

手动注册时在 Config 类中列举:

1
2
3
4
5
6
7
@Bean
public ToolCallbackProvider tools(TimeTools time, WeatherTools weather,
DatabaseTools db, FileTools file) {
return MethodToolCallbackProvider.builder()
.toolObjects(time, weather, db, file)
.build();
}

或者开启注解扫描器(默认开启),什么都不用配。框架自动帮你收集所有 @Tool 方法。

3. 异步工具处理

对于耗时操作,使用 ASYNC 模式:

1
spring.ai.mcp.server.type: ASYNC
1
2
3
4
5
6
@Tool(description = "Generate a complex report (may take a while)")
public String generateReport(
@ToolParam(description = "Report type") String type) {
// 这个操作可能耗时 30 秒
return reportService.generate(type);
}

4. 安全防护

MCP 服务器暴露在网络上时,安全性至关重要:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 通过 Spring Security 或自定义 Filter 实现认证
@Component
public class McpAuthFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) {
String token = request.getHeader("Authorization");
if (!isValid(token)) {
response.setStatus(401);
return;
}
chain.doFilter(request, response);
}
}

客户端配置(.mcp.json):

1
2
3
4
5
6
7
8
9
10
11
{
"mcpServers": {
"my-server": {
"type": "http",
"url": "http://localhost:8080/mcp",
"headers": {
"Authorization": "Bearer my-secret-key"
}
}
}
}

常见踩坑实录

坑 1:Java 版本不对

1
error: switch expressions are not supported in -source 17

Spring AI 1.1.x 推荐 Java 21。如果你用了 switch 表达式等新语法,确保 pom.xml 里:

1
<java.version>21</java.version>

坑 2:用了 SSE Starter 却配了 STREAMABLE

1
2
3
4
5
# pom.xml 里引的是 spring-ai-starter-mcp-server-webmvc
# 但 application.yml 里写了
protocol: SSE # 这个组合是可以的

# 反过来,用 stdio starter 配 STREAMABLE —— 启动直接报错

规则: WebMvc/WebFlux Starter 支持 SSE 和 STREAMABLE。stdio Starter 只支持 stdio。别混搭。

坑 3:@Tool 方法没被发现

检查清单:

  1. 方法所在类是否是 Spring Bean(有 @Service@Component 等注解)?
  2. annotation-scanner.enabled 是否被意外设为 false
  3. 如果用手动注册,MethodToolCallbackProvider.builder().toolObjects(...) 里是否遗漏了你的 Bean?

坑 4:description 写得太模糊

1
2
3
4
// AI 完全不知道什么时候该用这个工具
@Tool(description = "do something")

// AI:你是在为难我

description 是 AI 识别和调用你工具的唯一线索。 想象你在写一份招聘启事——不写清楚岗位职责,谁知道要不要来应聘?

坑 5:返回值太大

如果你的工具返回了一个巨大的 JSON(比如几万条数据库记录),AI 的上下文窗口会被撑爆。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 反面教材
@Tool(description = "Query all users")
public List<User> getAllUsers() {
return userRepo.findAll(); // 返回 10 万条记录,AI 当场去世
}

// 正面教材
@Tool(description = "Query users with pagination")
public List<User> getUsers(
@ToolParam(description = "Page number, starting from 0") int page,
@ToolParam(description = "Page size, max 50") int size) {
return userRepo.findAll(PageRequest.of(page, Math.min(size, 50))).getContent();
}

写在最后

Spring AI 让 MCP Server 的开发变得前所未有的简单:

  1. 一个 Starter 搞定所有依赖
  2. 两个注解@Tool + @ToolParam)定义工具
  3. 三行配置 启动服务

从某种意义上说,写一个 MCP Server 比写一个 REST Controller 还简单——你甚至不需要操心 URL 映射、请求序列化、响应格式这些破事。

AI 不再只是聊天的搭子,而是你应用的调用者。 而 Spring AI 让这个过程变得像写普通业务代码一样自然。

去吧,写你的第一个 MCP 工具,让 AI 替你打工。


本文档基于 Spring AI 1.1.4 + Spring Boot 3.4.4 编写,配套项目源码就在本仓库中。
如果这篇文档帮到了你,给个 Star,不帮到你……那就提个 Issue 来骂我吧。