前置开发环境:
| 组件 | 版本要求 | 说明 |
|---|---|---|
| JDK | 17+ 或者21 | Spring AI 最低要求 |
| Maven | 3.8+ | 或使用提供的 ./mvnw |
| Spring Boot | 4.0.5 | Spring AI 支持 Spring Boot 3.4.x 和 3.5.x。 |
| Spring AI | 1.0.5 或 1.1.4 | ⚠️ 关键版本要求 |
| IDE 开发工具 | IntelliJ IDEA | 社区版/旗舰版,Spring 官方首选 |
| Visual Studio Code + Spring Boot Extension Pack | 轻量级,插件生态丰富 | |
| Eclipse + Spring Tools (STS) | 经典选择,企业环境常用 |
第一章:MCP价值定位
Spring AI 在 MCP 生态中的定位
Spring AI 通过 Boot Starters 和 注解驱动编程模型,将 MCP 的复杂性封装为熟悉的 Spring 开发体验。
🍃 Spring AI 技术栈
🌐 MCP 生态系统
集成实现
图例
客户端应用
协议层
服务端资源
Spring Starter
核心SDK
开发注解
🤖 AI 应用层
Claude / Cursor / Cline
🔗 MCP 协议层
标准化通信协议
💾 MCP 服务器层
数据库 / API / 文件系统
🚀 Spring AI MCP Client Starter
客户端快速启动
🛠️ MCP Java SDK
官方维护核心库
⚙️ Spring AI MCP Server Starter
服务端快速启动
✨ 注解驱动编程
@McpTool / @McpResource
版本要求 :本文档适用于 Spring AI 1.0.5 或 1.1.4。
第二章:MCP架构
2.1 客户端-服务器架构
MCP 采用经典的分层架构,明确划分了三个核心角色:
- 调用工具
- MCP协议通信
- 数据源
🖥️ MCP Server
能力提供者
Tools
图书查询/借阅
Resources
馆藏数据/用户记录
Prompts
借阅模板/推荐语
🔌 MCP Client
协议通信组件
Transport Layer
STDIO/HTTP/SSE
🏠 Host 宿主应用
用户直接交互的AI应用
ChatClient
对话管理
提示工程
上下文管理
工作流编排
图书馆
数据库
角色职责矩阵:
| 角色 | 职责范围 | 技术关注点 | 典型开发者 |
|---|---|---|---|
| Host | 用户交互界面、LLM 编排、对话管理 | 提示工程、UX 设计、多轮对话 | AI 应用开发者 |
| Client | 协议转换、连接管理、传输层处理 | 网络通信、序列化、错误重试 | 框架开发者(Spring AI) |
| Server | 业务能力暴露、数据访问、权限控制 | 领域模型、数据访问、业务规则 | 领域专家/后端开发 |
2.2 双向通信能力:打破传统的请求-响应模式
与传统 API 不同,MCP 支持服务器到客户端的主动通信:
大语言模型 MCP Server (图书馆) MCP Client Host 应用 大语言模型 MCP Server (图书馆) MCP Client Host 应用 用户询问:"帮我找《Spring实战》" alt [长时操作(进度过半)] opt [需要 AI 生成内容(Sampling)] opt [记录操作日志] 初始化连接 initialize 请求 能力声明 (tools/resources) 发送提示 + 可用工具列表 决定调用 searchBooks 执行工具调用 调用 searchBooks(title="Spring实战") progressNotification (50%) 更新进度条 createMessage 请求 转发采样请求 生成推荐语 返回结果 返回生成内容 返回 poem/recommendation 返回图书结果 格式化数据 loggingNotification 记录审计日志 最终回答 + 工具结果 自然语言回复
关键洞察:这种双向通信让 MCP Server 不再是被动响应的"数据库包装器",而是能够与 AI 协同工作的"智能代理"。
第三章:MCP 核心概念详解
3.1 能力原语(Capability Primitives)
MCP 定义了四种核心能力原语,构成 AI 与外部世界交互的完整词汇表:
| 原语 | 类比 | 控制权 | 用途示例 | 所有权 |
|---|---|---|---|---|
| Tools | 函数调用 | LLM 决定何时调用 | 查询图书、创建借阅、计算逾期费用 | LLM |
| Resources | 只读数据源 | 应用显式读取 | 图书目录、用户借阅历史、馆藏地图 | Host |
| Prompts | 预制模板 | 用户/应用选择 | 借书确认模板、逾期提醒话术 | Host |
| Sampling | LLM 委托 | Server 请求生成 | 生成个性化推荐语、撰写书评 | Host |
⚠️ 重要区别 :Tools 是唯一由 LLM 自主决策 调用的原语。Host 只控制"提供哪些工具描述"给 LLM,而 LLM 自主决定调用时机、顺序和参数。
3.2 传输协议(Transports)
MCP 支持多种传输机制,适应不同部署场景:
选型决策树
传输层选项
是
否
是
否
是
否
STDIO
标准输入输出
适用于本地进程
如 Claude Desktop
Streamable HTTP
单向请求-响应
无状态/有状态
生产环境首选
SSE
Server-Sent Events
单向服务器推送
传统兼容方案
本地桌面应用?
需要双向流?
简单HTTP够吗?
协议对比:
| 协议 | 连接模式 | 适用场景 | Spring AI Starter |
|---|---|---|---|
| STDIO | 本地进程通信 | Claude Desktop、本地工具 | spring-ai-starter-mcp-server-stdio |
| Streamable HTTP | 请求-响应流 | 微服务、云原生部署 | spring-ai-starter-mcp-server-webmvc |
| SSE | 服务器单向推送 | 浏览器客户端、传统兼容 | 内置于 WebMVC Starter |
第四章:实战------构建图书馆 MCP 服务器
4.1 项目初始化
创建名为 mcp-library-server 的 Spring Boot 应用,添加依赖:
xml
<dependencies>
<!-- MCP Server 支持 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>
<!-- 数据访问(简化示例使用内存存储) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
配置文件 (application.yml):
yaml
server:
port: 8080
spring:
ai:
mcp:
server:
name: library-management-server
version: 1.0.0
protocol: STREAMABLE # 可选:STDIO, STREAMABLE, SSE
# 启用H2控制台用于调试
h2:
console:
enabled: true
path: /h2-console
4.2 领域模型设计
java
@Entity
public class Book {
@Id @GeneratedValue
private Long id;
private String isbn;
private String title;
private String author;
private String category;
private boolean available;
private String location; // 馆藏位置,如 "A区-3排-2层"
// getters, setters, constructors
}
@Entity
public class BorrowRecord {
@Id @GeneratedValue
private Long id;
private Long bookId;
private String userId;
private LocalDateTime borrowDate;
private LocalDateTime dueDate;
private LocalDateTime returnDate;
// getters, setters
}
4.3 核心业务服务
java
@Service
public class LibraryService {
private final BookRepository bookRepository;
private final BorrowRecordRepository borrowRepository;
public LibraryService(BookRepository bookRepository,
BorrowRecordRepository borrowRepository) {
this.bookRepository = bookRepository;
this.borrowRepository = borrowRepository;
}
/**
* 查询图书 - 核心工具
* 支持按标题、作者、ISBN模糊查询
*/
@McpTool(description = """
查询图书馆馆藏图书。支持按标题、作者或ISBN搜索。
返回图书的可用状态、馆藏位置等关键信息。
示例:查找所有可用的Java编程书籍
""")
public List<BookInfo> searchBooks(
@McpToolParam(description = "搜索关键词,可匹配标题、作者") String keyword,
@McpToolParam(description = "是否只显示可借阅的图书", required = false) Boolean availableOnly) {
List<Book> books = bookRepository.searchByKeyword(keyword);
return books.stream()
.filter(b -> availableOnly == null || b.isAvailable() == availableOnly)
.map(this::convertToInfo)
.collect(Collectors.toList());
}
/**
* 借阅图书 - 事务性操作
*/
@McpTool(description = """
为指定用户借阅图书。需要图书ID和用户ID。
自动计算应还日期(当前日期+30天)。
返回借阅确认信息或错误原因(如图书已借出)。
""")
public BorrowResult borrowBook(
@McpToolParam(description = "图书ID", required = true) Long bookId,
@McpToolParam(description = "用户ID", required = true) String userId) {
Book book = bookRepository.findById(bookId)
.orElseThrow(() -> new BookNotFoundException("图书不存在: " + bookId));
if (!book.isAvailable()) {
return new BorrowResult(false, "该图书已被借出", null);
}
// 创建借阅记录
BorrowRecord record = new BorrowRecord();
record.setBookId(bookId);
record.setUserId(userId);
record.setBorrowDate(LocalDateTime.now());
record.setDueDate(LocalDateTime.now().plusDays(30));
borrowRepository.save(record);
// 更新图书状态
book.setAvailable(false);
bookRepository.save(book);
return new BorrowResult(true, "借阅成功", record.getDueDate());
}
/**
* 查询用户借阅历史
*/
@McpTool(description = "查询指定用户的所有借阅记录,包括当前在借和历史已还图书")
public List<BorrowHistory> getUserBorrowHistory(
@McpToolParam(description = "用户ID") String userId,
@McpToolParam(description = "是否只显示逾期未还", required = false) Boolean overdueOnly) {
List<BorrowRecord> records = borrowRepository.findByUserId(userId);
return records.stream()
.filter(r -> {
if (overdueOnly == null || !overdueOnly) return true;
return r.getReturnDate() == null &&
r.getDueDate().isBefore(LocalDateTime.now());
})
.map(this::convertToHistory)
.collect(Collectors.toList());
}
// 辅助方法...
private BookInfo convertToInfo(Book book) { /* ... */ }
private BorrowHistory convertToHistory(BorrowRecord record) { /* ... */ }
// DTO 记录定义
public record BookInfo(Long id, String isbn, String title,
String author, boolean available, String location) {}
public record BorrowResult(boolean success, String message, LocalDateTime dueDate) {}
public record BorrowHistory(Long bookId, String bookTitle,
LocalDateTime borrowDate, LocalDateTime dueDate,
LocalDateTime returnDate, boolean overdue) {}
}
4.4 架构可视化
uses
uses
annotated with @McpTool
LibraryService
+searchBooks(keyword, availableOnly) : List<BookInfo>
+borrowBook(bookId, userId) : BorrowResult
+getUserBorrowHistory(userId, overdueOnly) : List<BorrowHistory>
BookRepository
+searchByKeyword(keyword) : List<Book>
+findById(id) : Optional<Book>
+save(book) : Book
BorrowRecordRepository
+findByUserId(userId) : List<BorrowRecord>
+save(record) : BorrowRecord
McpServer
+registerTool(name, handler)
+handleRequest(request)
MCP工具暴露层\n业务逻辑与协议解耦
由Spring AI自动配置\n处理协议序列化
第五章:高级功能------双向通信与AI协作
5.1 功能概述
MCP 的真正威力在于服务器与客户端的双向智能协作。本章扩展图书馆服务器,实现:
- 进度追踪:批量导入图书时的实时进度反馈
- 结构化日志:操作审计与调试信息推送
- 智能采样(Sampling):让 AI 生成个性化推荐语
5.2 增强型图书馆服务
java
@Service
public class AdvancedLibraryService {
private static final Logger logger = LoggerFactory.getLogger(AdvancedLibraryService.class);
private final LibraryService libraryService;
private final BookRepository bookRepository;
public AdvancedLibraryService(LibraryService libraryService, BookRepository bookRepository) {
this.libraryService = libraryService;
this.bookRepository = bookRepository;
}
/**
* 批量入库 - 展示进度追踪和采样能力
*/
@McpTool(description = """
批量导入新图书到馆藏。这是一个长时操作,会实时报告进度。
每导入一本图书,可能会请求AI生成推荐语(如果客户端支持采样)。
返回导入摘要,包括成功数量、失败数量和生成的推荐语列表。
""")
public BatchImportResult batchImportBooks(
McpSyncServerExchange exchange, // (1) 注入通信上下文
@McpToolParam(description = "图书列表JSON") List<NewBookRequest> books,
@ProgressToken String progressToken) { // (2) 进度追踪令牌
int total = books.size();
int success = 0;
List<String> recommendations = new ArrayList<>();
// 记录操作开始
exchange.loggingNotification(LoggingMessageNotification.builder()
.level(LoggingLevel.INFO)
.data("开始批量导入 " + total + " 本图书")
.meta(Map.of("operator", "system", "batchSize", String.valueOf(total)))
.build());
for (int i = 0; i < books.size(); i++) {
try {
NewBookRequest bookReq = books.get(i);
// 更新进度(每本10%)
double progress = (i + 1) * 1.0 / total;
exchange.progressNotification(new ProgressNotification(
progressToken,
progress,
1.0,
"正在导入: " + bookReq.title()
));
// 保存图书
Book book = saveBook(bookReq);
success++;
// 采样:请求AI生成推荐语(如果客户端支持)
if (exchange.getClientCapabilities().sampling() != null) {
String prompt = String.format(
"为图书《%s》(作者:%s,类别:%s)生成一段吸引人的50字推荐语。",
bookReq.title(), bookReq.author(), bookReq.category()
);
CreateMessageResult samplingResult = exchange.createMessage(
CreateMessageRequest.builder()
.systemPrompt("你是一位专业图书推荐师,擅长用富有感染力的语言推荐图书。")
.messages(List.of(new SamplingMessage(
Role.USER,
new TextContent(prompt)
)))
.build()
);
String recommendation = ((TextContent) samplingResult.content()).text();
recommendations.add(bookReq.title() + ": " + recommendation);
// 记录生成的推荐语
exchange.loggingNotification(LoggingMessageNotification.builder()
.level(LoggingLevel.DEBUG)
.data("生成推荐语 for " + bookReq.title())
.meta(Map.of("bookId", book.getId().toString()))
.build());
}
} catch (Exception e) {
exchange.loggingNotification(LoggingMessageNotification.builder()
.level(LoggingLevel.ERROR)
.data("导入失败: " + e.getMessage())
.meta(Map.of("index", String.valueOf(i)))
.build());
}
}
// 完成通知
exchange.progressNotification(new ProgressNotification(
progressToken, 1.0, 1.0, "导入完成"
));
return new BatchImportResult(total, success, total - success, recommendations);
}
/**
* 智能图书推荐 - 基于用户历史使用采样生成个性化建议
*/
@McpTool(description = """
基于用户的借阅历史,生成个性化的图书推荐。
分析用户偏好,并通过AI采样生成人性化的推荐文案。
""")
public PersonalizedRecommendation recommendBooksForUser(
McpSyncServerExchange exchange,
@McpToolParam(description = "用户ID") String userId) {
// 获取用户历史
List<BorrowHistory> history = libraryService.getUserBorrowHistory(userId, false);
// 分析偏好类别
Map<String, Long> categoryCounts = history.stream()
.collect(Collectors.groupingBy(BorrowHistory::category, Collectors.counting()));
String favoriteCategory = categoryCounts.entrySet().stream()
.max(Map.Entry.comparingByValue())
.map(Map.Entry::getKey)
.orElse("通用");
// 查找该类别热门图书
List<Book> recommendations = bookRepository.findTop5ByCategoryAndAvailable(favoriteCategory, true);
// 使用Sampling生成个性化消息
String message = "根据您的借阅历史,您可能喜欢" + favoriteCategory + "类图书。";
if (exchange.getClientCapabilities().sampling() != null && !recommendations.isEmpty()) {
String booksList = recommendations.stream()
.map(b -> "《"+b.getTitle()+"》")
.collect(Collectors.joining("、"));
CreateMessageResult result = exchange.createMessage(
CreateMessageRequest.builder()
.systemPrompt("你是一位贴心的图书馆管理员,了解用户阅读品味。")
.messages(List.of(new SamplingMessage(Role.USER,
new TextContent("用户喜欢" + favoriteCategory +
"类图书,请为以下图书生成一段温暖的推荐语:" + booksList))))
.build()
);
message = ((TextContent) result.content()).text();
}
return new PersonalizedRecommendation(favoriteCategory,
recommendations.stream().map(Book::getTitle).toList(),
message);
}
public record NewBookRequest(String isbn, String title, String author, String category, String location) {}
public record BatchImportResult(int total, int success, int failed, List<String> aiRecommendations) {}
public record PersonalizedRecommendation(String preferredCategory, List<String> recommendedBooks, String personalMessage) {}
}
5.3 双向通信流程详解
大语言模型 Library Server MCP Client Host应用 Spring AI Client 大语言模型 Library Server MCP Client Host应用 Spring AI Client 批量导入10本图书场景 alt [客户端支持 Sampling] [不支持 Sampling] loop [剩余8本图书] batchImportBooks(books) progressNotification(10%) 显示进度: 正在导入: 《Java核心技术》 loggingNotification(INFO, "开始导入...") 记录审计日志 createMessage(生成推荐语请求) 转发采样请求 生成推荐文案 "这是一本经典的Java教程..." 返回生成内容 TextContent loggingNotification(DEBUG, "推荐语已生成") 使用默认描述 progressNotification(20%) 更新进度条 重复导入流程 progressNotification(30%-100%) 返回 BatchImportResult (包含AI生成的推荐语列表) 汇总结果并生成自然语言回复 "已成功导入10本图书, 其中《Java核心技术》推荐语是..."
关键机制解析:
- McpSyncServerExchange :通信上下文对象,提供
loggingNotification()、progressNotification()、createMessage()等方法 - @ProgressToken:Spring AI 自动注入的唯一标识符,用于关联进度更新与具体操作
- Sampling 能力协商 :
exchange.getClientCapabilities().sampling()检查客户端是否支持 AI 生成,避免调用失败
第六章:构建 MCP 客户端------连接图书馆服务
6.1 项目配置
创建 mcp-library-client 项目,添加依赖:
xml
<dependencies>
<!-- MCP Client -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
<!-- 选择你的LLM提供商 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-anthropic</artifactId>
</dependency>
<!-- 或 OpenAI -->
<!-- <dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency> -->
</dependencies>
配置 (application.yml):
yaml
spring:
main:
web-application-type: none # CLI应用,非Web
ai:
anthropic:
api-key: ${ANTHROPIC_API_KEY}
mcp:
client:
streamable-http:
connections:
library-server:
url: http://localhost:8080
# 可选配置
request-timeout: 30s
headers:
X-API-Version: 1.0
6.2 客户端应用实现
java
@SpringBootApplication
public class LibraryClientApplication {
public static void main(String[] args) {
SpringApplication.run(LibraryClientApplication.class, args).close();
}
@Bean
public ChatClient chatClient(ChatClient.Builder builder) {
return builder.build();
}
@Bean
public CommandLineRunner interactiveSession(
ChatClient chatClient,
ToolCallbackProvider mcpToolProvider, // 自动注入所有MCP工具
McpClientHandlers mcpHandlers) { // 自定义处理器
return args -> {
Scanner scanner = new Scanner(System.in);
System.out.println("🎉 欢迎使用智能图书馆助手!");
System.out.println("输入 'exit' 退出,输入 'help' 查看示例问题。");
System.out.println("已连接到图书馆服务器:" +
mcpToolProvider.getToolCallbacks().size() + " 个工具可用");
// 显示可用工具(调试用)
mcpToolProvider.getToolCallbacks().forEach(tool ->
System.out.println(" - " + tool.getToolDefinition().name())
);
while (true) {
System.out.print("\n📚 你的问题: ");
String input = scanner.nextLine();
if ("exit".equalsIgnoreCase(input)) break;
if ("help".equalsIgnoreCase(input)) {
showHelp();
continue;
}
try {
String response = chatClient.prompt(input)
.toolCallbacks(mcpToolProvider) // 关键:注入MCP工具
.toolContext(Map.of("progressToken", "pt-" + System.currentTimeMillis()))
.call()
.content();
System.out.println("\n🤖 助手回复:\n" + response);
} catch (Exception e) {
System.err.println("错误: " + e.getMessage());
}
}
System.out.println("感谢使用,再见!");
};
}
private void showHelp() {
System.out.println("""
示例问题:
- "我想借《Spring实战》,书号12345,用户U001"
- "查找所有关于人工智能的可用图书"
- "用户U001有哪些逾期未还的书?"
- "推荐几本适合用户U001的书,并说明理由"
""");
}
}
6.3 客户端处理器------响应服务器主动通信
java
@Service
public class McpClientHandlers {
private static final Logger logger = LoggerFactory.getLogger(McpClientHandlers.class);
private final ChatClient chatClient;
public McpClientHandlers(@Lazy ChatClient chatClient) {
this.chatClient = chatClient;
}
/**
* 进度处理器 - 实时显示服务器操作进度
*/
@McpProgress(clients = "library-server") // 关联特定服务器
public void handleProgress(ProgressNotification notification) {
int percentage = (int) (notification.progress() * 100);
String bar = "█".repeat(percentage / 5) + "░".repeat(20 - percentage / 5);
System.out.printf("\r⏳ 进度 [%s] %d%% - %s",
bar, percentage, notification.message());
if (notification.progress() >= 1.0) {
System.out.println(); // 完成时换行
}
logger.info("MCP进度更新 [{}]: {}%", notification.progressToken(), percentage);
}
/**
* 日志处理器 - 记录服务器操作日志
*/
@McpLogging(clients = "library-server")
public void handleLogging(LoggingMessageNotification log) {
switch (log.level()) {
case DEBUG -> logger.debug("MCP[{}]: {}", log.level(), log.data());
case ERROR -> logger.error("MCP[{}]: {}", log.level(), log.data());
default -> logger.info("MCP[{}]: {}", log.level(), log.data());
}
// 实时显示重要日志到控制台
if (log.level() == LoggingLevel.INFO || log.level() == LoggingLevel.ERROR) {
System.out.printf("📝 [%s] %s%n", log.level(), log.data());
}
}
/**
* 采样处理器 - 服务器请求AI生成内容时的处理
* 这是 MCP 最强大特性:服务器可以"借用"客户端的LLM能力
*/
@McpSampling(clients = "library-server")
public CreateMessageResult handleSampling(CreateMessageRequest request) {
logger.info("收到采样请求,系统提示: {}", request.systemPrompt());
// 使用客户端配置的ChatClient处理生成请求
String content = chatClient.prompt()
.system(request.systemPrompt())
.user(extractUserMessage(request))
.call()
.content();
return CreateMessageResult.builder()
.content(new TextContent(content))
.model("claude-3-opus") // 标识使用的模型
.stopReason(StopReason.END_TURN)
.build();
}
private String extractUserMessage(CreateMessageRequest request) {
return request.messages().stream()
.filter(m -> m.role() == Role.USER)
.findFirst()
.map(m -> ((TextContent) m.content()).text())
.orElse("");
}
}
6.4 客户端架构可视化
☁️ 外部服务与资源
🖥️ MCP Client Application (Spring Boot)
🔗 传输层
🛠️ MCP 处理器层
🍃 Spring AI 核心层
MCP 协议通信
提示词/响应
采样请求
进度更新
日志流
UI更新
控制台输出
🚀 命令行界面
CommandLineRunner
💬 ChatClient
对话编排核心
🔌 ToolCallbackProvider
工具自动发现
📊 @McpProgress
进度条显示
📝 @McpLogging
日志记录
🎲 @McpSampling
内容生成/采样
🌐 HTTP Client
连接 library-server
🧠 Anthropic Claude
大语言模型
📚 Library MCP Server
localhost:8080
第七章:多服务器编排与高级场景
7.1 多服务器配置
实际应用中,AI 助手往往需要同时连接多个 MCP 服务器(图书馆系统、用户认证、支付系统等):
yaml
spring:
ai:
mcp:
client:
# 服务器1:图书馆核心服务(HTTP)
streamable-http:
connections:
library-core:
url: http://library.internal:8080
library-analytics:
url: http://analytics.internal:8081
# 服务器2:用户认证服务(STDIO本地进程)
stdio:
connections:
auth-server:
command: java
args:
- "-jar"
- "/opt/mcp/auth-server.jar"
env:
DB_URL: jdbc:postgresql://authdb:5432/users
# 服务器3:邮件通知服务(SSE)
sse:
connections:
notification-service:
url: http://notify.internal:8082/sse
7.2 多服务器架构图
MCP Server Ecosystem
MCP Client Layer
🏠 Host AI Application
可用工具集
ChatClient
统一编排
ToolCallbackProvider
聚合多个服务器工具
library-core
searchBooks
library-core
borrowBook
auth-server
verifyUser
notification-service
sendEmail
HTTP Transport
library-core
STDIO Transport
auth-server
SSE Transport
notification-service
📚 Library Server
🔐 Auth Server
📧 Notification Server
图书数据库
用户数据库
SMTP服务
7.3 跨服务器工作流示例
场景:用户说"帮我借《AI时代》,并发送确认邮件到我的邮箱"
java
// LLM 自动规划执行链:
// 1. 调用 auth-server/verifyUser 验证权限
// 2. 调用 library-core/searchBooks 查找图书ID
// 3. 调用 library-core/borrowBook 执行借阅
// 4. 调用 notification-service/sendEmail 发送确认
客户端代码无需显式编排,LLM 通过分析工具描述自动决定调用顺序:
java
@Bean
public CommandLineRunner complexWorkflow(
ChatClient chatClient,
ToolCallbackProvider mcpTools) {
return args -> {
String complexQuery = """
我是用户U001,想借阅《人工智能:一种现代的方法》。
借到后请发送确认邮件到我的注册邮箱,并告诉我应还日期。
""";
// LLM 自动处理多步骤流程
String result = chatClient.prompt(complexQuery)
.toolCallbacks(mcpTools) // 包含所有3个服务器的工具
.call()
.content();
System.out.println(result);
// 输出将包含:借阅确认 + 应还日期 + 邮件发送状态
};
}
第八章:错误处理、安全与最佳实践
8.1 错误处理模式
注:错误处理策略。
java
@Service
public class RobustLibraryService {
/**
* 健壮的工具实现模式
*/
@McpTool(description = "安全借阅图书,包含完整错误处理")
public BorrowResult safeBorrowBook(
McpSyncServerExchange exchange,
@McpToolParam(description = "图书ID") Long bookId,
@McpToolParam(description = "用户ID") String userId) {
try {
// 1. 前置条件检查
if (bookId == null || userId == null || userId.isBlank()) {
return new BorrowResult(false, "参数错误:图书ID和用户ID不能为空", null);
}
// 2. 记录操作尝试
exchange.loggingNotification(LoggingMessageNotification.builder()
.level(LoggingLevel.INFO)
.data("尝试借阅: 图书" + bookId + " 用户" + userId)
.build());
// 3. 业务逻辑(带超时)
return executeWithTimeout(() -> {
// ... 借阅逻辑
return new BorrowResult(true, "成功", LocalDateTime.now().plusDays(30));
}, Duration.ofSeconds(5));
} catch (BookNotFoundException e) {
exchange.loggingNotification(LoggingMessageNotification.builder()
.level(LoggingLevel.WARNING)
.data("图书不存在: " + bookId)
.build());
return new BorrowResult(false, "图书不存在", null);
} catch (ConcurrentBorrowException e) {
// 乐观锁冲突,图书刚被他人借走
return new BorrowResult(false, "图书刚刚被借走,请刷新重试", null);
} catch (Exception e) {
exchange.loggingNotification(LoggingMessageNotification.builder()
.level(LoggingLevel.ERROR)
.data("系统错误: " + e.getMessage())
.build());
// 不向客户端暴露内部错误细节
return new BorrowResult(false, "服务暂不可用,请稍后重试", null);
}
}
private <T> T executeWithTimeout(Supplier<T> action, Duration timeout) {
// 实现超时控制,防止阻塞LLM
return CompletableFuture.supplyAsync(action)
.orTimeout(timeout.toMillis(), TimeUnit.MILLISECONDS)
.join();
}
}
8.2 安全最佳实践
| 风险点 | 防护措施 | 实现方式 |
|---|---|---|
| 工具注入攻击 | 参数校验与 SQL 注入防护 | 使用 JPA 参数绑定,避免字符串拼接 SQL |
| 敏感数据泄露 | 字段级脱敏 | DTO 转换时隐藏用户手机号、身份证等 |
| 未授权访问 | MCP 层认证 | 配置 spring.ai.mcp.server.security.api-key |
| 采样滥用 | 采样配额限制 | 客户端限制每小时采样调用次数 |
| 进度令牌伪造 | 令牌签名验证 | 使用 JWT 签名 progressToken |
安全配置示例:
yaml
spring:
ai:
mcp:
server:
security:
enabled: true
api-key-header: X-MCP-API-Key
# 生产环境使用 OAuth2
oauth2:
issuer-uri: https://auth.company.com
audience: mcp-library-server
8.3 性能优化建议
-
连接池配置:
yamlspring: ai: mcp: client: streamable-http: connections: library-server: url: http://localhost:8080 pool: max-connections: 20 max-per-route: 10 -
工具描述优化 :详细的
@McpTool描述帮助 LLM 更准确地选择工具,减少不必要的调用。 -
采样缓存:对重复的采样请求(如相同图书的推荐语)使用本地缓存,避免重复调用 LLM。
-
异步处理:长时操作(如批量导入)应返回任务ID,通过进度通知异步更新,而非阻塞等待。
第九章:小结
9.1 核心概念回顾
MCP
核心概念
架构
Host: 用户交互界面
Client: 协议通信
Server: 能力提供者
能力原语
Tools: LLM控制,函数调用
Resources: 只读数据
Prompts: 预制模板
Sampling: AI生成委托
通信模式
请求响应: 工具调用
服务器推送: 日志,进度
双向采样: Server请求AI生成
SpringAI集成
注解驱动: @McpTool
自动配置: Starter依赖
类型安全: 强类型参数
9.2 演进路线图
MCP 生态正在快速发展,Spring AI 将在以下方向持续增强:
- Authorization 2.0 支持:OAuth2 与 JWT 令牌的标准化集成
- Resources 完整实现:当前版本 Tools 支持最完善,Resources 和 Prompts 将在后续版本增强
- Streaming 支持:流式响应支持,用于长文本生成场景
- AOT 原生镜像:GraalVM 原生镜像支持,降低 Serverless 部署冷启动时间
9.3 决策 checklist
在开始 MCP 项目前,评估以下要素:
- 是否适合 MCP? 需要 LLM 动态决策调用的场景,而非固定流程
- 协议选择:本地工具用 STDIO,云服务用 HTTP,浏览器用 SSE
- 采样需求:是否需要 Server 借助 Client 的 AI 能力?
- 错误边界:每个工具都有完善的降级策略吗?
- 监控埋点:通过 Logging 通知实现操作审计了吗?
附录:完整代码仓库结构
mcp-library-demo/
├── mcp-library-server/ # MCP服务器
│ ├── src/main/java/
│ │ └── com/example/library/
│ │ ├── McpServerApplication.java
│ │ ├── service/
│ │ │ ├── LibraryService.java # 基础CRUD
│ │ │ └── AdvancedLibraryService.java # 采样与进度
│ │ ├── entity/ # JPA实体
│ │ └── repository/ # 数据访问
│ └── pom.xml
│
├── mcp-library-client/ # MCP客户端
│ ├── src/main/java/
│ │ └── com/example/client/
│ │ ├── LibraryClientApplication.java
│ │ └── handler/
│ │ └── McpClientHandlers.java # 进度/日志/采样处理
│ └── application.yml # 多服务器配置
│
└── docker-compose.yml # 一键启动环境
下一步:基于此架构,您可以:
- 将图书馆系统替换为您的领域服务(ERP、CRM、IoT平台)
- 添加更多的 MCP Servers 构建 AI 中台
- 集成到 Claude Desktop 或 Cursor 提供本地知识库问答