diff --git a/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/McpClientAutoConfiguration.java b/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/McpClientAutoConfiguration.java index b89b70b66be..7b6da8eb2db 100644 --- a/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/McpClientAutoConfiguration.java +++ b/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/McpClientAutoConfiguration.java @@ -1,334 +1,341 @@ -/* - * Copyright 2025-2025 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.ai.autoconfigure.mcp.client; - -import java.util.ArrayList; -import java.util.List; - -import io.modelcontextprotocol.client.McpAsyncClient; -import io.modelcontextprotocol.client.McpClient; -import io.modelcontextprotocol.client.McpSyncClient; -import io.modelcontextprotocol.spec.McpSchema; - -import org.springframework.ai.autoconfigure.mcp.client.configurer.McpAsyncClientConfigurer; -import org.springframework.ai.autoconfigure.mcp.client.configurer.McpSyncClientConfigurer; -import org.springframework.ai.autoconfigure.mcp.client.properties.McpClientCommonProperties; -import org.springframework.ai.mcp.AsyncMcpToolCallbackProvider; -import org.springframework.ai.mcp.SyncMcpToolCallbackProvider; -import org.springframework.ai.mcp.customizer.McpAsyncClientCustomizer; -import org.springframework.ai.mcp.customizer.McpSyncClientCustomizer; -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.util.CollectionUtils; - -/** - * Auto-configuration for Model Context Protocol (MCP) client support. - * - *

- * This configuration class sets up the necessary beans for MCP client functionality, - * including both synchronous and asynchronous clients along with their respective tool - * callbacks. It is automatically enabled when the required classes are present on the - * classpath and can be explicitly disabled through properties. - * - *

- * Configuration Properties: - *

- * - *

- * The configuration is activated after the transport-specific auto-configurations (Stdio, - * SSE HTTP, and SSE WebFlux) to ensure proper initialization order. At least one - * transport must be available for the clients to be created. - * - *

- * Key features: - *

- * - * @see McpSyncClient - * @see McpAsyncClient - * @see McpClientCommonProperties - * @see McpSyncClientCustomizer - * @see McpAsyncClientCustomizer - * @see StdioTransportAutoConfiguration - * @see SseHttpClientTransportAutoConfiguration - * @see SseWebFluxTransportAutoConfiguration - */ -@AutoConfiguration(after = { StdioTransportAutoConfiguration.class, SseHttpClientTransportAutoConfiguration.class, - SseWebFluxTransportAutoConfiguration.class }) -@ConditionalOnClass({ McpSchema.class }) -@EnableConfigurationProperties(McpClientCommonProperties.class) -@ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", - matchIfMissing = true) -public class McpClientAutoConfiguration { - - /** - * Create a dynamic client name based on the client name and the name of the server - * connection. - * @param clientName the client name as defined by the configuration - * @param serverConnectionName the name of the server connection being used by the - * client - * @return the connected client name - */ - private String connectedClientName(String clientName, String serverConnectionName) { - return clientName + " - " + serverConnectionName; - } - - /** - * Creates a list of {@link McpSyncClient} instances based on the available - * transports. - * - *

- * Each client is configured with: - *

- * - *

- * If initialization is enabled in properties, the clients are automatically - * initialized. - * @param mcpSyncClientConfigurer the configurer for customizing client creation - * @param commonProperties common MCP client properties - * @param transportsProvider provider of named MCP transports - * @return list of configured MCP sync clients - */ - @Bean - @ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "SYNC", - matchIfMissing = true) - public List mcpSyncClients(McpSyncClientConfigurer mcpSyncClientConfigurer, - McpClientCommonProperties commonProperties, - ObjectProvider> transportsProvider) { - - List mcpSyncClients = new ArrayList<>(); - - List namedTransports = transportsProvider.stream().flatMap(List::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 syncSpec = McpClient.sync(namedTransport.transport()) - .clientInfo(clientInfo) - .requestTimeout(commonProperties.getRequestTimeout()); - - syncSpec = mcpSyncClientConfigurer.configure(namedTransport.name(), syncSpec); - - var syncClient = syncSpec.build(); - - if (commonProperties.isInitialized()) { - syncClient.initialize(); - } - - mcpSyncClients.add(syncClient); - } - } - - return mcpSyncClients; - } - - /** - * Creates tool callbacks for all configured MCP clients. - * - *

- * These callbacks enable integration with Spring AI's tool execution framework, - * allowing MCP tools to be used as part of AI interactions. - * @param mcpClientsProvider provider of MCP sync clients - * @return list of tool callbacks for MCP integration - */ - @Bean - @ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "SYNC", - matchIfMissing = true) - public ToolCallbackProvider toolCallbacks(ObjectProvider> mcpClientsProvider) { - List mcpClients = mcpClientsProvider.stream().flatMap(List::stream).toList(); - return new SyncMcpToolCallbackProvider(mcpClients); - } - - /** - * @deprecated replaced by {@link #toolCallbacks(ObjectProvider)} that returns a - * {@link ToolCallbackProvider} instead of a list of {@link ToolCallback} - */ - @Deprecated - @Bean - @ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "SYNC", - matchIfMissing = true) - public List toolCallbacksDeprecated(ObjectProvider> mcpClientsProvider) { - List mcpClients = mcpClientsProvider.stream().flatMap(List::stream).toList(); - return List.of(new SyncMcpToolCallbackProvider(mcpClients).getToolCallbacks()); - } - - /** - * Record class that implements {@link AutoCloseable} to ensure proper cleanup of MCP - * clients. - * - *

- * This class is responsible for closing all MCP sync clients when the application - * context is closed, preventing resource leaks. - */ - public record CloseableMcpSyncClients(List clients) implements AutoCloseable { - - @Override - public void close() { - this.clients.forEach(McpSyncClient::close); - } - } - - /** - * Creates a closeable wrapper for MCP sync clients to ensure proper resource cleanup. - * @param clients the list of MCP sync clients to manage - * @return a closeable wrapper for the clients - */ - @Bean - @ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "SYNC", - matchIfMissing = true) - public CloseableMcpSyncClients makeSyncClientsClosable(List clients) { - return new CloseableMcpSyncClients(clients); - } - - /** - * Creates the default {@link McpSyncClientConfigurer} if none is provided. - * - *

- * This configurer aggregates all available {@link McpSyncClientCustomizer} instances - * to allow for customization of MCP sync client creation. - * @param customizerProvider provider of MCP sync client customizers - * @return the configured MCP sync client configurer - */ - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "SYNC", - matchIfMissing = true) - McpSyncClientConfigurer mcpSyncClientConfigurer(ObjectProvider customizerProvider) { - return new McpSyncClientConfigurer(customizerProvider.orderedStream().toList()); - } - - // Async client configuration - - @Bean - @ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "ASYNC") - public List mcpAsyncClients(McpAsyncClientConfigurer mcpSyncClientConfigurer, - McpClientCommonProperties commonProperties, - ObjectProvider> transportsProvider) { - - List mcpSyncClients = new ArrayList<>(); - - List namedTransports = transportsProvider.stream().flatMap(List::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 syncSpec = McpClient.async(namedTransport.transport()) - .clientInfo(clientInfo) - .requestTimeout(commonProperties.getRequestTimeout()); - - syncSpec = mcpSyncClientConfigurer.configure(namedTransport.name(), syncSpec); - - var syncClient = syncSpec.build(); - - if (commonProperties.isInitialized()) { - syncClient.initialize().block(); - } - - mcpSyncClients.add(syncClient); - } - } - - return mcpSyncClients; - } - - /** - * @deprecated replaced by {@link #asyncToolCallbacks(ObjectProvider)} that returns a - * {@link ToolCallbackProvider} instead of a list of {@link ToolCallback} - */ - @Deprecated - @Bean - @ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "ASYNC") - public List asyncToolCallbacksDeprecated(ObjectProvider> mcpClientsProvider) { - List mcpClients = mcpClientsProvider.stream().flatMap(List::stream).toList(); - return List.of(new AsyncMcpToolCallbackProvider(mcpClients).getToolCallbacks()); - } - - @Bean - @ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "ASYNC") - public ToolCallbackProvider asyncToolCallbacks(ObjectProvider> mcpClientsProvider) { - List mcpClients = mcpClientsProvider.stream().flatMap(List::stream).toList(); - return new AsyncMcpToolCallbackProvider(mcpClients); - } - - public record CloseableMcpAsyncClients(List clients) implements AutoCloseable { - @Override - public void close() { - this.clients.forEach(McpAsyncClient::close); - } - } - - @Bean - @ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "ASYNC") - public CloseableMcpAsyncClients makeAsynClientsClosable(List clients) { - return new CloseableMcpAsyncClients(clients); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "ASYNC") - McpAsyncClientConfigurer mcpAsyncClientConfigurer(ObjectProvider customizerProvider) { - return new McpAsyncClientConfigurer(customizerProvider.orderedStream().toList()); - } - -} +/* + * Copyright 2025-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.autoconfigure.mcp.client; + +import java.util.ArrayList; +import java.util.List; + +import io.modelcontextprotocol.client.McpAsyncClient; +import io.modelcontextprotocol.client.McpClient; +import io.modelcontextprotocol.client.McpSyncClient; +import io.modelcontextprotocol.spec.McpSchema; + +import org.springframework.ai.autoconfigure.mcp.client.configurer.McpAsyncClientConfigurer; +import org.springframework.ai.autoconfigure.mcp.client.configurer.McpSyncClientConfigurer; +import org.springframework.ai.autoconfigure.mcp.client.properties.McpClientCommonProperties; +import org.springframework.ai.mcp.AsyncMcpToolCallbackProvider; +import org.springframework.ai.mcp.SyncMcpToolCallbackProvider; +import org.springframework.ai.mcp.customizer.McpAsyncClientCustomizer; +import org.springframework.ai.mcp.customizer.McpSyncClientCustomizer; +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.util.CollectionUtils; + +/** + * Auto-configuration for Model Context Protocol (MCP) client support. + * + *

+ * This configuration class sets up the necessary beans for MCP client functionality, + * including both synchronous and asynchronous clients along with their respective tool + * callbacks. It is automatically enabled when the required classes are present on the + * classpath and can be explicitly disabled through properties. + * + *

+ * Configuration Properties: + *

+ * + *

+ * The configuration is activated after the transport-specific auto-configurations (Stdio, + * SSE HTTP, and SSE WebFlux) to ensure proper initialization order. At least one + * transport must be available for the clients to be created. + * + *

+ * Key features: + *

+ * + * @see McpSyncClient + * @see McpAsyncClient + * @see McpClientCommonProperties + * @see McpSyncClientCustomizer + * @see McpAsyncClientCustomizer + * @see StdioTransportAutoConfiguration + * @see SseHttpClientTransportAutoConfiguration + * @see SseWebFluxTransportAutoConfiguration + */ +@AutoConfiguration(after = { + StdioTransportAutoConfiguration.class, + SseWebFluxTransportAutoConfiguration.class, // WebFlux + SseHttpClientTransportAutoConfiguration.class // HTTP Client +}) +@ConditionalOnClass({ McpSchema.class }) +@EnableConfigurationProperties(McpClientCommonProperties.class) +@ConditionalOnProperty( + prefix = McpClientCommonProperties.CONFIG_PREFIX, + name = "enabled", + havingValue = "true", + matchIfMissing = true +) +public class McpClientAutoConfiguration { + + /** + * Create a dynamic client name based on the client name and the name of the server + * connection. + * @param clientName the client name as defined by the configuration + * @param serverConnectionName the name of the server connection being used by the + * client + * @return the connected client name + */ + private String connectedClientName(String clientName, String serverConnectionName) { + return clientName + " - " + serverConnectionName; + } + + /** + * Creates a list of {@link McpSyncClient} instances based on the available + * transports. + * + *

+ * Each client is configured with: + *

+ * + *

+ * If initialization is enabled in properties, the clients are automatically + * initialized. + * @param mcpSyncClientConfigurer the configurer for customizing client creation + * @param commonProperties common MCP client properties + * @param transportsProvider provider of named MCP transports + * @return list of configured MCP sync clients + */ + @Bean + @ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "SYNC", + matchIfMissing = true) + public List mcpSyncClients(McpSyncClientConfigurer mcpSyncClientConfigurer, + McpClientCommonProperties commonProperties, + ObjectProvider> transportsProvider) { + + List mcpSyncClients = new ArrayList<>(); + + List namedTransports = transportsProvider.stream().flatMap(List::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 syncSpec = McpClient.sync(namedTransport.transport()) + .clientInfo(clientInfo) + .requestTimeout(commonProperties.getRequestTimeout()); + + syncSpec = mcpSyncClientConfigurer.configure(namedTransport.name(), syncSpec); + + var syncClient = syncSpec.build(); + + if (commonProperties.isInitialized()) { + syncClient.initialize(); + } + + mcpSyncClients.add(syncClient); + } + } + + return mcpSyncClients; + } + + /** + * Creates tool callbacks for all configured MCP clients. + * + *

+ * These callbacks enable integration with Spring AI's tool execution framework, + * allowing MCP tools to be used as part of AI interactions. + * @param mcpClientsProvider provider of MCP sync clients + * @return list of tool callbacks for MCP integration + */ + @Bean + @ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "SYNC", + matchIfMissing = true) + public ToolCallbackProvider toolCallbacks(ObjectProvider> mcpClientsProvider) { + List mcpClients = mcpClientsProvider.stream().flatMap(List::stream).toList(); + return new SyncMcpToolCallbackProvider(mcpClients); + } + + /** + * @deprecated replaced by {@link #toolCallbacks(ObjectProvider)} that returns a + * {@link ToolCallbackProvider} instead of a list of {@link ToolCallback} + */ + @Deprecated + @Bean + @ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "SYNC", + matchIfMissing = true) + public List toolCallbacksDeprecated(ObjectProvider> mcpClientsProvider) { + List mcpClients = mcpClientsProvider.stream().flatMap(List::stream).toList(); + return List.of(new SyncMcpToolCallbackProvider(mcpClients).getToolCallbacks()); + } + + /** + * Record class that implements {@link AutoCloseable} to ensure proper cleanup of MCP + * clients. + * + *

+ * This class is responsible for closing all MCP sync clients when the application + * context is closed, preventing resource leaks. + */ + public record CloseableMcpSyncClients(List clients) implements AutoCloseable { + + @Override + public void close() { + this.clients.forEach(McpSyncClient::close); + } + } + + /** + * Creates a closeable wrapper for MCP sync clients to ensure proper resource cleanup. + * @param clients the list of MCP sync clients to manage + * @return a closeable wrapper for the clients + */ + @Bean + @ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "SYNC", + matchIfMissing = true) + public CloseableMcpSyncClients makeSyncClientsClosable(List clients) { + return new CloseableMcpSyncClients(clients); + } + + /** + * Creates the default {@link McpSyncClientConfigurer} if none is provided. + * + *

+ * This configurer aggregates all available {@link McpSyncClientCustomizer} instances + * to allow for customization of MCP sync client creation. + * @param customizerProvider provider of MCP sync client customizers + * @return the configured MCP sync client configurer + */ + @Bean + @ConditionalOnMissingBean + @ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "SYNC", + matchIfMissing = true) + McpSyncClientConfigurer mcpSyncClientConfigurer(ObjectProvider customizerProvider) { + return new McpSyncClientConfigurer(customizerProvider.orderedStream().toList()); + } + + // Async client configuration + + @Bean + @ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "ASYNC") + public List mcpAsyncClients(McpAsyncClientConfigurer mcpSyncClientConfigurer, + McpClientCommonProperties commonProperties, + ObjectProvider> transportsProvider) { + + List mcpSyncClients = new ArrayList<>(); + + List namedTransports = transportsProvider.stream().flatMap(List::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 syncSpec = McpClient.async(namedTransport.transport()) + .clientInfo(clientInfo) + .requestTimeout(commonProperties.getRequestTimeout()); + + syncSpec = mcpSyncClientConfigurer.configure(namedTransport.name(), syncSpec); + + var syncClient = syncSpec.build(); + + if (commonProperties.isInitialized()) { + syncClient.initialize().block(); + } + + mcpSyncClients.add(syncClient); + } + } + + return mcpSyncClients; + } + + /** + * @deprecated replaced by {@link #asyncToolCallbacks(ObjectProvider)} that returns a + * {@link ToolCallbackProvider} instead of a list of {@link ToolCallback} + */ + @Deprecated + @Bean + @ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "ASYNC") + public List asyncToolCallbacksDeprecated(ObjectProvider> mcpClientsProvider) { + List mcpClients = mcpClientsProvider.stream().flatMap(List::stream).toList(); + return List.of(new AsyncMcpToolCallbackProvider(mcpClients).getToolCallbacks()); + } + + @Bean + @ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "ASYNC") + public ToolCallbackProvider asyncToolCallbacks(ObjectProvider> mcpClientsProvider) { + List mcpClients = mcpClientsProvider.stream().flatMap(List::stream).toList(); + return new AsyncMcpToolCallbackProvider(mcpClients); + } + + public record CloseableMcpAsyncClients(List clients) implements AutoCloseable { + @Override + public void close() { + this.clients.forEach(McpAsyncClient::close); + } + } + + @Bean + @ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "ASYNC") + public CloseableMcpAsyncClients makeAsynClientsClosable(List clients) { + return new CloseableMcpAsyncClients(clients); + } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "ASYNC") + McpAsyncClientConfigurer mcpAsyncClientConfigurer(ObjectProvider customizerProvider) { + return new McpAsyncClientConfigurer(customizerProvider.orderedStream().toList()); + } + +} diff --git a/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/SseHttpClientTransportAutoConfiguration.java b/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/SseHttpClientTransportAutoConfiguration.java index 0fe02a9dc20..ffea6aa5f6c 100644 --- a/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/SseHttpClientTransportAutoConfiguration.java +++ b/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/SseHttpClientTransportAutoConfiguration.java @@ -16,28 +16,26 @@ package org.springframework.ai.autoconfigure.mcp.client; -import java.net.http.HttpClient; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - import com.fasterxml.jackson.databind.ObjectMapper; import io.modelcontextprotocol.client.McpSyncClient; import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport; import io.modelcontextprotocol.spec.McpSchema; - import org.springframework.ai.autoconfigure.mcp.client.properties.McpClientCommonProperties; import org.springframework.ai.autoconfigure.mcp.client.properties.McpSseClientProperties; import org.springframework.ai.autoconfigure.mcp.client.properties.McpSseClientProperties.SseParameters; 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.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; +import java.net.http.HttpClient; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + /** * Auto-configuration for Server-Sent Events (SSE) HTTP client transport in the Model * Context Protocol (MCP). @@ -59,6 +57,10 @@ *

  • Supports multiple named server connections with different URLs * * + * @author Christian Tzolov + * @author qjc + * @description Added SSE transport mode configuration to control HTTP Client implementation + * @Email qjc1024@aliyun.com * @see HttpClientSseClientTransport * @see McpSseClientProperties */ @@ -66,8 +68,11 @@ @ConditionalOnClass({ McpSchema.class, McpSyncClient.class }) @ConditionalOnMissingClass("io.modelcontextprotocol.client.transport.WebFluxSseClientTransport") @EnableConfigurationProperties({ McpSseClientProperties.class, McpClientCommonProperties.class }) -@ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", - matchIfMissing = true) +@ConditionalOnProperty( + prefix = McpSseClientProperties.CONFIG_PREFIX, + name = "transport-mode", + havingValue = "HTTP_CLIENT" +) public class SseHttpClientTransportAutoConfiguration { /** diff --git a/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/SseWebFluxTransportAutoConfiguration.java b/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/SseWebFluxTransportAutoConfiguration.java index 8c6496fc72e..8afa9eb443d 100644 --- a/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/SseWebFluxTransportAutoConfiguration.java +++ b/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/SseWebFluxTransportAutoConfiguration.java @@ -16,13 +16,8 @@ package org.springframework.ai.autoconfigure.mcp.client; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - import com.fasterxml.jackson.databind.ObjectMapper; import io.modelcontextprotocol.client.transport.WebFluxSseClientTransport; - import org.springframework.ai.autoconfigure.mcp.client.properties.McpClientCommonProperties; import org.springframework.ai.autoconfigure.mcp.client.properties.McpSseClientProperties; import org.springframework.ai.autoconfigure.mcp.client.properties.McpSseClientProperties.SseParameters; @@ -34,6 +29,10 @@ import org.springframework.context.annotation.Bean; import org.springframework.web.reactive.function.client.WebClient; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + /** * Auto-configuration for WebFlux-based Server-Sent Events (SSE) client transport in the * Model Context Protocol (MCP). @@ -52,14 +51,22 @@ *
  • Supports multiple named server connections with different base URLs * * + * @author Christian Tzolov + * @author qjc + * @description Added SSE transport mode configuration to control WebFlux vs HTTP Client implementation + * @Email qjc1024@aliyun.com * @see WebFluxSseClientTransport * @see McpSseClientProperties */ @AutoConfiguration @ConditionalOnClass(WebFluxSseClientTransport.class) @EnableConfigurationProperties({ McpSseClientProperties.class, McpClientCommonProperties.class }) -@ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", - matchIfMissing = true) +@ConditionalOnProperty( + prefix = McpSseClientProperties.CONFIG_PREFIX, + name = "transport-mode", + havingValue = "WEBFLUX", + matchIfMissing = true // 默认使用 WebFlux +) public class SseWebFluxTransportAutoConfiguration { /** diff --git a/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/properties/McpSseClientProperties.java b/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/properties/McpSseClientProperties.java index ed5575a1422..511868564da 100644 --- a/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/properties/McpSseClientProperties.java +++ b/auto-configurations/spring-ai-mcp-client/src/main/java/org/springframework/ai/autoconfigure/mcp/client/properties/McpSseClientProperties.java @@ -15,11 +15,11 @@ */ package org.springframework.ai.autoconfigure.mcp.client.properties; +import org.springframework.boot.context.properties.ConfigurationProperties; + import java.util.HashMap; import java.util.Map; -import org.springframework.boot.context.properties.ConfigurationProperties; - /** * Configuration properties for Server-Sent Events (SSE) based MCP client connections. * @@ -38,6 +38,7 @@ * * * @author Christian Tzolov + * @author qjc * @since 1.0.0 * @see SseParameters */ @@ -62,6 +63,30 @@ public record SseParameters(String url) { */ private final Map connections = new HashMap<>(); + /** + * The transport mode for SSE connections. Defaults to WEBFLUX. + */ + private SseTransportMode transportMode = SseTransportMode.WEBFLUX; + + /** + * SSE transport mode for MCP client. + * + * @author qjc + * @description Controls which transport implementation to use for SSE connections + * @Email qjc1024@aliyun.com + */ + public enum SseTransportMode { + /** + * Use WebFlux for SSE transport (default). + */ + WEBFLUX, + + /** + * Use HTTP Client for SSE transport. + */ + HTTP_CLIENT + } + /** * Returns the map of configured SSE connections. * @return map of connection names to their SSE parameters @@ -70,4 +95,12 @@ public Map getConnections() { return this.connections; } + public SseTransportMode getTransportMode() { + return transportMode; + } + + public void setTransportMode(SseTransportMode transportMode) { + this.transportMode = transportMode; + } + } diff --git a/auto-configurations/spring-ai-mcp-client/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/auto-configurations/spring-ai-mcp-client/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 00000000000..e77f5d7dc07 --- /dev/null +++ b/auto-configurations/spring-ai-mcp-client/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,26 @@ +{ + "properties": [ + { + "name": "spring.ai.mcp.client.sse.transport-mode", + "type": "org.springframework.ai.autoconfigure.mcp.client.properties.McpSseClientProperties$SseTransportMode", + "description": "The transport mode for SSE connections. Can be either 'WEBFLUX' (default) or 'HTTP_CLIENT'.", + "defaultValue": "WEBFLUX", + "sourceType": "org.springframework.ai.autoconfigure.mcp.client.properties.McpSseClientProperties" + } + ], + "hints": [ + { + "name": "spring.ai.mcp.client.sse.transport-mode", + "values": [ + { + "value": "WEBFLUX", + "description": "Use WebFlux for SSE transport (default). Provides reactive streaming support." + }, + { + "value": "HTTP_CLIENT", + "description": "Use HTTP Client for SSE transport. Traditional blocking HTTP client implementation." + } + ] + } + ] +} \ No newline at end of file