Spring AI MCP Server 开发指南

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


目录

  • [写在前面:MCP 是个啥?](#写在前面:MCP 是个啥? "#%E5%86%99%E5%9C%A8%E5%89%8D%E9%9D%A2mcp-%E6%98%AF%E4%B8%AA%E5%95%A5")
  • [Spring AI 版本演进史](#Spring AI 版本演进史 "#spring-ai-%E7%89%88%E6%9C%AC%E6%BC%94%E8%BF%9B%E5%8F%B2")
  • [从零搭建一个 MCP Server](#从零搭建一个 MCP Server "#%E4%BB%8E%E9%9B%B6%E6%90%AD%E5%BB%BA%E4%B8%80%E4%B8%AA-mcp-server")
  • 核心注解详解
  • 配置参数大全
  • 传输协议的前世今生
  • 进阶玩法
  • 常见踩坑实录

写在前面:MCP 是个啥?

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

css 复制代码
传统 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 生态里很常见------老用户有安全网,新用户有最新功能,冒险家有尝鲜渠道。各取所需,和谐共处。

版本演进时间线

scss 复制代码
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 工具的魔法转换器
java 复制代码
// 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 请求/响应模型,天然兼容各种代理、网关和负载均衡器。

yaml 复制代码
# 一行配置,从此告别 SSE
spring.ai.mcp.server.protocol: STREAMABLE

2. 注解扫描器 (Annotation Scanner)

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

yaml 复制代码
# 默认就是 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 贡献者和极限运动爱好者。

版本选择建议

markdown 复制代码
               你是什么类型的开发者?
                /       |       \
              稳健型   进取型    冒险型
               |        |        |
            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,简洁得像一首俳句:

xml 复制代码
<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 技术栈

第二步:写启动类

java 复制代码
@SpringBootApplication
public class McpWebServerDemo {
    public static void main(String[] args) {
        SpringApplication.run(McpWebServerDemo.class, args);
    }
}

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

第三步:写工具类

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

java 复制代码
@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 在需要的时候就会来调用你。

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

java 复制代码
@Configuration
public class McpToolConfig {

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

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

第五步:配置文件

yaml 复制代码
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 协议调用它。

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

description 写法的艺术:

java 复制代码
// 反面教材 ------ 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 理解该传什么值。

java 复制代码
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。但有几个最佳实践:

java 复制代码
// 推荐:返回 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 真正需要配的只有三行

yaml 复制代码
spring:
  ai:
    mcp:
      server:
        name: mcp-web-demo        # 你是谁
        version: 1.0.0            # 你是哪个版本
        protocol: STREAMABLE      # 你用什么协议说话

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

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

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

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

xml 复制代码
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>

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

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


完整配置树

yaml 复制代码
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

yaml 复制代码
name: mcp-web-demo
version: 1.0.0

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

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

type ------ 同步 vs 异步

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

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

protocol ------ 传输协议

yaml 复制代码
protocol: STREAMABLE  # 或 SSE

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

capabilities ------ 能力声明

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

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

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

yaml 复制代码
capabilities:
  tool: true
  resource: false   # 不提供资源
  prompt: false     # 不提供 prompt 模板
  completion: false # 不提供补全

变更通知

yaml 复制代码
tool-change-notification: true
resource-change-notification: true
prompt-change-notification: true

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

streamable-http 配置块

yaml 复制代码
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

yaml 复制代码
annotation-scanner:
  enabled: true

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

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


传输协议的前世今生

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

stdio ------ 本地青梅竹马

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

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

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

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

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

Streamable HTTP ------ 当代最优解

css 复制代码
[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 管理的组件:

java 复制代码
@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. 多工具类组织

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

java 复制代码
// 按领域拆分
@Service public class TimeTools { ... }      // 时间相关
@Service public class WeatherTools { ... }   // 天气相关
@Service public class DatabaseTools { ... }  // 数据库操作
@Service public class FileTools { ... }      // 文件操作

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

java 复制代码
@Bean
public ToolCallbackProvider tools(TimeTools time, WeatherTools weather,
                                   DatabaseTools db, FileTools file) {
    return MethodToolCallbackProvider.builder()
            .toolObjects(time, weather, db, file)
            .build();
}

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

3. 异步工具处理

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

yaml 复制代码
spring.ai.mcp.server.type: ASYNC
java 复制代码
@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 服务器暴露在网络上时,安全性至关重要:

java 复制代码
// 通过 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):

json 复制代码
{
  "mcpServers": {
    "my-server": {
      "type": "http",
      "url": "http://localhost:8080/mcp",
      "headers": {
        "Authorization": "Bearer my-secret-key"
      }
    }
  }
}

常见踩坑实录

坑 1:Java 版本不对

go 复制代码
error: switch expressions are not supported in -source 17

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

xml 复制代码
<java.version>21</java.version>

坑 2:用了 SSE Starter 却配了 STREAMABLE

yaml 复制代码
# 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 写得太模糊

java 复制代码
// AI 完全不知道什么时候该用这个工具
@Tool(description = "do something")

// AI:你是在为难我

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

坑 5:返回值太大

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

java 复制代码
// 反面教材
@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 来骂我吧。

相关推荐
雾喔2 小时前
【学习笔记2】快速上手调用 AI API & Prompt Engineering
人工智能·笔记·学习
黑不溜秋的2 小时前
AI文章阅读 - 大语言模型介绍
人工智能
南璋2 小时前
MySQL排序踩坑:为什么"10"比"2"小?
后端
何陋轩2 小时前
Elasticsearch搜索引擎深度解析:把搜索核心讲透,面试都是小菜
后端·面试
算.子2 小时前
【Spring 实战】Spring AI 进阶专题:Token 成本优化与 Structured Output
java·人工智能·spring
linyb极客之路2 小时前
OpenSpec Commands 全解析:让 AI 编码工作流更规范高效
人工智能
Java编程爱好者2 小时前
JVM 详解:从内存结构到调优实战,Java 开发者必读
后端
小瓦码J码2 小时前
如何手动部署一个向量模型服务
人工智能·后端
Carsene2 小时前
Spring Boot 包扫描新姿势:AutoScan vs @Import vs @ComponentScan 深度对比
spring boot·后端