SpringBoot 整合 MCP
SpringBoot 整合 MCP
MCP
MCP 协议主要分为:
- Client 客户端(一般就是指 openai,deepseek 这些大模型)
- Server 服务端(也就是我们的业务系统)我们要做的就是把我们存量系统配置成 MCP Server
环境
- JDK17
- SpringBoot 3
引入依赖
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-core</artifactId><version>1.0.0-M6</version></dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-spring-boot-autoconfigure</artifactId><version>1.0.0-M6</version></dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-openai-spring-boot-starter</artifactId><version>1.0.0-M6</version></dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-mcp-server-webmvc-spring-boot-starter</artifactId><version>1.0.0-M6</version></dependency>
配置 yaml
spring:ai:openai:base-url: https://api.deepseek.comapi-key: sk-xxxxxxxx # deepseek 的 api-keychat:enabled: trueoptions:model: deepseek-chat # 使用这个模型temperature: 0.7stream-usage: true # 有的模型不支持logging:level:org.springframework.ai: debug # 开启 debug,打印思考链路
工具类
工具类的作用就是获取 springboot 里所有需要注册的 bean,这里是策略是 获取所有 “Controller”, “Service”, “Manager” 结尾的 bean,可以自行修改。
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;/*** Spring 框架工具类** @author wen7.online*/
@Slf4j
@Component
public class SpringTools{@Resourceprivate ApplicationContext applicationContext;/*** 获取所有 "Controller", "Service", "Manager" 结尾的 bean,里面的 @Tool 注解的方法作为大模型上下文 MCP** @return 所有 "Controller", "Service", "Manager" 结尾的 bean*/public List<Object> findToolCallbackBeans() {String[] suffixes = {"Controller", "Service", "Manager"};String[] excludeNames = {"AiController"}; //这里是因为在 AiController 里循环引用了Set<String> excludeSet = Arrays.stream(excludeNames).collect(Collectors.toSet());return Arrays.stream(applicationContext.getBeanNamesForAnnotation(Component.class)).filter(beanName -> {log.info("beanName: {}", beanName);Class<?> type = applicationContext.getType(beanName);if (type == null) return false;String simpleName = type.getSimpleName();if (excludeSet.contains(simpleName)) return false;return Arrays.stream(suffixes).anyMatch(simpleName.replace("$$SpringCGLIB$$0","")::endsWith); //有可能获取的是代理对象,$$SpringCGLIB$$0 结尾}).map(applicationContext::getBean).collect(Collectors.toList());}public Object unwrapProxy(Object bean) {if (AopUtils.isAopProxy(bean)) { // 检查是否是代理对象try {Object target = ((Advised) bean).getTargetSource().getTarget();// 递归解包,确保多层代理情况下能获取到最终原始对象return unwrapProxy(target);} catch (Exception e) {return bean;}}return bean; // 非代理对象直接返回}}
配置类
mport com.quick.common.utils.spring.SpringTools;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.ToolCallback;
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;import java.util.Arrays;
import java.util.List;/*** ChatClient 配置*/
@Slf4j
@Configuration
public class ChatClientConfiguration {@Beanpublic ToolCallbackProvider toolCallbackProvider(SpringTools springTools) {List<Object> toolObjects = springTools.findToolCallbackBeans().stream().map(springTools::unwrapProxy) // 获取源对象,防止代理原因.toList();//核心,把所有的 bean 注入,会自动读取 @Tool 注解MethodToolCallbackProvider provider = MethodToolCallbackProvider.builder().toolObjects(toolObjects.toArray()).build();List<ToolCallback> tools = Arrays.stream(provider.getToolCallbacks()).toList();tools.stream().forEach(tool->{log.info("Register Tool: {}.{}", tool.getName(),tool.getDescription());});return provider;}@Beanpublic ChatClient chatClient(ChatClient.Builder builder, ToolCallbackProvider toolCallbackProvider) {return builder.defaultSystem("""本系统是一个 SaaS 平台,分为平台,租户,用户每次操作 token 中携带了 tenantId有 tenantId 说明是租户内的雇员在操作,tenantId = 1 是平台管理员在操作,没有 tenantId 说明是用户在操作""").defaultTools(toolCallbackProvider).build();}
}
修改源码
主要在方法上添加注解,注意 name 有命名规范,不能是中文,最好类似 selectMenuIdsByRoleIds。
-
@Tool(name = "selectMenuIdsByRoleIds", description = "根据角色id列表查询菜单id列表")
还可以在字段,方法参数上添加
-
@ToolParam(description = "角色id列表")
/*** 根据角色id查询菜单id** @param roleIds 角色id* @return 菜单id, 平铺, 去重*/@Tool(name = "selectMenuIdsByRoleIds", description = "根据角色id列表查询菜单id列表")public List<Long> selectMenuIdsByRoleIds(@ToolParam(description = "角色id列表") List<Long> roleIds) {List<RoleMenuPo> poList = roleMenuRepository.findByRoleIdIn(roleIds);List<Long> menuIdList = poList.stream().map(RoleMenuPo::getMenuId).distinct().collect(Collectors.toList());log.info("根据角色id查询菜单id, roleIds:{}, menuIdList:{}", roleIds, menuIdList);return menuIdList;}
配置聊天接口
import com.quick.ai.pojo.dto.ChatRequest;
import com.quick.common.utils.lang.StringUtils;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;import java.nio.charset.StandardCharsets;/*** ai 对话** @author wen7.online*/
@Slf4j
@RestController
@RequestMapping(value = "/ai", name = "ai聊天")
public class AiController {@Resourceprivate ChatClient chatClient;@PostMapping(value = "/v1/chat", name = "聊天")public String chat(@RequestBody ChatRequest chatRequest, HttpServletResponse response) {String userMessage = chatRequest.getMessage();log.info("用户问题 message:{}", userMessage);if (StringUtils.isEmpty(userMessage)) {return "";}String content = chatClient.prompt().user(userMessage).call().content();return new String(content.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8);}//配置 produces = MediaType.TEXT_EVENT_STREAM_VALUE@PostMapping(value = "/v1/chat/stream", name = "聊天流式数据", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public Flux<String> chatStream(@RequestBody ChatRequest chatRequest) {String userMessage = chatRequest.getMessage();Flux<String> flux = chatClient.prompt().user(userMessage).stream().content();return flux;}}
接口访问
调用接口
http://127.0.0.1:8080/ai/v1/chat
http://127.0.0.1:8080/ai/v1/chat/stream
前端代码 vue3
https://wen7.online/social/social_wechat
实现效果
通过自然语言实现,调用内部函数或接口,
虽然略有瑕疵,但是 领导说了,先上线吧,以后慢慢优化

相关文章:
SpringBoot 整合 MCP
SpringBoot 整合 MCP MCP MCP 协议主要分为: Client 客户端(一般就是指 openai,deepseek 这些大模型)Server 服务端(也就是我们的业务系统)我们要做的就是把我们存量系统配置成 MCP Server 环境 JDK17…...
【详细】MySQL 8 安装解压即用 (包含MySQL 5 卸载)
卸载MySQL 1.卸载 2.安装目录删除残余文件(当初安装的位置) 3.删除programData下面的mysql数据文件 4.检查mysql服务是否存在,如果存在则删除(先暂停mysql服务) sc delete mysql 5.删除注册表中残留信息 安装MySQL 8&…...
显示器各类异常处理方法
显示器各类异常处理方法 导航 文章目录 显示器各类异常处理方法导航画面无显示/黑屏/无HDMI信号输入显示器闪烁显示器花屏显示画面模糊或扭曲显示器颜色异常显示器出现死点或亮点 画面无显示/黑屏/无HDMI信号输入 首先应该检查的是显示器电源(真的有人弄掉电源…...
关于Spring MVC中@RequestParam注解的详细说明,用于在前后端参数名称不一致时实现参数映射。包含代码示例和总结表格
以下是关于Spring MVC中RequestParam注解的详细说明,用于在前后端参数名称不一致时实现参数映射。包含代码示例和总结表格: 1. 核心作用 RequestParam用于显式绑定HTTP请求参数到方法参数,支持以下场景: 参数名不一致࿱…...
HTML的svg元素
<svg>元素 <svg>是一种用于描述二维矢量图形的 XML 格式,可以直接嵌入 HTML 文档中。 <svg>基本用法 <svg>的几种基本用法,包括圆形,正方形,三角形,直线 ,折线等 <body><svg widt…...
一、简单的 Django 服务
一、配置虚拟环境 1.1 创建一个文件夹在导航栏输入cmd打开 1.2 安装依赖两个库 pip install virtualenv virtualenvwrapper-win -i https://pypi.tuna.tsinghua.edu.cn/simple验证是否安装成功 virtualenv --version pip show virtualenvwrapper-win 1.3 创建虚拟环境 mkvi…...
二分算法的入门笔记
二分查找 使用前提:有序。可理解为枚举的一种技巧。时间复杂度: l o g ( n ) log(n) log(n) 基础模版代码 使用时根据情景进行相应的变化。注意跳出条件and分支处理方式and返回答案,三者之间的配合,不要进入死循环。可以在模拟…...
k8s黑科技:Linux+Vagrant+VirtualBox开启Kubernetes奇幻之旅
文章目录 1. 准备硬件2. 安装系统3. 安装 VNC4. 基础配置4.1 路由转发4.2 防火墙4.3 selinux4.4 安装包4.5 重启 5. 配置代理6. 安装 virtuabox7. 安装 vagrant8. 配置 kubespray8.1 安装依赖工具8.2 定制 Vagrantfile8.3 配置代理与时间同步8.4 配置私有镜像仓库 9. 安装虚拟机…...
34% 关税冲击下 LabVIEW 开发的变局
2025 年 4 月 4 日,中国国务院关税税则委员会宣布,自 4 月 10 日起对原产于美国的所有进口商品加征 34% 关税。这一举措,给 LabVIEW 开发领域带来显著影响,相关使用者和用户亟需采取应对策略。 从成本层面看,LabVI…...
42、JavaEE高级主题:WebSocket详解
WebSocket 一、WebSocket协议与实现 WebSocket是一种基于TCP协议的全双工通信协议,能够在客户端和服务器之间建立实时、双向的通信通道。通过WebSocket,客户端和服务器可以在任何时候发送数据,并立即接收到对方的响应。 1.1 WebSocket协议…...
Http代理服务器选型与搭建
代理服务器选型-Squid 缓存加速 缓存频繁访问的网页、图片等静态资源,减少对原始服务器的重复请求,提升响应速度支持HTTP、HTTPS、FTP等协议,通过本地缓存直接响应客户端请求 访问控制 基于ACL(访问控制列表)实现精细…...
蓝桥杯第十一届省赛C++B组真题解析
蓝桥杯第十一届省赛CB组真题解析 八、回文日期https://www.lanqiao.cn/problems/348/learning 方法一:暴力枚举所有的日期,记录有多少个回文日期。 #include <bits/stdc.h> using namespace std; int month[13]{0,31,28,31,30,31,30,31,31,30,31…...
Linux主要开发工具之gcc、gdb与make
此系列还有两篇,大家想完整掌握可以阅读另外两篇 Linux文本编辑与shell程序设计-CSDN博客 Linux基础知识详解与命令大全(超详细)-CSDN博客 1.gcc编译系统 1.1 文件名后缀 文件名后缀 文 件 类 型 文件名后缀 文 件 类 型 .c C源…...
排序算法(快速排序,选择排序......)【泪光2929】
hello,大家好!今天给大家分享一下各种排序: 1,选择排序 首先从原始数组中 选择最小的1个数据,将其和位于第1个位置的数据交换。接着从剩下的n-1个数据中选择次小的1个元素,将其和第2个位置的数据交换然后…...
Conda使用方法详解
Conda是一个开源的包管理和环境管理系统,主要用于Python/R等科学计算领域,可以轻松管理不同项目的依赖关系。以下是Conda的详细使用方法: 一、安装与配置 1.安装Miniconda/Anaconda Miniconda是精简版,只包含conda和Python Ana…...
数据库的MVCC机制详解
MVCC(Multi-Version Concurrency Control,多版本并发控制)是数据库系统中常用的并发控制机制,它允许数据库在同一时间点保存数据的多个版本,从而实现非阻塞的读操作,提高并发性能。 MVCC的核心思想是&…...
C++初阶-C++入门基础
目录 编辑 1.C的简介 1.1C的产生和发展 1.2C的参考文档 1.3C优势和难度 1.4C学习的建议 2.C的第一个程序 2.1打印Hello world 2.2头文件 2.3namespace命名空间 2.4::作用域限定符 2.5namespace的延伸 2.6C的输入输出 3.总结 1.C的简介 …...
关于量化交易在拉盘砸盘方面应用的部分思考
关于“砸盘”的深层解析与操盘逻辑 一、砸盘的本质与市场含义 砸盘指通过集中抛售大量筹码导致价格快速下跌的行为,其核心目标是制造恐慌、清洗浮筹或实现利益再分配。不同场景下的砸盘含义不同: 主动砸盘(操控…...
idea手动创建resources文件夹
有时maven没有构建成功可能造成,resources文件夹不创建的现象 此时我们可以手动创建 手动创建...
第十五届蓝桥杯大赛软件赛省赛Python 大学 C 组题目试做(中)【本期题目:回文数组,挖矿】
OK,继续写我们的第十五届蓝桥杯大赛软件赛省赛Python 大学 C 组题目,后面的题目比较麻烦了,所以我们再分两期讲。 这一期的题有 : 回文数组,挖矿 文章目录 回文数组基本思路第一步,获取半个数组每个数需要…...
Qt动画 QAbstractAnimation
文章目录 简介QVariantAnimation 数值动画QPropertyAnimation 属性动画 QAnimationGroup 一组动画QParallelAnimationGroup 并行动画组QSequentialAnimationGroup 串行动画组 简介 QAbstractAnimation 是所有 Qt 动画的基类。 该类定义了所有动画应该都会有的功能函数。 要想实…...
SpringMvc的请求-获得请求参数
客户端请求参数的格式是: namevalue&namevalue..… 服务器端要获得请求的参数,有时还需要进行数据的封装,SpringMVC可以接收如下类型的参数: 基本类型参数 POJO类型参数 数组类型参数 集合类型参数 获得基本类型参数 Controller中的业务方法…...
flutter开发音乐APP(前提准备)
1、项目的一些环境: 2、接口文档: 酷狗音乐 NodeJS 版 API 3、接口数据结构化 Instantly parse JSON in any language | quicktype UI样式借鉴参考: Coffee-Expert/Apple-Music-New-UI: Apple Music Clone on Flutter, with redesigned UI…...
使用docker搭建redis镜像时云服务器无法访问到国外的docker官网时如何解决
下载redis镜像 docker redis:版本号 此时截图中无法访问到国外的docker官网 解决方案: 通过更换镜像源来正常下载redis镜像 sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<EOF {"registry-mirrors": ["https://docker.1…...
双引擎驱动:解密音视频体验的QoS技术底座与QoE感官革命
QoS 定义:QoS(Quality of Service,服务质量)衡量音视频传输技术层面的性能表现,聚焦网络传输和系统处理能力,通过客观指标量化服务质量。核心指标 码率/带宽:数据传输速率上限,直接…...
Qt之QNetworkInterface
简介 用于表示网络接口(即网卡)信息 常用接口 static QList<QNetworkInterface> allInterfaces(); static QList<QHostAddress> allAddresses(); QList<QNetworkAddressEntry> addressEntries() const;接口类型 用枚举InterfaceTy…...
pom导包成功,但是就是无法使用相关类,同时报错:Library:Maven ‘xxx‘ has broken path
开发环境:Intellij 2023 一、问题记录 在maven工程的pom文件导入如下某一依赖(JGit)。没有显示导包的错误,同时在maven仓库里面找到对应的包是正常下载到相应jar的。 但是就是无法引入相关的类。打开Project Structure,在Dependencies中发现…...
大数据技术之Scala
Spark运行架构核心是一个计算引擎 核心组件 1. Driver(驱动器) 角色:Spark作业的“大脑”,负责解析用户代码、生成任务并调度执行。 功能: 将用户程序转换为作业(Job)。 …...
LeetCode刷题常见的Java排序
1. 字符串排序(字母排序) 首先,你的代码实现了根据字母表顺序对字符串中的字母进行排序,忽略了大小写并且保留了非字母字符的位置。关键点是: 提取和排序字母:通过 Character.isLetter() 判断是否为字母,并利用 Character.toLowerCase() 来忽略大小写进行排序。保留非字…...
mysql的下载和安装2025.4.8
mysql下载和安装 MySQL的下载网址: https://www.mysql.com/downloads/ 点击进入Windows版本下载:我们可以选择需要的MySQL版本以及所需的操作系统,这里选择离线安装: 注意:MySQL 8.0 是带有 MySQL Installer 的最后一…...
