SpringAI(GA):SpringAI下的MCP源码解读

原文链接:SpringAI(GA):SpringAI下的MCP源码解读

教程说明

说明:本教程将采用2025年5月20日正式的GA版,给出如下内容

  1. 核心功能模块的快速上手教程
  2. 核心功能模块的源码级解读
  3. 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):ChatClient调用链路解读

第二章内容

SpringAI的Advisor:快速上手+源码解读

SpringAI(GA):Sqlite、Mysql、Redis消息存储快速上手

第三章内容

SpringAI(GA):Tool工具整合---快速上手

SpringAI(GA):Tool源码+工具触发链路解读

第五章内容

SpringAI(GA):内存、Redis、ES的向量数据库存储---快速上手

SpringAI(GA):向量数据库理论源码解读+Redis、Es接入源码

第六章内容

SpringAI(GA):RAG快速上手+模块化解读

SpringAI(GA):RAG下的ETL快速上手

SpringAI(GA):RAG下的ETL源码解读

第七章内容

SpringAI(GA):Nacos2下的分布式MCP

SpringAI(GA):Nacos3下的分布式MCP

SpringAI(GA):MCP源码解读

整理不易,获取更好的观赏体验,可付费获取飞书云文档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 字段。该字段决定了是否提供 ToolCallbackProvider

    • boolean 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,逻辑如下

  1. 读取所有配置的 SSE 连接(如 server1、server2)
  2. 为每个连接克隆一个 WebClient.Builder,设置对应的 baseUrl
  3. 构建 WebFluxSseClientTransport 实例,设置端点和 JSON 处理器
  4. 封装为 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。目前新建了一个交流群,一个人走得快,一群人走得远,关注公众号后可获得个人微信,添加微信后备注"交流"入群。另外,本人长期维护一套飞书云文档笔记,涵盖后端、大数据系统化的面试资料,可私信免费获取

相关推荐
java干货13 分钟前
虚拟线程与消息队列:Spring Boot 3.5 中异步架构的演进与选择
spring boot·后端·架构
SoFlu软件机器人19 分钟前
智能生成完整 Java 后端架构,告别手动编写 ControllerServiceDao
java·开发语言·架构
说私域25 分钟前
定制开发开源AI智能名片驱动下的海报工厂S2B2C商城小程序运营策略——基于社群口碑传播与子市场细分的实证研究
人工智能·小程序·开源·零售
HillVue1 小时前
AI,如何重构理解、匹配与决策?
人工智能·重构
skywalk81631 小时前
市面上哪款AI开源软件做ppt最好?
人工智能·powerpoint
西陵1 小时前
前端框架渲染DOM的的方式你知道多少?
前端·javascript·架构
小九九的爸爸1 小时前
我是如何让AI帮我还原设计稿的
前端·人工智能·ai编程
hanniuniu132 小时前
网络安全厂商F5推出AI Gateway,化解大模型应用风险
人工智能·web安全·gateway
Iamccc13_2 小时前
智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
人工智能·数据分析·自动化
蹦蹦跳跳真可爱5892 小时前
Python----目标检测(使用YOLO 模型进行线程安全推理和流媒体源)
人工智能·python·yolo·目标检测·目标跟踪