Spring AI MCP 技术从使用到项目实战深度解析

目录

一、前言

[二、Function Calling 介绍](#二、Function Calling 介绍)

[2.1 什么是 Function Calling](#2.1 什么是 Function Calling)

[2.1.1 工作原理](#2.1.1 工作原理)

[2.2.2 应用场景和优势](#2.2.2 应用场景和优势)

[2.3 Spring AI Function Calling 介绍](#2.3 Spring AI Function Calling 介绍)

[2.3.1 核心特点](#2.3.1 核心特点)

[2.3.2 Spring AI Function Calling 应用场景](#2.3.2 Spring AI Function Calling 应用场景)

[2.3.3 使用流程](#2.3.3 使用流程)

三、前置案例操作演示

[3.1 代码操作过程](#3.1 代码操作过程)

[3.1.1 增加依赖](#3.1.1 增加依赖)

[3.1.2 添加配置信息](#3.1.2 添加配置信息)

[3.1.3 增加配置类](#3.1.3 增加配置类)

[3.1.4 添加测试接口](#3.1.4 添加测试接口)

[3.1.5 接口效果测试](#3.1.5 接口效果测试)

[四、Tool 操作案例演示](#四、Tool 操作案例演示)

[4.1 Tool Calling 操作案例一](#4.1 Tool Calling 操作案例一)

[4.2 Tool Calling 操作案例二](#4.2 Tool Calling 操作案例二)

[4.3 Tool Calling 操作案例三](#4.3 Tool Calling 操作案例三)

[五、Spring AI Tool 操作数据库实战](#五、Spring AI Tool 操作数据库实战)

[5.1 前置准备过程](#5.1 前置准备过程)

[5.1.1 添加配置文件](#5.1.1 添加配置文件)

[5.1.2 添加测试数据表](#5.1.2 添加测试数据表)

[5.2 代码操作演示](#5.2 代码操作演示)

[5.2.1 增加book的实体类](#5.2.1 增加book的实体类)

[5.2.2 业务实现基本增删改查](#5.2.2 业务实现基本增删改查)

[5.2.3 自定义Tool工具类](#5.2.3 自定义Tool工具类)

[5.2.4 自定义ChatClient](#5.2.4 自定义ChatClient)

[5.2.5 mybatis层操作数据表](#5.2.5 mybatis层操作数据表)

[5.2.6 增加测试接口](#5.2.6 增加测试接口)

[5.3 接口效果测试](#5.3 接口效果测试)

[5.3.1 新增数据测试](#5.3.1 新增数据测试)

[5.3.2 查询数据测试](#5.3.2 查询数据测试)

[六、MCP 技术介绍](#六、MCP 技术介绍)

[6.1 什么是MCP](#6.1 什么是MCP)

[6.2 MCP 核心特点](#6.2 MCP 核心特点)

[6.3 MCP 核心价值](#6.3 MCP 核心价值)

[6.4 MCP 与Function Calling 区别](#6.4 MCP 与Function Calling 区别)

[七、Spring AI MCP 架构介绍](#七、Spring AI MCP 架构介绍)

[7.1 整体架构](#7.1 整体架构)

[7.1.1 三层架构实现说明](#7.1.1 三层架构实现说明)

[7.2 服务端与客户端](#7.2 服务端与客户端)

[7.2.1 MCP 服务端](#7.2.1 MCP 服务端)

[7.2.2 MCP 客户端](#7.2.2 MCP 客户端)

[7.3 MCP中SSE 和 STDIO两种模式](#7.3 MCP中SSE 和 STDIO两种模式)

[7.3.1 SSE模式介绍](#7.3.1 SSE模式介绍)

[7.3.2 STDIO模式介绍](#7.3.2 STDIO模式介绍)

[7.3.3 SSE 与STDIO对比](#7.3.3 SSE 与STDIO对比)

[八、Spring AI MCP 案例操作演示](#八、Spring AI MCP 案例操作演示)

[8.1 构建MCP Server服务](#8.1 构建MCP Server服务)

[8.1.1 pom导入依赖](#8.1.1 pom导入依赖)

[8.1.2 添加配置文件](#8.1.2 添加配置文件)

[8.1.3 提供Tool 工具类](#8.1.3 提供Tool 工具类)

[8.1.4 注册工具类](#8.1.4 注册工具类)

[8.2 构建MCP Client服务](#8.2 构建MCP Client服务)

[8.2.1 导入核心依赖](#8.2.1 导入核心依赖)

[8.2.2 增加配置信息](#8.2.2 增加配置信息)

[8.2.3 增加一个对话接口](#8.2.3 增加一个对话接口)

[8.2.4 效果测试](#8.2.4 效果测试)

[8.3 MCP 底层原理解析](#8.3 MCP 底层原理解析)

[8.3.1 server端底层执行逻辑](#8.3.1 server端底层执行逻辑)

[8.3.2 client 端底层执行逻辑](#8.3.2 client 端底层执行逻辑)

九、写在文末


一、前言

在人工智能与软件开发深度融合时代,Spring AI 作为一个强大的连接AI大模型与应用开发的技术框架,持续为开发者提供着高效且便捷的工具,从而实现与大语言模型(LLM)的无缝交互。Spring AI 近期正式发布Release版,并引入一系列令人瞩目的特性,其中 Function Calling 到 Tool Calling 的转换以及模型上下文协议(MCP)的应用,标志着该框架在 AI 集成领域的又一次重大飞跃。本文将详细介绍Spring AI MCP技术的使用。

二、Function Calling 介绍

要说清楚Spring AI MCP这个技术,需要首先深入学习和了解一下Function Calling,因为这个是后面探讨MCP技术的关键。

2.1 什么是 Function Calling

Function Calling,即函数调用,是大型语言模型(LLM)中的一项重要功能,它允许AI模型在需要时调用外部函数或工具来完成特定任务。目前很多主流大模型都已经支持Function Calling的能力,可以认为是大模型的标配。

Function Calling 是指AI模型能够识别用户请求中需要执行特定操作的场景,然后以结构化格式输出这些操作请求,让应用程序可以实际执行这些函数。

官方文档:Tool Calling :: Spring AI Reference

2.1.1 工作原理

Function Calling 执行原理如下:

  • 用户输入:用户提出需要调用外部功能的问题或请求

  • 模型分析:AI模型分析请求并决定是否需要调用函数

  • 生成调用请求:如果需要,模型会生成包含函数名和参数的JSON格式请求

  • 执行函数:应用程序接收并执行实际函数

  • 返回结果:函数结果返回给模型,模型再生成最终响应

2.2.2 应用场景和优势

Function Calling 主要包含下面使用场景:

  • 数据检索:从数据库或API获取最新信息

  • 数学计算:执行复杂计算

  • 系统操作:与外部系统交互

  • 多工具协作:串联多个工具完成复杂任务

  • 实时信息获取:获取模型训练数据之外的实时信息

Function Calling 具备如下优势:

  • 扩展能力:突破模型固有知识限制

  • 准确性:通过专门工具获得更精确结果

  • 实时性:获取最新信息而非训练时的静态知识

  • 灵活性:可根据需求集成各种专业工具

2.3 Spring AI Function Calling 介绍

Spring AI 是 Spring 官方推出的 AI 应用开发框架,它简化了在 Spring 应用中集成 AI 功能的过程,包括 Function Calling 功能。

Spring AI Function Calling 允许你的 Spring 应用与 AI 模型交互时,动态调用 Java 方法作为外部函数,从而扩展 AI 的能力。

如下,是Spring AI Function Calling的执行过程:

2.3.1 核心特点

Spring AI Function Calling 具有如下特点:

  • 与 Spring 生态无缝集成:利用 Spring 的依赖注入和 AOP 等特性

  • 声明式函数注册:通过注解简单定义可调用函数

  • 类型安全:基于 Java 方法签名,避免 JSON 解析错误

  • 自动参数转换:AI 提供的参数自动转换为 Java 类型

2.3.2 Spring AI Function Calling 应用场景

Tool Calling 应用场景:

  • 信息检索

    • 用于从外部来源(例如数据库、Web 服务、文件系统或 Web搜索引擎)检索信息。

    • 其目标是增强模型的知识,使其能够回答原本无法回答的问题。因此,它们可用于检索增强生成 (RAG)场景。

      • 例如,可以使用工具检索给定位置的当前天气、检索最新新闻文章或查询数据库中的特定记录。
  • 采取行动

    • 使用工具用于在软件系统中采取行动,例如发送电子邮件、在数据库中创建新记录、提交表单或触发工作流。

    • 目标是自动化原本需要人工干预或明确编程的任务。例如,可以使用工具为与聊天机器人交互的客户预订航班、在网页上填写表单,或在代码生成场景中基于自动化测试 (TDD) 实现 Java 类。

      • 尽管我们通常将工具调用称为模型功能,但实际上工具调用逻辑是由客户端应用程序提供的。模型只能请求工具调用并提供输入参数,而应用程序负责根据输入参数执行工具调用并返回结果。

2.3.3 使用流程

tools 使用流程如下:

  • 定义tool:通过注解或接口定义工具的功能和描述。

  • 注册tool:将工具注册到 LLM 的调用上下文中。

  • 调用tool:在用户输入中触发工具调用,LLM 根据工具的描述生成调用请求。

三、前置案例操作演示

在开始深入了解和使用Function Calling之前,接下来先通过一个实际案例,演示下现有大模型在处理一些特定场景下业务时遇到的问题。

3.1 代码操作过程

3.1.1 增加依赖

在springboot工程中添加如下依赖,版本可以根据自己的情况选择

java 复制代码
<dependency>
    <groupId>com.alibaba.cloud.ai</groupId>
    <artifactId>spring-ai-alibaba-starter</artifactId>
    <version>1.0.0-M6.1</version>
</dependency>

3.1.2 添加配置信息

配置文件中增加如下配置信息

java 复制代码
server:
  port: 8081
#使用阿里云百炼平台的apikey
spring:
  ai:
    dashscope:
      api-key: 你的apikey
      chat:
        options:
          model: qwen-plus

3.1.3 增加配置类

增加一个配置类,用于初始化ChatClient,可以在ChatClient对象中初始化一些参数信息,比如设置会话记忆,tool,系统角色等信息,参考下面的代码:

java 复制代码
package com.congge.config;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;

@Configuration
public class ChatConfig {

    @Bean
    public ChatClient chatClient(ChatClient.Builder chatClientBuilder) throws IOException {

        var chatClient = chatClientBuilder
                .defaultSystem("You are a helpful assistant.")
                .defaultAdvisors(new SimpleLoggerAdvisor()) // LOG
                .build();
        return chatClient;
    }

}

3.1.4 添加测试接口

增加下面的一个接口用于效果测试

java 复制代码
package com.congge.web;

import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/ai/tool")
public class ToolChatController {

    @Resource
    private ChatClient chatClient;

    //localhost:8081/ai/tool/chat?message=北京现在天气如何
    @GetMapping("/chat")
    public String completion(@RequestParam("message") String message) {
        return chatClient
                .prompt()
                .user(message)
                .call()
                .content();
    }
}

3.1.5 接口效果测试

调用一下上面接口,问一个问题,比如:北京今天的天气,通过接口响应不难看出大模型是无法回答这个问题的

  • 出现这个问题的根本原因在于这个问题超出了目前使用的这个大模型的认知范围,此时就要借助 tool来进行实现

四、Tool 操作案例演示

基于大模型自身局限,接下来使用Tool来完善大模型能力,通过下面几个案例近距离感受下Function Calling的魅力。

4.1 Tool Calling 操作案例一

在下面的案例中,我们希望通过提问给AI大模型,然后大模型能够调用我们自定义的Tool 来查询系统时间,首先定义一个工具配置类,参考下面的代码:

java 复制代码
package com.congge.tool;

import java.util.function.Function;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Description;

@Configuration
public class FunctionTools {

    private static final Logger logger = LoggerFactory.getLogger(FunctionTools.class);

    public record AddOperationRequest(int d1, int d2) {

    }

    public record MulOperationRequest(int d1, int d2) {

    }

    @Bean
    @Description("加法运算")
    public Function<AddOperationRequest, Integer> addOperation() {
        return request -> {
            logger.info("加法运算函数被调用了:" + request.d1 + "," + request.d2);
            return request.d1 + request.d2;
        };
    }

    @Bean
    @Description("乘法运算")
    public Function<MulOperationRequest, Integer> mulOperation() {
        return request -> {
            logger.info("乘法运算函数被调用了:" + request.d1 + "," + request.d2);
            return request.d1 * request.d2;
        };
    }

}

增加一个测试接口,参考下面的代码

java 复制代码
package com.congge.web;


import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/ai/function")
public class FunctionCallController {

    @Autowired
    private ChatModel chatModel;

    //localhost:8081/ai/function/chat?userMessage=你是谁
    //localhost:8081/ai/function/chat?userMessage=3加7等于几
    @GetMapping(value = "/chat", produces = MediaType.APPLICATION_STREAM_JSON_VALUE)
    public String ragJsonText(@RequestParam(value = "userMessage") String userMessage){
        return ChatClient.builder(chatModel)
                .build()
                .prompt()
                .system("""
                  您是算术计算器的代理。
                  您能够支持加法运算、乘法运算等操作,其余功能将在后续版本中添加,如果用户问的问题不支持请告知详情。
                   在提供加法运算、乘法运算等操作之前,您必须从用户处获取如下信息:两个数字,运算类型。
                   请调用自定义函数执行加法运算、乘法运算。
                   请讲中文。
               """)
                .user(userMessage)
                .functions("addOperation", "mulOperation")
                .call()
                .content();
    }

}

启动工程后,调用一下接口,从效果来看,很明显底层调用了我们自己编写的工具方法

4.2 Tool Calling 操作案例二

下面这个案例中,向大模型提问,大模型调用我们自定义的tool工具类,从而获得当前系统时间,首先定义一个tool的工具配置类

java 复制代码
package com.congge.tool;

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Component
public class DateTimeTools {

    @Tool(description = "Get the current date and time in the user's timezone")
    String getCurrentDateTime() {
        return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
    }

    @Tool(description = "Set a user alarm for the given time, provided in ISO-8601 format")
    void setAlarm(String time) {
        LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
        System.out.println("Alarm set for " + alarmTime);
    }

}

增加一个测试用的接口

java 复制代码
private ChatClient chatClient;

/**
 * localhost:8081/deepseek/tool?prompt=今天是几号
 * @param prompt
 * @return
 */
@GetMapping("/tool")
public String get(@RequestParam(value = "prompt", required = false) String prompt) {
    return chatClient
            .prompt(prompt)
            .tools(new DateTimeTools())
            .call()
            .content();
}

4.3 Tool Calling 操作案例三

使用Tool还可以远程调用外部API执行特定动作,比如发送邮件、短信、预定机票等,在接下来的案例中,使用Tool实现一个调用外部API接口获取真实的天气信息功能。首先增加一个Tool的工具类

java 复制代码
package com.congge.tool;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;

import java.net.URI;
import java.util.Map;

@Component
public class WeatherTools {

    @Tool(description = "获取当前天气预报")
    public String getCurrentWeather(String city) {
        RestClient client = RestClient.create(
                URI.create("https://api.vvhan.com")) ;
        Map<?, ?> result = client.get()
                .uri("/api/weather?city={0}", city)
                .retrieve()
                .body(Map.class) ;
        try {
            return new ObjectMapper().writeValueAsString(result) ;
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e) ;
        }
    }
}

增加一个测试接口,参考下面的代码

java 复制代码
package com.congge.web;

import com.congge.tool.WeatherTools;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/weather/chat")
public class WeatherChatController {

    private final WeatherTools weatherTools ;

    private final ChatClient chatClient ;

    public WeatherChatController(WeatherTools weatherTools, ChatClient chatClient) {
        this.weatherTools = weatherTools ;
        this.chatClient = chatClient ;
    }

    //localhost:8081/weather/chat/get?prompt=查询今天上海天气
    @GetMapping("/get")
    public ResponseEntity<String> getCurrentWeather(String prompt) {
        String response = this.chatClient
                .prompt(prompt)
                .tools(new WeatherTools())
                .call().content() ;
        return ResponseEntity.ok(response) ;
    }

}

启动工程后,调用下接口,当询问获取上海的天气时能够得到上海今天的天气情况

五、Spring AI Tool 操作数据库实战

在AI大模型应用开发中,使用 Spring AI+Tools 可以完成很多定制化场景下业务开发,与大模型结合,甚至可以完成很多复杂的功能。Tools 赋予了大语言模型与外部工具或API交互能力,使其能动态调用函数执行复杂任务。其核心作用包括:

  • 突破模型文本生成的局限,实现查询天气、调用数据库、调用三方平台API等实时操作能力;

  • 构建多步骤智能体,支持旅行规划、电商下单、机票预定、发送邮件等跨工具协同场景;

  • 与企业系统深度集成,自动调用CRM、ERP等业务接口处理客户订单、库存查询等需求,推动AI从对话助手向业务执行者跃迁。

接下来通过一个实际案例,演示如何基于Spring AI+Tools整合springboot项目中完成对数据库的增删改查功能。

5.1 前置准备过程

5.1.1 添加配置文件

在工程配置文件中增加下面的配置信息

java 复制代码
server:
  port: 8081

spring:
  ai:
    dashscope:
      #使用阿里云百炼平台的apikey
      api-key: 你的apikey
      #新加的
      base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
      chat:
        options:
          model: qwen-turbo
          #qwen-plus

# 数据库相关配置
  datasource:
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://IP:3306/test_db
    username: root
    password: 123456

# mybatis配置
mybatis:
  type-aliases-package: com.congge.entity
  mapper-locations: classpath:mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true

5.1.2 添加测试数据表

在后文演示中,需要操作数据表,提前创建如下数据表:

sql 复制代码
CREATE TABLE `book` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(64) DEFAULT NULL,
  `author` varchar(32) DEFAULT NULL,
  `price` varchar(32) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

5.2 代码操作演示

5.2.1 增加book的实体类

作为与book表映射的对象

java 复制代码
package com.congge.entity;

public class Book {

    private Integer id;

    private String name;

    private String author;

    private String price;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public String getPrice() {
        return price;
    }

    public void setPrice(String price) {
        this.price = price;
    }
}

5.2.2 业务实现基本增删改查

增加一个业务实现类,这里我提供了插入和查询方法,可以结合自身的情况添加

java 复制代码
package com.congge.service;

import com.congge.dao.BookMapper;
import com.congge.entity.Book;
import com.congge.entity.vo.BookVO;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class BookService {

    @Autowired
    private BookMapper bookMapper;

    @Transactional
    public void addBook(BookVO book) {
        Book book1 = new Book();
        BeanUtils.copyProperties(book,book1);
        bookMapper.insert(book1);
    }

    public Book getBookById(Integer id) {
        return bookMapper.getBookById(id);
    }

}

5.2.3 自定义Tool工具类

在实际结合大模型应用开发中,Tool工具类的开发才是核心,其作用相当于是连接了实际业务与大模型之间的桥梁,如下,提供两个工具方法,分别用于执行数据库插入和查询。

java 复制代码
package com.congge.tool;

import com.congge.entity.Book;
import com.congge.entity.vo.BookVO;
import com.congge.service.BookService;
import jakarta.annotation.Resource;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Component;

@Component
public class BookTools {

    @Resource
    private BookService bookService;

    @Tool(description = "新增一本书")
    public void addBook(BookVO book) {
        bookService.addBook(book);
    }

    @Tool(description = "根据指定ID查询书籍")
    public Book queryBookById(Integer id) {
        Book dbbook = bookService.getBookById(id);
        return dbbook;
    }

}

1)Tool 注解关键属性补充说明:

  • 需掌握Tool相关几个注解,以及注解中参数的含义,以便更好的应对开发;

  • Tool 注解支持几个关键属性:

    • name :工具名称。若未指定,默认使用方法名。名称需在单个类中唯一,且在同一聊天请求中所有工具名称不可重复。

    • description :工具描述,用于帮助模型理解调用时机和用途。若未指定,默认使用方法名,但强烈建议提供详细描述以避免模型误用或忽略工具。

    • returnDirect :是否直接将结果返回客户端而非传递给模型(详见"直接返回"章节)。

    • resultConverter :指定ToolCallResultConverter实现,用于将工具调用结果转换为字符串返回模型(详见"结果转换"章节)。

2)Tool 参数注解补充说明:

Spring AI会自动为@Tool方法生成输入参数的JSON Schema,大模型据此理解调用格式。通过@ToolParam注解可补充参数信息,参考如下示例代码:

java 复制代码
class DateTimeTools {

    @Tool(description = "设置指定时间的用户闹钟")
    void setAlarm(@ToolParam(description = "ISO-8601格式的时间") String time) {
        LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
        System.out.println("闹钟已设置为 " + alarmTime);
    }
}

@ToolParam支持以下属性:

  • description :

    • 参数描述(如格式要求、允许值等),帮助模型正确使用参数。
  • required :

    • 是否为必填参数。默认所有参数为必填,但标注@Nullable的参数会被视为可选(除非显式标记为required=true)

此外,可结合Swagger的**@Schema或Jackson的@JsonProperty**注解定义JSON Schema

5.2.4 自定义ChatClient

自定义ChatClient,将提示词模板嵌入到spring ai 上下文环境中,以便在与大模型交互的时候使用,参考下面的代码

java 复制代码
package com.congge.config;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class BookChatConfig {

    @Bean
    ChatClient bookChat(ChatClient.Builder chatClientBuilder) {
        String systemMessage = """
                  当前时间:{date}。输出结果使用HTML table表格展示。需要自适应页面大小(宽度),字体大小为12px。除HTML相关的内容,不要有任何其它内容。
                """;
        ChatClient chatClient = chatClientBuilder
                .defaultSystem(systemMessage)
                .build();
        return chatClient;
    }

}

5.2.5 mybatis层操作数据表

如下在mybatis文件中操作数据表的sql

java 复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.congge.dao.BookMapper">

    <resultMap type="com.congge.entity.Book" id="BookMap">
        <result property="id"    column="id"    />
        <result property="name"    column="name"    />
        <result property="author"    column="author"    />
        <result property="price"    column="price"    />
    </resultMap>

    <insert id="insert">
        insert into book(`name`,author,price)
        values(#{name},#{author},#{price})
    </insert>

    <select id="getBookById" resultMap="BookMap">
        select * from book where id = #{id}
    </select>

</mapper>

5.2.6 增加测试接口

增加一个测试接口,通过接口调用测试是否可以通过tool直接完成对数据表的数据插入和查询

java 复制代码
package com.congge.web;

import com.congge.tool.BookTools;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;

@RestController
@RequestMapping("/book/chat")
public class BookChatController {

    private final BookTools bookTools ;

    private final ChatClient chatClient ;

    public BookChatController(BookTools bookTools, ChatClient chatClient) {
        this.bookTools = bookTools ;
        this.chatClient = chatClient ;
    }

    //localhost:8081/book/chat/v1?message=新增一本书,书名是《java从入门到精通》,作者是张三,价格是100元
    @GetMapping("/v1")
    public ResponseEntity<?> chat(String message) {
        Prompt prompt = new Prompt(message) ;
        String content = this.chatClient
                .prompt(prompt)
                // 设置系统文本中的占位符参数
                .system(p -> p.param("date", new Date()))
                .tools(bookTools)
                .call()
                .content() ;
        return ResponseEntity.ok(content) ;
    }
}

5.3 接口效果测试

5.3.1 新增数据测试

在下面的接口调用中,通过上面的接口增加一条数据到数据表:

bash 复制代码
localhost:8081/book/chat/v1?message=新增一本书,书名是《java从入门到精通》,作者是张三,价格是100元

执行成功后,由于返回的是html,所以能够渲染到浏览器网页

数据表,可以看到增加了一条数据

5.3.2 查询数据测试

下面根据ID来查询上面新增的这条记录,能够按照预期查询出结果并展示在网页上,虽然样式一般,也是按照预期查询到了数据

六、MCP 技术介绍

6.1 什么是MCP

MCP ,模型上下文协议(Model Context Protocol, MCP)。由 Anthropic 公司(Claude 大模型母公司)在 2024 年 11 月推出的开放协议,旨在标准化大型语言模型与外部数据源和工具之间的交互方式。MCP 被形象地比喻为 AI 领域的"USB-C 接口",因为它提供了连接各类服务的统一框架。从而增强模型的实时交互和任务执行能力。

6.2 MCP 核心特点

MCP 具备如下核心特点:

  • 标准化集成

    • MCP统一了不同AI模型(如Claude、GPT等)与外部工具(如数据库、API、浏览器等)的交互方式,避免了传统定制化API开发的重复劳动
  • 双向通信

    • 不同于传统AI只能被动接收数据,MCP支持AI主动检索信息并执行操作(如更新数据库、触发工作流),实现真正的"代理式AI"
  • 动态上下文管理

    • MCP允许AI在会话中持续携带上下文信息(如用户偏好、历史记录),使多步骤任务(如"查询天气→推荐行程→预订酒店")能自动串联执行
  • 安全与灵活性

    • MCP支持本地或云端部署,通过OAuth 2.1认证和数据沙箱机制保障敏感信息的安全访问

6.3 MCP 核心价值

MCP 解决了 AI 应用开发中的几个关键问题:

  • 打破数据孤岛:让大模型可以直接访问实时数据和本地资源(如数据库、文件系统)

  • 降低开发成本:开发者只需编写一次 MCP 服务端,所有兼容 MCP 的模型都能调用

  • 提升安全性:内置权限控制和加密机制,比直接开放数据库更安全

  • 促进生态统一:类似 USB 接口,让不同厂商的工具能"即插即用"

6.4 MCP 与Function Calling 区别

与传统的 Function Calling 相比,MCP 具有以下优势:

  • 复用性:Function Calling 通常与具体 AI 应用紧密绑定,难以复用;而 MCP 设计用于跨系统、跨工具场景

  • 交互能力:MCP 着重解决系统间交互问题,使数据和功能能在不同系统间灵活传递

  • 协议标准化:MCP 通过协议标准化简化了分布式系统开发

七、Spring AI MCP 架构介绍

spring在大模型开始火热时,推出了Spring AI,与时俱进,伴随MCP热度走高,Spring AI MCP也随之推出与大众见面,官网入口:Model Context Protocol (MCP) :: Spring AI Reference

7.1 整体架构

Spring 官网对于MCP给出了下面的架构图

Spring AI MCP 采用模块化架构,包含下面的核心组件:

  • Spring AI 应用程序:使用 Spring AI 框架构建的生成式 AI 应用

  • Spring MCP 客户端:MCP 协议的 Spring AI 实现,与服务器保持 1:1 连接

  • MCP 服务器:轻量级程序,通过标准化协议公开特定功能

  • 本地数据源:MCP 服务器可安全访问的计算机文件、数据库和服务

  • 远程服务:MCP 服务器可通过互联网(如 API)连接的外部系统

7.1.1 三层架构实现说明

Java MCP 实现遵循三层架构:

  • 客户端/服务器层:McpClient 处理客户端操作,McpServer 管理服务器端协议操作

  • 会话层(McpSession):管理通信模式和状态

  • 传输层(McpTransport):处理 JSON-RPC 消息的序列化和反序列化,支持多种传输方式

7.2 服务端与客户端

在实际开发和使用时,MCP的开发主要涉及到服务端与客户端,两者的作用各有差异,下面分别做说明。

7.2.1 MCP 服务端

MCP 服务端主要负责下面的功能:

  • 服务端协议操作实现

  • 工具暴露与发现

  • 基于 URI 的资源管理

  • 提示词模板的提供与处理

  • 客户端并发连接管理

7.2.2 MCP 客户端

MCP 客户端是架构中的关键组件,其主要负责下面的功能:

  • 协议版本协商

  • 能力协商以确定可用功能

  • 消息传输和 JSON-RPC 通信

  • 工具发现和执行

  • 资源访问和管理

  • 支持同步和异步操作

7.3 MCP中SSE 和 STDIO两种模式

在Spring AI MCP实现中,SSE和STDIO是两种主要通信方式,它们各自适用不同的场景,下面详细分析两者区别。

7.3.1 SSE模式介绍

SSE全称,Server-Sent Events,是一种服务器推送技术,允许服务器向客户端推送实时更新。在MCP中,SSE主要用于流式返回AI模型生成的内容。其主要特点如下:

  • 单向通信:服务器向客户端发送数据,客户端不能通过SSE向服务器发送数据

  • 实时性:支持服务器实时推送数据到客户端

  • 自动重连:客户端断开连接后会自动尝试重新连接

  • 基于HTTP:使用标准HTTP协议,易于穿越防火墙

  • 轻量级:相比WebSocket更加轻量,适合单向通信场景

在AI大模型中的交互使用场景:

  • 流式返回模型生成的文本,实现类似打字机的效果;

  • 提供实时反馈,如生成进度、中间结果等;

  • 减少首字节时间(TTFB),提升用户体验;

SSE的HTTP格式示例:

bash 复制代码
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
 
data: {"content":"这是第一段对话"}
 
data: {"content":"这是第二段对话"}
 
data: {"content":"这是最后一段对话"}
 
data: [DONE]

7.3.2 STDIO模式介绍

STDIO全称,Standard Input/Output,STDIO 是指标准输入输出流,是一种最基本的程序通信机制。在MCP中,STDIO主要用于与本地部署的模型或通过命令行工具访问的模型进行交互。STDIO主要有如下特点:

  • 双向通信

    • 程序可以通过标准输入接收数据,通过标准输出发送数据
  • 进程间通信

    • 通常用于父子进程之间的通信
  • 无网络依赖

    • 不依赖网络协议,适合本地应用场景
  • 命令行友好

    • 与命令行工具天然集成
  • 低开销

    • 通信开销小,适合高性能场景

在AI大模型中的交互使用场景:

  • 与本地部署大模型交互,如Ollama、LLaMA.cpp等

  • 通过命令行工具访问AI大模型服务

  • 构建管道式AI处理流程

STDIO 通信流程如下示例:

应用程序 -> 标准输入 -> AI模型进程 -> 标准输出 -> 应用程序

7.3.3 SSE 与STDIO对比

SSE 与STDIO两种模式具有如下差异:

|---------|-------------|-----------|
| 特征项 | SSE | STDIO |
| 通信方向 | 单向 :服务器到客户端 | 双向 |
| 网络依赖 | 依赖HTTP | 不依赖网络 |
| 部署要求 | 需要网络服务 | 本地即可 |
| 性能开销 | 较高(HTTP开销) | 较低 |
| 扩展性 | 支持多客户端 | 一对一通信 |
| 实现复杂度 | 中等 | 简单 |
| 错误恢复 | 自动重连机制 | 需手动处理 |
| 应用场景 | 网络API通信 | 本地进程通信 |

八、Spring AI MCP 案例操作演示

接下来通过一个实际案例详细演示基于SSE 这种模式下Spring AI MCP是如何使用的。

mcp项目分为server服务端(提供mcp服务),client客户端(去请求mcp服务)

8.1 构建MCP Server服务

8.1.1 pom导入依赖

创建一个springboot server端工程,并导入下面核心依赖

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

8.1.2 添加配置文件

在配置文件中添加如下信息

java 复制代码
spring:
  ai:
    mcp:
      server:
        name: mcp-server
        version: 1.0.0
        type: ASYNC
        sse-message-endpoint: /mcp/messages

8.1.3 提供Tool 工具类

在MCP实现中,Tool 是一个很重要连接点,借助Tool ,客户端调用具体服务时,可理解为MCP就能找到符合要求的Tool 实现,从而给客户端预期的响应结果。

在server项目启动时,会将Tool工具方法注册,统一汇聚在一个类似于服务注册中心的列表上去,客户端通过配置Server的地址后,就能进行调用,在下面的类中,提供了2个工具方法,第一个是模拟调用天气的API获取天气信息,第二个是获取用户所在的时区时间

java 复制代码
package com.congge.tool;

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;

@Service("weatherServiceTool")
public class WeatherServiceTool {

    @Tool(description = "获取指定城市的天气")
    public String getWeather(String cityName){

        if(cityName.equals("北京")){
            return "晴天";
        }else if(cityName.equals("上海")){
            return "阴天";
        }else {
            return "未知";
        }
    }

    @Tool(description = "获取当前用户所在的时区时间")
    String getCurrentDateTime() {
        return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
    }

}

8.1.4 注册工具类

提供一个配置类,将上述的Tool工具类进行注册

java 复制代码
package com.congge.config;

import com.congge.tool.WeatherServiceTool;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ToolCallbackProviderConfig {

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

到这里基本上就结束了,如果在server工程中,还需要提供接口,继续添加即可。在实际开发中,server端主要是提供针对各类场景下的tool服务,接下来启动工程,在控制台的启动日志中可以看到成功注册了两个服务。

8.2 构建MCP Client服务

8.2.1 导入核心依赖

在pom文件中导入下面的核心依赖,基本和server端的相同,注意版本号保持一致,在client端,主要增加了mcp-client的依赖,以及阿里的spring-ai的依赖,参考下面的依赖信息:

java 复制代码
<dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-mcp-client</artifactId>
    </dependency>

8.2.2 增加配置信息

在配置文件中增加下面的信息

java 复制代码
server:
  port: 8082

spring:
  ai:
    dashscope:
      api-key: 你的apikey  #这里使用阿里云百炼平台的apikey
    mcp:
      client:
        sse:
          connections:
            server1:
              url: http://localhost:8080
        toolcallback:
          enabled: true

8.2.3 增加一个对话接口

增加一个聊天对话接口,用于测试客户端调用server的服务是否正常,参考下面的代码

java 复制代码
package com.congge.web;

import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/client/ai")
public class ChatController {

    private final ChatClient chatClient;

    public ChatController(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder.build();
    }

    @Resource
    private ToolCallbackProvider toolCallbackProvider;

    //localhost:8082/client/ai/chat?message=今天北京的天气如何
    //localhost:8082/client/ai/chat?message=你是谁
    //localhost:8082/client/ai/chat?message=当前时间是多少
    @GetMapping("/chat")
    public String chat(String message){
        return chatClient
                .prompt()
                .user(message)
                .tools(toolCallbackProvider.getToolCallbacks())
                .call()
                .content();
    }
}

8.2.4 效果测试

启动上面client的服务,依次调用几个接口测试下是否能够成功调用到server端的服务。

1)测试效果一

调用接口:localhost:8082/client/ai/chat?message= *今天北京的天气如何 ,*在server端,提供了一个获取天气信息的模拟接口,所以在下面的接口响应中可以看到正常返回结果

2)测试效果二

调用接口:localhost:8082/client/ai/chat?message= *你是谁 ,*这是一个与server端2个工具方法无关的问题,所以最后由大模型返回的响应结果

3)测试效果三

调用接口:localhost:8082/client/ai/chat?message=当前时间是多少 ,server中提供了获取时间的工具方法,所以在调用结果中可以看到返回了预期的结果

8.3 MCP 底层原理解析

在上面的案例中,演示了如何在client端调用server端的tool服务,细心的伙伴会问,这是怎么实现的呢?clent端怎么就能准确的调用到server端提供的服务了呢?下面简单说明下其底层的实现原理。

8.3.1 server端底层执行逻辑

在server端,代码中搜索这个McpAsyncServer类,server端项目启动时,通过这个类的执行,将tool的相关方法注册到一个指定的位置,然后暴露出去,从而客户端只要配置了与server端的连接,即可获取到这些tool列表,类似于我们在使用nacos之类的服务注册中心时候,只要服务消费者连上注册中心,就可以获取并使用上面注册的服务列表类似的道理。

在下面这个类中,提供了多个与Tool相关的方法,如下:

启动server工程,首先进入了该类的构造方法中,走到构造方法中,可以看到获取到了工程中提供的2个tool方法

同时在下面这段代码中,暴露了一个重要端口: tools/list ,简单理解就是为客户端暴露了一个获取server端工具列表的接口

  • tools/list ,暴露工具列表;

  • tools/call,执行工具调用;

8.3.2 client 端底层执行逻辑

在客户端,在上面案例演示,在配置文件中配置了连接server端的IP+端口号,然后在接口中注入相关的类就可以调用了,简单理解就是,通过这样的配置之后,客户端在发起对server端的tool方法调用时,从tool列表中找到合适的tool,然后执行调用,从而返回预期的结果。

在client端代码中,通过ToolCallbackProvider这个接口进入,可以看到,这是一个接口,里面对应了多个具体的实现类,不同的实现对应了不同场景下对于tool的调用模式,在上述的案例代码中,对应的便是下面这个SyncMcpToolCallbackProvider类的代码执行。

在启动工程之后,通过一次接口调用后,来到下面的McpSyncClient这个类的代码中,首先会执行下面的listTools方法,获取当前客户端能够拿到的tool列表,通过debug代码可以看到已经拿到了server端提供的2个tool

通过中间一些列的过滤、筛选得到具体执行的那个tool,然后执行本类中的callTool方法,而且是一个同步阻塞方法,需要等待执行结果的返回

继续往下走,就看到了在上一个解析server端源码中的那个 tools/call的接口了,在这里相当于是客户端找到了具体的执行tool之后,向server端发起request,然后等待server的执行结果

九、写在文末

本文通过较大的篇幅,详细介绍了Spring AI MCP的技术使用,并通过较多的案例操作演示介绍了具体的使用过程,有兴趣的同学可以继续此继续深入研究,本篇到此结束,感谢观看。

相关推荐
小码农叔叔1 个月前
【AI智能体】Spring AI MCP 服务常用开发模式实战详解
mcp·springboot使用mcp·mcp使用详解·mcp使用·spring ai mcp·mcp 详解·mcp开发模式
小码农叔叔2 个月前
【AI智能体】Spring AI MCP 从使用到操作实战详解
spring ai mcp使用·spring ai mcp详解·springboot使用mcp·springboot整合mcp·mcp使用详解·mcp使用