原文链接:SpringAI(GA):SpringAI下的MCP源码解读
教程说明
说明:本教程将采用2025年5月20日正式的GA版,给出如下内容
- 核心功能模块的快速上手教程
- 核心功能模块的源码级解读
- Spring ai alibaba增强的快速上手教程 + 源码级解读
版本:JDK21 + SpringBoot3.4.5 + SpringAI 1.0.0 + SpringAI Alibaba 1.0.0.2
将陆续完成如下章节教程。本章是第七章(MCP使用范式)下的SpringAI下的MCP,建议配合SpringAI(GA):MCP源码解读+SpringAI(GA):Tool源码+工具触发链路解读一起理清MCP层面触发工具的全链路
代码开源如下:github.com/GTyingzi/sp...

微信推文往届解读可参考:
第一章内容
SpringAI(GA)的chat:快速上手+自动注入源码解读
第二章内容
SpringAI(GA):Sqlite、Mysql、Redis消息存储快速上手
第三章内容
第五章内容
SpringAI(GA):内存、Redis、ES的向量数据库存储---快速上手
SpringAI(GA):向量数据库理论源码解读+Redis、Es接入源码
第六章内容
第七章内容
整理不易,获取更好的观赏体验,可付费获取飞书云文档Spring AI最新教程权限,目前49.9,随着内容不断完善,会逐步涨价。
注:M6版快速上手教程+源码解读飞书云文档已免费提供
为鼓励大家积极参与为Spring Ai Alibaba开源社区 :github.com/alibaba/spr...
SpringAI 下的 MCP
pom.xml 文件
xml
// 服务端
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
</dependency>
// 客户端
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client-webflux</artifactId>
</dependency>
- spring-ai-autoconfigure-mcp-server:server 自动注入
- spring-ai-autoconfigure-mcp-client:client 自动注入
- spring-ai-mcp:SpringAI 下集成 MCP 转换 ToolCallback
- mcp-spring-webflux:主要是提供了 WebFluxSseServerTransportProvider、WebFluxSseClientTransport 两个类
SpringAI 下 MCP 各类的说明

server 自动注入
McpServerProperties
MCP 服务器的配置类,通过 @ConfigurationProperties 注解将配置文件中以 spring.ai.mcp.server 为前缀的属性映射到类的字段中
-
boolean enabled(默认为true)
:启用/禁用整个 MCP 服务器。若为 false,服务器及其组件不会初始化 -
boolean stdio(默认为false)
:是否启用标准输入/输出(stdio)传输。启用后,服务器通过标准输入监听消息,标准输出发送响应 -
String name(默认为"mcp-server")
:服务器实例名称,用于日志和监控中的标识 -
String version(默认为"1.0.0")
:服务器版本号,报告给客户端用于兼容性检查 -
boolean resourceChangeNotification(默认为true)
:是否启用资源变更通知(如资源增删改),仅当服务器支持资源能力时生效 -
boolean toolChangeNotification(默认为true)
:是否启用工具变更通知(如工具注册/注销),仅当服务器支持工具能力时生效 -
boolean promptChangeNotification(默认为true)
:是否启用提示模板变更通知,仅当服务器支持提示能力时生效 -
String baseUrl(默认为"")
:服务器的基础 URL,用于构建资源路径。需确保不为 null -
String sseEndpoint(默认为"sse")
:Server-Sent Events (SSE) 的端点路径。仅在 WebMvc/WebFlux 传输模式下使用 -
String sseMessageEndpoint(默认为"/mcp/message")
:SSE 消息端点路径,用于客户端与服务器的消息通信 -
ServerType type(默认为ServerType.
SYNC)
:服务器类型,可选 SYNC 或 ASYNC -
Duration requestTimeout(默认20s)
:请求超时时间,适用于所有客户端请求(如工具调用、资源访问) -
Capabilities capabilities
:封装服务器支持的核心能力开关,包括:资源、工具、提示、完成(completion)能力是否启用- boolean resource(默认为 true):是否支持资源管理能力(如文件、数据读取)
- boolean tool(默认为 true):是否支持工具调用能力(如外部 API 调用)
- boolean prompt(默认为 true):是否支持提示模板管理能力(如动态提示生成)
- boolean completion(默认为 true):是否支持补全能力(如文本生成)
-
Map<String, String> toolResponseMimeType
:按工具名称指定响应的 MIME 类型(如 "toolA": "application/json"),用于自定义工具返回格式 -
String instructions
:当前服务端的指导建议,便于客户端识别
java
package org.springframework.ai.mcp.server.autoconfigure;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.Assert;
@ConfigurationProperties("spring.ai.mcp.server")
public class McpServerProperties {
public static final String CONFIGPREFIX = "spring.ai.mcp.server";
private boolean enabled = true;
private boolean stdio = false;
private String name = "mcp-server";
private String version = "1.0.0";
private String instructions = null;
private boolean resourceChangeNotification = true;
private boolean toolChangeNotification = true;
private boolean promptChangeNotification = true;
private String baseUrl = "";
private String sseEndpoint = "/sse";
private String sseMessageEndpoint = "/mcp/message";
private ServerType type;
private Capabilities capabilities;
private Duration requestTimeout;
private Map<String, String> toolResponseMimeType;
public McpServerProperties() {
this.type = McpServerProperties.ServerType.SYNC;
this.capabilities = new Capabilities();
this.requestTimeout = Duration.ofSeconds(20L);
this.toolResponseMimeType = new HashMap();
}
public Duration getRequestTimeout() {
return this.requestTimeout;
}
public void setRequestTimeout(Duration requestTimeout) {
Assert.notNull(requestTimeout, "Request timeout must not be null");
this.requestTimeout = requestTimeout;
}
public Capabilities getCapabilities() {
return this.capabilities;
}
public boolean isStdio() {
return this.stdio;
}
public void setStdio(boolean stdio) {
this.stdio = stdio;
}
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getName() {
return this.name;
}
public void setName(String name) {
Assert.hasText(name, "Name must not be empty");
this.name = name;
}
public String getVersion() {
return this.version;
}
public void setVersion(String version) {
Assert.hasText(version, "Version must not be empty");
this.version = version;
}
public String getInstructions() {
return this.instructions;
}
public void setInstructions(String instructions) {
this.instructions = instructions;
}
public boolean isResourceChangeNotification() {
return this.resourceChangeNotification;
}
public void setResourceChangeNotification(boolean resourceChangeNotification) {
this.resourceChangeNotification = resourceChangeNotification;
}
public boolean isToolChangeNotification() {
return this.toolChangeNotification;
}
public void setToolChangeNotification(boolean toolChangeNotification) {
this.toolChangeNotification = toolChangeNotification;
}
public boolean isPromptChangeNotification() {
return this.promptChangeNotification;
}
public void setPromptChangeNotification(boolean promptChangeNotification) {
this.promptChangeNotification = promptChangeNotification;
}
public String getBaseUrl() {
return this.baseUrl;
}
public void setBaseUrl(String baseUrl) {
Assert.notNull(baseUrl, "Base URL must not be null");
this.baseUrl = baseUrl;
}
public String getSseEndpoint() {
return this.sseEndpoint;
}
public void setSseEndpoint(String sseEndpoint) {
Assert.hasText(sseEndpoint, "SSE endpoint must not be empty");
this.sseEndpoint = sseEndpoint;
}
public String getSseMessageEndpoint() {
return this.sseMessageEndpoint;
}
public void setSseMessageEndpoint(String sseMessageEndpoint) {
Assert.hasText(sseMessageEndpoint, "SSE message endpoint must not be empty");
this.sseMessageEndpoint = sseMessageEndpoint;
}
public ServerType getType() {
return this.type;
}
public void setType(ServerType serverType) {
Assert.notNull(serverType, "Server type must not be null");
this.type = serverType;
}
public Map<String, String> getToolResponseMimeType() {
return this.toolResponseMimeType;
}
public static enum ServerType {
SYNC,
ASYNC;
}
public static class Capabilities {
private boolean resource = true;
private boolean tool = true;
private boolean prompt = true;
private boolean completion = true;
public boolean isResource() {
return this.resource;
}
public void setResource(boolean resource) {
this.resource = resource;
}
public boolean isTool() {
return this.tool;
}
public void setTool(boolean tool) {
this.tool = tool;
}
public boolean isPrompt() {
return this.prompt;
}
public void setPrompt(boolean prompt) {
this.prompt = prompt;
}
public boolean isCompletion() {
return this.completion;
}
public void setCompletion(boolean completion) {
this.completion = completion;
}
}
}
McpWebFluxServerAutoConfiguration
MCP 服务器的的 WebFlux 自动配置类,仅当满足以下条件时自动配置生效
- @ConditionalOnClass({ WebFluxSseServerTransportProvider.class }):类路径包含该类(来自 mcp-spring-webflux 依赖)
- @ConditionalOnMissingBean(McpServerTransportProvider.class):未手动定义 McpServerTransportProvider Bean
- McpServerStdioDisabledCondition 条件成立(即 stdio 配置为 false)
|--------------------------|-------------------------------------------------------------------------|
| 方法名称 | 描述 |
| webFluxTransport | 提供WebFluxSseServerTransportProvider的Bean:提供基于 Spring WebFlux 的 SSE 传输实现 |
| webfluxMcpRouterFunction | 提供RouterFunction的Bean:定义 WebFlux 的 路由规则,将 HTTP 请求映射到 MCP 服务器的 SSE 处理逻辑 |
java
package org.springframework.ai.mcp.server.autoconfigure;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.server.transport.WebFluxSseServerTransportProvider;
import io.modelcontextprotocol.spec.McpServerTransportProvider;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.web.reactive.function.server.RouterFunction;
@AutoConfiguration
@ConditionalOnClass({WebFluxSseServerTransportProvider.class})
@ConditionalOnMissingBean({McpServerTransportProvider.class})
@Conditional({McpServerStdioDisabledCondition.class})
public class McpWebFluxServerAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public WebFluxSseServerTransportProvider webFluxTransport(ObjectProvider<ObjectMapper> objectMapperProvider, McpServerProperties serverProperties) {
ObjectMapper objectMapper = (ObjectMapper)objectMapperProvider.getIfAvailable(ObjectMapper::new);
return new WebFluxSseServerTransportProvider(objectMapper, serverProperties.getBaseUrl(), serverProperties.getSseMessageEndpoint(), serverProperties.getSseEndpoint());
}
@Bean
public RouterFunction<?> webfluxMcpRouterFunction(WebFluxSseServerTransportProvider webFluxProvider) {
return webFluxProvider.getRouterFunction();
}
}
McpWebMvcServerAutoConfiguration
MCP 服务器的的 WebFlux 自动配置类,仅当满足以下条件时自动配置生效
- @ConditionalOnClass({ WebMvcSseServerTransportProvider.class }):类路径包含该类(来自 mcp-spring-webmvc 依赖)
- @ConditionalOnMissingBean(McpServerTransportProvider.class):未手动定义 McpServerTransportProvider Bean
- McpServerStdioDisabledCondition 条件成立(即 stdio 配置为 false)
|----------------------------------|-----------------------------------------------------------------------|
| 方法名称 | 描述 |
| webMvcSseServerTransportProvider | 提供WebMvcSseServerTransportProvider的Bean:提供基于 Spring MVC 的 SSE 传输实现 |
| mvcMcpRouterFunction | 提供RouterFunction的Bean:定义 WebMVC 的 路由规则,将 HTTP 请求映射到 MCP 服务器的 SSE 处理逻辑 |
java
package org.springframework.ai.mcp.server.autoconfigure;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.server.transport.WebMvcSseServerTransportProvider;
import io.modelcontextprotocol.spec.McpServerTransportProvider;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.ServerResponse;
@AutoConfiguration
@ConditionalOnClass({WebMvcSseServerTransportProvider.class})
@ConditionalOnMissingBean({McpServerTransportProvider.class})
@Conditional({McpServerStdioDisabledCondition.class})
public class McpWebMvcServerAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public WebMvcSseServerTransportProvider webMvcSseServerTransportProvider(ObjectProvider<ObjectMapper> objectMapperProvider, McpServerProperties serverProperties) {
ObjectMapper objectMapper = (ObjectMapper)objectMapperProvider.getIfAvailable(ObjectMapper::new);
return new WebMvcSseServerTransportProvider(objectMapper, serverProperties.getBaseUrl(), serverProperties.getSseMessageEndpoint(), serverProperties.getSseEndpoint());
}
@Bean
public RouterFunction<ServerResponse> mvcMcpRouterFunction(WebMvcSseServerTransportProvider transportProvider) {
return transportProvider.getRouterFunction();
}
}
McpServerStdioDisabledCondition
条件注解类,用于判断是否满足以下两个核心条件:
- MCP 服务器已启用
- STDIO 传输模式已禁用
java
package org.springframework.ai.mcp.server.autoconfigure;
import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.ConfigurationCondition.ConfigurationPhase;
public class McpServerStdioDisabledCondition extends AllNestedConditions {
public McpServerStdioDisabledCondition() {
super(ConfigurationPhase.PARSECONFIGURATION);
}
@ConditionalOnProperty(
prefix = "spring.ai.mcp.server",
name = {"enabled"},
havingValue = "true",
matchIfMissing = true
)
static class McpServerEnabledCondition {
}
@ConditionalOnProperty(
prefix = "spring.ai.mcp.server",
name = {"stdio"},
havingValue = "false",
matchIfMissing = true
)
static class StdioDisabledCondition {
}
}
McpServerAutoConfiguration
根据配置动态创建同步/异步服务器实例,并集成工具、资源、提示等能力,支持多种传输协议(STDIO/WebMvc/WebFlux),仅当满足以下条件时自动配置生效
- 类路径包含 McpSchema 和 McpSyncServer(MCP SDK 依赖)
- 配置项 spring.ai.mcp.server.enabled 为 true 时(默认为 true)
|----------------------|----------------------------------------------------------------|
| 方法名称 | 描述 |
| stdioServerTransport | 提供McpServerTransportProvider的Bean,默认为STDIO传输 |
| capabilitiesBuilder | 提供McpSchema.ServerCapabilities.Builder的Bean,初始化MCP服务器的能力构建器 |
| syncTools | 提供List的Bean,将 ToolCallback 转换为 SyncToolSpecification,支持同步工具调用 |
| asyncTools | 提供List的Bean,将 ToolCallback 转换为 AsyncToolSpecification,支持异步工具调用 |
| mcpSyncServer | 提供McpSyncServer的Bean,创建同步模式的 MCP 服务器实例 |
| mcpAsyncServer | 提供McpAsyncServer的Bean,创建异步模式的 MCP 服务器实例 |
java
package org.springframework.ai.mcp.server.autoconfigure;
import io.modelcontextprotocol.server.McpAsyncServer;
import io.modelcontextprotocol.server.McpAsyncServerExchange;
import io.modelcontextprotocol.server.McpServer;
import io.modelcontextprotocol.server.McpServerFeatures;
import io.modelcontextprotocol.server.McpSyncServer;
import io.modelcontextprotocol.server.McpSyncServerExchange;
import io.modelcontextprotocol.server.transport.StdioServerTransportProvider;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.McpServerTransportProvider;
import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import org.springframework.ai.mcp.McpToolUtils;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.core.log.LogAccessor;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MimeType;
import reactor.core.publisher.Mono;
@AutoConfiguration(
after = {McpWebMvcServerAutoConfiguration.class, McpWebFluxServerAutoConfiguration.class}
)
@ConditionalOnClass({McpSchema.class, McpSyncServer.class})
@EnableConfigurationProperties({McpServerProperties.class})
@ConditionalOnProperty(
prefix = "spring.ai.mcp.server",
name = {"enabled"},
havingValue = "true",
matchIfMissing = true
)
public class McpServerAutoConfiguration {
private static final LogAccessor logger = new LogAccessor(McpServerAutoConfiguration.class);
@Bean
@ConditionalOnMissingBean
public McpServerTransportProvider stdioServerTransport() {
return new StdioServerTransportProvider();
}
@Bean
@ConditionalOnMissingBean
public McpSchema.ServerCapabilities.Builder capabilitiesBuilder() {
return ServerCapabilities.builder();
}
@Bean
@ConditionalOnProperty(
prefix = "spring.ai.mcp.server",
name = {"type"},
havingValue = "SYNC",
matchIfMissing = true
)
public List<McpServerFeatures.SyncToolSpecification> syncTools(ObjectProvider<List<ToolCallback>> toolCalls, List<ToolCallback> toolCallbacksList, McpServerProperties serverProperties) {
List<ToolCallback> tools = new ArrayList(toolCalls.stream().flatMap(Collection::stream).toList());
if (!CollectionUtils.isEmpty(toolCallbacksList)) {
tools.addAll(toolCallbacksList);
}
return this.toSyncToolSpecifications(tools, serverProperties);
}
private List<McpServerFeatures.SyncToolSpecification> toSyncToolSpecifications(List<ToolCallback> tools, McpServerProperties serverProperties) {
return ((Map)tools.stream().collect(Collectors.toMap((tool) -> tool.getToolDefinition().name(), (tool) -> tool, (existing, replacement) -> existing))).values().stream().map((tool) -> {
String toolName = tool.getToolDefinition().name();
MimeType mimeType = serverProperties.getToolResponseMimeType().containsKey(toolName) ? MimeType.valueOf((String)serverProperties.getToolResponseMimeType().get(toolName)) : null;
return McpToolUtils.toSyncToolSpecification(tool, mimeType);
}).toList();
}
@Bean
@ConditionalOnProperty(
prefix = "spring.ai.mcp.server",
name = {"type"},
havingValue = "SYNC",
matchIfMissing = true
)
public McpSyncServer mcpSyncServer(McpServerTransportProvider transportProvider, McpSchema.ServerCapabilities.Builder capabilitiesBuilder, McpServerProperties serverProperties, ObjectProvider<List<McpServerFeatures.SyncToolSpecification>> tools, ObjectProvider<List<McpServerFeatures.SyncResourceSpecification>> resources, ObjectProvider<List<McpServerFeatures.SyncPromptSpecification>> prompts, ObjectProvider<List<McpServerFeatures.SyncCompletionSpecification>> completions, ObjectProvider<BiConsumer<McpSyncServerExchange, List<McpSchema.Root>>> rootsChangeConsumers, List<ToolCallbackProvider> toolCallbackProvider) {
McpSchema.Implementation serverInfo = new McpSchema.Implementation(serverProperties.getName(), serverProperties.getVersion());
McpServer.SyncSpecification serverBuilder = McpServer.sync(transportProvider).serverInfo(serverInfo);
if (serverProperties.getCapabilities().isTool()) {
logger.info("Enable tools capabilities, notification: " + serverProperties.isToolChangeNotification());
capabilitiesBuilder.tools(serverProperties.isToolChangeNotification());
List<McpServerFeatures.SyncToolSpecification> toolSpecifications = new ArrayList(tools.stream().flatMap(Collection::stream).toList());
List<ToolCallback> providerToolCallbacks = toolCallbackProvider.stream().map((pr) -> List.of(pr.getToolCallbacks())).flatMap(Collection::stream).filter((fc) -> fc instanceof ToolCallback).map((fc) -> fc).toList();
toolSpecifications.addAll(this.toSyncToolSpecifications(providerToolCallbacks, serverProperties));
if (!CollectionUtils.isEmpty(toolSpecifications)) {
serverBuilder.tools(toolSpecifications);
logger.info("Registered tools: " + toolSpecifications.size());
}
}
if (serverProperties.getCapabilities().isResource()) {
logger.info("Enable resources capabilities, notification: " + serverProperties.isResourceChangeNotification());
capabilitiesBuilder.resources(false, serverProperties.isResourceChangeNotification());
List<McpServerFeatures.SyncResourceSpecification> resourceSpecifications = resources.stream().flatMap(Collection::stream).toList();
if (!CollectionUtils.isEmpty(resourceSpecifications)) {
serverBuilder.resources(resourceSpecifications);
logger.info("Registered resources: " + resourceSpecifications.size());
}
}
if (serverProperties.getCapabilities().isPrompt()) {
logger.info("Enable prompts capabilities, notification: " + serverProperties.isPromptChangeNotification());
capabilitiesBuilder.prompts(serverProperties.isPromptChangeNotification());
List<McpServerFeatures.SyncPromptSpecification> promptSpecifications = prompts.stream().flatMap(Collection::stream).toList();
if (!CollectionUtils.isEmpty(promptSpecifications)) {
serverBuilder.prompts(promptSpecifications);
logger.info("Registered prompts: " + promptSpecifications.size());
}
}
if (serverProperties.getCapabilities().isCompletion()) {
logger.info("Enable completions capabilities");
capabilitiesBuilder.completions();
List<McpServerFeatures.SyncCompletionSpecification> completionSpecifications = completions.stream().flatMap(Collection::stream).toList();
if (!CollectionUtils.isEmpty(completionSpecifications)) {
serverBuilder.completions(completionSpecifications);
logger.info("Registered completions: " + completionSpecifications.size());
}
}
rootsChangeConsumers.ifAvailable((consumer) -> {
serverBuilder.rootsChangeHandler((exchange, roots) -> consumer.accept(exchange, roots));
logger.info("Registered roots change consumer");
});
serverBuilder.capabilities(capabilitiesBuilder.build());
serverBuilder.instructions(serverProperties.getInstructions());
serverBuilder.requestTimeout(serverProperties.getRequestTimeout());
return serverBuilder.build();
}
@Bean
@ConditionalOnProperty(
prefix = "spring.ai.mcp.server",
name = {"type"},
havingValue = "ASYNC"
)
public List<McpServerFeatures.AsyncToolSpecification> asyncTools(ObjectProvider<List<ToolCallback>> toolCalls, List<ToolCallback> toolCallbackList, McpServerProperties serverProperties) {
List<ToolCallback> tools = new ArrayList(toolCalls.stream().flatMap(Collection::stream).toList());
if (!CollectionUtils.isEmpty(toolCallbackList)) {
tools.addAll(toolCallbackList);
}
return this.toAsyncToolSpecification(tools, serverProperties);
}
private List<McpServerFeatures.AsyncToolSpecification> toAsyncToolSpecification(List<ToolCallback> tools, McpServerProperties serverProperties) {
return ((Map)tools.stream().collect(Collectors.toMap((tool) -> tool.getToolDefinition().name(), (tool) -> tool, (existing, replacement) -> existing))).values().stream().map((tool) -> {
String toolName = tool.getToolDefinition().name();
MimeType mimeType = serverProperties.getToolResponseMimeType().containsKey(toolName) ? MimeType.valueOf((String)serverProperties.getToolResponseMimeType().get(toolName)) : null;
return McpToolUtils.toAsyncToolSpecification(tool, mimeType);
}).toList();
}
@Bean
@ConditionalOnProperty(
prefix = "spring.ai.mcp.server",
name = {"type"},
havingValue = "ASYNC"
)
public McpAsyncServer mcpAsyncServer(McpServerTransportProvider transportProvider, McpSchema.ServerCapabilities.Builder capabilitiesBuilder, McpServerProperties serverProperties, ObjectProvider<List<McpServerFeatures.AsyncToolSpecification>> tools, ObjectProvider<List<McpServerFeatures.AsyncResourceSpecification>> resources, ObjectProvider<List<McpServerFeatures.AsyncPromptSpecification>> prompts, ObjectProvider<List<McpServerFeatures.AsyncCompletionSpecification>> completions, ObjectProvider<BiConsumer<McpAsyncServerExchange, List<McpSchema.Root>>> rootsChangeConsumer, List<ToolCallbackProvider> toolCallbackProvider) {
McpSchema.Implementation serverInfo = new McpSchema.Implementation(serverProperties.getName(), serverProperties.getVersion());
McpServer.AsyncSpecification serverBuilder = McpServer.async(transportProvider).serverInfo(serverInfo);
if (serverProperties.getCapabilities().isTool()) {
List<McpServerFeatures.AsyncToolSpecification> toolSpecifications = new ArrayList(tools.stream().flatMap(Collection::stream).toList());
List<ToolCallback> providerToolCallbacks = toolCallbackProvider.stream().map((pr) -> List.of(pr.getToolCallbacks())).flatMap(Collection::stream).filter((fc) -> fc instanceof ToolCallback).map((fc) -> fc).toList();
toolSpecifications.addAll(this.toAsyncToolSpecification(providerToolCallbacks, serverProperties));
logger.info("Enable tools capabilities, notification: " + serverProperties.isToolChangeNotification());
capabilitiesBuilder.tools(serverProperties.isToolChangeNotification());
if (!CollectionUtils.isEmpty(toolSpecifications)) {
serverBuilder.tools(toolSpecifications);
logger.info("Registered tools: " + toolSpecifications.size());
}
}
if (serverProperties.getCapabilities().isResource()) {
logger.info("Enable resources capabilities, notification: " + serverProperties.isResourceChangeNotification());
capabilitiesBuilder.resources(false, serverProperties.isResourceChangeNotification());
List<McpServerFeatures.AsyncResourceSpecification> resourceSpecifications = resources.stream().flatMap(Collection::stream).toList();
if (!CollectionUtils.isEmpty(resourceSpecifications)) {
serverBuilder.resources(resourceSpecifications);
logger.info("Registered resources: " + resourceSpecifications.size());
}
}
if (serverProperties.getCapabilities().isPrompt()) {
logger.info("Enable prompts capabilities, notification: " + serverProperties.isPromptChangeNotification());
capabilitiesBuilder.prompts(serverProperties.isPromptChangeNotification());
List<McpServerFeatures.AsyncPromptSpecification> promptSpecifications = prompts.stream().flatMap(Collection::stream).toList();
if (!CollectionUtils.isEmpty(promptSpecifications)) {
serverBuilder.prompts(promptSpecifications);
logger.info("Registered prompts: " + promptSpecifications.size());
}
}
if (serverProperties.getCapabilities().isCompletion()) {
logger.info("Enable completions capabilities");
capabilitiesBuilder.completions();
List<McpServerFeatures.AsyncCompletionSpecification> completionSpecifications = completions.stream().flatMap(Collection::stream).toList();
if (!CollectionUtils.isEmpty(completionSpecifications)) {
serverBuilder.completions(completionSpecifications);
logger.info("Registered completions: " + completionSpecifications.size());
}
}
rootsChangeConsumer.ifAvailable((consumer) -> {
BiFunction<McpAsyncServerExchange, List<McpSchema.Root>, Mono<Void>> asyncConsumer = (exchange, roots) -> {
consumer.accept(exchange, roots);
return Mono.empty();
};
serverBuilder.rootsChangeHandler(asyncConsumer);
logger.info("Registered roots change consumer");
});
serverBuilder.capabilities(capabilitiesBuilder.build());
serverBuilder.instructions(serverProperties.getInstructions());
serverBuilder.requestTimeout(serverProperties.getRequestTimeout());
return serverBuilder.build();
}
}
client 自动注入
McpClientCommonProperties
MCP 客户端的通用配置参数,适用于所有传输类型(stdio、http、sse 等),通过 @ConfigurationProperties 注解,将以 spring.ai.mcp.client 为前缀的配置项自动绑定到该类的字段
-
boolean enabled(默认为true)
:是否启用 MCP 客户端,true 表示启用,false 表示不初始化相关组件 -
String name(默认为"spring-ai-mcp-client")
:MCP 客户端实例名称 -
String version(默认为"1.0.0")
:MCP 客户端版本号 -
boolean initialized(默认为true)
:标记 MCP 客户端是否需要初始化 -
Duration requestTimeout(默认为20s)
:客户端请求超时时间,默认 20 秒,所有请求(如工具调用、资源访问等)都受此超时控制 -
ClientType type(默认为ClientType.
SYNC)
:客户端类型,枚举值有 SYNC、ASYNC,决定通信模式 -
boolean rootChangeNotification(默认为true)
:是否启用根变更通知,启用后,根配置变更时客户端会收到通知 -
Toolcallback toolcallback
:工具回调相关配置,包含一个 enabled 字段。该字段决定了是否提供 ToolCallbackProviderboolean enabled(默认为true)
:是否启用工具回调
java
package org.springframework.ai.mcp.client.autoconfigure.properties;
import java.time.Duration;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("spring.ai.mcp.client")
public class McpClientCommonProperties {
public static final String CONFIGPREFIX = "spring.ai.mcp.client";
private boolean enabled = true;
private String name = "spring-ai-mcp-client";
private String version = "1.0.0";
private boolean initialized = true;
private Duration requestTimeout = Duration.ofSeconds(20L);
private ClientType type;
private boolean rootChangeNotification;
private Toolcallback toolcallback;
public McpClientCommonProperties() {
this.type = McpClientCommonProperties.ClientType.SYNC;
this.rootChangeNotification = true;
this.toolcallback = new Toolcallback();
}
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getVersion() {
return this.version;
}
public void setVersion(String version) {
this.version = version;
}
public boolean isInitialized() {
return this.initialized;
}
public void setInitialized(boolean initialized) {
this.initialized = initialized;
}
public Duration getRequestTimeout() {
return this.requestTimeout;
}
public void setRequestTimeout(Duration requestTimeout) {
this.requestTimeout = requestTimeout;
}
public ClientType getType() {
return this.type;
}
public void setType(ClientType type) {
this.type = type;
}
public boolean isRootChangeNotification() {
return this.rootChangeNotification;
}
public void setRootChangeNotification(boolean rootChangeNotification) {
this.rootChangeNotification = rootChangeNotification;
}
public Toolcallback getToolcallback() {
return this.toolcallback;
}
public void setToolcallback(Toolcallback toolcallback) {
this.toolcallback = toolcallback;
}
public static enum ClientType {
SYNC,
ASYNC;
}
public static class Toolcallback {
private boolean enabled = true;
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public boolean isEnabled() {
return this.enabled;
}
}
}
McpSseClientProperties
基于 SSE 的 MCP 客户端连接参数,通过 @ConfigurationProperties("spring.ai.mcp.client.sse")绑定配置项
Map<String, SseParameters> connections
:存储多个命名的 SSE 连接配置
java
package org.springframework.ai.mcp.client.autoconfigure.properties;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("spring.ai.mcp.client.sse")
public class McpSseClientProperties {
public static final String CONFIGPREFIX = "spring.ai.mcp.client.sse";
private final Map<String, SseParameters> connections = new HashMap();
public Map<String, SseParameters> getConnections() {
return this.connections;
}
public static record SseParameters(String url, String sseEndpoint) {
}
}
McpStdioClientProperties
基于 stdio 的 MCP 客户端连接参数配置,通过 @ConfigurationProperties("spring.ai.mcp.client.stdio")绑定配置项
Resource serversConfiguration
:外部资源文件(如 JSON),包含 MCP 服务器的 stdio 连接配置。可集中管理多个服务器的命令、参数、环境变量等Map<String, Parameters> connections
:以 Map 形式存储多个命名的 stdio 连接配置。key 为连接名称,value 为该连接的参数(命令、参数、环境变量)
java
package org.springframework.ai.mcp.client.autoconfigure.properties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.client.transport.ServerParameters;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.io.Resource;
@ConfigurationProperties("spring.ai.mcp.client.stdio")
public class McpStdioClientProperties {
public static final String CONFIGPREFIX = "spring.ai.mcp.client.stdio";
private Resource serversConfiguration;
private final Map<String, Parameters> connections = new HashMap();
public Resource getServersConfiguration() {
return this.serversConfiguration;
}
public void setServersConfiguration(Resource stdioConnectionResources) {
this.serversConfiguration = stdioConnectionResources;
}
public Map<String, Parameters> getConnections() {
return this.connections;
}
private Map<String, ServerParameters> resourceToServerParameters() {
try {
Map<String, Map<String, Parameters>> stdioConnection = (Map)(new ObjectMapper()).readValue(this.serversConfiguration.getInputStream(), new TypeReference<Map<String, Map<String, Parameters>>>() {
});
Map<String, Parameters> mcpServerJsonConfig = (Map)((Map.Entry)stdioConnection.entrySet().iterator().next()).getValue();
return (Map)mcpServerJsonConfig.entrySet().stream().collect(Collectors.toMap((kv) -> (String)kv.getKey(), (kv) -> {
Parameters parameters = (Parameters)kv.getValue();
return ServerParameters.builder(parameters.command()).args(parameters.args()).env(parameters.env()).build();
}));
} catch (Exception e) {
throw new RuntimeException("Failed to read stdio connection resource", e);
}
}
public Map<String, ServerParameters> toServerParameters() {
Map<String, ServerParameters> serverParameters = new HashMap();
if (this.serversConfiguration != null) {
serverParameters.putAll(this.resourceToServerParameters());
}
for(Map.Entry<String, Parameters> entry : this.connections.entrySet()) {
serverParameters.put((String)entry.getKey(), ((Parameters)entry.getValue()).toServerParameters());
}
return serverParameters;
}
@JsonInclude(Include.NONABSENT)
public static record Parameters(String command, List<String> args, Map<String, String> env) {
public Parameters(@JsonProperty("command") String command, @JsonProperty("args") List<String> args, @JsonProperty("env") Map<String, String> env) {
this.command = command;
this.args = args;
this.env = env;
}
public ServerParameters toServerParameters() {
return ServerParameters.builder(this.command()).args(this.args()).env(this.env()).build();
}
@JsonProperty("command")
public String command() {
return this.command;
}
@JsonProperty("args")
public List<String> args() {
return this.args;
}
@JsonProperty("env")
public Map<String, String> env() {
return this.env;
}
}
}
NamedClientMcpTransport
封装带有名称的 MCP 客户端传输对象,标识和管理多个 MCP 客户端传输
java
package org.springframework.ai.mcp.client.autoconfigure;
import io.modelcontextprotocol.spec.McpClientTransport;
public record NamedClientMcpTransport(String name, McpClientTransport transport) {
}
SseWebFluxTransportAutoConfiguration
自动配置基于 WebFlux 的 SSE MCP 客户端传输能力,仅当满足以下条件时自动配置生效
- 类路径中有 WebFluxSseClientTransport
- 配置项 spring.ai.mcp.client.enabled=true(默认为 true)
对外提供 List的 Bean,逻辑如下
- 读取所有配置的 SSE 连接(如 server1、server2)
- 为每个连接克隆一个 WebClient.Builder,设置对应的 baseUrl
- 构建 WebFluxSseClientTransport 实例,设置端点和 JSON 处理器
- 封装为 NamedClientMcpTransport,加入列表
java
package org.springframework.ai.mcp.client.autoconfigure;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.client.transport.WebFluxSseClientTransport;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.springframework.ai.mcp.client.autoconfigure.properties.McpClientCommonProperties;
import org.springframework.ai.mcp.client.autoconfigure.properties.McpSseClientProperties;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.web.reactive.function.client.WebClient;
@AutoConfiguration
@ConditionalOnClass({WebFluxSseClientTransport.class})
@EnableConfigurationProperties({McpSseClientProperties.class, McpClientCommonProperties.class})
@ConditionalOnProperty(
prefix = "spring.ai.mcp.client",
name = {"enabled"},
havingValue = "true",
matchIfMissing = true
)
public class SseWebFluxTransportAutoConfiguration {
@Bean
public List<NamedClientMcpTransport> webFluxClientTransports(McpSseClientProperties sseProperties, ObjectProvider<WebClient.Builder> webClientBuilderProvider, ObjectProvider<ObjectMapper> objectMapperProvider) {
List<NamedClientMcpTransport> sseTransports = new ArrayList();
WebClient.Builder webClientBuilderTemplate = (WebClient.Builder)webClientBuilderProvider.getIfAvailable(WebClient::builder);
ObjectMapper objectMapper = (ObjectMapper)objectMapperProvider.getIfAvailable(ObjectMapper::new);
for(Map.Entry<String, McpSseClientProperties.SseParameters> serverParameters : sseProperties.getConnections().entrySet()) {
WebClient.Builder webClientBuilder = webClientBuilderTemplate.clone().baseUrl(((McpSseClientProperties.SseParameters)serverParameters.getValue()).url());
String sseEndpoint = ((McpSseClientProperties.SseParameters)serverParameters.getValue()).sseEndpoint() != null ? ((McpSseClientProperties.SseParameters)serverParameters.getValue()).sseEndpoint() : "/sse";
WebFluxSseClientTransport transport = WebFluxSseClientTransport.builder(webClientBuilder).sseEndpoint(sseEndpoint).objectMapper(objectMapper).build();
sseTransports.add(new NamedClientMcpTransport((String)serverParameters.getKey(), transport));
}
return sseTransports;
}
}
SseHttpClientTransportAutoConfiguration
主要用于在没有 WebFlux 环境时,自动配置基于 JDK HttpClient 的 SSE(Server-Sent Events)MCP 客户端传输能力,仅当满足以下条件时自动配置生效
- 类路径中有 McpSchema、McpSyncClient
- 类路径缺失:io.modelcontextprotocol.client.transport.WebFluxSseClientTransport
- 配置项 spring.ai.mcp.client.enabled=true(默认为 true)
对外提供 List的 Bean
java
package org.springframework.ai.mcp.client.autoconfigure;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.client.McpSyncClient;
import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport;
import io.modelcontextprotocol.spec.McpSchema;
import java.net.http.HttpClient;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.springframework.ai.mcp.client.autoconfigure.properties.McpClientCommonProperties;
import org.springframework.ai.mcp.client.autoconfigure.properties.McpSseClientProperties;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
@AutoConfiguration(
after = {SseWebFluxTransportAutoConfiguration.class}
)
@ConditionalOnClass({McpSchema.class, McpSyncClient.class})
@ConditionalOnMissingClass({"io.modelcontextprotocol.client.transport.WebFluxSseClientTransport"})
@EnableConfigurationProperties({McpSseClientProperties.class, McpClientCommonProperties.class})
@ConditionalOnProperty(
prefix = "spring.ai.mcp.client",
name = {"enabled"},
havingValue = "true",
matchIfMissing = true
)
public class SseHttpClientTransportAutoConfiguration {
@Bean
public List<NamedClientMcpTransport> mcpHttpClientTransports(McpSseClientProperties sseProperties, ObjectProvider<ObjectMapper> objectMapperProvider) {
ObjectMapper objectMapper = (ObjectMapper)objectMapperProvider.getIfAvailable(ObjectMapper::new);
List<NamedClientMcpTransport> sseTransports = new ArrayList();
for(Map.Entry<String, McpSseClientProperties.SseParameters> serverParameters : sseProperties.getConnections().entrySet()) {
String baseUrl = ((McpSseClientProperties.SseParameters)serverParameters.getValue()).url();
String sseEndpoint = ((McpSseClientProperties.SseParameters)serverParameters.getValue()).sseEndpoint() != null ? ((McpSseClientProperties.SseParameters)serverParameters.getValue()).sseEndpoint() : "/sse";
HttpClientSseClientTransport transport = HttpClientSseClientTransport.builder(baseUrl).sseEndpoint(sseEndpoint).clientBuilder(HttpClient.newBuilder()).objectMapper(objectMapper).build();
sseTransports.add(new NamedClientMcpTransport((String)serverParameters.getKey(), transport));
}
return sseTransports;
}
}
StdioTransportAutoConfiguration
自动配置基于 Stdio MCP 客户端传输能力,仅当满足以下条件时自动配置生效
- 类路径中有 McpSchema
- 配置项 spring.ai.mcp.client.enabled=true(默认为 true)
对外提供 List的 Bean
java
package org.springframework.ai.mcp.client.autoconfigure;
import io.modelcontextprotocol.client.transport.ServerParameters;
import io.modelcontextprotocol.client.transport.StdioClientTransport;
import io.modelcontextprotocol.spec.McpSchema;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.springframework.ai.mcp.client.autoconfigure.properties.McpClientCommonProperties;
import org.springframework.ai.mcp.client.autoconfigure.properties.McpStdioClientProperties;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
@AutoConfiguration
@ConditionalOnClass({McpSchema.class})
@EnableConfigurationProperties({McpStdioClientProperties.class, McpClientCommonProperties.class})
@ConditionalOnProperty(
prefix = "spring.ai.mcp.client",
name = {"enabled"},
havingValue = "true",
matchIfMissing = true
)
public class StdioTransportAutoConfiguration {
@Bean
public List<NamedClientMcpTransport> stdioTransports(McpStdioClientProperties stdioProperties) {
List<NamedClientMcpTransport> stdioTransports = new ArrayList();
for(Map.Entry<String, ServerParameters> serverParameters : stdioProperties.toServerParameters().entrySet()) {
StdioClientTransport transport = new StdioClientTransport((ServerParameters)serverParameters.getValue());
stdioTransports.add(new NamedClientMcpTransport((String)serverParameters.getKey(), transport));
}
return stdioTransports;
}
}
McpClientAutoConfiguration
自动装配 MCP 客户端的核心组件,包括 Sync、Async 的客户端,依赖于传输层(stdio、SSE HTTP、SSE WebFlux)的自动配置,确保在有可用传输通道时自动创建 MCP 客户端实例,作用描述如下:
- 自动装配 MCP 客户端:根据配置(spring.ai.mcp.client.type),自动创建 Sync、Async 客户端实例
- 多连接支持:支持多个命名传输通道(如多个服务器),为每个通道分别创建对应的客户端实例
- 客户端信息与定制:支持通过配置设置客户端名称、版本、请求超时等参数,并允许通过自定义器(Customizer/Configurer)扩展客户端行为
- 生命周期管理:提供可关闭的客户端包装类,确保应用关闭时资源被正确释放
|--------------------------|----------------------------------------------------------------------------------|
| 方法名称 | 描述 |
| mcpSyncClients | 提供List的Bean,按需创建并暴露所有同步 MCP 客户端实例,每个实例对应一个命名传输通道。用于阻塞式调用场景 |
| mcpAsyncClients | 提供List的Bean,按需创建并暴露所有异步 MCP 客户端实例,每个实例对应一个命名传输通道。用于非阻塞式调用场景 |
| mcpSyncClientConfigurer | 提供McpSyncClientConfigurer的Bean,聚合所有 McpSyncClientCustomizer,用于定制同步客户端的创建和配置 |
| mcpAsyncClientConfigurer | 提供McpAsyncClientConfigurer的Bean,聚合所有 McpAsyncClientCustomizer,用于定制异步客户端的创建和配置 |
| makeSyncClientsClosable | 提供CloseableMcpSyncClients的Bean,封装所有同步客户端,实现 AutoCloseable,用于 Spring 容器关闭时自动释放资源 |
| makeAsyncClientsClosable | 提供CloseableMcpAsyncClients的Bean,封装所有异步客户端,实现 AutoCloseable,用于 Spring 容器关闭时自动释放资源 |
java
package org.springframework.ai.mcp.client.autoconfigure;
import io.modelcontextprotocol.client.McpAsyncClient;
import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.client.McpSyncClient;
import io.modelcontextprotocol.spec.McpSchema;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.ai.mcp.client.autoconfigure.configurer.McpAsyncClientConfigurer;
import org.springframework.ai.mcp.client.autoconfigure.configurer.McpSyncClientConfigurer;
import org.springframework.ai.mcp.client.autoconfigure.properties.McpClientCommonProperties;
import org.springframework.ai.mcp.customizer.McpAsyncClientCustomizer;
import org.springframework.ai.mcp.customizer.McpSyncClientCustomizer;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.util.CollectionUtils;
@AutoConfiguration(
after = {StdioTransportAutoConfiguration.class, SseHttpClientTransportAutoConfiguration.class, SseWebFluxTransportAutoConfiguration.class}
)
@ConditionalOnClass({McpSchema.class})
@EnableConfigurationProperties({McpClientCommonProperties.class})
@ConditionalOnProperty(
prefix = "spring.ai.mcp.client",
name = {"enabled"},
havingValue = "true",
matchIfMissing = true
)
public class McpClientAutoConfiguration {
private String connectedClientName(String clientName, String serverConnectionName) {
return clientName + " - " + serverConnectionName;
}
@Bean
@ConditionalOnProperty(
prefix = "spring.ai.mcp.client",
name = {"type"},
havingValue = "SYNC",
matchIfMissing = true
)
public List<McpSyncClient> mcpSyncClients(McpSyncClientConfigurer mcpSyncClientConfigurer, McpClientCommonProperties commonProperties, ObjectProvider<List<NamedClientMcpTransport>> transportsProvider) {
List<McpSyncClient> mcpSyncClients = new ArrayList();
List<NamedClientMcpTransport> namedTransports = transportsProvider.stream().flatMap(Collection::stream).toList();
if (!CollectionUtils.isEmpty(namedTransports)) {
for(NamedClientMcpTransport namedTransport : namedTransports) {
McpSchema.Implementation clientInfo = new McpSchema.Implementation(this.connectedClientName(commonProperties.getName(), namedTransport.name()), commonProperties.getVersion());
McpClient.SyncSpec spec = McpClient.sync(namedTransport.transport()).clientInfo(clientInfo).requestTimeout(commonProperties.getRequestTimeout());
spec = mcpSyncClientConfigurer.configure(namedTransport.name(), spec);
McpSyncClient client = spec.build();
if (commonProperties.isInitialized()) {
client.initialize();
}
mcpSyncClients.add(client);
}
}
return mcpSyncClients;
}
@Bean
@ConditionalOnProperty(
prefix = "spring.ai.mcp.client",
name = {"type"},
havingValue = "SYNC",
matchIfMissing = true
)
public CloseableMcpSyncClients makeSyncClientsClosable(List<McpSyncClient> clients) {
return new CloseableMcpSyncClients(clients);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(
prefix = "spring.ai.mcp.client",
name = {"type"},
havingValue = "SYNC",
matchIfMissing = true
)
McpSyncClientConfigurer mcpSyncClientConfigurer(ObjectProvider<McpSyncClientCustomizer> customizerProvider) {
return new McpSyncClientConfigurer(customizerProvider.orderedStream().toList());
}
@Bean
@ConditionalOnProperty(
prefix = "spring.ai.mcp.client",
name = {"type"},
havingValue = "ASYNC"
)
public List<McpAsyncClient> mcpAsyncClients(McpAsyncClientConfigurer mcpAsyncClientConfigurer, McpClientCommonProperties commonProperties, ObjectProvider<List<NamedClientMcpTransport>> transportsProvider) {
List<McpAsyncClient> mcpAsyncClients = new ArrayList();
List<NamedClientMcpTransport> namedTransports = transportsProvider.stream().flatMap(Collection::stream).toList();
if (!CollectionUtils.isEmpty(namedTransports)) {
for(NamedClientMcpTransport namedTransport : namedTransports) {
McpSchema.Implementation clientInfo = new McpSchema.Implementation(this.connectedClientName(commonProperties.getName(), namedTransport.name()), commonProperties.getVersion());
McpClient.AsyncSpec spec = McpClient.async(namedTransport.transport()).clientInfo(clientInfo).requestTimeout(commonProperties.getRequestTimeout());
spec = mcpAsyncClientConfigurer.configure(namedTransport.name(), spec);
McpAsyncClient client = spec.build();
if (commonProperties.isInitialized()) {
client.initialize().block();
}
mcpAsyncClients.add(client);
}
}
return mcpAsyncClients;
}
@Bean
@ConditionalOnProperty(
prefix = "spring.ai.mcp.client",
name = {"type"},
havingValue = "ASYNC"
)
public CloseableMcpAsyncClients makeAsyncClientsClosable(List<McpAsyncClient> clients) {
return new CloseableMcpAsyncClients(clients);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(
prefix = "spring.ai.mcp.client",
name = {"type"},
havingValue = "ASYNC"
)
McpAsyncClientConfigurer mcpAsyncClientConfigurer(ObjectProvider<McpAsyncClientCustomizer> customizerProvider) {
return new McpAsyncClientConfigurer(customizerProvider.orderedStream().toList());
}
public static record CloseableMcpSyncClients(List<McpSyncClient> clients) implements AutoCloseable {
public void close() {
this.clients.forEach(McpSyncClient::close);
}
}
public static record CloseableMcpAsyncClients(List<McpAsyncClient> clients) implements AutoCloseable {
public void close() {
this.clients.forEach(McpAsyncClient::close);
}
}
}
McpToolCallbackAutoConfiguration
用于自动装配 MCP 客户端与 Spring AI 的 ToolCallback 集成的 Bean,主要作用如下
- 自动装配 MCP 工具回调:自动为所有已配置的 MCP 客户端(同步或异步)创建对应的 ToolCallbackProvider
- 条件生效(由 McpToolCallbackAutoConfigurationCondition 条件装配类控制):仅在 spring.ai.mcp.client.enabled=true 且 spring.ai.mcp.client.toolcallback.enabled=true、时生效,确保按需启用
- 客户端支持:支持为所有已配置的 MCP 客户端(支持多连接)批量创建工具回调,便于多服务器/多通道场景下的统一管理
|-----------------------|-----------------------------------------------------------------|
| 方法名称 | 描述 |
| mcpToolCallbacks | 提供SyncMcpToolCallbackProvider的Bean,为所有同步 MCP 客户端创建ToolCallback |
| mcpAsyncToolCallbacks | 提供AsyncMcpToolCallbackProvider的Bean,为所有异步 MCP 客户端创建ToolCallback |
java
package org.springframework.ai.mcp.client.autoconfigure;
import io.modelcontextprotocol.client.McpAsyncClient;
import io.modelcontextprotocol.client.McpSyncClient;
import java.util.Collection;
import java.util.List;
import org.springframework.ai.mcp.AsyncMcpToolCallbackProvider;
import org.springframework.ai.mcp.SyncMcpToolCallbackProvider;
import org.springframework.ai.mcp.client.autoconfigure.properties.McpClientCommonProperties;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.ConfigurationCondition.ConfigurationPhase;
@AutoConfiguration(
after = {McpClientAutoConfiguration.class}
)
@EnableConfigurationProperties({McpClientCommonProperties.class})
@Conditional({McpToolCallbackAutoConfigurationCondition.class})
public class McpToolCallbackAutoConfiguration {
@Bean
@ConditionalOnProperty(
prefix = "spring.ai.mcp.client",
name = {"type"},
havingValue = "SYNC",
matchIfMissing = true
)
public SyncMcpToolCallbackProvider mcpToolCallbacks(ObjectProvider<List<McpSyncClient>> syncMcpClients) {
List<McpSyncClient> mcpClients = syncMcpClients.stream().flatMap(Collection::stream).toList();
return new SyncMcpToolCallbackProvider(mcpClients);
}
@Bean
@ConditionalOnProperty(
prefix = "spring.ai.mcp.client",
name = {"type"},
havingValue = "ASYNC"
)
public AsyncMcpToolCallbackProvider mcpAsyncToolCallbacks(ObjectProvider<List<McpAsyncClient>> mcpClientsProvider) {
List<McpAsyncClient> mcpClients = mcpClientsProvider.stream().flatMap(Collection::stream).toList();
return new AsyncMcpToolCallbackProvider(mcpClients);
}
public static class McpToolCallbackAutoConfigurationCondition extends AllNestedConditions {
public McpToolCallbackAutoConfigurationCondition() {
super(ConfigurationPhase.PARSECONFIGURATION);
}
@ConditionalOnProperty(
prefix = "spring.ai.mcp.client",
name = {"enabled"},
havingValue = "true",
matchIfMissing = true
)
static class McpAutoConfigEnabled {
}
@ConditionalOnProperty(
prefix = "spring.ai.mcp.client.toolcallback",
name = {"enabled"},
havingValue = "true",
matchIfMissing = true
)
static class ToolCallbackProviderEnabled {
}
}
}
SpringAI下集成MCP转换ToolCallback
SyncMcpToolCallbackProvider
集成 MCP 同步客户端的 ToolCallbackProvider,负责从一个或多个 MCP 同步服务器(通过 McpSyncClient)自动发现、收集所有可用的工具,支持对工具进行过滤,确保工具名唯一
List<McpSyncClient> mcpClients
:存储所有需要集成的 MCP 同步客户端实例,用于从多个 MCP 服务器拉取工具列表BiPredicate<McpSyncClient, Tool> toolFilter
:工具过滤器,允许根据客户端和工具元数据自定义过滤逻辑,决定哪些工具被暴露
|-----------------------------|---------------------------------------------------------------------------|
| 方法名称 | 描述 |
| SyncMcpToolCallbackProvider | 根据MCP同步客户端、工具过滤器等实现构造器 |
| getToolCallbacks | 从所有 MCP 客户端拉取工具列表,应用过滤器,包装为 SyncMcpToolCallback,并校验工具名唯一性,最终返回所有可用工具的回调数组 |
| syncToolCallbacks | 静态工具方法,快速从一组 MCP 客户端获取所有工具回调,便于批量集成 |
java
package org.springframework.ai.mcp;
import io.modelcontextprotocol.client.McpSyncClient;
import io.modelcontextprotocol.spec.McpSchema;
import java.util.List;
import java.util.function.BiPredicate;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.support.ToolUtils;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
public class SyncMcpToolCallbackProvider implements ToolCallbackProvider {
private final List<McpSyncClient> mcpClients;
private final BiPredicate<McpSyncClient, McpSchema.Tool> toolFilter;
public SyncMcpToolCallbackProvider(BiPredicate<McpSyncClient, McpSchema.Tool> toolFilter, List<McpSyncClient> mcpClients) {
Assert.notNull(mcpClients, "MCP clients must not be null");
Assert.notNull(toolFilter, "Tool filter must not be null");
this.mcpClients = mcpClients;
this.toolFilter = toolFilter;
}
public SyncMcpToolCallbackProvider(List<McpSyncClient> mcpClients) {
this((mcpClient, tool) -> true, mcpClients);
}
public SyncMcpToolCallbackProvider(BiPredicate<McpSyncClient, McpSchema.Tool> toolFilter, McpSyncClient... mcpClients) {
this(toolFilter, List.of(mcpClients));
}
public SyncMcpToolCallbackProvider(McpSyncClient... mcpClients) {
this(List.of(mcpClients));
}
public ToolCallback[] getToolCallbacks() {
ToolCallback[] array = (ToolCallback[])this.mcpClients.stream().flatMap((mcpClient) -> mcpClient.listTools().tools().stream().filter((tool) -> this.toolFilter.test(mcpClient, tool)).map((tool) -> new SyncMcpToolCallback(mcpClient, tool))).toArray((x$0) -> new ToolCallback[x$0]);
this.validateToolCallbacks(array);
return array;
}
private void validateToolCallbacks(ToolCallback[] toolCallbacks) {
List<String> duplicateToolNames = ToolUtils.getDuplicateToolNames(toolCallbacks);
if (!duplicateToolNames.isEmpty()) {
throw new IllegalStateException("Multiple tools with the same name (%s)".formatted(String.join(", ", duplicateToolNames)));
}
}
public static List<ToolCallback> syncToolCallbacks(List<McpSyncClient> mcpClients) {
return CollectionUtils.isEmpty(mcpClients) ? List.of() : List.of((new SyncMcpToolCallbackProvider(mcpClients)).getToolCallbacks());
}
}
SyncMcpToolCallback
MCP 同步工具适配为 SpringAI 中 ToolCallback 的桥接实现
McpSyncClient mcpClient
:MCP 同步客户端实例,负责与 MCP 服务器通信、发起工具调用Tool tool
:MCP 工具定义对象,包含工具的名称、描述、输入参数 schema 等元数据
|---------------------|--------------------------------------------------------------------------------|
| 方法名称 | 描述 |
| SyncMcpToolCallback | 根据MCP同步客户端、工具定义实现构造器 |
| getToolDefinition | 将 MCP 工具定义转换为 Spring AI 的 ToolDefinition,包括名称(带前缀防止冲突)、描述、输入参数 schema(JSON 格式) |
| call | 执行工具调用。将 JSON 字符串参数转为 Map,调用 MCP 工具,处理异常和错误,并将结果序列化为 JSON 字符串返回 |
java
package org.springframework.ai.mcp;
import io.modelcontextprotocol.client.McpSyncClient;
import io.modelcontextprotocol.spec.McpSchema;
import java.util.Map;
import org.springframework.ai.chat.model.ToolContext;
import org.springframework.ai.model.ModelOptionsUtils;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.definition.DefaultToolDefinition;
import org.springframework.ai.tool.definition.ToolDefinition;
public class SyncMcpToolCallback implements ToolCallback {
private final McpSyncClient mcpClient;
private final McpSchema.Tool tool;
public SyncMcpToolCallback(McpSyncClient mcpClient, McpSchema.Tool tool) {
this.mcpClient = mcpClient;
this.tool = tool;
}
public ToolDefinition getToolDefinition() {
return DefaultToolDefinition.builder().name(McpToolUtils.prefixedToolName(this.mcpClient.getClientInfo().name(), this.tool.name())).description(this.tool.description()).inputSchema(ModelOptionsUtils.toJsonString(this.tool.inputSchema())).build();
}
public String call(String functionInput) {
Map<String, Object> arguments = ModelOptionsUtils.jsonToMap(functionInput);
McpSchema.CallToolResult response = this.mcpClient.callTool(new McpSchema.CallToolRequest(this.tool.name(), arguments));
if (response.isError() != null && response.isError()) {
throw new IllegalStateException("Error calling tool: " + String.valueOf(response.content()));
} else {
return ModelOptionsUtils.toJsonString(response.content());
}
}
public String call(String toolArguments, ToolContext toolContext) {
return this.call(toolArguments);
}
}
AsyncMcpToolCallbackProvider
集成 MCP 异步客户端的 ToolCallbackProvider,其余同 SyncMcpToolCallbackProvider 一致
List<McpAsyncClient> mcpClients
:存储所有需要集成的 MCP 同步客户端实例,用于从多个 MCP 服务器拉取工具列表BiPredicate<McpAsyncClient, Tool> toolFilter
:工具过滤器,允许根据客户端和工具元数据自定义过滤逻辑,决定哪些工具被暴露
|------------------------------|----------------------------------------------------------------------------|
| 方法名称 | 描述 |
| AsyncMcpToolCallbackProvider | 根据MCP异步客户端、工具过滤器等实现构造器 |
| getToolCallbacks | 从所有 MCP 客户端拉取工具列表,应用过滤器,包装为 AsyncMcpToolCallback,并校验工具名唯一性,最终返回所有可用工具的回调数组 |
| asyncToolCallbacks | 静态工具方法,快速从一组 MCP 客户端获取所有工具回调,便于批量集成 |
java
package org.springframework.ai.mcp;
import io.modelcontextprotocol.client.McpAsyncClient;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.util.Assert;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiPredicate;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.support.ToolUtils;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Flux;
public class AsyncMcpToolCallbackProvider implements ToolCallbackProvider {
private final List<McpAsyncClient> mcpClients;
private final BiPredicate<McpAsyncClient, McpSchema.Tool> toolFilter;
public AsyncMcpToolCallbackProvider(BiPredicate<McpAsyncClient, McpSchema.Tool> toolFilter, List<McpAsyncClient> mcpClients) {
Assert.notNull(mcpClients, "MCP clients must not be null");
Assert.notNull(toolFilter, "Tool filter must not be null");
this.mcpClients = mcpClients;
this.toolFilter = toolFilter;
}
public AsyncMcpToolCallbackProvider(List<McpAsyncClient> mcpClients) {
this((mcpClient, tool) -> true, mcpClients);
}
public AsyncMcpToolCallbackProvider(BiPredicate<McpAsyncClient, McpSchema.Tool> toolFilter, McpAsyncClient... mcpClients) {
this(toolFilter, List.of(mcpClients));
}
public AsyncMcpToolCallbackProvider(McpAsyncClient... mcpClients) {
this(List.of(mcpClients));
}
public ToolCallback[] getToolCallbacks() {
List<ToolCallback> toolCallbackList = new ArrayList();
for(McpAsyncClient mcpClient : this.mcpClients) {
ToolCallback[] toolCallbacks = (ToolCallback[])mcpClient.listTools().map((response) -> (ToolCallback[])response.tools().stream().filter((tool) -> this.toolFilter.test(mcpClient, tool)).map((tool) -> new AsyncMcpToolCallback(mcpClient, tool)).toArray((x$0) -> new ToolCallback[x$0])).block();
this.validateToolCallbacks(toolCallbacks);
toolCallbackList.addAll(List.of(toolCallbacks));
}
return (ToolCallback[])toolCallbackList.toArray(new ToolCallback[0]);
}
private void validateToolCallbacks(ToolCallback[] toolCallbacks) {
List<String> duplicateToolNames = ToolUtils.getDuplicateToolNames(toolCallbacks);
if (!duplicateToolNames.isEmpty()) {
throw new IllegalStateException("Multiple tools with the same name (%s)".formatted(String.join(", ", duplicateToolNames)));
}
}
public static Flux<ToolCallback> asyncToolCallbacks(List<McpAsyncClient> mcpClients) {
return CollectionUtils.isEmpty(mcpClients) ? Flux.empty() : Flux.fromArray((new AsyncMcpToolCallbackProvider(mcpClients)).getToolCallbacks());
}
}
AsyncMcpToolCallback
MCP 异步工具适配为 SpringAI 中 ToolCallback 的桥接实现
McpAsyncClient mcpClient
:MCP 异步客户端实例,负责与 MCP 服务器通信、发起工具调用Tool tool
:MCP 工具定义对象,包含工具的名称、描述、输入参数 schema 等元数据
|----------------------|--------------------------------------------------------------------------------|
| 方法名称 | 描述 |
| AsyncMcpToolCallback | 根据MCP异步客户端、工具定义实现构造器 |
| getToolDefinition | 将 MCP 工具定义转换为 Spring AI 的 ToolDefinition,包括名称(带前缀防止冲突)、描述、输入参数 schema(JSON 格式) |
| call | 执行工具调用。将 JSON 字符串参数转为 Map,调用 MCP 工具,处理异常和错误,并将结果序列化为 JSON 字符串返回 |
java
package org.springframework.ai.mcp;
import io.modelcontextprotocol.client.McpAsyncClient;
import io.modelcontextprotocol.spec.McpSchema;
import java.util.Map;
import org.springframework.ai.chat.model.ToolContext;
import org.springframework.ai.model.ModelOptionsUtils;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.definition.DefaultToolDefinition;
import org.springframework.ai.tool.definition.ToolDefinition;
public class AsyncMcpToolCallback implements ToolCallback {
private final McpAsyncClient asyncMcpClient;
private final McpSchema.Tool tool;
public AsyncMcpToolCallback(McpAsyncClient mcpClient, McpSchema.Tool tool) {
this.asyncMcpClient = mcpClient;
this.tool = tool;
}
public ToolDefinition getToolDefinition() {
return DefaultToolDefinition.builder().name(McpToolUtils.prefixedToolName(this.asyncMcpClient.getClientInfo().name(), this.tool.name())).description(this.tool.description()).inputSchema(ModelOptionsUtils.toJsonString(this.tool.inputSchema())).build();
}
public String call(String functionInput) {
Map<String, Object> arguments = ModelOptionsUtils.jsonToMap(functionInput);
return (String)this.asyncMcpClient.callTool(new McpSchema.CallToolRequest(this.tool.name(), arguments)).map((response) -> {
if (response.isError() != null && response.isError()) {
throw new IllegalStateException("Error calling tool: " + String.valueOf(response.content()));
} else {
return ModelOptionsUtils.toJsonString(response.content());
}
}).block();
}
public String call(String toolArguments, ToolContext toolContext) {
return this.call(toolArguments);
}
}
McpToolUtils
作为 SpringAI 与 MCP 协议集成的工具类,负责将 SpringAI 的 ToolCallback 转换为 MCP 协议兼容的同步/异步工具规范
|----------------------------------|-------------------------------------------------|
| 方法名称 | 描述 |
| prefixedToolName | 避免工具名冲突,确保命名唯一性 |
| toSyncToolSpecification | 将ToolCallback转换为SyncToolSpecification |
| toSyncToolSpecifications | 批量将ToolCallback转换为SyncToolSpecification |
| getToolCallbacksFromSyncClients | 从多个同步 MCP 客户端中提取ToolCallback |
| toAsyncToolSpecification | 将ToolCallback转换为AsyncToolSpecification |
| toAsyncToolSpecifications | 批量将ToolCallback转换为AsyncToolSpecification |
| getToolCallbacksFromAsyncClients | 从多个异步 MCP 客户端中提取ToolCallback |
| getMcpExchange | 从 ToolContext 中提取 MCP 交换对象,用于在工具调用时传递 MCP 上下文信息 |
java
package org.springframework.ai.mcp;
import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.micrometer.common.util.StringUtils;
import io.modelcontextprotocol.client.McpAsyncClient;
import io.modelcontextprotocol.client.McpSyncClient;
import io.modelcontextprotocol.server.McpServerFeatures;
import io.modelcontextprotocol.server.McpSyncServerExchange;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.McpSchema.Role;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.springframework.ai.chat.model.ToolContext;
import org.springframework.ai.model.ModelOptionsUtils;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MimeType;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
public final class McpToolUtils {
public static final String TOOLCONTEXTMCPEXCHANGEKEY = "exchange";
private McpToolUtils() {
}
public static String prefixedToolName(String prefix, String toolName) {
if (!StringUtils.isEmpty(prefix) && !StringUtils.isEmpty(toolName)) {
String input = prefix + "" + toolName;
String formatted = input.replaceAll("[^a-zA-Z0-9-]", "");
formatted = formatted.replaceAll("-", "");
if (formatted.length() > 64) {
formatted = formatted.substring(formatted.length() - 64);
}
return formatted;
} else {
throw new IllegalArgumentException("Prefix or toolName cannot be null or empty");
}
}
public static List<McpServerFeatures.SyncToolSpecification> toSyncToolSpecification(List<ToolCallback> toolCallbacks) {
return toolCallbacks.stream().map(McpToolUtils::toSyncToolSpecification).toList();
}
public static List<McpServerFeatures.SyncToolSpecification> toSyncToolSpecifications(ToolCallback... toolCallbacks) {
return toSyncToolSpecification(List.of(toolCallbacks));
}
public static McpServerFeatures.SyncToolSpecification toSyncToolSpecification(ToolCallback toolCallback) {
return toSyncToolSpecification(toolCallback, (MimeType)null);
}
public static McpServerFeatures.SyncToolSpecification toSyncToolSpecification(ToolCallback toolCallback, MimeType mimeType) {
McpSchema.Tool tool = new McpSchema.Tool(toolCallback.getToolDefinition().name(), toolCallback.getToolDefinition().description(), toolCallback.getToolDefinition().inputSchema());
return new McpServerFeatures.SyncToolSpecification(tool, (exchange, request) -> {
try {
String callResult = toolCallback.call(ModelOptionsUtils.toJsonString(request), new ToolContext(Map.of("exchange", exchange)));
return mimeType != null && mimeType.toString().startsWith("image") ? new McpSchema.CallToolResult(List.of(new McpSchema.ImageContent(List.of(Role.ASSISTANT), (Double)null, callResult, mimeType.toString())), false) : new McpSchema.CallToolResult(List.of(new McpSchema.TextContent(callResult)), false);
} catch (Exception e) {
return new McpSchema.CallToolResult(List.of(new McpSchema.TextContent(e.getMessage())), true);
}
});
}
public static Optional<McpSyncServerExchange> getMcpExchange(ToolContext toolContext) {
return toolContext != null && toolContext.getContext().containsKey("exchange") ? Optional.ofNullable((McpSyncServerExchange)toolContext.getContext().get("exchange")) : Optional.empty();
}
public static List<McpServerFeatures.AsyncToolSpecification> toAsyncToolSpecifications(List<ToolCallback> toolCallbacks) {
return toolCallbacks.stream().map(McpToolUtils::toAsyncToolSpecification).toList();
}
public static List<McpServerFeatures.AsyncToolSpecification> toAsyncToolSpecifications(ToolCallback... toolCallbacks) {
return toAsyncToolSpecifications(List.of(toolCallbacks));
}
public static McpServerFeatures.AsyncToolSpecification toAsyncToolSpecification(ToolCallback toolCallback) {
return toAsyncToolSpecification(toolCallback, (MimeType)null);
}
public static McpServerFeatures.AsyncToolSpecification toAsyncToolSpecification(ToolCallback toolCallback, MimeType mimeType) {
McpServerFeatures.SyncToolSpecification syncToolSpecification = toSyncToolSpecification(toolCallback, mimeType);
return new McpServerFeatures.AsyncToolSpecification(syncToolSpecification.tool(), (exchange, map) -> Mono.fromCallable(() -> (McpSchema.CallToolResult)syncToolSpecification.call().apply(new McpSyncServerExchange(exchange), map)).subscribeOn(Schedulers.boundedElastic()));
}
public static List<ToolCallback> getToolCallbacksFromSyncClients(McpSyncClient... mcpClients) {
return getToolCallbacksFromSyncClients(List.of(mcpClients));
}
public static List<ToolCallback> getToolCallbacksFromSyncClients(List<McpSyncClient> mcpClients) {
return CollectionUtils.isEmpty(mcpClients) ? List.of() : List.of((new SyncMcpToolCallbackProvider(mcpClients)).getToolCallbacks());
}
public static List<ToolCallback> getToolCallbacksFromAsyncClients(McpAsyncClient... asyncMcpClients) {
return getToolCallbacksFromAsyncClients(List.of(asyncMcpClients));
}
public static List<ToolCallback> getToolCallbacksFromAsyncClients(List<McpAsyncClient> asyncMcpClients) {
return CollectionUtils.isEmpty(asyncMcpClients) ? List.of() : List.of((new AsyncMcpToolCallbackProvider(asyncMcpClients)).getToolCallbacks());
}
@JsonIgnoreProperties(
ignoreUnknown = true
)
private static record Base64Wrapper(MimeType mimeType, String data) {
private Base64Wrapper(@JsonAlias({"mimetype"}) @Nullable MimeType mimeType, @JsonAlias({"base64", "b64", "imageData"}) @Nullable String data) {
this.mimeType = mimeType;
this.data = data;
}
@JsonAlias({"mimetype"})
@Nullable
public MimeType mimeType() {
return this.mimeType;
}
@JsonAlias({"base64", "b64", "imageData"})
@Nullable
public String data() {
return this.data;
}
}
}
学习交流圈
你好,我是影子,曾先后在🐻、新能源、老铁就职,现在是一名AI研发工程师,同时作为Spring AI Alibaba开源社区的Committer。目前新建了一个交流群,一个人走得快,一群人走得远,关注公众号后可获得个人微信,添加微信后备注"交流"入群。另外,本人长期维护一套飞书云文档笔记,涵盖后端、大数据系统化的面试资料,可私信免费获取

