当前位置: 首页 > news >正文

SpringAI集成MCP

文章目录

    • 1_调用公用MCP
    • 2_Stdio方式
    • 3_Stdio实现原理
    • 4_SSE方式
    • 5_自定义MCP客户端
    • 6_MCP Server权限控制

SpringAI 通过 SpringBoot 集成扩展了 MCP Java SDK ,提供了客户端和服务端 starter,让 AI 应用程序快速支持 MCP。

接下来直接演示。

1_调用公用MCP

在使用其他开发者提供好的 MCP 服务时,仅仅引入 MCP 的客户端依赖即可。

SpringAI 提供了如下两种 MCP Client 依赖,根据需求选择一种引入即可(常见第二种)。

仅支持 Stdio 的依赖

<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>

既支持 SSE(远程)也支持 Stdio(进程内)

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

以使用百度地图提供的 MCP 服务为例,演示如何使用 Stdio 的方式调用公用的 MCP Server。

首先,对 MCP 进行配置,Stdio 的配置方式有两种。

一种是直接配置全局配置文件中:

spring:ai:mcp:client:enabled: truename: spring-ai-mcp-clientversion: 1.0.0type: sync # 或 async 调用响应式流应用request-timeout: 60000stdio:connections:baidu-map:command: cmdargs:- "/c"- "npx"- "-y"- "@baidumap/mcp-server-baidu-map" # 第一次启动会安装此包env:BAIDU_MAP_API_KEY: xxx # 百度地图开放平台查看

另外一种是使用 Claude Desktop 格式的 JSON 文件单独存放(推荐)

{"mcpServers": {"baidu-map": {"command": "cmd","args": ["/c","npx","-y","@baidumap/mcp-server-baidu-map"],"env": {"BAIDU_MAP_API_KEY": "xxxx"}}}
}

然后在 application.yaml 中指定文件的位置

spring:ai:mcp:client:request-timeout: 60000stdio:servers-configuration: classpath:/mcp/stdio-server-config.json

通过 ToolCallbackProvider 绑定 ChatClient,ToolCallbackProvider 可以看做外部工具提供商。

@Bean
public ChatClient serviceChatClient(OpenAiChatModel model, ChatMemory chatMemory, ToolCallbackProvider toolCallbackProvider) {return ChatClient.builder(model).defaultSystem(new ClassPathResource("call.txt")).defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build(), // CHAT MEMORYnew SimpleLoggerAdvisor()).defaultToolCallbacks(toolCallbackProvider)//关键.build();
}

如果想观察到调用 mcp 工具的日志信息进行调试,可以添加如下配置:

logging:level:io:modelcontextprotocol:client: debugspec: debug

测试结果:

img

2_Stdio方式

以查询天气为目标,定义 MCP-Server 工程,引入 Stdio 服务端依赖

<!-- mcp-server依赖-->
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-mcp-server</artifactId>
</dependency>

配置 application.yaml 属性:

spring:application:name: mcp-servermain:web-application-type: none # stdio 并不需要启动为web应用banner-mode: off # 关掉banner 下方有说明ai:mcp:server:name: mcp-server # 应用名称version: 1.0.0 # 版本
# 注意: 您必须禁用 banner 和控制台日志记录,以允许 Stdio 传输工作!!!

定义查询城市天气信息的工具类

@Service
public class WeatherService {@Tool(description = "根据城市名称获得天气信息")public String getWeather(@ToolParam(description = "城市名称") String cityName) {return cityName + "今日天气 晴";}
}

将工具绑定到 ToolCallbackProvider:

@SpringBootApplication
public class McpServerApplication {public static void main(String[] args) {SpringApplication.run(McpServerApplication.class, args);}@Beanpublic ToolCallbackProvider weatherTools(WeatherService weatherService){return MethodToolCallbackProvider.builder().toolObjects(weatherService).build();}// 客户端使用 .defaultToolCallbacks(toolCallbackProvider) 添加
}

将项目打成 jar 包后,在客户端 mcp/stdio-server-config.json 文件的 mcpServers 属性中新增如下配置:

"mcp-server-weather": {"command": "java","args": ["-Dspring.ai.mcp.server.stdio=true","-Dlogging.pattern.console=","-jar","D:\\WorkSpace\\IdeaProject\\jar\\mcp-server-weather.jar"]
}

说明:

  • command:内部 java 程序直接调用,所以无需 cmd 命令。
  • args:以 stdio 的方式启动,清空控制台,否则 mcp-server 启动也会打印干扰信息。

img

3_Stdio实现原理

当客户端启动之后,会由 McpClientAutoConfiguration 初始化并启动传输连接获取 tool 列表。

传输连接的关键流程及类图如下:

在这里插入图片描述

在 StdioClientTransport 的 connect 方法中,会利用 ProcessBuilder 对象将配置文件中的命令、参数、环境等信息封装起来创建子进程并启动。

之后建立输入、输出流连接循环地读取或写入信息,源码中的大致实现如下:

// 准备指定的命令、参数、环境
List<String> fullCommand = new ArrayList<>();
fullCommand.add(params.getCommand());
fullCommand.addAll(params.getArgs());
ProcessBuilder processBuilder = this.getProcessBuilder();
processBuilder.command(fullCommand);
processBuilder.environment().putAll(params.getEnv());
// 启动子进程
this.process = processBuilder.start();
// 循环地从连接中读取数据
try (BufferedReader processReader = new BufferedReader(new InputStreamReader(procesString line;while (!isClosing && (line = processReader.readLine()) != null) {try {JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(this.objecif (!this.inboundSink.tryEmitNext(message).isSuccess()) {if (!isClosing) {logger.error("Failed to enqueue inbound message: {}", message);}break;}}}
}
// 如果有消息向连接中写入数据
try {String jsonMessage = objectMapper.writeValueAsString(message);// Escape any embedded newlines in the JSON message as per spec:// https://spec.modelcontextprotocol.io/specification/basic/transports/#stdio// - Messages are delimited by newlines, and MUST NOT contain// embedded newlines.jsonMessage = jsonMessage.replace("\r\n", "\\n").replace("\n", "\\n").replace("\r", "\\n");var os = this.process.getOutputStream();synchronized (os) {os.write(jsonMessage.getBytes(StandardCharsets.UTF_8));os.write("\n".getBytes(StandardCharsets.UTF_8));os.flush();}s.next(message);
}

注意:这种方式传输信息时,服务端运行时打印的各种信息,如:banner、日志等都会成为干扰项。

4_SSE方式

最新版 MCP 协议已经弃用了这种方式,转为感知的 Streamable HTTP,但 SpringAI 目前还不支持 Streamable HTTP。

SSE 需要将 MCP Server 部署为 Web 服务。

以加法计算为例,定义 MCP-Server 工程,引入 SSE 服务端依赖

<!--支持webflux以及stdio的方式,还支持webflux的方式暴露服务,-->
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
</dependency>
<!---webmvc是基于mvc的依赖,同样支持stdio,还支持-webmvc的方式暴露服务-->
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>

配置 application.yaml 属性:

spring:application:name: mcp-servermain:# (mvc,webflux依赖都引入则只能使用webflux,不然客户端会404)web-application-type: reactive # webflux的方式暴露服务,除此外还有mvc-servletai:mcp:server:name: mcp-server # 应用名称version: 1.0.0 # 版本type: async # 异步响应通信方式base-url:  # base-urlsse-endpoint: /sse # 切入点,client默认会向url/sse下发送请求
server:port: 8888

定义加法计算的工具类,这里让它返回错误的结果方便验证

@Service
public class CalculationService {@Tool(description = "两数加法计算")public Long getWeather(@ToolParam(description = "加数") Long addend,@ToolParam(description = "被加数") Long summand) {return addend * summand;}
}

将工具绑定到 ToolCallbackProvider:

@SpringBootApplication
public class McpServerApplication {public static void main(String[] args) {SpringApplication.run(McpServerApplication.class, args);}@Beanpublic ToolCallbackProvider weatherTools(CalculationService calculationService) {return MethodToolCallbackProvider.builder().toolObjects(calculationService).build();}// 客户端使用 .defaultToolCallbacks(toolCallbackProvider) 添加
}

直接启动 MCP Server 后,客户端新增如下配置即调用服务端提供的工具

spring:ai:mcp:client:sse: # 新增此处配置,与stdio同级,其他配置无需更改connections: # 配置多个连接addition-calculation: # 服务名称url: http://localhost:8888 # mcp-server 地址

验证:

img

5_自定义MCP客户端

在 Spring AI 中,MCP 客户端分为两类:

  • 同步客户端(Sync Client):默认类型,适用于传统的请求-响应模式,使用阻塞操作。
  • 异步客户端(Async Client):适用于响应式应用,使用非阻塞操作。可以通过配置项 spring.ai.mcp.client.type=async 启用。

Spring 提供了自动装配机制,开发者只需实现 McpSyncClientCustomizer 或 McpAsyncClientCustomizer 接口,就能对 MCP 客户端的各个方面进行个性化配置。

例如,控制请求超时、事件监听以及消息处理逻辑。

import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.McpSchema.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.mcp.customizer.McpSyncClientCustomizer;
import org.springframework.stereotype.Component;import java.time.Duration;
import java.util.List;/*** 自定义同步 MCP 客户端配置器* (兼容所有的传输方式: stdio、mvc、webflux 传输方式)*/
@Slf4j
@Component
public class CustomMcpSyncClientCustomizer implements McpSyncClientCustomizer {@Overridepublic void customize(String serverConfigurationName, McpClient.SyncSpec spec) {log.info("正在自定义 MCP 同步客户端配置: {}", serverConfigurationName);// 1. 配置请求超时时间spec.requestTimeout(Duration.ofSeconds(30));// 2. 工具变更监听spec.toolsChangeConsumer((List<Tool> tools) -> {log.info("工具列表发生变更,共 {} 个", tools.size());tools.forEach(tool -> log.debug("工具: {} - {}", tool.name(), tool.description()));handleToolsChange(serverConfigurationName, tools);});// 3. 资源变更监听spec.resourcesChangeConsumer((List<Resource> resources) -> {log.info("资源列表发生变更,共 {} 个", resources.size());resources.forEach(resource -> log.debug("资源: {} - {} ({})",resource.name(), resource.description(), resource.mimeType()));handleResourcesChange(serverConfigurationName, resources);});// 4. 提示变更监听spec.promptsChangeConsumer((List<Prompt> prompts) -> {log.info("提示列表发生变更,共 {} 个", prompts.size());prompts.forEach(prompt -> log.debug("提示: {} - {}", prompt.name(), prompt.description()));handlePromptsChange(serverConfigurationName, prompts);});// 5. 日志消息处理spec.loggingConsumer((McpSchema.LoggingMessageNotification logMsg) -> {switch (logMsg.level()) {case ERROR -> log.error("MCP 服务器错误: {}", logMsg.data());case WARNING -> log.warn("MCP 服务器警告: {}", logMsg.data());case INFO -> log.info("MCP 服务器信息: {}", logMsg.data());case DEBUG -> log.debug("MCP 服务器调试: {}", logMsg.data());default -> log.trace("MCP 服务器日志: {}", logMsg.data());}});log.info("MCP 同步客户端配置完成: {}", serverConfigurationName);// 2. 设置文件系统根目录访问权限// 3. 自定义采样处理器//  - 处理LLM生成请求//  - 客户端保持对模型访问、选择和权限的控制//  - 服务器无需API密钥即可利用AI能力}/** 工具变更处理逻辑 */private void handleToolsChange(String serverName, List<Tool> tools) {log.info("为服务器 {} 更新工具缓存", serverName);// 可扩展:更新缓存、通知下游服务等}/** 资源变更处理逻辑 */private void handleResourcesChange(String serverName, List<Resource> resources) {log.info("为服务器 {} 刷新资源索引", serverName);// 可扩展:刷新索引、更新界面等}/** 提示变更处理逻辑 */private void handlePromptsChange(String serverName, List<Prompt> prompts) {log.info("为服务器 {} 重新加载提示模板", serverName);// 可扩展:重新加载提示、推荐新模板等}
}

MCP 客户端 bean 会自动配置并且可以通过注入的方式调用:

@Autowired
private List<McpSyncClient> mcpSyncClients;  // For sync client
// OR
@Autowired
private List<McpAsyncClient> mcpAsyncClients;  // For async client

当启用工具回调(默认行为)时,所有 MCP 客户端注册的 MCP 工具都将作为 ToolCallbackProvider 实例提供:

@Autowired
private SyncMcpToolCallbackProvider toolCallbackProvider;
ToolCallback[] toolCallbacks = toolCallbackProvider.getToolCallbacks();

6_MCP Server权限控制

系统提示词,由调用方传递用户数据:可以将用户信息传递到工具中,但是可以随意去查询其他用户的数据,不可取。

Stdio:

  • 环境变量传递授权信息,但是目前不支持动态修改环境变量。
  • 可以自己尝试实现(销毁原来的 Stdio、建立新的 Stdio,不推荐),由于多个用户存在 Stdio 可能会造成并发竞争因此不推荐。

SSE:

按照传统的 Web 鉴权实现即可,推荐,也不会多个用户竞争同一个进程“切换权限”造成并发。

http://www.dtcms.com/a/337155.html

相关文章:

  • 详细探讨AI在金融、医疗、教育和制造业四大领域的具体落地案例,并通过代码、流程图、Prompt示例和图表等方式展示这些应用的实际效果。
  • 【金融数据分析】用Python对金融产品价格进行时间序列分解
  • MySQL的锁:
  • Python入门第9课:面向对象编程(OOP)从零开始,类、对象与三大特性
  • Leetcode 3650. Minimum Cost Path with Edge Reversals
  • Vue Router的常用API有哪些?
  • 05 定时器,延时器、递归、内置对象(Object 对象+Math 对象+Date 对象+String对象)
  • Less (CSS 预处理器)
  • 8.18网络编程——基于UDP的TFTP文件传输客户端
  • 后端通用基础代码
  • 电源电路介绍
  • OpenTelemetry、Jaeger 与 Zipkin:分布式链路追踪方案对比与实践
  • 窗口看门狗(WWDG)
  • 网络基础——协议认识
  • Linux权限的学习
  • 抽象类与接口的区别
  • 【C语言篇】操作符详解
  • Ubuntu下无法在huggingface下载指定模型的解决方法
  • Read Frog:一款开源AI浏览器语言学习扩展
  • 如何解决IDEA/Datagrip无法连接数据库的问题:解决方法为添加参数-Djava.net.preferIPv4Stack=true
  • Java原子类详解
  • 并发编程原理与实战(二十四)Java并发基石LockSupport park/unpark机制全解析
  • 车e估牵头正式启动乘用车金融价值评估师编制
  • AI出题人给出的Java后端面经(十八)(日更)
  • Java基础八股复习3 jvm-内存结构
  • 数据仓库理论
  • 具身智能2硬件架构(人形机器人)摘自Openloong社区
  • Vue3 中使用 Element Plus 完整指南
  • 博客项目 Spring + Redis + Mysql
  • 利用DeepSeek辅助WPS电子表格ET格式分析