【Spring AI】从一个MCP小实例开始

前置开发环境:

组件 版本要求 说明
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.51.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.51.1.4


第二章:MCP架构

2.1 客户端-服务器架构

MCP 采用经典的分层架构,明确划分了三个核心角色:

  1. 调用工具
  2. MCP协议通信
  3. 数据源
    🖥️ 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 的真正威力在于服务器与客户端的双向智能协作。本章扩展图书馆服务器,实现:

  1. 进度追踪:批量导入图书时的实时进度反馈
  2. 结构化日志:操作审计与调试信息推送
  3. 智能采样(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核心技术》推荐语是..."

关键机制解析:

  1. McpSyncServerExchange :通信上下文对象,提供 loggingNotification()progressNotification()createMessage() 等方法
  2. @ProgressToken:Spring AI 自动注入的唯一标识符,用于关联进度更新与具体操作
  3. 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 性能优化建议

  1. 连接池配置

    yaml 复制代码
    spring:
      ai:
        mcp:
          client:
            streamable-http:
              connections:
                library-server:
                  url: http://localhost:8080
                  pool:
                    max-connections: 20
                    max-per-route: 10
  2. 工具描述优化 :详细的 @McpTool 描述帮助 LLM 更准确地选择工具,减少不必要的调用。

  3. 采样缓存:对重复的采样请求(如相同图书的推荐语)使用本地缓存,避免重复调用 LLM。

  4. 异步处理:长时操作(如批量导入)应返回任务ID,通过进度通知异步更新,而非阻塞等待。


第九章:小结

9.1 核心概念回顾

MCP

核心概念
架构
Host: 用户交互界面
Client: 协议通信
Server: 能力提供者
能力原语
Tools: LLM控制,函数调用
Resources: 只读数据
Prompts: 预制模板
Sampling: AI生成委托
通信模式
请求响应: 工具调用
服务器推送: 日志,进度
双向采样: Server请求AI生成
SpringAI集成
注解驱动: @McpTool
自动配置: Starter依赖
类型安全: 强类型参数

9.2 演进路线图

MCP 生态正在快速发展,Spring AI 将在以下方向持续增强:

  1. Authorization 2.0 支持:OAuth2 与 JWT 令牌的标准化集成
  2. Resources 完整实现:当前版本 Tools 支持最完善,Resources 和 Prompts 将在后续版本增强
  3. Streaming 支持:流式响应支持,用于长文本生成场景
  4. 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           # 一键启动环境

下一步:基于此架构,您可以:

  1. 将图书馆系统替换为您的领域服务(ERP、CRM、IoT平台)
  2. 添加更多的 MCP Servers 构建 AI 中台
  3. 集成到 Claude Desktop 或 Cursor 提供本地知识库问答

相关推荐
天地沧海2 小时前
自动测试平台里的智能编排到底怎么设计
人工智能
Aaron15882 小时前
RFSOC+VU13P中在线部分可重构技术的应用分析
人工智能·算法·matlab·fpga开发·重构·信息与通信·信号处理
明月_清风2 小时前
告别碎片化收藏:基于 LLM Wiki 搭建“自动生长”的个人深度知识库
人工智能
计算机魔术师2 小时前
【技术硬核 | 存储】ClickHouse 原理与 Langfuse 存储实践:当 LLM Trace 爆炸时,PG 还扛得住吗?
人工智能·clickhouse·工程实践·sbti·职场焦虑
Rick19932 小时前
Spring Boot自动装配原理
java·spring boot·后端
manduic2 小时前
昆泰芯 KTH5701 三轴霍尔传感器 如何从根源解决摇杆漂移,升级智能交互体验
人工智能·交互
yanghuashuiyue2 小时前
langchain AI应用框架研究【前端-篇二】
人工智能·python·langchain
我命由我123452 小时前
Android Jetpack Compose - 组件分类:布局组件、交互组件、文本组件
android·java·java-ee·kotlin·android studio·android jetpack·android-studio
档案宝档案管理2 小时前
2026档案管理系统排名解析,易用性+安全性双维度对比
大数据·数据库·人工智能·档案管理