SpringAI-04-MCP协议详解

一、MCP 协议是什么

​ MCP(Model Context Protocol)是 Spring AI 提供的模型上下文协议,提供了一种标准化的方式来连接 AI 模型和外部工具。

简单理解:

  • 之前我们直接在应用中使用 @Tool 定义工具
  • MCP 协议可以把工具服务独立出来,作为一个独立的进程或服务
  • AI 应用通过 MCP 协议调用这些独立的工具服务

MCP 的优势:

  • 解耦:工具服务和 AI 应用解耦,可以独立开发和部署
  • 标准化:提供了标准化的协议,便于集成
  • 灵活:支持多种传输方式,适应不同场景
  • 可扩展:可以轻松添加新的工具服务

使用场景:

  • 工具服务需要独立部署
  • 多个 AI 应用需要共享同一套工具
  • 工具服务需要跨语言实现
  • 需要进程隔离的场景

二、MCP 的三种传输方式

​ Spring AI 支持三种 MCP 传输方式:

1. Client + 公共 MCP-Server

  • 连接到公共的 MCP 服务器
  • 适合使用第三方提供的工具服务

2. STDIO(标准输入输出)

  • 通过标准输入输出流进行通信
  • 适合本地工具服务
  • 进程间通信

3. SSE(Server-Sent Events)

  • 通过 HTTP + SSE 进行通信
  • 适合远程工具服务
  • 网络通信

本文主要介绍 STDIO 和 SSE 两种方式。

三、MCP Client 配置

3.1 依赖配置

​ 在 AI 应用(调用方)中添加 MCP Client 依赖:

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

注意:

  • 这个依赖包含了 STDIO 和 SSE 两种客户端
  • 需要 Spring WebFlux 支持(因为使用了响应式编程)

3.2 STDIO 配置

application.properties:

1
2
3
4
5
6
# MCP Client STDIO 配置
spring.ai.mcp.client.stdio.servers-configuration=classpath:/mcp/mcp-servers.json
spring.ai.mcp.client.timeout=60000

# 日志配置(调试时开启)
logging.level.io.modelcontextprotocol=DEBUG

mcp-servers.json:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"mcpServers": {
"getweather": {
"command": "java",
"args": [
"-Dspring.ai.mcp.server.stdio=true",
"-Dlogging.pattern.console=",
"-jar",
"D:\\git\\code\\study\\spring-ai-study\\05mcp-server-stdio\\target\\05mcp-server-stdio-1.0-SNAPSHOT.jar"
]
}
}
}

配置说明:

  • servers-configuration:MCP Server 配置文件路径
  • timeout:超时时间(毫秒)
  • command:启动 MCP Server 的命令(这里是 java)
  • args:启动参数
    • -Dspring.ai.mcp.server.stdio=true:启用 STDIO 模式
    • -Dlogging.pattern.console=:禁用控制台日志(避免干扰通信)
    • -jar:指定 jar 包路径

注意事项:

  • jar 包路径建议使用绝对路径
  • Windows 路径使用 \\/ 都可以
  • 确保 jar 包已经打包好

3.3 SSE 配置

application.properties:

1
2
3
4
5
6
7
# MCP Client SSE 配置
spring.ai.mcp.client.sse.connections.userScores.url=http://localhost:8088
spring.ai.mcp.client.sse.connections.userScores.sse-endpoint=/sse
spring.ai.mcp.client.timeout=60000

# 日志配置
logging.level.io.modelcontextprotocol=DEBUG

配置说明:

  • connections.userScores:连接名称,可以自定义
  • url:MCP Server 的地址
  • sse-endpoint:SSE 端点路径(通常是 /sse

多服务器配置:

1
2
3
4
5
6
7
# 天气服务
spring.ai.mcp.client.sse.connections.weather.url=http://localhost:8088
spring.ai.mcp.client.sse.connections.weather.sse-endpoint=/sse

# 用户分数服务
spring.ai.mcp.client.sse.connections.userScores.url=http://localhost:8089
spring.ai.mcp.client.sse.connections.userScores.sse-endpoint=/sse

四、MCP Server - STDIO 方式

4.1 项目搭建

依赖配置:

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

注意:

  • spring-ai-starter-mcp-server:MCP Server 的核心依赖
  • spring-boot-starter-web:虽然 STDIO 不需要 Web,但某些场景可能需要

4.2 工具服务实现

WeatherService:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package org.study.ai;

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;

@Service
public class WeatherService {

private final RestClient restClient;

public WeatherService() {
restClient = RestClient.builder().build();
}

@Tool(description = "获取指定经纬度的天气预报")
public String getWeather(double latitude, double longitude) {
return restClient.get()
.uri("https://api.open-meteo.com/v1/forecast?latitude={latitude}&longitude={longitude}&current_weather=true",
latitude, longitude)
.retrieve()
.body(String.class);
}
}

关键点:

  • 使用 @Tool 注解定义工具方法
  • 工具方法的实现和普通 Spring Bean 一样
  • 可以调用外部 API、数据库等

4.3 启动配置

启动类:

1
2
3
4
5
6
7
8
9
10
11
12
13
@SpringBootApplication
public class McpServerApplication {
public static void main(String[] args) {
SpringApplication.run(McpServerApplication.class, args);
}

@Bean
public ToolCallbackProvider toolCallbackProvider(WeatherService weatherService) {
return MethodToolCallbackProvider.builder()
.toolObjects(weatherService)
.build();
}
}

打包:

1
mvn clean package

启动参数:

1
2
3
java -Dspring.ai.mcp.server.stdio=true \
-Dlogging.pattern.console= \
-jar mcp-server-stdio.jar

关键参数:

  • -Dspring.ai.mcp.server.stdio=true:启用 STDIO 模式
  • -Dlogging.pattern.console=:禁用控制台日志(重要!否则会干扰通信)

4.4 工作原理

STDIO 工作流程:

  1. 启动进程:MCP Client 根据配置启动 MCP Server 进程
  2. 标准输入:MCP Client 通过标准输入(stdin)发送工具调用请求
  3. 标准输出:MCP Server 通过标准输出(stdout)返回结果
  4. 进程通信:通过进程间通信实现数据传输

通信格式:

  • 使用 JSON-RPC 2.0 协议
  • 请求和响应都是 JSON 格式
  • 通过标准输入输出流传输

优点:

  • 进程隔离,工具服务崩溃不影响主应用
  • 部署简单,只需要一个 jar 包
  • 适合本地工具服务

缺点:

  • 只能本地部署
  • 进程启动有开销
  • 调试相对困难

五、MCP Server - SSE 方式

5.1 项目搭建

依赖配置:

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

注意:

  • spring-ai-starter-mcp-server-webflux:SSE 方式的 MCP Server
  • 需要 WebFlux 支持(响应式编程)

5.2 工具服务实现

UserToolService:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package org.study.ai;

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

import java.util.Map;

@Service
public class UserToolService {

private Map<String, Double> userScore = Map.of(
"xushu", 2.0,
"xiaoming", 1.0
);

@Tool(description = "获取用户分数")
public String getWeather(String userName) {
if (userScore.containsKey(userName)) {
return userScore.get(userName).toString();
}
return "未找到用户分数";
}
}

启动类:

1
2
3
4
5
6
7
8
9
10
11
12
13
@SpringBootApplication
public class McpServerSSEApplication {
public static void main(String[] args) {
SpringApplication.run(McpServerSSEApplication.class, args);
}

@Bean
public ToolCallbackProvider toolCallbackProvider(UserToolService userToolService) {
return MethodToolCallbackProvider.builder()
.toolObjects(userToolService)
.build();
}
}

配置:

1
server.port=8088

5.3 工作原理

SSE 工作流程:

  1. HTTP 连接:MCP Client 通过 HTTP 连接到 MCP Server
  2. 建立 SSE 流:建立 Server-Sent Events 流
  3. 双向通信:通过 SSE 流实现双向通信
  4. 实时传输:支持实时数据传输

通信格式:

  • 使用 JSON-RPC 2.0 协议
  • 通过 HTTP + SSE 传输
  • 支持实时双向通信

SSE 端点:

  • MCP Server 会自动暴露 SSE 端点
  • 默认路径:/sse
  • 可以通过配置修改

优点:

  • 可以远程部署
  • 支持多个客户端连接
  • 实时性好
  • 调试方便(可以用浏览器测试)

缺点:

  • 需要网络连接
  • 需要 Web 服务器支持
  • 配置相对复杂

六、STDIO vs SSE

特性 STDIO SSE
部署方式 本地进程 远程服务
通信方式 标准输入输出 HTTP + SSE
进程隔离 ✅ 是 ❌ 否
远程访问 ❌ 否 ✅ 是
实时性
调试难度 较难 较易
适用场景 本地工具服务 远程工具服务

选择建议:

  • 本地工具服务:使用 STDIO
  • 远程工具服务:使用 SSE
  • 需要进程隔离:使用 STDIO
  • 需要多个客户端共享:使用 SSE

七、实际案例

7.1 天气服务(STDIO)

场景: 提供一个天气查询工具,通过 STDIO 方式提供服务。

MCP Server 项目结构:

1
2
3
4
5
6
05mcp-server-stdio/
├── src/main/java/
│ └── org/study/ai/
│ ├── McpServerApplication.java
│ └── WeatherService.java
└── pom.xml

WeatherService:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Service
public class WeatherService {
private final RestClient restClient;

public WeatherService() {
restClient = RestClient.builder().build();
}

@Tool(description = "获取指定经纬度的天气预报")
public String getWeather(double latitude, double longitude) {
return restClient.get()
.uri("https://api.open-meteo.com/v1/forecast?latitude={latitude}&longitude={longitude}&current_weather=true",
latitude, longitude)
.retrieve()
.body(String.class);
}
}

MCP Client 配置(调用方):

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"mcpServers": {
"getweather": {
"command": "java",
"args": [
"-Dspring.ai.mcp.server.stdio=true",
"-Dlogging.pattern.console=",
"-jar",
"path/to/mcp-server-stdio.jar"
]
}
}
}

使用:

1
2
3
// 在 AI 应用中,MCP Client 会自动发现并调用这个工具
// 用户问:"北京(39.9, 116.4)的天气怎么样?"
// AI 会自动调用 getWeather(39.9, 116.4)

7.2 用户分数服务(SSE)

场景: 提供一个用户分数查询工具,通过 SSE 方式提供服务。

MCP Server 项目结构:

1
2
3
4
5
6
06mcp-server-sse/
├── src/main/java/
│ └── org/study/ai/
│ ├── McpServerSSEApplication.java
│ └── UserToolService.java
└── pom.xml

UserToolService:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Service
public class UserToolService {
private Map<String, Double> userScore = Map.of(
"xushu", 2.0,
"xiaoming", 1.0
);

@Tool(description = "获取用户分数")
public String getWeather(String userName) {
if (userScore.containsKey(userName)) {
return userScore.get(userName).toString();
}
return "未找到用户分数";
}
}

MCP Client 配置(调用方):

1
2
spring.ai.mcp.client.sse.connections.userScores.url=http://localhost:8088
spring.ai.mcp.client.sse.connections.userScores.sse-endpoint=/sse

使用:

1
2
3
// 在 AI 应用中,MCP Client 会自动连接到 SSE 服务
// 用户问:"xushu 的分数是多少?"
// AI 会自动调用 getWeather("xushu")

八、常见问题

8.1 STDIO 方式启动失败

问题: MCP Server 启动失败,提示找不到类或配置错误。

原因:

  • jar 包路径错误
  • jar 包未正确打包
  • 启动参数错误

解决:

  1. 检查 jar 包路径是否正确(使用绝对路径)
  2. 确认 jar 包已正确打包:mvn clean package
  3. 检查启动参数是否正确

8.2 STDIO 方式没有输出

问题: MCP Client 调用工具但没有返回结果。

原因:

  • 日志输出干扰了通信
  • 工具执行失败
  • 通信格式错误

解决:

  1. 确保设置了 -Dlogging.pattern.console=
  2. 检查工具方法是否有异常
  3. 开启 DEBUG 日志查看通信内容

8.3 SSE 方式连接失败

问题: MCP Client 无法连接到 SSE 服务。

原因:

  • 服务未启动
  • URL 配置错误
  • 端口被占用

解决:

  1. 确认 MCP Server 已启动
  2. 检查 URL 和端口是否正确
  3. 使用浏览器访问 http://localhost:8088/sse 测试

8.4 工具调用超时

问题: 工具调用超时。

原因:

  • 工具执行时间过长
  • 网络延迟
  • 超时时间设置过短

解决:

1
2
# 增加超时时间(毫秒)
spring.ai.mcp.client.timeout=120000

8.5 多个 MCP Server 配置

问题: 如何配置多个 MCP Server?

STDIO 方式:

1
2
3
4
5
6
7
8
9
10
11
12
{
"mcpServers": {
"weather": {
"command": "java",
"args": ["-jar", "weather-server.jar"]
},
"stock": {
"command": "java",
"args": ["-jar", "stock-server.jar"]
}
}
}

SSE 方式:

1
2
3
4
5
spring.ai.mcp.client.sse.connections.weather.url=http://localhost:8088
spring.ai.mcp.client.sse.connections.weather.sse-endpoint=/sse

spring.ai.mcp.client.sse.connections.stock.url=http://localhost:8089
spring.ai.mcp.client.sse.connections.stock.sse-endpoint=/sse

九、最佳实践

9.1 工具服务设计

单一职责
​ 每个 MCP Server 只提供一类工具:

  • 天气服务:只提供天气相关工具
  • 用户服务:只提供用户相关工具

错误处理

1
2
3
4
5
6
7
8
9
10
11
@Tool(description = "获取天气信息")
public String getWeather(double latitude, double longitude) {
try {
return restClient.get()
.uri("https://api.open-meteo.com/v1/forecast?...")
.retrieve()
.body(String.class);
} catch (Exception e) {
return "获取天气信息失败:" + e.getMessage();
}
}

日志记录

1
2
3
4
5
6
7
@Tool(description = "获取天气信息")
public String getWeather(double latitude, double longitude) {
log.info("查询天气,纬度:{},经度:{}", latitude, longitude);
// ... 业务逻辑
log.info("查询结果:{}", result);
return result;
}

9.2 部署建议

STDIO 方式:

  • jar 包路径使用绝对路径
  • 确保 jar 包有执行权限
  • 考虑使用进程管理工具(如 systemd)

SSE 方式:

  • 使用反向代理(如 Nginx)
  • 配置健康检查
  • 考虑负载均衡

9.3 性能优化

连接池
​ SSE 方式可以配置连接池:

1
spring.ai.mcp.client.sse.connections.userScores.max-connections=10

超时设置
​ 合理设置超时时间:

1
spring.ai.mcp.client.timeout=60000

资源管理
​ 及时释放资源,避免内存泄漏。

十、总结

  1. MCP 协议:提供了标准化的工具调用协议,实现工具服务和 AI 应用的解耦
  2. STDIO 方式:适合本地工具服务,通过标准输入输出通信
  3. SSE 方式:适合远程工具服务,通过 HTTP + SSE 通信
  4. 配置要点:STDIO 需要配置文件,SSE 需要 URL 配置
  5. 工具实现:使用 @Tool 注解定义工具,和普通 Spring Bean 一样
  6. 常见问题:启动失败、连接失败、超时等
  7. 最佳实践:单一职责、错误处理、日志记录、性能优化

​ 下一篇文章会介绍 RAG(检索增强生成),这是 Spring AI 提供的另一个重要功能。