Refactor: MCP Autoconfig Modularization

Core Architecture Changes:
- Split MCP into dedicated client/server modules
- Created separate starters: spring-ai-starter-mcp-webmvc and spring-ai-starter-mcp-webflux
- Removed property-based transport configuration in favor of auto-configuration
- Added support for multiple transport types (STDIO, WebMVC, WebFlux)

Client Improvements:
- Added support for both synchronous and asynchronous MCP clients
- Fixed client auto-configuration issues
- Added root change notification property to common properties

Configuration Enhancements:
- Improved configuration properties organization and validation
- Added ConditionalOnMissingBean for WebMvc/WebFlux configurations
- Enhanced lifecycle management and customization support

Testing and Documentation:
- Added comprehensive integration tests for McpClientAutoConfiguration
- Updated McpServerAutoConfigurationIT
- Added extensive JavaDoc documentation
- Improved MCP client/server starter documentation
- Added documentation for common utilities
- Updated navigation for new MCP documentation sections

Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
This commit is contained in:
Christian Tzolov
2025-02-07 14:57:18 +01:00
committed by Mark Pollack
parent 015662a118
commit fe377ee5e1
62 changed files with 5133 additions and 659 deletions

View File

@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>spring-ai-mcp-client-spring-boot-autoconfigure</artifactId>
<packaging>jar</packaging>
<name>Spring AI MCP Client Auto Configuration</name>
<description>Spring AI MCP Client Auto Configuration</description>
<url>https://github.com/spring-projects/spring-ai</url>
<scm>
<url>https://github.com/spring-projects/spring-ai</url>
<connection>git://github.com/spring-projects/spring-ai.git</connection>
<developerConnection>git@github.com:spring-projects/spring-ai.git</developerConnection>
</scm>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp</artifactId>
<version>${project.parent.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-spring-webflux</artifactId>
<optional>true</optional>
</dependency>
<!-- NOTE: Currently the webmvc doesn't implement client transport.
We will add it in the future based on ResrtClient.
-->
<!-- <dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-spring-webmvc</artifactId>
<optional>true</optional>
</dependency> -->
<!-- Test dependencies -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-test</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,293 @@
/*
* 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.McpToolUtils;
import org.springframework.ai.mcp.customizer.McpAsyncClientCustomizer;
import org.springframework.ai.mcp.customizer.McpSyncClientCustomizer;
import org.springframework.ai.tool.ToolCallback;
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.
*
* <p>
* 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.
*
* <p>
* Configuration Properties:
* <ul>
* <li>{@code spring.ai.mcp.client.enabled} - Enable/disable MCP client support (default:
* true)
* <li>{@code spring.ai.mcp.client.type} - Client type: SYNC or ASYNC (default: SYNC)
* <li>{@code spring.ai.mcp.client.name} - Client implementation name
* <li>{@code spring.ai.mcp.client.version} - Client implementation version
* <li>{@code spring.ai.mcp.client.request-timeout} - Request timeout duration
* <li>{@code spring.ai.mcp.client.initialized} - Whether to initialize clients on
* creation
* </ul>
*
* <p>
* 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.
*
* <p>
* Key features:
* <ul>
* <li>Synchronous and Asynchronous Client Support:
* <ul>
* <li>Creates and configures MCP clients based on available transports
* <li>Supports both blocking (sync) and non-blocking (async) operations
* <li>Automatic client initialization if enabled
* </ul>
* <li>Integration Support:
* <ul>
* <li>Sets up tool callbacks for Spring AI integration
* <li>Supports multiple named transports
* <li>Proper lifecycle management with automatic cleanup
* </ul>
* <li>Customization Options:
* <ul>
* <li>Extensible through {@link McpSyncClientCustomizer} and
* {@link McpAsyncClientCustomizer}
* <li>Configurable timeouts and client information
* <li>Support for custom transport implementations
* </ul>
* </ul>
*
* @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 {
/**
* Creates a list of {@link McpSyncClient} instances based on the available
* transports.
*
* <p>
* Each client is configured with:
* <ul>
* <li>Client information (name and version) from common properties
* <li>Request timeout settings
* <li>Custom configurations through {@link McpSyncClientConfigurer}
* </ul>
*
* <p>
* 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<McpSyncClient> mcpSyncClients(McpSyncClientConfigurer mcpSyncClientConfigurer,
McpClientCommonProperties commonProperties,
ObjectProvider<List<NamedClientMcpTransport>> transportsProvider) {
List<McpSyncClient> mcpSyncClients = new ArrayList<>();
List<NamedClientMcpTransport> namedTransports = transportsProvider.stream().flatMap(List::stream).toList();
if (!CollectionUtils.isEmpty(namedTransports)) {
for (NamedClientMcpTransport namedTransport : namedTransports) {
McpSchema.Implementation clientInfo = new McpSchema.Implementation(commonProperties.getName(),
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.
*
* <p>
* 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 List<ToolCallback> toolCallbacks(ObjectProvider<List<McpSyncClient>> mcpClientsProvider) {
List<McpSyncClient> mcpClients = mcpClientsProvider.stream().flatMap(List::stream).toList();
return McpToolUtils.getToolCallbacksFromSyncClients(mcpClients);
}
/**
* Record class that implements {@link AutoCloseable} to ensure proper cleanup of MCP
* clients.
*
* <p>
* This class is responsible for closing all MCP sync clients when the application
* context is closed, preventing resource leaks.
*/
public record ClosebleMcpSyncClients(List<McpSyncClient> 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 ClosebleMcpSyncClients makeSyncClientsClosable(List<McpSyncClient> clients) {
return new ClosebleMcpSyncClients(clients);
}
/**
* Creates the default {@link McpSyncClientConfigurer} if none is provided.
*
* <p>
* 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<McpSyncClientCustomizer> customizerProvider) {
return new McpSyncClientConfigurer(customizerProvider.orderedStream().toList());
}
// Async client configuration
@Bean
@ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "ASYNC")
public List<McpAsyncClient> mcpAsyncClients(McpAsyncClientConfigurer mcpSyncClientConfigurer,
McpClientCommonProperties commonProperties,
ObjectProvider<List<NamedClientMcpTransport>> transportsProvider) {
List<McpAsyncClient> mcpSyncClients = new ArrayList<>();
List<NamedClientMcpTransport> namedTransports = transportsProvider.stream().flatMap(List::stream).toList();
if (!CollectionUtils.isEmpty(namedTransports)) {
for (NamedClientMcpTransport namedTransport : namedTransports) {
McpSchema.Implementation clientInfo = new McpSchema.Implementation(commonProperties.getName(),
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();
}
mcpSyncClients.add(syncClient);
}
}
return mcpSyncClients;
}
@Bean
@ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "ASYNC")
public List<ToolCallback> asyncToolCallbacks(ObjectProvider<List<McpAsyncClient>> mcpClientsProvider) {
List<McpAsyncClient> mcpClients = mcpClientsProvider.stream().flatMap(List::stream).toList();
return McpToolUtils.getToolCallbacksFromAsyncClinents(mcpClients);
}
public record ClosebleMcpAsyncClients(List<McpAsyncClient> clients) implements AutoCloseable {
@Override
public void close() {
this.clients.forEach(McpAsyncClient::close);
}
}
@Bean
@ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "ASYNC")
public ClosebleMcpAsyncClients makeAsynClientsClosable(List<McpAsyncClient> clients) {
return new ClosebleMcpAsyncClients(clients);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "type", havingValue = "ASYNC")
McpAsyncClientConfigurer mcpAsyncClientConfigurer(ObjectProvider<McpAsyncClientCustomizer> customizerProvider) {
return new McpAsyncClientConfigurer(customizerProvider.orderedStream().toList());
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2024 - 2024 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 io.modelcontextprotocol.spec.ClientMcpTransport;
/**
* A named MCP client transport. Usually created by the transport auto-configurations, but
* you can also create them manually. Expose the list castom NamedClientMcpTransport
* as @Bean.
*
* @param name the name of the transport. Usually the name of the server connection.
* @param transport the MCP client transport.
* @author Christian Tzolov
* @since 1.0.0
*/
public record NamedClientMcpTransport(String name, ClientMcpTransport transport) {
}

View File

@@ -0,0 +1,116 @@
/*
* 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.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.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;
/**
* Auto-configuration for Server-Sent Events (SSE) HTTP client transport in the Model
* Context Protocol (MCP).
*
* <p>
* This configuration class sets up the necessary beans for SSE-based HTTP client
* transport when WebFlux is not available. It provides HTTP client-based SSE transport
* implementation for MCP client communication.
*
* <p>
* The configuration is activated after the WebFlux SSE transport auto-configuration to
* ensure proper fallback behavior when WebFlux is not available.
*
* <p>
* Key features:
* <ul>
* <li>Creates HTTP client-based SSE transports for configured MCP server connections
* <li>Configures ObjectMapper for JSON serialization/deserialization
* <li>Supports multiple named server connections with different URLs
* </ul>
*
* @see HttpClientSseClientTransport
* @see McpSseClientProperties
*/
@AutoConfiguration(after = SseWebFluxTransportAutoConfiguration.class)
@ConditionalOnClass({ McpSchema.class, McpSyncClient.class })
@ConditionalOnMissingClass("io.modelcontextprotocol.client.transport.public class WebFluxSseClientTransport")
@EnableConfigurationProperties({ McpSseClientProperties.class, McpClientCommonProperties.class })
@ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
matchIfMissing = true)
public class SseHttpClientTransportAutoConfiguration {
/**
* Creates a list of HTTP client-based SSE transports for MCP communication.
*
* <p>
* Each transport is configured with:
* <ul>
* <li>A new HttpClient instance
* <li>Server URL from properties
* <li>ObjectMapper for JSON processing
* </ul>
* @param sseProperties the SSE client properties containing server configurations
* @param objectMapper the ObjectMapper for JSON serialization/deserialization
* @return list of named MCP transports
*/
@Bean
public List<NamedClientMcpTransport> mcpHttpClientTransports(McpSseClientProperties sseProperties,
ObjectMapper objectMapper) {
List<NamedClientMcpTransport> sseTransports = new ArrayList<>();
for (Map.Entry<String, SseParameters> serverParameters : sseProperties.getConnections().entrySet()) {
var transport = new HttpClientSseClientTransport(HttpClient.newBuilder(), serverParameters.getValue().url(),
objectMapper);
sseTransports.add(new NamedClientMcpTransport(serverParameters.getKey(), transport));
}
return sseTransports;
}
/**
* Creates the default ObjectMapper if none is provided.
*
* <p>
* This ObjectMapper is used for JSON serialization and deserialization in the SSE
* transport implementation.
* @return the configured ObjectMapper instance
*/
@Bean
@ConditionalOnMissingBean
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
}

View File

@@ -0,0 +1,124 @@
/*
* 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 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;
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.web.reactive.function.client.WebClient;
/**
* Auto-configuration for WebFlux-based Server-Sent Events (SSE) client transport in the
* Model Context Protocol (MCP).
*
* <p>
* This configuration class sets up the necessary beans for SSE-based WebFlux transport,
* providing reactive transport implementation for MCP client communication when WebFlux
* is available on the classpath.
*
* <p>
* Key features:
* <ul>
* <li>Creates WebFlux-based SSE transports for configured MCP server connections
* <li>Configures WebClient.Builder for HTTP client operations
* <li>Sets up ObjectMapper for JSON serialization/deserialization
* <li>Supports multiple named server connections with different base URLs
* </ul>
*
* @see WebFluxSseClientTransport
* @see McpSseClientProperties
*/
@AutoConfiguration
@ConditionalOnClass(WebFluxSseClientTransport.class)
@EnableConfigurationProperties({ McpSseClientProperties.class, McpClientCommonProperties.class })
@ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
matchIfMissing = true)
public class SseWebFluxTransportAutoConfiguration {
/**
* Creates a list of WebFlux-based SSE transports for MCP communication.
*
* <p>
* Each transport is configured with:
* <ul>
* <li>A cloned WebClient.Builder with server-specific base URL
* <li>ObjectMapper for JSON processing
* <li>Server connection parameters from properties
* </ul>
* @param sseProperties the SSE client properties containing server configurations
* @param webClientBuilderTemplate the template WebClient.Builder to clone for each
* connection
* @param objectMapper the ObjectMapper for JSON serialization/deserialization
* @return list of named MCP transports
*/
@Bean
public List<NamedClientMcpTransport> webFluxClientTransports(McpSseClientProperties sseProperties,
WebClient.Builder webClientBuilderTemplate, ObjectMapper objectMapper) {
List<NamedClientMcpTransport> sseTransports = new ArrayList<>();
for (Map.Entry<String, SseParameters> serverParameters : sseProperties.getConnections().entrySet()) {
var webClientBuilder = webClientBuilderTemplate.clone().baseUrl(serverParameters.getValue().url());
var transport = new WebFluxSseClientTransport(webClientBuilder, objectMapper);
sseTransports.add(new NamedClientMcpTransport(serverParameters.getKey(), transport));
}
return sseTransports;
}
/**
* Creates the default WebClient.Builder if none is provided.
*
* <p>
* This builder serves as a template for creating server-specific WebClient instances
* used in SSE transport implementation.
* @return the configured WebClient.Builder instance
*/
@Bean
@ConditionalOnMissingBean
public WebClient.Builder webClientBuilder() {
return WebClient.builder();
}
/**
* Creates the default ObjectMapper if none is provided.
*
* <p>
* This ObjectMapper is used for JSON serialization and deserialization in the SSE
* transport implementation.
* @return the configured ObjectMapper instance
*/
@Bean
@ConditionalOnMissingBean
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
}

View File

@@ -0,0 +1,87 @@
/*
* 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 java.util.Map;
import io.modelcontextprotocol.client.transport.ServerParameters;
import io.modelcontextprotocol.client.transport.StdioClientTransport;
import io.modelcontextprotocol.spec.McpSchema;
import org.springframework.ai.autoconfigure.mcp.client.properties.McpClientCommonProperties;
import org.springframework.ai.autoconfigure.mcp.client.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;
/**
* Auto-configuration for Standard Input/Output (stdio) transport in the Model Context
* Protocol (MCP).
*
* <p>
* This configuration class sets up the necessary beans for stdio-based transport,
* enabling communication with MCP servers through standard input and output streams.
*
* <p>
* Key features:
* <ul>
* <li>Creates stdio transports for configured MCP server connections
* <li>Supports multiple named server connections with different parameters
* <li>Configures transport with server-specific parameters
* </ul>
*
* @see StdioClientTransport
* @see McpStdioClientProperties
*/
@AutoConfiguration
@ConditionalOnClass({ McpSchema.class })
@EnableConfigurationProperties({ McpStdioClientProperties.class, McpClientCommonProperties.class })
@ConditionalOnProperty(prefix = McpClientCommonProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
matchIfMissing = true)
public class StdioTransportAutoConfiguration {
/**
* Creates a list of stdio-based transports for MCP communication.
*
* <p>
* Each transport is configured with:
* <ul>
* <li>Server-specific parameters from properties
* <li>Unique connection name for identification
* </ul>
* @param sdioProperties the stdio client properties containing server configurations
* @return list of named MCP transports
*/
@Bean
public List<NamedClientMcpTransport> stdioTransports(McpStdioClientProperties sdioProperties) {
List<NamedClientMcpTransport> stdoiTransports = new ArrayList<>();
for (Map.Entry<String, ServerParameters> serverParameters : sdioProperties.toServerParameters().entrySet()) {
var transport = new StdioClientTransport(serverParameters.getValue());
stdoiTransports.add(new NamedClientMcpTransport(serverParameters.getKey(), transport));
}
return stdoiTransports;
}
}

View File

@@ -14,30 +14,30 @@
* limitations under the License.
*/
package org.springframework.ai.autoconfigure.mcp.client.stdio;
package org.springframework.ai.autoconfigure.mcp.client.configurer;
import java.util.List;
import io.modelcontextprotocol.client.McpClient;
import org.springframework.ai.mcp.McpSyncClientCustomizer;
import org.springframework.ai.mcp.customizer.McpAsyncClientCustomizer;
public class McpSyncClientConfigurer {
public class McpAsyncClientConfigurer {
private List<McpSyncClientCustomizer> customizers;
private List<McpAsyncClientCustomizer> customizers;
void setCustomizers(List<McpSyncClientCustomizer> customizers) {
public McpAsyncClientConfigurer(List<McpAsyncClientCustomizer> customizers) {
this.customizers = customizers;
}
public McpClient.SyncSpec configure(String name, McpClient.SyncSpec spec) {
public McpClient.AsyncSpec configure(String name, McpClient.AsyncSpec spec) {
applyCustomizers(name, spec);
return spec;
}
private void applyCustomizers(String name, McpClient.SyncSpec spec) {
private void applyCustomizers(String name, McpClient.AsyncSpec spec) {
if (this.customizers != null) {
for (McpSyncClientCustomizer customizer : this.customizers) {
for (McpAsyncClientCustomizer customizer : this.customizers) {
customizer.customize(name, spec);
}
}

View File

@@ -0,0 +1,76 @@
/*
* 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.configurer;
import java.util.List;
import io.modelcontextprotocol.client.McpClient;
import org.springframework.ai.mcp.customizer.McpSyncClientCustomizer;
/**
* Configurer class for customizing MCP synchronous clients.
*
* <p>
* This class manages a collection of {@link McpSyncClientCustomizer} instances that can
* be applied to customize the configuration of MCP synchronous clients during their
* creation.
*
* <p>
* The configurer applies customizations in the order they are registered, allowing for
* sequential modifications to the client specifications.
*
* @see McpSyncClientCustomizer
* @see McpClient.SyncSpec
*/
public class McpSyncClientConfigurer {
private List<McpSyncClientCustomizer> customizers;
public McpSyncClientConfigurer(List<McpSyncClientCustomizer> customizers) {
this.customizers = customizers;
}
/**
* Configures an MCP sync client specification by applying all registered customizers.
* @param name the name of the client being configured
* @param spec the specification to customize
* @return the customized specification
*/
public McpClient.SyncSpec configure(String name, McpClient.SyncSpec spec) {
applyCustomizers(name, spec);
return spec;
}
/**
* Applies all registered customizers to the given specification.
*
* <p>
* Customizers are applied in the order they were registered. If no customizers are
* registered, this method has no effect.
* @param name the name of the client being customized
* @param spec the specification to customize
*/
private void applyCustomizers(String name, McpClient.SyncSpec spec) {
if (this.customizers != null) {
for (McpSyncClientCustomizer customizer : this.customizers) {
customizer.customize(name, spec);
}
}
}
}

View File

@@ -0,0 +1,160 @@
/*
* 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.properties;
import java.time.Duration;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Common Configuration properties for the Model Context Protocol (MCP) clients shared for
* all transport types.
*
* @author Christian Tzolov
* @since 1.0.0
*/
@ConfigurationProperties(McpClientCommonProperties.CONFIG_PREFIX)
public class McpClientCommonProperties {
public static final String CONFIG_PREFIX = "spring.ai.mcp.client";
/**
* Enable/disable the MCP client.
* <p>
* When set to false, the MCP client and all its components will not be initialized.
*/
private boolean enabled = true;
/**
* The name of the MCP client instance.
* <p>
* This name is reported to clients and used for compatibility checks.
*/
private String name = "spring-ai-mcp-client";
/**
* The version of the MCP client instance.
* <p>
* This version is reported to clients and used for compatibility checks.
*/
private String version = "1.0.0";
/**
* Flag to indicate if the MCP client has to be initialized.
*/
private boolean initialized = true;
/**
* The timeout duration for MCP client requests.
* <p>
* Defaults to 20 seconds.
*/
private Duration requestTimeout = Duration.ofSeconds(20);
/**
* The type of client to use for MCP client communication.
* <p>
* Supported types are:
* <ul>
* <li>SYNC - Standard synchronous client (default)</li>
* <li>ASYNC - Asynchronous client</li>
* </ul>
*/
private ClientType type = ClientType.SYNC;
/**
* Client types supported by the MCP client.
*/
public enum ClientType {
/**
* Synchronous (McpSyncClient) client
*/
SYNC,
/**
* Asynchronous (McpAsyncClient) client
*/
ASYNC
}
/**
* Flag to enable/disable root change notifications.
* <p>
* When enabled, the client will be notified of changes to the root configuration.
* Defaults to true.
*/
private boolean rootChangeNotification = true;
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;
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2024 - 2024 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.properties;
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.
*
* <p>
* These properties allow configuration of multiple named SSE connections to MCP servers.
* Each connection is configured with a URL endpoint for SSE communication.
*
* <p>
* Example configuration: <pre>
* spring.ai.mcp.client.sse:
* connections:
* server1:
* url: http://localhost:8080/events
* server2:
* url: http://otherserver:8081/events
* </pre>
*
* @author Christian Tzolov
* @since 1.0.0
* @see SseParameters
*/
@ConfigurationProperties(McpSseClientProperties.CONFIG_PREFIX)
public class McpSseClientProperties {
public static final String CONFIG_PREFIX = "spring.ai.mcp.client.sse";
/**
* Parameters for configuring an SSE connection to an MCP server.
*
* @param url the URL endpoint for SSE communication with the MCP server
*/
public record SseParameters(String url) {
}
/**
* Map of named SSE connection configurations.
* <p>
* The key represents the connection name, and the value contains the SSE parameters
* for that connection.
*/
private final Map<String, SseParameters> connections = new HashMap<>();
/**
* Returns the map of configured SSE connections.
* @return map of connection names to their SSE parameters
*/
public Map<String, SseParameters> getConnections() {
return this.connections;
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.ai.autoconfigure.mcp.client.stdio;
package org.springframework.ai.autoconfigure.mcp.client.properties;
import java.time.Duration;
import java.util.HashMap;
@@ -47,35 +47,6 @@ public class McpStdioClientProperties {
public static final String CONFIG_PREFIX = "spring.ai.mcp.client.stdio";
/**
* Enable/disable the MCP client.
* <p>
* When set to false, the MCP client and all its components will not be initialized.
*/
private boolean enabled = false;
/**
* The version of the MCP client instance.
* <p>
* This version is reported to clients and used for compatibility checks.
*/
private String version = "1.0.0";
/**
* The timeout duration for MCP client requests.
* <p>
* Defaults to 20 seconds.
*/
private Duration requestTimeout = Duration.ofSeconds(20);
/**
* Flag to enable/disable root change notifications.
* <p>
* When enabled, the client will be notified of changes to the root configuration.
* Defaults to true.
*/
private boolean rootChangeNotification = true;
/**
* Resource containing the MCP servers configuration.
* <p>
@@ -90,12 +61,7 @@ public class McpStdioClientProperties {
* Each entry represents a named connection with its specific configuration
* parameters.
*/
private final Map<String, McpStdioConnection> stdioConnections = new HashMap<>();
/**
* Flag to indicate if the MCP client has to be initialized.
*/
private boolean initialize = true;
private final Map<String, Parameters> connections = new HashMap<>();
public Resource getServersConfiguration() {
return this.serversConfiguration;
@@ -105,50 +71,8 @@ public class McpStdioClientProperties {
this.serversConfiguration = stdioConnectionResources;
}
public Map<String, McpStdioConnection> getStdioConnections() {
return this.stdioConnections;
}
public boolean isRootChangeNotification() {
return this.rootChangeNotification;
}
public void setRootChangeNotification(boolean rootChangeNotification) {
this.rootChangeNotification = rootChangeNotification;
}
public Duration getRequestTimeout() {
return this.requestTimeout;
}
public void setRequestTimeout(Duration requestTimeout) {
Assert.notNull(requestTimeout, "Request timeout must not be null");
this.requestTimeout = requestTimeout;
}
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getVersion() {
return this.version;
}
public void setVersion(String version) {
Assert.hasText(version, "Version must not be empty");
this.version = version;
}
public boolean isInitialize() {
return this.initialize;
}
public void setInitialize(boolean initialize) {
this.initialize = initialize;
public Map<String, Parameters> getConnections() {
return this.connections;
}
/**
@@ -170,6 +94,11 @@ public class McpStdioClientProperties {
* Map of environment variables for the server process.
*/
@JsonProperty("env") Map<String, String> env) {
public ServerParameters toServerParameters() {
return ServerParameters.builder(this.command()).args(this.args()).env(this.env()).build();
}
}
private Map<String, ServerParameters> resourceToServerParameters() {
@@ -200,7 +129,7 @@ public class McpStdioClientProperties {
serverParameters.putAll(resourceToServerParameters());
}
for (Map.Entry<String, McpStdioConnection> entry : this.stdioConnections.entrySet()) {
for (Map.Entry<String, Parameters> entry : this.connections.entrySet()) {
serverParameters.put(entry.getKey(), entry.getValue().toServerParameters());
}
return serverParameters;

View File

@@ -0,0 +1,21 @@
#
# 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.
#
org.springframework.ai.autoconfigure.mcp.client.StdioTransportAutoConfiguration
org.springframework.ai.autoconfigure.mcp.client.SseWebFluxTransportAutoConfiguration
org.springframework.ai.autoconfigure.mcp.client.SseHttpClientTransportAutoConfiguration
org.springframework.ai.autoconfigure.mcp.client.McpClientAutoConfiguration

View File

@@ -0,0 +1,190 @@
/*
* 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.time.Duration;
import java.util.List;
import java.util.function.Function;
import com.fasterxml.jackson.core.type.TypeReference;
import io.modelcontextprotocol.client.McpAsyncClient;
import io.modelcontextprotocol.client.McpSyncClient;
import io.modelcontextprotocol.spec.ClientMcpTransport;
import io.modelcontextprotocol.spec.McpSchema;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import reactor.core.publisher.Mono;
import org.springframework.ai.autoconfigure.mcp.client.configurer.McpSyncClientConfigurer;
import org.springframework.ai.autoconfigure.mcp.client.properties.McpClientCommonProperties;
import org.springframework.ai.mcp.customizer.McpSyncClientCustomizer;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
@Disabled
public class McpClientAutoConfigurationIT {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(McpClientAutoConfiguration.class));
@Test
void defaultConfiguration() {
this.contextRunner.withUserConfiguration(TestTransportConfiguration.class).run(context -> {
List<McpSyncClient> clients = context.getBean("mcpSyncClients", List.class);
assertThat(clients).hasSize(1);
McpClientCommonProperties properties = context.getBean(McpClientCommonProperties.class);
assertThat(properties.getName()).isEqualTo("mcp-client");
assertThat(properties.getVersion()).isEqualTo("1.0.0");
assertThat(properties.getType()).isEqualTo(McpClientCommonProperties.ClientType.SYNC);
assertThat(properties.getRequestTimeout()).isEqualTo(Duration.ofSeconds(30));
assertThat(properties.isInitialized()).isTrue();
});
}
@Test
void asyncConfiguration() {
this.contextRunner
.withPropertyValues("spring.ai.mcp.client.type=ASYNC", "spring.ai.mcp.client.name=test-client",
"spring.ai.mcp.client.version=2.0.0", "spring.ai.mcp.client.request-timeout=60s",
"spring.ai.mcp.client.initialized=false")
.withUserConfiguration(TestTransportConfiguration.class)
.run(context -> {
List<McpAsyncClient> clients = context.getBean("mcpAsyncClients", List.class);
assertThat(clients).hasSize(1);
McpClientCommonProperties properties = context.getBean(McpClientCommonProperties.class);
assertThat(properties.getName()).isEqualTo("test-client");
assertThat(properties.getVersion()).isEqualTo("2.0.0");
assertThat(properties.getType()).isEqualTo(McpClientCommonProperties.ClientType.ASYNC);
assertThat(properties.getRequestTimeout()).isEqualTo(Duration.ofSeconds(60));
assertThat(properties.isInitialized()).isFalse();
});
}
@Test
void disabledConfiguration() {
this.contextRunner.withPropertyValues("spring.ai.mcp.client.enabled=false").run(context -> {
assertThat(context).doesNotHaveBean(McpSyncClient.class);
assertThat(context).doesNotHaveBean(McpAsyncClient.class);
assertThat(context).doesNotHaveBean(ToolCallback.class);
});
}
@Test
void customTransportConfiguration() {
this.contextRunner.withUserConfiguration(CustomTransportConfiguration.class).run(context -> {
List<NamedClientMcpTransport> transports = context.getBean("customTransports", List.class);
assertThat(transports).hasSize(1);
assertThat(transports.get(0).transport()).isInstanceOf(CustomClientTransport.class);
});
}
@Test
void clientCustomization() {
this.contextRunner.withUserConfiguration(TestTransportConfiguration.class, CustomizerConfiguration.class)
.run(context -> {
assertThat(context).hasSingleBean(McpSyncClientConfigurer.class);
List<McpSyncClient> clients = context.getBean("mcpSyncClients", List.class);
assertThat(clients).hasSize(1);
});
}
@Test
void toolCallbacksCreation() {
this.contextRunner.withUserConfiguration(TestTransportConfiguration.class).run(context -> {
assertThat(context).hasSingleBean(List.class);
List<ToolCallback> callbacks = context.getBean("toolCallbacks", List.class);
assertThat(callbacks).isNotEmpty();
});
}
@Test
void closeableWrappersCreation() {
this.contextRunner.withUserConfiguration(TestTransportConfiguration.class).run(context -> {
assertThat(context).hasSingleBean(McpClientAutoConfiguration.ClosebleMcpSyncClients.class);
});
}
@Configuration
static class TestTransportConfiguration {
@Bean
List<NamedClientMcpTransport> testTransports() {
return List.of(new NamedClientMcpTransport("test", Mockito.mock(ClientMcpTransport.class)));
}
}
@Configuration
static class CustomTransportConfiguration {
@Bean
List<NamedClientMcpTransport> customTransports() {
return List.of(new NamedClientMcpTransport("custom", new CustomClientTransport()));
}
}
@Configuration
static class CustomizerConfiguration {
@Bean
McpSyncClientCustomizer testCustomizer() {
return (name, spec) -> {
/* no-op */ };
}
}
static class CustomClientTransport implements ClientMcpTransport {
@Override
public void close() {
// Test implementation
}
@Override
public Mono<Void> connect(
Function<Mono<McpSchema.JSONRPCMessage>, Mono<McpSchema.JSONRPCMessage>> messageHandler) {
return Mono.empty(); // Test implementation
}
@Override
public Mono<Void> sendMessage(McpSchema.JSONRPCMessage message) {
return Mono.empty(); // Test implementation
}
@Override
public <T> T unmarshalFrom(Object value, TypeReference<T> type) {
return null; // Test implementation
}
@Override
public Mono<Void> closeGracefully() {
return Mono.empty(); // Test implementation
}
}
}

View File

@@ -0,0 +1,10 @@
# Test MCP STDIO client configuration
spring.ai.mcp.client.stdio.enabled=true
spring.ai.mcp.client.stdio.version=test-version
spring.ai.mcp.client.stdio.request-timeout=15s
spring.ai.mcp.client.stdio.root-change-notification=false
# Test server configuration
spring.ai.mcp.client.stdio.stdio-connections.test-server.command=echo
spring.ai.mcp.client.stdio.stdio-connections.test-server.args[0]=test
spring.ai.mcp.client.stdio.stdio-connections.test-server.env.TEST_ENV=test-value

View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>spring-ai-mcp-server-spring-boot-autoconfigure</artifactId>
<packaging>jar</packaging>
<name>Spring AI MCP Server Auto Configuration</name>
<description>Spring AI MCP Server Auto Configuration</description>
<url>https://github.com/spring-projects/spring-ai</url>
<scm>
<url>https://github.com/spring-projects/spring-ai</url>
<connection>git://github.com/spring-projects/spring-ai.git</connection>
<developerConnection>git@github.com:spring-projects/spring-ai.git</developerConnection>
</scm>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp</artifactId>
<version>${project.parent.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-spring-webflux</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-spring-webmvc</artifactId>
<optional>true</optional>
</dependency>
<!-- test dependencies -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-test</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -25,7 +25,6 @@ import org.springframework.util.Assert;
* These properties control the behavior and configuration of the MCP server, including:
* <ul>
* <li>Server identification (name and version)</li>
* <li>Transport type (STDIO, WEBMVC, or WEBFLUX)</li>
* <li>Change notification settings for tools, resources, and prompts</li>
* <li>Web transport endpoint configuration</li>
* </ul>
@@ -46,7 +45,15 @@ public class McpServerProperties {
* <p>
* When set to false, the MCP server and all its components will not be initialized.
*/
private boolean enabled = false;
private boolean enabled = true;
/**
* Enable/disable the standard input/output (stdio) transport.
* <p>
* When enabled, the server will listen for incoming messages on the standard input
* and write responses to the standard output.
*/
private boolean stdio = false;
/**
* The name of the MCP server instance.
@@ -88,18 +95,6 @@ public class McpServerProperties {
*/
private boolean promptChangeNotification = true;
/**
* The transport type to use for MCP server communication.
* <p>
* Supported types are:
* <ul>
* <li>STDIO - Standard input/output transport (default)</li>
* <li>WEBMVC - Spring MVC Server-Sent Events transport</li>
* <li>WEBFLUX - Spring WebFlux Server-Sent Events transport</li>
* </ul>
*/
private Transport transport = Transport.STDIO;
/**
* The endpoint path for Server-Sent Events (SSE) when using web transports.
* <p>
@@ -118,31 +113,6 @@ public class McpServerProperties {
*/
private ServerType type = ServerType.SYNC;
/**
* Transport types supported by the MCP server.
*/
public enum Transport {
/**
* Standard input/output transport, suitable for command-line tools and local
* development.
*/
STDIO,
/**
* Spring MVC Server-Sent Events transport, requires spring-boot-starter-web and
* mcp-spring-webmvc.
*/
WEBMVC,
/**
* Spring WebFlux Server-Sent Events transport, requires
* spring-boot-starter-webflux and mcp-spring-webflux.
*/
WEBFLUX
}
/**
* Server types supported by the MCP server.
*/
@@ -160,6 +130,14 @@ public class McpServerProperties {
}
public boolean isStdio() {
return this.stdio;
}
public void setStdio(boolean stdio) {
this.stdio = stdio;
}
public boolean isEnabled() {
return this.enabled;
}
@@ -210,15 +188,6 @@ public class McpServerProperties {
this.promptChangeNotification = promptChangeNotification;
}
public Transport getTransport() {
return this.transport;
}
public void setTransport(Transport transport) {
Assert.notNull(transport, "Transport must not be null");
this.transport = transport;
}
public String getSseMessageEndpoint() {
return this.sseMessageEndpoint;
}

View File

@@ -20,19 +20,23 @@ import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import io.modelcontextprotocol.server.McpServer;
import reactor.core.publisher.Mono;
import io.modelcontextprotocol.server.McpServer.SyncSpec;
import io.modelcontextprotocol.server.McpServer.AsyncSpec;
import io.modelcontextprotocol.server.McpServerFeatures;
import io.modelcontextprotocol.server.McpServerFeatures.SyncToolRegistration;
import io.modelcontextprotocol.server.McpServerFeatures.AsyncToolRegistration;
import io.modelcontextprotocol.server.McpSyncServer;
import io.modelcontextprotocol.server.McpAsyncServer;
import io.modelcontextprotocol.server.McpServer;
import io.modelcontextprotocol.server.McpServer.AsyncSpec;
import io.modelcontextprotocol.server.McpServer.SyncSpec;
import io.modelcontextprotocol.server.McpServerFeatures;
import io.modelcontextprotocol.server.McpServerFeatures.AsyncPromptRegistration;
import io.modelcontextprotocol.server.McpServerFeatures.AsyncResourceRegistration;
import io.modelcontextprotocol.server.McpServerFeatures.AsyncToolRegistration;
import io.modelcontextprotocol.server.McpServerFeatures.SyncPromptRegistration;
import io.modelcontextprotocol.server.McpServerFeatures.SyncResourceRegistration;
import io.modelcontextprotocol.server.McpServerFeatures.SyncToolRegistration;
import io.modelcontextprotocol.server.McpSyncServer;
import io.modelcontextprotocol.server.transport.StdioServerTransport;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.McpSchema.Implementation;
import io.modelcontextprotocol.spec.ServerMcpTransport;
import reactor.core.publisher.Mono;
import org.springframework.ai.mcp.McpToolUtils;
import org.springframework.ai.tool.ToolCallback;
@@ -45,6 +49,7 @@ 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;
/**
* {@link EnableAutoConfiguration Auto-configuration} for the Model Context Protocol (MCP)
@@ -94,18 +99,17 @@ import org.springframework.core.log.LogAccessor;
* @see MpcWebMvcServerAutoConfiguration
* @see org.springframework.ai.mcp.ToolCallback
*/
@AutoConfiguration
@AutoConfiguration(after = { MpcWebMvcServerAutoConfiguration.class, MpcWebFluxServerAutoConfiguration.class })
@ConditionalOnClass({ McpSchema.class, McpSyncServer.class })
@EnableConfigurationProperties(McpServerProperties.class)
@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true")
@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
matchIfMissing = true)
public class MpcServerAutoConfiguration {
private static final LogAccessor logger = new LogAccessor(MpcServerAutoConfiguration.class);
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "transport", havingValue = "STDIO",
matchIfMissing = true)
public ServerMcpTransport stdioServerTransport() {
return new StdioServerTransport();
}
@@ -119,8 +123,9 @@ public class MpcServerAutoConfiguration {
@Bean
@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "type", havingValue = "SYNC",
matchIfMissing = true)
public List<McpServerFeatures.SyncToolRegistration> syncTools(List<ToolCallback> toolCalls) {
return McpToolUtils.toSyncToolRegistration(toolCalls);
public List<McpServerFeatures.SyncToolRegistration> syncTools(ObjectProvider<List<ToolCallback>> toolCalls) {
var tools = toolCalls.stream().flatMap(List::stream).toList();
return McpToolUtils.toSyncToolRegistration(tools);
}
@Bean
@@ -128,9 +133,8 @@ public class MpcServerAutoConfiguration {
matchIfMissing = true)
public McpSyncServer mcpSyncServer(ServerMcpTransport transport,
McpSchema.ServerCapabilities.Builder capabilitiesBuilder, McpServerProperties serverProperties,
ObjectProvider<List<SyncToolRegistration>> tools,
ObjectProvider<List<McpServerFeatures.SyncResourceRegistration>> resources,
ObjectProvider<List<McpServerFeatures.SyncPromptRegistration>> prompts,
ObjectProvider<List<SyncToolRegistration>> tools, ObjectProvider<List<SyncResourceRegistration>> resources,
ObjectProvider<List<SyncPromptRegistration>> prompts,
ObjectProvider<Consumer<List<McpSchema.Root>>> rootsChangeConsumers) {
McpSchema.Implementation serverInfo = new Implementation(serverProperties.getName(),
@@ -139,26 +143,29 @@ public class MpcServerAutoConfiguration {
// Create the server with both tool and resource capabilities
SyncSpec serverBuilder = McpServer.sync(transport).serverInfo(serverInfo);
tools.ifAvailable(toolsList -> {
serverBuilder.tools(toolsList);
List<SyncToolRegistration> toolResgistrations = tools.stream().flatMap(List::stream).toList();
if (!CollectionUtils.isEmpty(toolResgistrations)) {
serverBuilder.tools(toolResgistrations);
capabilitiesBuilder.tools(serverProperties.isToolChangeNotification());
logger.info("Registered tools" + toolsList.size() + " notification: "
logger.info("Registered tools" + toolResgistrations.size() + " notification: "
+ serverProperties.isToolChangeNotification());
});
}
resources.ifAvailable(resourceList -> {
serverBuilder.resources(resourceList);
List<SyncResourceRegistration> resourceResgistrations = resources.stream().flatMap(List::stream).toList();
if (!CollectionUtils.isEmpty(resourceResgistrations)) {
serverBuilder.resources(resourceResgistrations);
capabilitiesBuilder.resources(false, serverProperties.isResourceChangeNotification());
logger.info("Registered resources" + resourceList.size() + " notification: "
logger.info("Registered resources" + resourceResgistrations.size() + " notification: "
+ serverProperties.isResourceChangeNotification());
});
}
prompts.ifAvailable(promptList -> {
serverBuilder.prompts(promptList);
List<SyncPromptRegistration> promptResgistrations = prompts.stream().flatMap(List::stream).toList();
if (!CollectionUtils.isEmpty(promptResgistrations)) {
serverBuilder.prompts(promptResgistrations);
capabilitiesBuilder.prompts(serverProperties.isPromptChangeNotification());
logger.info("Registered prompts" + promptList.size() + " notification: "
logger.info("Registered prompts" + promptResgistrations.size() + " notification: "
+ serverProperties.isPromptChangeNotification());
});
}
rootsChangeConsumers.ifAvailable(consumer -> {
serverBuilder.rootsChangeConsumer(consumer);
@@ -172,8 +179,9 @@ public class MpcServerAutoConfiguration {
@Bean
@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "type", havingValue = "ASYNC")
public List<McpServerFeatures.AsyncToolRegistration> asyncTools(List<ToolCallback> toolCalls) {
return McpToolUtils.toAsyncToolRegistration(toolCalls);
public List<McpServerFeatures.AsyncToolRegistration> asyncTools(ObjectProvider<List<ToolCallback>> toolCalls) {
var tools = toolCalls.stream().flatMap(List::stream).toList();
return McpToolUtils.toAsyncToolRegistration(tools);
}
@Bean
@@ -181,8 +189,8 @@ public class MpcServerAutoConfiguration {
public McpAsyncServer mcpAsyncServer(ServerMcpTransport transport,
McpSchema.ServerCapabilities.Builder capabilitiesBuilder, McpServerProperties serverProperties,
ObjectProvider<List<AsyncToolRegistration>> tools,
ObjectProvider<List<McpServerFeatures.AsyncResourceRegistration>> resources,
ObjectProvider<List<McpServerFeatures.AsyncPromptRegistration>> prompts,
ObjectProvider<List<AsyncResourceRegistration>> resources,
ObjectProvider<List<AsyncPromptRegistration>> prompts,
ObjectProvider<Consumer<List<McpSchema.Root>>> rootsChangeConsumer) {
McpSchema.Implementation serverInfo = new Implementation(serverProperties.getName(),
@@ -191,26 +199,29 @@ public class MpcServerAutoConfiguration {
// Create the server with both tool and resource capabilities
AsyncSpec serverBilder = McpServer.async(transport).serverInfo(serverInfo);
tools.ifAvailable(toolsList -> {
serverBilder.tools(toolsList);
List<AsyncToolRegistration> toolResgistrations = tools.stream().flatMap(List::stream).toList();
if (!CollectionUtils.isEmpty(toolResgistrations)) {
serverBilder.tools(toolResgistrations);
capabilitiesBuilder.tools(serverProperties.isToolChangeNotification());
logger.info("Registered tools" + toolsList.size() + " notification: "
logger.info("Registered tools" + toolResgistrations.size() + " notification: "
+ serverProperties.isToolChangeNotification());
});
}
resources.ifAvailable(resourceList -> {
serverBilder.resources(resourceList);
List<AsyncResourceRegistration> resourceResgistrations = resources.stream().flatMap(List::stream).toList();
if (!CollectionUtils.isEmpty(resourceResgistrations)) {
serverBilder.resources(resourceResgistrations);
capabilitiesBuilder.resources(false, serverProperties.isResourceChangeNotification());
logger.info("Registered resources" + resourceList.size() + " notification: "
logger.info("Registered resources" + resourceResgistrations.size() + " notification: "
+ serverProperties.isResourceChangeNotification());
});
}
prompts.ifAvailable(promptList -> {
serverBilder.prompts(promptList);
List<AsyncPromptRegistration> promptResgistrations = prompts.stream().flatMap(List::stream).toList();
if (!CollectionUtils.isEmpty(promptResgistrations)) {
serverBilder.prompts(promptResgistrations);
capabilitiesBuilder.prompts(serverProperties.isPromptChangeNotification());
logger.info("Registered prompts" + promptList.size() + " notification: "
logger.info("Registered prompts" + promptResgistrations.size() + " notification: "
+ serverProperties.isPromptChangeNotification());
});
}
rootsChangeConsumer.ifAvailable(consumer -> {
Function<List<McpSchema.Root>, Mono<Void>> asyncConsumer = roots -> {

View File

@@ -18,6 +18,7 @@ package org.springframework.ai.autoconfigure.mcp.server;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.server.transport.WebFluxSseServerTransport;
import io.modelcontextprotocol.spec.ServerMcpTransport;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -64,7 +65,9 @@ import org.springframework.web.reactive.function.server.RouterFunction;
*/
@AutoConfiguration
@ConditionalOnClass({ WebFluxSseServerTransport.class })
@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "transport", havingValue = "WEBFLUX")
@ConditionalOnMissingBean(ServerMcpTransport.class)
@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "stdio", havingValue = "false",
matchIfMissing = true)
public class MpcWebFluxServerAutoConfiguration {
@Bean

View File

@@ -18,6 +18,7 @@ package org.springframework.ai.autoconfigure.mcp.server;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.server.transport.WebMvcSseServerTransport;
import io.modelcontextprotocol.spec.ServerMcpTransport;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -48,10 +49,6 @@ import org.springframework.web.servlet.function.ServerResponse;
* <p>
* Required dependencies: <pre>{@code
* <dependency>
* <groupId>io.modelcontextprotocol.sdk</groupId>
* <artifactId>mcp-spring-webmvc</artifactId>
* </dependency>
* <dependency>
* <groupId>org.springframework.boot</groupId>
* <artifactId>spring-boot-starter-web</artifactId>
* </dependency>
@@ -64,7 +61,9 @@ import org.springframework.web.servlet.function.ServerResponse;
*/
@AutoConfiguration
@ConditionalOnClass({ WebMvcSseServerTransport.class })
@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "transport", havingValue = "WEBMVC")
@ConditionalOnMissingBean(ServerMcpTransport.class)
@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "stdio", havingValue = "false",
matchIfMissing = true)
public class MpcWebMvcServerAutoConfiguration {
@Bean

View File

@@ -0,0 +1,20 @@
#
# 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.
#
org.springframework.ai.autoconfigure.mcp.server.MpcServerAutoConfiguration
org.springframework.ai.autoconfigure.mcp.server.MpcWebMvcServerAutoConfiguration
org.springframework.ai.autoconfigure.mcp.server.MpcWebFluxServerAutoConfiguration

View File

@@ -0,0 +1,311 @@
/*
* 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.server;
import com.fasterxml.jackson.core.type.TypeReference;
import io.modelcontextprotocol.client.McpSyncClient;
import io.modelcontextprotocol.server.McpAsyncServer;
import io.modelcontextprotocol.server.McpServerFeatures.AsyncToolRegistration;
import io.modelcontextprotocol.server.McpServerFeatures.SyncToolRegistration;
import io.modelcontextprotocol.server.McpServerFeatures.SyncResourceRegistration;
import io.modelcontextprotocol.server.McpServerFeatures.SyncPromptRegistration;
import io.modelcontextprotocol.server.McpSyncServer;
import io.modelcontextprotocol.server.transport.StdioServerTransport;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.ServerMcpTransport;
import org.mockito.Mockito;
import org.junit.jupiter.api.Test;
import org.springframework.ai.mcp.SyncMcpToolCallback;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import static org.assertj.core.api.Assertions.assertThat;
public class McpServerAutoConfigurationIT {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(MpcServerAutoConfiguration.class));
@Test
void defaultConfiguration() {
this.contextRunner.run(context -> {
assertThat(context).hasSingleBean(McpSyncServer.class);
assertThat(context).hasSingleBean(ServerMcpTransport.class);
assertThat(context.getBean(ServerMcpTransport.class)).isInstanceOf(StdioServerTransport.class);
McpServerProperties properties = context.getBean(McpServerProperties.class);
assertThat(properties.getName()).isEqualTo("mcp-server");
assertThat(properties.getVersion()).isEqualTo("1.0.0");
assertThat(properties.getType()).isEqualTo(McpServerProperties.ServerType.SYNC);
assertThat(properties.isToolChangeNotification()).isTrue();
assertThat(properties.isResourceChangeNotification()).isTrue();
assertThat(properties.isPromptChangeNotification()).isTrue();
});
}
@Test
void asyncConfiguration() {
this.contextRunner
.withPropertyValues("spring.ai.mcp.server.type=ASYNC", "spring.ai.mcp.server.name=test-server",
"spring.ai.mcp.server.version=2.0.0")
.run(context -> {
assertThat(context).hasSingleBean(McpAsyncServer.class);
assertThat(context).doesNotHaveBean(McpSyncServer.class);
McpServerProperties properties = context.getBean(McpServerProperties.class);
assertThat(properties.getName()).isEqualTo("test-server");
assertThat(properties.getVersion()).isEqualTo("2.0.0");
assertThat(properties.getType()).isEqualTo(McpServerProperties.ServerType.ASYNC);
});
}
@Test
void transportConfiguration() {
this.contextRunner.withUserConfiguration(CustomTransportConfiguration.class).run(context -> {
assertThat(context).hasSingleBean(ServerMcpTransport.class);
assertThat(context.getBean(ServerMcpTransport.class)).isInstanceOf(CustomServerTransport.class);
});
}
@Test
void serverNotificationConfiguration() {
this.contextRunner
.withPropertyValues("spring.ai.mcp.server.tool-change-notification=false",
"spring.ai.mcp.server.resource-change-notification=false")
.run(context -> {
McpServerProperties properties = context.getBean(McpServerProperties.class);
assertThat(properties.isToolChangeNotification()).isFalse();
assertThat(properties.isResourceChangeNotification()).isFalse();
});
}
// @Test
void invalidConfigurationThrowsException() {
this.contextRunner.withPropertyValues("spring.ai.mcp.server.version=invalid-version").run(context -> {
assertThat(context).hasFailed();
assertThat(context).getFailure()
.hasRootCauseInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Invalid version format");
});
}
@Test
void disabledConfiguration() {
this.contextRunner.withPropertyValues("spring.ai.mcp.server.enabled=false").run(context -> {
assertThat(context).doesNotHaveBean(McpSyncServer.class);
assertThat(context).doesNotHaveBean(McpAsyncServer.class);
assertThat(context).doesNotHaveBean(ServerMcpTransport.class);
});
}
@Test
void notificationConfiguration() {
this.contextRunner
.withPropertyValues("spring.ai.mcp.server.tool-change-notification=false",
"spring.ai.mcp.server.resource-change-notification=false",
"spring.ai.mcp.server.prompt-change-notification=false")
.run(context -> {
McpServerProperties properties = context.getBean(McpServerProperties.class);
assertThat(properties.isToolChangeNotification()).isFalse();
assertThat(properties.isResourceChangeNotification()).isFalse();
assertThat(properties.isPromptChangeNotification()).isFalse();
});
}
@Test
void stdioConfiguration() {
this.contextRunner.withPropertyValues("spring.ai.mcp.server.stdio=true").run(context -> {
McpServerProperties properties = context.getBean(McpServerProperties.class);
assertThat(properties.isStdio()).isTrue();
});
}
@Test
void serverCapabilitiesConfiguration() {
this.contextRunner.run(context -> {
assertThat(context).hasSingleBean(McpSchema.ServerCapabilities.Builder.class);
McpSchema.ServerCapabilities.Builder builder = context.getBean(McpSchema.ServerCapabilities.Builder.class);
assertThat(builder).isNotNull();
});
}
@Test
void toolRegistrationConfiguration() {
this.contextRunner.withUserConfiguration(TestToolConfiguration.class).run(context -> {
List<SyncToolRegistration> tools = context.getBean("syncTools", List.class);
assertThat(tools).hasSize(1);
});
}
@Test
void resourceRegistrationConfiguration() {
this.contextRunner.withUserConfiguration(TestResourceConfiguration.class).run(context -> {
McpSyncServer server = context.getBean(McpSyncServer.class);
assertThat(server).isNotNull();
});
}
@Test
void promptRegistrationConfiguration() {
this.contextRunner.withUserConfiguration(TestPromptConfiguration.class).run(context -> {
McpSyncServer server = context.getBean(McpSyncServer.class);
assertThat(server).isNotNull();
});
}
@Test
void asyncToolRegistrationConfiguration() {
this.contextRunner.withPropertyValues("spring.ai.mcp.server.type=ASYNC")
.withUserConfiguration(TestToolConfiguration.class)
.run(context -> {
List<AsyncToolRegistration> tools = context.getBean("asyncTools", List.class);
assertThat(tools).hasSize(1);
});
}
@Test
void customCapabilitiesBuilder() {
this.contextRunner.withUserConfiguration(CustomCapabilitiesConfiguration.class).run(context -> {
assertThat(context).hasSingleBean(McpSchema.ServerCapabilities.Builder.class);
assertThat(context.getBean(McpSchema.ServerCapabilities.Builder.class))
.isInstanceOf(CustomCapabilitiesBuilder.class);
});
}
@Test
void rootsChangeConsumerConfiguration() {
this.contextRunner.withUserConfiguration(TestRootsChangeConfiguration.class).run(context -> {
McpSyncServer server = context.getBean(McpSyncServer.class);
assertThat(server).isNotNull();
});
}
@Configuration
static class TestResourceConfiguration {
@Bean
List<SyncResourceRegistration> testResources() {
return List.of();
}
}
@Configuration
static class TestPromptConfiguration {
@Bean
List<SyncPromptRegistration> testPrompts() {
return List.of();
}
}
@Configuration
static class CustomCapabilitiesConfiguration {
@Bean
McpSchema.ServerCapabilities.Builder customCapabilitiesBuilder() {
return new CustomCapabilitiesBuilder();
}
}
static class CustomCapabilitiesBuilder extends McpSchema.ServerCapabilities.Builder {
// Custom implementation for testing
}
@Configuration
static class TestToolConfiguration {
@Bean
List<ToolCallback> testTool() {
McpSyncClient mockClient = Mockito.mock(McpSyncClient.class);
McpSchema.Tool mockTool = Mockito.mock(McpSchema.Tool.class);
McpSchema.CallToolResult mockResult = Mockito.mock(McpSchema.CallToolResult.class);
Mockito.when(mockTool.name()).thenReturn("test-tool");
Mockito.when(mockTool.description()).thenReturn("Test Tool");
Mockito.when(mockClient.callTool(Mockito.any(McpSchema.CallToolRequest.class))).thenReturn(mockResult);
return List.of(new SyncMcpToolCallback(mockClient, mockTool));
}
}
@Configuration
static class TestRootsChangeConfiguration {
@Bean
Consumer<List<McpSchema.Root>> rootsChangeConsumer() {
return roots -> {
// Test implementation
};
}
}
static class CustomServerTransport implements ServerMcpTransport {
@Override
public Mono<Void> connect(
Function<Mono<McpSchema.JSONRPCMessage>, Mono<McpSchema.JSONRPCMessage>> messageHandler) {
return Mono.empty(); // Test implementation
}
@Override
public Mono<Void> sendMessage(McpSchema.JSONRPCMessage message) {
return Mono.empty(); // Test implementation
}
@Override
public <T> T unmarshalFrom(Object value, TypeReference<T> type) {
return null; // Test implementation
}
@Override
public void close() {
// Test implementation
}
@Override
public Mono<Void> closeGracefully() {
return Mono.empty(); // Test implementation
}
}
@Configuration
static class CustomTransportConfiguration {
@Bean
ServerMcpTransport customTransport() {
return new CustomServerTransport();
}
}
}

View File

@@ -0,0 +1,340 @@
= Spring AI MCP Client Boot Starter
The Spring AI MCP (Model Context Protocol) Client Boot Starter provides auto-configuration for MCP client functionality in Spring Boot applications. It supports both synchronous and asynchronous client implementations with various transport options.
The MCP Client Boot Starter provides:
* Automatic client initialization (if enabled)
* Support for multiple named transports
* Integration with Spring AI's tool execution framework
* Proper lifecycle management with automatic cleanup
* Customizable client creation through customizers
== Dependencies
=== Core Starter
[source,xml]
----
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId>
<version>${spring-ai.version}</version>
</dependency>
----
It will connect, simultaneously, to one or more MCP Servers over `STDIO` (in-process) and/or `SSE` (remote) transports.
The SSE connection uses the HttpClient-based transport implementation.
Every connection to an MCP Server creates a new MCP Client instance.
You can opt for either `SYNC` or `ASYNC` MCP Clients (Note: you cannot mix sync and async clients).
For more enterprise-ready deployment, it is recommended to use the WebFlux-based SSE connection using the `spring-ai-mcp-client-webflux-spring-boot-starter` starter.
=== WebFlux Starter
Similar to the core starter, it allows configuring one or more STDIO and SSE connections, but uses WebFlux-based SSE transport implementation.
[source,xml]
----
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-client-webflux-spring-boot-starter</artifactId>
<version>${spring-ai.version}</version>
</dependency>
----
== Configuration Properties
=== Common Properties
All common configuration properties are prefixed with `spring.ai.mcp.client`:
[cols="3,4,3"]
|===
|Property |Description |Default Value
|`enabled`
|Enable/disable the MCP client
|`true`
|`name`
|Name of the MCP client instance (used for compatibility checks)
|`spring-ai-mcp-client`
|`version`
|Version of the MCP client instance
|`1.0.0`
|`initialized`
|Whether to initialize clients on creation
|`true`
|`request-timeout`
|Timeout duration for MCP client requests
|`20s`
|`type`
|Client type (SYNC or ASYNC). You can not mix client type. All clients can be either sync or async
|`SYNC`
|`root-change-notification`
|Enable/disable root change notifications for all clients
|`true`
|===
=== SSE Transport Properties
Properties for Server-Sent Events (SSE) transport are prefixed with `spring.ai.mcp.client.sse`:
[cols="2,4"]
|===
|Property |Description
|`connections`
|Map of named SSE connection configurations
|`connections.[name].url`
|URL endpoint for SSE communication with the MCP server
|===
Example configuration:
[source,yaml]
----
spring:
ai:
mcp:
client:
sse:
connections:
server1:
url: http://localhost:8080
server2:
url: http://otherserver:8081
----
=== Stdio Transport Properties
Properties for Standard I/O transport are prefixed with `spring.ai.mcp.client.stdio`:
[cols="3,4,3"]
|===
|Property |Description |Default Value
|`servers-configuration`
|Resource containing the MCP servers configuration in JSON format
|-
|`connections`
|Map of named stdio connection configurations
|-
|`connections.[name].command`
|The command to execute for the MCP server
|-
|`connections.[name].args`
|List of command arguments
|-
|`connections.[name].env`
|Map of environment variables for the server process
|-
|===
Example configuration:
[source,yaml]
----
spring:
ai:
mcp:
client:
stdio:
root-change-notification: true
connections:
server1:
command: /path/to/server
args:
- --port=8080
- --mode=production
env:
API_KEY: your-api-key
DEBUG: "true"
----
Alternatively, you can configure stdio connections using an external JSON file using the link:https://modelcontextprotocol.io/quickstart/user[Claude Desctop format]:
[source,yaml]
----
spring:
ai:
mcp:
client:
stdio:
servers-configuration: classpath:mcp-servers.json
----
The Claude Destop format looks like this:
[source,json]
----
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"/Users/username/Desktop",
"/Users/username/Downloads"
]
}
}
}
----
Currently the Claude Destop supports only STDIO connection types.
== Features
=== Client Types
The starter supports two types of clients:
1. *Synchronous Client (SYNC)*
* Default client type
* Blocking operations
* Suitable for traditional request-response patterns
2. *Asynchronous Client (ASYNC)*
* Non-blocking operations
* Suitable for reactive applications
* Must be explicitly configured using `spring.ai.mcp.client.type=ASYNC`
=== Client Customization
The auto-configuration supports customization through:
* `McpSyncClientCustomizer` for synchronous clients
* `McpAsyncClientCustomizer` for asynchronous clients
== Usage Example
1. Add the appropriate starter dependency to your project.
2. Configure the client in `application.properties` or `application.yml`:
[source,yaml]
----
spring:
ai:
mcp:
client:
enabled: true
name: my-mcp-client
version: 1.0.0
request-timeout: 30s
type: SYNC # or ASYNC for reactive applications
sse:
connections:
server1:
url: http://localhost:8080
server2:
url: http://otherserver:8081
stdio:
root-change-notification: false
connections:
server1:
command: /path/to/server
args:
- --port=8080
- --mode=production
env:
API_KEY: your-api-key
DEBUG: "true"
----
3. The MCP client beans will be automatically configured and available for injection:
[source,java]
----
@Autowired
private List<McpSyncClient> mcpSyncClients; // For sync client
// OR
@Autowired
private List<McpAsyncClient> mcpAsyncClients; // For async client
----
== Transport Support
The auto-configuration supports multiple transport types:
* Standard I/O (Stdio)
* SSE HTTP
* SSE WebFlux (requires `spring-ai-starter-mcp-client-webflux`)
At least one transport must be available for the clients to be created.
== Integration with Spring AI
The starter automatically configures tool callbacks that integrate with Spring AI's tool execution framework, allowing MCP tools to be used as part of AI interactions.
== Lifecycle Management
The auto-configuration includes proper lifecycle management:
* Automatic initialization of clients (if enabled)
* Proper cleanup of resources when the application context is closed
* Management of multiple client instances
== Best Practices
1. Choose the appropriate client type based on your application's needs:
* Use SYNC client for traditional applications
* Use ASYNC client for reactive applications
2. Configure appropriate timeout values based on your use case:
[source,yaml]
----
spring:
ai:
mcp:
client:
request-timeout: 30s
----
3. Use customizers for advanced client configuration:
[source,java]
----
@Component
public class MyMcpClientCustomizer implements McpSyncClientCustomizer {
@Override
public void customize(String name, McpClient.SyncSpec clientSpec) {
// Custom configuration
}
}
----
== Troubleshooting
Common issues and solutions:
1. *Client Not Created*
* Verify that at least one transport is available
* Check if the client is enabled in configuration
* Ensure required dependencies are present
2. *Timeout Issues*
* Adjust the `request-timeout` property
* Check network connectivity
* Verify server response times
3. *Integration Issues*
* Ensure proper transport configuration
* Check client initialization status
== Additional Resources
* link:https://docs.spring.io/spring-ai/reference/[Spring AI Documentation]
* link:https://modelcontextprotocol.github.io/specification/[Model Context Protocol Specification]
* link:https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.developing-auto-configuration[Spring Boot Auto-configuration]

View File

@@ -0,0 +1,284 @@
# Spring AI MCP Client Boot Starter
The Spring AI MCP (Model Context Protocol) Client Boot Starter provides auto-configuration for MCP client functionality in Spring Boot applications. It supports both synchronous and asynchronous client implementations with various transport options.
The MCP Client Boot Starter provides:
- Automatic client initialization (if enabled)
- Support for multiple named transports
- Integration with Spring AI's tool execution framework
- Proper lifecycle management with automatic cleanup
- Customizable client creation through customizers
## Dependencies
### Core Starter
```xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId>
<version>${spring-ai.version}</version>
</dependency>
```
It will connect, simultaneously, to one or more MCP Servers over `STDIO` (in-process) and/or `SSE` (remote) transports.
The SSE connection uses the HttpClient-based transport implementation.
Every connection to an MCP Server creates a new MCP Client instance.
You can opt for either `SYNC` or `ASYNC` MCP Clients (Note: you cannot mix sync and async clients).
For more enterprise-ready deployment, it is recommended to use the WebFlux-based SSE connection using the `spring-ai-mcp-client-webflux-spring-boot-starter` starter.
### WebFlux Starter
Similar to the core starter, it allows configuring one or more STDIO and SSE connections, but uses WebFlux-based SSE transport implementation.
```xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-client-webflux-spring-boot-starter</artifactId>
<version>${spring-ai.version}</version>
</dependency>
```
## Configuration Properties
### Common Properties
All common configuration properties are prefixed with `spring.ai.mcp.client`:
| Property | Description | Default Value |
|----------|-------------|---------------|
| `enabled` | Enable/disable the MCP client | `true` |
| `name` | Name of the MCP client instance (used for compatibility checks) | `spring-ai-mcp-client` |
| `version` | Version of the MCP client instance | `1.0.0` |
| `initialized` | Whether to initialize clients on creation | `true` |
| `request-timeout` | Timeout duration for MCP client requests | `20s` |
| `type` | Client type (SYNC or ASYNC). You can not mix client type. All clients can be either sync or async | `SYNC` |
| `root-change-notification` | Enable/disable root change notifications for all clients| `true` |
### SSE Transport Properties
Properties for Server-Sent Events (SSE) transport are prefixed with `spring.ai.mcp.client.sse`:
| Property | Description |
|----------|-------------|
| `connections` | Map of named SSE connection configurations |
| `connections.[name].url` | URL endpoint for SSE communication with the MCP server |
Example configuration:
```yaml
spring:
ai:
mcp:
client:
sse:
connections:
server1:
url: http://localhost:8080
server2:
url: http://otherserver:8081
```
### Stdio Transport Properties
Properties for Standard I/O transport are prefixed with `spring.ai.mcp.client.stdio`:
| Property | Description | Default Value |
|----------|-------------|---------------|
| `servers-configuration` | Resource containing the MCP servers configuration in JSON format | - |
| `connections` | Map of named stdio connection configurations | - |
| `connections.[name].command` | The command to execute for the MCP server | - |
| `connections.[name].args` | List of command arguments | - |
| `connections.[name].env` | Map of environment variables for the server process | - |
Example configuration:
```yaml
spring:
ai:
mcp:
client:
stdio:
root-change-notification: true
connections:
server1:
command: /path/to/server
args:
- --port=8080
- --mode=production
env:
API_KEY: your-api-key
DEBUG: "true"
```
Alternatively, you can configure stdio connections using an external JSON file using the [Claude Desctop format](https://modelcontextprotocol.io/quickstart/user):
```yaml
spring:
ai:
mcp:
client:
stdio:
servers-configuration: classpath:mcp-servers.json
```
The Claude Destop format looks like this:
```json
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"/Users/username/Desktop",
"/Users/username/Downloads"
]
}
}
}
```
Currently the Claude Destop supports only STDIO connection types.
## Features
### Client Types
The starter supports two types of clients:
1. **Synchronous Client (SYNC)**
- Default client type
- Blocking operations
- Suitable for traditional request-response patterns
2. **Asynchronous Client (ASYNC)**
- Non-blocking operations
- Suitable for reactive applications
- Must be explicitly configured using `spring.ai.mcp.client.type=ASYNC`
### Client Customization
The auto-configuration supports customization through:
- `McpSyncClientCustomizer` for synchronous clients
- `McpAsyncClientCustomizer` for asynchronous clients
## Usage Example
1. Add the appropriate starter dependency to your project.
2. Configure the client in `application.properties` or `application.yml`:
```yaml
spring:
ai:
mcp:
client:
enabled: true
name: my-mcp-client
version: 1.0.0
request-timeout: 30s
type: SYNC # or ASYNC for reactive applications
sse:
connections:
server1:
url: http://localhost:8080
server2:
url: http://otherserver:8081
stdio:
root-change-notification: false
connections:
server1:
command: /path/to/server
args:
- --port=8080
- --mode=production
env:
API_KEY: your-api-key
DEBUG: "true"
```
3. The MCP client beans will be automatically configured and available for injection:
```java
@Autowired
private List<McpSyncClient> mcpSyncClients; // For sync client
// OR
@Autowired
private List<McpAsyncClient> mcpAsyncClients; // For async client
```
## Transport Support
The auto-configuration supports multiple transport types:
- Standard I/O (Stdio)
- SSE HTTP
- SSE WebFlux (requires `spring-ai-starter-mcp-client-webflux`)
At least one transport must be available for the clients to be created.
## Integration with Spring AI
The starter automatically configures tool callbacks that integrate with Spring AI's tool execution framework, allowing MCP tools to be used as part of AI interactions.
## Lifecycle Management
The auto-configuration includes proper lifecycle management:
- Automatic initialization of clients (if enabled)
- Proper cleanup of resources when the application context is closed
- Management of multiple client instances
## Best Practices
1. Choose the appropriate client type based on your application's needs:
- Use SYNC client for traditional applications
- Use ASYNC client for reactive applications
2. Configure appropriate timeout values based on your use case:
```yaml
spring:
ai:
mcp:
client:
request-timeout: 30s
```
3. Use customizers for advanced client configuration:
```java
@Component
public class MyMcpClientCustomizer implements McpSyncClientCustomizer {
@Override
public void customize(String name, McpClient.SyncSpec clientSpec) {
// Custom configuration
}
}
```
## Troubleshooting
Common issues and solutions:
1. **Client Not Created**
- Verify that at least one transport is available
- Check if the client is enabled in configuration
- Ensure required dependencies are present
2. **Timeout Issues**
- Adjust the `request-timeout` property
- Check network connectivity
- Verify server response times
3. **Integration Issues**
- Ensure proper transport configuration
- Check client initialization status
## Additional Resources
- [Spring AI Documentation](https://docs.spring.io/spring-ai/reference/)
- [Model Context Protocol Specification](https://modelcontextprotocol.github.io/specification/)
- [Spring Boot Auto-configuration](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.developing-auto-configuration)

168
mcp-helpers.adoc Normal file
View File

@@ -0,0 +1,168 @@
= Spring AI MCP Common Utilities
:page-title: Spring AI MCP Common Utilities
This document provides reference documentation for the common utilities in Spring AI's Model Context Protocol (MCP) integration.
== Overview
The MCP common utilities provide foundational support for integrating Model Context Protocol with Spring AI applications. These utilities enable seamless communication between Spring AI's tool system and MCP servers, supporting both synchronous and asynchronous operations.
== Core Components
=== Tool Callbacks
==== AsyncMcpToolCallback
Adapts MCP tools to Spring AI's tool interface with asynchronous execution support.
[source,java]
----
McpAsyncClient mcpClient = // obtain MCP client
Tool mcpTool = // obtain MCP tool definition
ToolCallback callback = new AsyncMcpToolCallback(mcpClient, mcpTool);
// Use the tool through Spring AI's interfaces
ToolDefinition definition = callback.getToolDefinition();
String result = callback.call("{\"param\": \"value\"}");
----
==== SyncMcpToolCallback
Similar to AsyncMcpToolCallback but provides synchronous execution semantics.
[source,java]
----
McpSyncClient mcpClient = // obtain MCP client
Tool mcpTool = // obtain MCP tool definition
ToolCallback callback = new SyncMcpToolCallback(mcpClient, mcpTool);
// Use the tool through Spring AI's interfaces
ToolDefinition definition = callback.getToolDefinition();
String result = callback.call("{\"param\": \"value\"}");
----
=== Tool Callback Providers
==== AsyncMcpToolCallbackProvider
Discovers and provides MCP tools asynchronously.
[source,java]
----
McpAsyncClient mcpClient = // obtain MCP client
ToolCallbackProvider provider = new AsyncMcpToolCallbackProvider(mcpClient);
// Get all available tools
ToolCallback[] tools = provider.getToolCallbacks();
----
The provider also offers a utility method for working with multiple clients:
[source,java]
----
List<McpAsyncClient> clients = // obtain list of clients
Flux<ToolCallback> callbacks = AsyncMcpToolCallbackProvider.asyncToolCallbacks(clients);
----
==== SyncMcpToolCallbackProvider
Similar to AsyncMcpToolCallbackProvider but works with synchronous clients.
[source,java]
----
McpSyncClient mcpClient = // obtain MCP client
ToolCallbackProvider provider = new SyncMcpToolCallbackProvider(mcpClient);
// Get all available tools
ToolCallback[] tools = provider.getToolCallbacks();
----
For multiple clients:
[source,java]
----
List<McpSyncClient> clients = // obtain list of clients
List<ToolCallback> callbacks = SyncMcpToolCallbackProvider.syncToolCallbacks(clients);
----
=== Client Customization
==== McpAsyncClientCustomizer
Allows customization of asynchronous MCP client configurations.
[source,java]
----
@Component
public class CustomMcpAsyncClientCustomizer implements McpAsyncClientCustomizer {
@Override
public void customize(String name, McpClient.AsyncSpec spec) {
// Customize the async client configuration
spec.requestTimeout(Duration.ofSeconds(30));
}
}
----
==== McpSyncClientCustomizer
Similar to McpAsyncClientCustomizer but for synchronous clients.
[source,java]
----
@Component
public class CustomMcpSyncClientCustomizer implements McpSyncClientCustomizer {
@Override
public void customize(String name, McpClient.SyncSpec spec) {
// Customize the sync client configuration
spec.requestTimeout(Duration.ofSeconds(30));
}
}
----
=== Utility Classes
==== McpToolUtils
Provides helper methods for working with MCP tools in a Spring AI environment.
Converting Spring AI tool callbacks to MCP tool registrations:
[source,java]
----
// For synchronous tools
List<ToolCallback> toolCallbacks = // obtain tool callbacks
List<SyncToolRegistration> syncRegs = McpToolUtils.toSyncToolRegistration(toolCallbacks);
// For asynchronous tools
List<AsyncToolRegistration> asyncRegs = McpToolUtils.toAsyncToolRegistration(toolCallbacks);
----
Getting tool callbacks from MCP clients:
[source,java]
----
// From sync clients
List<McpSyncClient> syncClients = // obtain sync clients
List<ToolCallback> syncCallbacks = McpToolUtils.getToolCallbacksFromSyncClients(syncClients);
// From async clients
List<McpAsyncClient> asyncClients = // obtain async clients
List<ToolCallback> asyncCallbacks = McpToolUtils.getToolCallbacksFromAsyncClients(asyncClients);
----
=== Native Image Support
==== McpHints
Provides GraalVM native image hints for MCP schema classes.
[source,java]
----
@Configuration
@ImportRuntimeHints(McpHints.class)
public class MyConfiguration {
// Configuration code
}
----
This class automatically registers all necessary reflection hints for MCP schema classes when building native images.

156
mcp-helpers.md Normal file
View File

@@ -0,0 +1,156 @@
# Spring AI MCP Common Utilities
This document provides reference documentation for the common utilities in Spring AI's Model Context Protocol (MCP) integration.
## Overview
The MCP common utilities provide foundational support for integrating Model Context Protocol with Spring AI applications. These utilities enable seamless communication between Spring AI's tool system and MCP servers, supporting both synchronous and asynchronous operations.
## Core Components
### Tool Callbacks
#### AsyncMcpToolCallback
Adapts MCP tools to Spring AI's tool interface with asynchronous execution support.
```java
McpAsyncClient mcpClient = // obtain MCP client
Tool mcpTool = // obtain MCP tool definition
ToolCallback callback = new AsyncMcpToolCallback(mcpClient, mcpTool);
// Use the tool through Spring AI's interfaces
ToolDefinition definition = callback.getToolDefinition();
String result = callback.call("{\"param\": \"value\"}");
```
#### SyncMcpToolCallback
Similar to AsyncMcpToolCallback but provides synchronous execution semantics.
```java
McpSyncClient mcpClient = // obtain MCP client
Tool mcpTool = // obtain MCP tool definition
ToolCallback callback = new SyncMcpToolCallback(mcpClient, mcpTool);
// Use the tool through Spring AI's interfaces
ToolDefinition definition = callback.getToolDefinition();
String result = callback.call("{\"param\": \"value\"}");
```
### Tool Callback Providers
#### AsyncMcpToolCallbackProvider
Discovers and provides MCP tools asynchronously.
```java
McpAsyncClient mcpClient = // obtain MCP client
ToolCallbackProvider provider = new AsyncMcpToolCallbackProvider(mcpClient);
// Get all available tools
ToolCallback[] tools = provider.getToolCallbacks();
```
The provider also offers a utility method for working with multiple clients:
```java
List<McpAsyncClient> clients = // obtain list of clients
Flux<ToolCallback> callbacks = AsyncMcpToolCallbackProvider.asyncToolCallbacks(clients);
```
#### SyncMcpToolCallbackProvider
Similar to AsyncMcpToolCallbackProvider but works with synchronous clients.
```java
McpSyncClient mcpClient = // obtain MCP client
ToolCallbackProvider provider = new SyncMcpToolCallbackProvider(mcpClient);
// Get all available tools
ToolCallback[] tools = provider.getToolCallbacks();
```
For multiple clients:
```java
List<McpSyncClient> clients = // obtain list of clients
List<ToolCallback> callbacks = SyncMcpToolCallbackProvider.syncToolCallbacks(clients);
```
### Client Customization
#### McpAsyncClientCustomizer
Allows customization of asynchronous MCP client configurations.
```java
@Component
public class CustomMcpAsyncClientCustomizer implements McpAsyncClientCustomizer {
@Override
public void customize(String name, McpClient.AsyncSpec spec) {
// Customize the async client configuration
spec.requestTimeout(Duration.ofSeconds(30));
}
}
```
#### McpSyncClientCustomizer
Similar to McpAsyncClientCustomizer but for synchronous clients.
```java
@Component
public class CustomMcpSyncClientCustomizer implements McpSyncClientCustomizer {
@Override
public void customize(String name, McpClient.SyncSpec spec) {
// Customize the sync client configuration
spec.requestTimeout(Duration.ofSeconds(30));
}
}
```
### Utility Classes
#### McpToolUtils
Provides helper methods for working with MCP tools in a Spring AI environment.
Converting Spring AI tool callbacks to MCP tool registrations:
```java
// For synchronous tools
List<ToolCallback> toolCallbacks = // obtain tool callbacks
List<SyncToolRegistration> syncRegs = McpToolUtils.toSyncToolRegistration(toolCallbacks);
// For asynchronous tools
List<AsyncToolRegistration> asyncRegs = McpToolUtils.toAsyncToolRegistration(toolCallbacks);
```
Getting tool callbacks from MCP clients:
```java
// From sync clients
List<McpSyncClient> syncClients = // obtain sync clients
List<ToolCallback> syncCallbacks = McpToolUtils.getToolCallbacksFromSyncClients(syncClients);
// From async clients
List<McpAsyncClient> asyncClients = // obtain async clients
List<ToolCallback> asyncCallbacks = McpToolUtils.getToolCallbacksFromAsyncClients(asyncClients);
```
### Native Image Support
#### McpHints
Provides GraalVM native image hints for MCP schema classes.
```java
@Configuration
@ImportRuntimeHints(McpHints.class)
public class MyConfiguration {
// Configuration code
}
```
This class automatically registers all necessary reflection hints for MCP schema classes when building native images.

View File

@@ -0,0 +1,310 @@
= Spring AI MCP Server Boot Starter
The Spring AI MCP (Model Context Protocol) Server Boot Starter provides auto-configuration for setting up an MCP server in Spring Boot applications. It enables seamless integration of MCP server capabilities with Spring Boot's auto-configuration system.
== Overview
The MCP Server Boot Starter offers:
* Automatic configuration of MCP server components
* Support for both synchronous and asynchronous operation modes
* Multiple transport layer options
* Flexible tool, resource, and prompt registration
* Change notification capabilities
== Starter Dependencies
Choose one of the following starters based on your transport requirements:
=== 1. Standard MCP Server
Full MCP Server features support with `STDIO` server transport.
[source,xml]
----
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId>
<version>${spring-ai.version}</version>
</dependency>
----
=== 2. WebMVC Server
Full MCP Server features support with `SSE` (Server-Sent Events) server transport based on Spring MVC and an optional `STDIO` transport.
[source,xml]
----
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-webmvc-spring-boot-starter</artifactId>
<version>${spring-ai.version}</version>
</dependency>
----
This starter includes:
* spring-boot-starter-web
* mcp-spring-webmvc
(optionally allows `stdio` transport deployment)
=== 3. WebFlux Server
Full MCP Server features support with `SSE` (Server-Sent Events) server transport based on Spring WebFlux and an optional `STDIO` transport.
[source,xml]
----
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-webflux-spring-boot-starter</artifactId>
<version>${spring-ai.version}</version>
</dependency>
----
This starter includes:
* spring-boot-starter-webflux
* mcp-spring-webflux
(optionally allows `stdio` transport deployment)
== Configuration Properties
All properties are prefixed with `spring.ai.mcp.server`:
[options="header"]
|===
|Property |Description |Default
|`enabled` |Enable/disable the MCP server |`true`
|`stdio` |Enable/disable stdio transport |`false`
|`name` |Server name for identification |`mcp-server`
|`version` |Server version |`1.0.0`
|`type` |Server type (SYNC/ASYNC) |`SYNC`
|`resource-change-notification` |Enable resource change notifications |`true`
|`tool-change-notification` |Enable tool change notifications |`true`
|`prompt-change-notification` |Enable prompt change notifications |`true`
|`sse-message-endpoint` |SSE endpoint path for web transport |`/mcp/message`
|===
== Server Types
=== Synchronous Server
* Default server type
* Uses `McpSyncServer`
* Suitable for straightforward request-response patterns
* Configure with `spring.ai.mcp.server.type=SYNC`
* Automatically configures synchronous tool registrations
=== Asynchronous Server
* Uses `McpAsyncServer`
* Suitable for non-blocking operations
* Configure with `spring.ai.mcp.server.type=ASYNC`
* Automatically configures asynchronous tool registrations with Project Reactor support
== Transport Options
The MCP Server supports three transport mechanisms, each with its dedicated starter:
=== 1. Standard Input/Output (STDIO)
* Use `spring-ai-mcp-server-spring-boot-starter`
* Default transport when using the standard starter
* Suitable for command-line tools and testing
* No additional web dependencies required
=== 2. Spring MVC (Server-Sent Events)
* Use `spring-ai-mcp-server-webmvc-spring-boot-starter`
* Provides HTTP-based transport using Spring MVC
* Uses `WebMvcSseServerTransport`
* Automatically configures SSE endpoints
* Ideal for traditional web applications
* Optionally you can deploy `STDIO` transport by setting the `spring.ai.mcp.server.stdio=true` property.
=== 3. Spring WebFlux (Reactive SSE)
* Use `spring-ai-mcp-server-webflux-spring-boot-starter`
* Provides reactive transport using Spring WebFlux
* Uses `WebFluxSseServerTransport`
* Automatically configures reactive SSE endpoints
* Ideal for reactive applications with non-blocking requirements
* Optionally you can deploy `STDIO` transport by setting the `spring.ai.mcp.server.stdio=true` property.
== Features and Capabilities
=== 1. Tools Registration
* Support for both sync and async tool execution
* Automatic tool registration through Spring beans
* Change notification support
* Tools are automatically converted to sync/async registrations based on server type
=== 2. Resource Management
* Static and dynamic resource registration
* Optional change notifications
* Support for resource templates
* Automatic conversion between sync/async resource registrations
=== 3. Prompt Templates
* Configurable prompt registration
* Change notification support
* Template versioning
* Automatic conversion between sync/async prompt registrations
=== 4. Root Change Consumers
* Support for monitoring root changes
* Automatic conversion to async consumers for reactive applications
* Optional registration through Spring beans
== Usage Examples
=== 1. Standard STDIO Server Configuration
[source,yaml]
----
# Using spring-ai-mcp-server-spring-boot-starter
spring:
ai:
mcp:
server:
name: stdio-mcp-server
version: 1.0.0
type: SYNC
stdio: true
----
=== 2. WebMVC Server Configuration
[source,yaml]
----
# Using spring-ai-mcp-server-webmvc-spring-boot-starter
spring:
ai:
mcp:
server:
name: webmvc-mcp-server
version: 1.0.0
type: SYNC
stdio: false
sse-message-endpoint: /mcp/messages
----
=== 3. WebFlux Server Configuration
[source,yaml]
----
# Using spring-ai-mcp-server-webflux-spring-boot-starter
spring:
ai:
mcp:
server:
name: webflux-mcp-server
version: 1.0.0
type: ASYNC # Recommended for reactive applications
stdio: false
sse-message-endpoint: /mcp/messages
----
=== Tool Registration Examples
==== 1. Synchronous Tool (for SYNC server type)
[source,java]
----
@Configuration
public class SyncToolConfig {
@Bean
public ToolCallback syncTool() {
return new ToolCallback() {
@Override
public String getName() {
return "syncTool";
}
@Override
public Object execute(Map<String, Object> params) {
// Synchronous implementation
return result;
}
};
}
}
----
==== 2. Asynchronous Tool (for ASYNC server type)
[source,java]
----
@Configuration
public class AsyncToolConfig {
@Bean
public ToolCallback asyncTool() {
return new ToolCallback() {
@Override
public String getName() {
return "asyncTool";
}
@Override
public Object execute(Map<String, Object> params) {
// Asynchronous implementation using Project Reactor
return Mono.just("result")
.map(r -> processResult(r))
.subscribeOn(Schedulers.boundedElastic());
}
};
}
}
----
== Auto-configuration Classes
The starter provides several auto-configuration classes:
1. `MpcServerAutoConfiguration`: Core server configuration
* Configures basic server components
* Handles tool, resource, and prompt registrations
* Manages server capabilities and change notifications
* Provides both sync and async server implementations
2. `MpcWebMvcServerAutoConfiguration`: Spring MVC transport
* Configures SSE endpoints for web transport
* Integrates with Spring MVC infrastructure
3. `MpcWebFluxServerAutoConfiguration`: Spring WebFlux transport
* Configures reactive SSE endpoints
* Integrates with Spring WebFlux infrastructure
These classes are conditionally enabled based on the classpath and configuration properties.
== Conditional Configuration
The auto-configuration is activated when:
* Required MCP classes are on the classpath
* `spring.ai.mcp.server.enabled=true` (default)
* Appropriate transport dependencies are available
== Best Practices
1. Choose the appropriate server type based on your use case:
* Use SYNC for simple request-response patterns
* Use ASYNC for non-blocking operations and reactive applications
2. Select the transport mechanism based on your application type:
* Use STDIO for command-line tools and testing
* Use WebMvc for traditional web applications
* Use WebFlux for reactive applications
3. Configure change notifications based on your needs:
* Enable only the notifications you need
* Consider performance implications of notifications
* Use appropriate consumers for root changes
4. Properly version your server and tools:
* Use semantic versioning
* Document version changes
* Handle version compatibility
5. Tool Implementation:
* Implement tools as Spring beans for automatic registration
* Return Mono/Flux for async operations in ASYNC mode
* Use appropriate error handling strategies
== Additional Resources
* link:https://docs.spring.io/spring-ai/reference/[Spring AI Documentation]
* link:https://modelcontextprotocol.github.io/specification/[Model Context Protocol Specification]
* link:https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.developing-auto-configuration[Spring Boot Auto-configuration]

View File

@@ -0,0 +1,294 @@
# Spring AI MCP Server Boot Starter
The Spring AI MCP (Model Context Protocol) Server Boot Starter provides auto-configuration for setting up an MCP server in Spring Boot applications. It enables seamless integration of MCP server capabilities with Spring Boot's auto-configuration system.
## Overview
The MCP Server Boot Starter offers:
- Automatic configuration of MCP server components
- Support for both synchronous and asynchronous operation modes
- Multiple transport layer options
- Flexible tool, resource, and prompt registration
- Change notification capabilities
## Starter Dependencies
Choose one of the following starters based on your transport requirements:
### 1. Standard MCP Server
Full MCP Server features support with `STDIO` server transport.
```xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId>
<version>${spring-ai.version}</version>
</dependency>
```
### 2. WebMVC Server
Full MCP Server features support with `SSE` (Server-Sent Events) server transport based on Spring MVC and an optional `STDIO` transport.
```xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-webmvc-spring-boot-starter</artifactId>
<version>${spring-ai.version}</version>
</dependency>
```
This starter includes:
- spring-boot-starter-web
- mcp-spring-webmvc
(optionally allows `stdio` transport deployment)
### 3. WebFlux Server
Full MCP Server features support with `SSE` (Server-Sent Events) server transport based on Spring WebFlux and an optional `STDIO` transport.
```xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-webflux-spring-boot-starter</artifactId>
<version>${spring-ai.version}</version>
</dependency>
```
This starter includes:
- spring-boot-starter-webflux
- mcp-spring-webflux
(optionally allows `stdio` transport deployment)
## Configuration Properties
All properties are prefixed with `spring.ai.mcp.server`:
| Property | Description | Default |
|----------|-------------|---------|
| `enabled` | Enable/disable the MCP server | `true` |
| `stdio` | Enable/disable stdio transport | `false` |
| `name` | Server name for identification | `mcp-server` |
| `version` | Server version | `1.0.0` |
| `type` | Server type (SYNC/ASYNC) | `SYNC` |
| `resource-change-notification` | Enable resource change notifications | `true` |
| `tool-change-notification` | Enable tool change notifications | `true` |
| `prompt-change-notification` | Enable prompt change notifications | `true` |
| `sse-message-endpoint` | SSE endpoint path for web transport | `/mcp/message` |
## Server Types
### Synchronous Server
- Default server type
- Uses `McpSyncServer`
- Suitable for straightforward request-response patterns
- Configure with `spring.ai.mcp.server.type=SYNC`
- Automatically configures synchronous tool registrations
### Asynchronous Server
- Uses `McpAsyncServer`
- Suitable for non-blocking operations
- Configure with `spring.ai.mcp.server.type=ASYNC`
- Automatically configures asynchronous tool registrations with Project Reactor support
## Transport Options
The MCP Server supports three transport mechanisms, each with its dedicated starter:
### 1. Standard Input/Output (STDIO)
- Use `spring-ai-mcp-server-spring-boot-starter`
- Default transport when using the standard starter
- Suitable for command-line tools and testing
- No additional web dependencies required
### 2. Spring MVC (Server-Sent Events)
- Use `spring-ai-mcp-server-webmvc-spring-boot-starter`
- Provides HTTP-based transport using Spring MVC
- Uses `WebMvcSseServerTransport`
- Automatically configures SSE endpoints
- Ideal for traditional web applications
- Optionally you can deploy `STDIO` transport by setting the `spring.ai.mcp.server.stdio=true` property.
### 3. Spring WebFlux (Reactive SSE)
- Use `spring-ai-mcp-server-webflux-spring-boot-starter`
- Provides reactive transport using Spring WebFlux
- Uses `WebFluxSseServerTransport`
- Automatically configures reactive SSE endpoints
- Ideal for reactive applications with non-blocking requirements
- Optionally you can deploy `STDIO` transport by setting the `spring.ai.mcp.server.stdio=true` property.
## Features and Capabilities
### 1. Tools Registration
- Support for both sync and async tool execution
- Automatic tool registration through Spring beans
- Change notification support
- Tools are automatically converted to sync/async registrations based on server type
### 2. Resource Management
- Static and dynamic resource registration
- Optional change notifications
- Support for resource templates
- Automatic conversion between sync/async resource registrations
### 3. Prompt Templates
- Configurable prompt registration
- Change notification support
- Template versioning
- Automatic conversion between sync/async prompt registrations
### 4. Root Change Consumers
- Support for monitoring root changes
- Automatic conversion to async consumers for reactive applications
- Optional registration through Spring beans
## Usage Examples
### 1. Standard STDIO Server Configuration
```yaml
# Using spring-ai-mcp-server-spring-boot-starter
spring:
ai:
mcp:
server:
name: stdio-mcp-server
version: 1.0.0
type: SYNC
stdio: true
```
### 2. WebMVC Server Configuration
```yaml
# Using spring-ai-mcp-server-webmvc-spring-boot-starter
spring:
ai:
mcp:
server:
name: webmvc-mcp-server
version: 1.0.0
type: SYNC
stdio: false
sse-message-endpoint: /mcp/messages
```
### 3. WebFlux Server Configuration
```yaml
# Using spring-ai-mcp-server-webflux-spring-boot-starter
spring:
ai:
mcp:
server:
name: webflux-mcp-server
version: 1.0.0
type: ASYNC # Recommended for reactive applications
stdio: false
sse-message-endpoint: /mcp/messages
```
### Tool Registration Examples
#### 1. Synchronous Tool (for SYNC server type)
```java
@Configuration
public class SyncToolConfig {
@Bean
public ToolCallback syncTool() {
return new ToolCallback() {
@Override
public String getName() {
return "syncTool";
}
@Override
public Object execute(Map<String, Object> params) {
// Synchronous implementation
return result;
}
};
}
}
```
#### 2. Asynchronous Tool (for ASYNC server type)
```java
@Configuration
public class AsyncToolConfig {
@Bean
public ToolCallback asyncTool() {
return new ToolCallback() {
@Override
public String getName() {
return "asyncTool";
}
@Override
public Object execute(Map<String, Object> params) {
// Asynchronous implementation using Project Reactor
return Mono.just("result")
.map(r -> processResult(r))
.subscribeOn(Schedulers.boundedElastic());
}
};
}
}
```
## Auto-configuration Classes
The starter provides several auto-configuration classes:
1. `MpcServerAutoConfiguration`: Core server configuration
- Configures basic server components
- Handles tool, resource, and prompt registrations
- Manages server capabilities and change notifications
- Provides both sync and async server implementations
2. `MpcWebMvcServerAutoConfiguration`: Spring MVC transport
- Configures SSE endpoints for web transport
- Integrates with Spring MVC infrastructure
3. `MpcWebFluxServerAutoConfiguration`: Spring WebFlux transport
- Configures reactive SSE endpoints
- Integrates with Spring WebFlux infrastructure
These classes are conditionally enabled based on the classpath and configuration properties.
## Conditional Configuration
The auto-configuration is activated when:
- Required MCP classes are on the classpath
- `spring.ai.mcp.server.enabled=true` (default)
- Appropriate transport dependencies are available
## Best Practices
1. Choose the appropriate server type based on your use case:
- Use SYNC for simple request-response patterns
- Use ASYNC for non-blocking operations and reactive applications
2. Select the transport mechanism based on your application type:
- Use STDIO for command-line tools and testing
- Use WebMvc for traditional web applications
- Use WebFlux for reactive applications
3. Configure change notifications based on your needs:
- Enable only the notifications you need
- Consider performance implications of notifications
- Use appropriate consumers for root changes
4. Properly version your server and tools:
- Use semantic versioning
- Document version changes
- Handle version compatibility
5. Tool Implementation:
- Implement tools as Spring beans for automatic registration
- Return Mono/Flux for async operations in ASYNC mode
- Use appropriate error handling strategies
## Additional Resources
- [Spring AI Documentation](https://docs.spring.io/spring-ai/reference/)
- [Model Context Protocol Specification](https://modelcontextprotocol.github.io/specification/)
- [Spring Boot Auto-configuration](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.developing-auto-configuration)

View File

@@ -0,0 +1,113 @@
/*
* 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.mcp;
import java.util.Map;
import io.modelcontextprotocol.client.McpAsyncClient;
import io.modelcontextprotocol.spec.McpSchema.CallToolRequest;
import io.modelcontextprotocol.spec.McpSchema.Tool;
import org.springframework.ai.model.ModelOptionsUtils;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.definition.ToolDefinition;
/**
* Implementation of {@link ToolCallback} that adapts MCP tools to Spring AI's tool
* interface with asynchronous execution support.
* <p>
* This class acts as a bridge between the Model Context Protocol (MCP) and Spring AI's
* tool system, allowing MCP tools to be used seamlessly within Spring AI applications.
* It:
* <ul>
* <li>Converts MCP tool definitions to Spring AI tool definitions</li>
* <li>Handles the asynchronous execution of tool calls through the MCP client</li>
* <li>Manages JSON serialization/deserialization of tool inputs and outputs</li>
* </ul>
* <p>
* Example usage: <pre>{@code
* McpAsyncClient mcpClient = // obtain MCP client
* Tool mcpTool = // obtain MCP tool definition
* ToolCallback callback = new AsyncMcpToolCallback(mcpClient, mcpTool);
*
* // Use the tool through Spring AI's interfaces
* ToolDefinition definition = callback.getToolDefinition();
* String result = callback.call("{\"param\": \"value\"}");
* }</pre>
*
* @author Christian Tzolov
* @see ToolCallback
* @see McpAsyncClient
* @see Tool
*/
public class AsyncMcpToolCallback implements ToolCallback {
private final McpAsyncClient asyncMcpClient;
private final Tool tool;
/**
* Creates a new {@code AsyncMcpToolCallback} instance.
* @param mcpClient the MCP client to use for tool execution
* @param tool the MCP tool definition to adapt
*/
public AsyncMcpToolCallback(McpAsyncClient mcpClient, Tool tool) {
this.asyncMcpClient = mcpClient;
this.tool = tool;
}
/**
* Returns a Spring AI tool definition adapted from the MCP tool.
* <p>
* The tool definition includes:
* <ul>
* <li>The tool's name from the MCP definition</li>
* <li>The tool's description from the MCP definition</li>
* <li>The input schema converted to JSON format</li>
* </ul>
* @return the Spring AI tool definition
*/
@Override
public ToolDefinition getToolDefinition() {
return ToolDefinition.builder()
.name(this.tool.name())
.description(this.tool.description())
.inputSchema(ModelOptionsUtils.toJsonString(this.tool.inputSchema()))
.build();
}
/**
* Executes the tool with the provided input asynchronously.
* <p>
* This method:
* <ol>
* <li>Converts the JSON input string to a map of arguments</li>
* <li>Calls the tool through the MCP client asynchronously</li>
* <li>Converts the tool's response content to a JSON string</li>
* </ol>
* @param functionInput the tool input as a JSON string
* @return the tool's response as a JSON string
*/
@Override
public String call(String functionInput) {
Map<String, Object> arguments = ModelOptionsUtils.jsonToMap(functionInput);
return this.asyncMcpClient.callTool(new CallToolRequest(this.getToolDefinition().name(), arguments))
.map(response -> ModelOptionsUtils.toJsonString(response.content()))
.block();
}
}

View File

@@ -0,0 +1,132 @@
/*
* 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.mcp;
import java.util.List;
import io.modelcontextprotocol.client.McpAsyncClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.util.ToolUtils;
import org.springframework.util.CollectionUtils;
/**
* Implementation of {@link ToolCallbackProvider} that discovers and provides MCP tools
* asynchronously.
* <p>
* This class acts as a tool provider for Spring AI, automatically discovering tools from
* an MCP server and making them available as Spring AI tools. It:
* <ul>
* <li>Connects to an MCP server through an async client</li>
* <li>Lists and retrieves available tools from the server</li>
* <li>Creates {@link AsyncMcpToolCallback} instances for each discovered tool</li>
* <li>Validates tool names to prevent duplicates</li>
* </ul>
* <p>
* Example usage: <pre>{@code
* McpAsyncClient mcpClient = // obtain MCP client
* ToolCallbackProvider provider = new AsyncMcpToolCallbackProvider(mcpClient);
*
* // Get all available tools
* ToolCallback[] tools = provider.getToolCallbacks();
* }</pre>
*
* @author Christian Tzolov
* @since 1.0.0
* @see ToolCallbackProvider
* @see AsyncMcpToolCallback
* @see McpAsyncClient
*/
public class AsyncMcpToolCallbackProvider implements ToolCallbackProvider {
private final McpAsyncClient mcpClient;
/**
* Creates a new {@code AsyncMcpToolCallbackProvider} instance.
* @param mcpClient the MCP client to use for discovering tools
*/
public AsyncMcpToolCallbackProvider(McpAsyncClient mcpClient) {
this.mcpClient = mcpClient;
}
/**
* Discovers and returns all available tools from the MCP server asynchronously.
* <p>
* This method:
* <ol>
* <li>Retrieves the list of tools from the MCP server</li>
* <li>Creates a {@link AsyncMcpToolCallback} for each tool</li>
* <li>Validates that there are no duplicate tool names</li>
* </ol>
* @return an array of tool callbacks, one for each discovered tool
* @throws IllegalStateException if duplicate tool names are found
*/
@Override
public ToolCallback[] getToolCallbacks() {
var toolCallbacks = this.mcpClient.listTools()
.map(response -> response.tools()
.stream()
.map(tool -> new AsyncMcpToolCallback(this.mcpClient, tool))
.toArray(ToolCallback[]::new))
.block();
validateToolCallbacks(toolCallbacks);
return toolCallbacks;
}
/**
* Validates that there are no duplicate tool names in the provided callbacks.
* <p>
* This method ensures that each tool has a unique name, which is required for proper
* tool resolution and execution.
* @param toolCallbacks the tool callbacks to validate
* @throws IllegalStateException if duplicate tool names are found
*/
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)));
}
}
/**
* Creates a reactive stream of tool callbacks from multiple MCP clients.
* <p>
* This utility method:
* <ol>
* <li>Takes a list of MCP clients</li>
* <li>Creates a provider for each client</li>
* <li>Retrieves and flattens all tool callbacks into a single stream</li>
* </ol>
* @param mcpClients the list of MCP clients to create callbacks from
* @return a Flux of tool callbacks from all provided clients
*/
public static Flux<ToolCallback> asyncToolCallbacks(List<McpAsyncClient> mcpClients) {
if (CollectionUtils.isEmpty(mcpClients)) {
return Flux.empty();
}
return Flux.fromIterable(mcpClients)
.flatMap(mcpClient -> Mono.just(new AsyncMcpToolCallbackProvider(mcpClient).getToolCallbacks()))
.flatMap(callbacks -> Flux.fromArray(callbacks));
}
}

View File

@@ -1,28 +0,0 @@
/*
* 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.mcp;
import io.modelcontextprotocol.client.McpClient;
/**
* @author Christian Tzolov
* @since 1.0.0
*/
public interface McpSyncClientCustomizer {
void customize(String name, McpClient.SyncSpec sync);
}

View File

@@ -17,6 +17,7 @@ package org.springframework.ai.mcp;
import java.util.List;
import io.modelcontextprotocol.client.McpAsyncClient;
import io.modelcontextprotocol.client.McpSyncClient;
import io.modelcontextprotocol.server.McpServerFeatures;
import io.modelcontextprotocol.server.McpServerFeatures.AsyncToolRegistration;
@@ -180,11 +181,32 @@ public final class McpToolUtils {
.subscribeOn(Schedulers.boundedElastic()));
}
public static List<ToolCallback> getToolCallbacks(McpSyncClient... mcpClients) {
return getToolCallbacks(List.of(mcpClients));
/**
* Convenience method to get tool callbacks from multiple synchronous MCP clients.
* <p>
* This is a varargs wrapper around {@link #getToolCallbacksFromSyncClients(List)} for
* easier usage when working with individual clients.
* @param mcpClients the synchronous MCP clients to get callbacks from
* @return a list of tool callbacks from all provided clients
* @see #getToolCallbacksFromSyncClients(List)
*/
public static List<ToolCallback> getToolCallbacksFromSyncClients(McpSyncClient... mcpClients) {
return getToolCallbacksFromSyncClients(List.of(mcpClients));
}
public static List<ToolCallback> getToolCallbacks(List<McpSyncClient> mcpClients) {
/**
* Gets tool callbacks from a list of synchronous MCP clients.
* <p>
* This method:
* <ol>
* <li>Takes a list of synchronous MCP clients</li>
* <li>Creates a provider for each client</li>
* <li>Retrieves and combines all tool callbacks into a single list</li>
* </ol>
* @param mcpClients the list of synchronous MCP clients to get callbacks from
* @return a list of tool callbacks from all provided clients
*/
public static List<ToolCallback> getToolCallbacksFromSyncClients(List<McpSyncClient> mcpClients) {
if (CollectionUtils.isEmpty(mcpClients)) {
return List.of();
@@ -195,4 +217,40 @@ public final class McpToolUtils {
.toList();
}
/**
* Convenience method to get tool callbacks from multiple asynchronous MCP clients.
* <p>
* This is a varargs wrapper around {@link #getToolCallbacksFromAsyncClinents(List)}
* for easier usage when working with individual clients.
* @param asynMcpClients the asynchronous MCP clients to get callbacks from
* @return a list of tool callbacks from all provided clients
* @see #getToolCallbacksFromAsyncClinents(List)
*/
public static List<ToolCallback> getToolCallbacksFromAsyncClients(McpAsyncClient... asynMcpClients) {
return getToolCallbacksFromAsyncClinents(List.of(asynMcpClients));
}
/**
* Gets tool callbacks from a list of asynchronous MCP clients.
* <p>
* This method:
* <ol>
* <li>Takes a list of asynchronous MCP clients</li>
* <li>Creates a provider for each client</li>
* <li>Retrieves and combines all tool callbacks into a single list</li>
* </ol>
* @param asynMcpClients the list of asynchronous MCP clients to get callbacks from
* @return a list of tool callbacks from all provided clients
*/
public static List<ToolCallback> getToolCallbacksFromAsyncClinents(List<McpAsyncClient> asynMcpClients) {
if (CollectionUtils.isEmpty(asynMcpClients)) {
return List.of();
}
return asynMcpClients.stream()
.map(mcpClient -> List.of((new AsyncMcpToolCallbackProvider(mcpClient).getToolCallbacks())))
.flatMap(List::stream)
.toList();
}
}

View File

@@ -55,19 +55,18 @@ import org.springframework.ai.tool.definition.ToolDefinition;
* @see McpSyncClient
* @see Tool
*/
public class McpToolCallback implements ToolCallback {
public class SyncMcpToolCallback implements ToolCallback {
private final McpSyncClient mcpClient;
private final Tool tool;
/**
* Creates a new {@code McpToolCallback} instance.
* Creates a new {@code SyncMcpToolCallback} instance.
* @param mcpClient the MCP client to use for tool execution
* @param tool the MCP tool definition to adapt
*/
public McpToolCallback(McpSyncClient mcpClient, Tool tool) {
public SyncMcpToolCallback(McpSyncClient mcpClient, Tool tool) {
this.mcpClient = mcpClient;
this.tool = tool;
}

View File

@@ -32,13 +32,13 @@ import org.springframework.util.CollectionUtils;
* <ul>
* <li>Connects to an MCP server through a sync client</li>
* <li>Lists and retrieves available tools from the server</li>
* <li>Creates {@link McpToolCallback} instances for each discovered tool</li>
* <li>Creates {@link SyncMcpToolCallback} instances for each discovered tool</li>
* <li>Validates tool names to prevent duplicates</li>
* </ul>
* <p>
* Example usage: <pre>{@code
* McpSyncClient mcpClient = // obtain MCP client
* ToolCallbackProvider provider = new McpToolCallbackProvider(mcpClient);
* ToolCallbackProvider provider = new SyncMcpToolCallbackProvider(mcpClient);
*
* // Get all available tools
* ToolCallback[] tools = provider.getToolCallbacks();
@@ -47,7 +47,7 @@ import org.springframework.util.CollectionUtils;
* @author Christian Tzolov
* @since 1.0.0
* @see ToolCallbackProvider
* @see McpToolCallback
* @see SyncMcpToolCallback
* @see McpSyncClient
*/
@@ -56,7 +56,7 @@ public class SyncMcpToolCallbackProvider implements ToolCallbackProvider {
private final McpSyncClient mcpClient;
/**
* Creates a new {@code McpToolCallbackProvider} instance.
* Creates a new {@code SyncMcpToolCallbackProvider} instance.
* @param mcpClient the MCP client to use for discovering tools
*/
public SyncMcpToolCallbackProvider(McpSyncClient mcpClient) {
@@ -69,7 +69,7 @@ public class SyncMcpToolCallbackProvider implements ToolCallbackProvider {
* This method:
* <ol>
* <li>Retrieves the list of tools from the MCP server</li>
* <li>Creates a {@link McpToolCallback} for each tool</li>
* <li>Creates a {@link SyncMcpToolCallback} for each tool</li>
* <li>Validates that there are no duplicate tool names</li>
* </ol>
* @return an array of tool callbacks, one for each discovered tool
@@ -81,7 +81,7 @@ public class SyncMcpToolCallbackProvider implements ToolCallbackProvider {
var toolCallbacks = this.mcpClient.listTools()
.tools()
.stream()
.map(tool -> new McpToolCallback(this.mcpClient, tool))
.map(tool -> new SyncMcpToolCallback(this.mcpClient, tool))
.toArray(ToolCallback[]::new);
validateToolCallbacks(toolCallbacks);
@@ -106,6 +106,18 @@ public class SyncMcpToolCallbackProvider implements ToolCallbackProvider {
}
}
/**
* Creates a list of tool callbacks from multiple MCP clients.
* <p>
* This utility method:
* <ol>
* <li>Takes a list of MCP clients</li>
* <li>Creates a provider for each client</li>
* <li>Retrieves and combines all tool callbacks into a single list</li>
* </ol>
* @param mcpClients the list of MCP clients to create callbacks from
* @return a list of tool callbacks from all provided clients
*/
public static List<ToolCallback> syncToolCallbacks(List<McpSyncClient> mcpClients) {
if (CollectionUtils.isEmpty(mcpClients)) {

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.mcp;
package org.springframework.ai.mcp.aot;
import java.util.ArrayList;
import java.util.Arrays;
@@ -30,12 +30,36 @@ import org.springframework.aot.hint.TypeReference;
import org.springframework.lang.Nullable;
/**
* Runtime hints registrar for Model Context Protocol (MCP) schema classes.
* <p>
* This class provides GraalVM native image hints for MCP schema classes to ensure proper
* reflection access in native images. It:
* <ul>
* <li>Registers all nested classes of {@link McpSchema} for reflection</li>
* <li>Enables all member categories (fields, methods, etc.) for registered types</li>
* <li>Ensures proper serialization/deserialization in native images</li>
* </ul>
*
* @author Josh Long
* @since 1.0.0
* @see RuntimeHintsRegistrar
* @see McpSchema
*/
@SuppressWarnings("unused")
public class McpHints implements RuntimeHintsRegistrar {
/**
* Registers runtime hints for MCP schema classes.
* <p>
* This method:
* <ol>
* <li>Discovers all nested classes within {@link McpSchema}</li>
* <li>Registers each discovered class for reflection access</li>
* <li>Enables all member categories for complete reflection support</li>
* </ol>
* @param hints the hints instance to register hints with
* @param classLoader the classloader to use (may be null)
*/
@Override
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
var mcs = MemberCategory.values();
@@ -45,12 +69,32 @@ public class McpHints implements RuntimeHintsRegistrar {
}
}
/**
* Discovers all inner classes of a given class.
* <p>
* This method recursively finds all nested classes (both declared and inherited) of
* the provided class and converts them to type references.
* @param clazz the class to find inner classes for
* @return a set of type references for all discovered inner classes
*/
private Set<TypeReference> innerClasses(Class<?> clazz) {
var indent = new HashSet<String>();
this.findNestedClasses(clazz, indent);
return indent.stream().map(TypeReference::of).collect(Collectors.toSet());
}
/**
* Recursively finds all nested classes of a given class.
* <p>
* This method:
* <ol>
* <li>Collects both declared and inherited nested classes</li>
* <li>Recursively processes each nested class</li>
* <li>Adds the class names to the provided set</li>
* </ol>
* @param clazz the class to find nested classes for
* @param indent the set to collect class names in
*/
private void findNestedClasses(Class<?> clazz, Set<String> indent) {
var classes = new ArrayList<Class<?>>();
classes.addAll(Arrays.asList(clazz.getDeclaredClasses()));

View File

@@ -0,0 +1,44 @@
/*
* 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.mcp.customizer;
import io.modelcontextprotocol.client.McpClient;
/**
* Interface for customizing asynchronous MCP client configurations.
* <p>
* This interface allows for customization of MCP client behavior through Spring's
* customizer pattern. Implementations can modify the client's configuration before it is
* used in the application.
* <p>
*
* @author Christian Tzolov
* @since 1.0.0
* @see io.modelcontextprotocol.client.McpClient.AsyncSpec
*/
public interface McpAsyncClientCustomizer {
/**
* Customizes an asynchronous MCP client configuration.
* <p>
* This method is called for each async MCP client being created, allowing for
* client-specific customizations based on the client's name and specification.
* @param name the name of the MCP client being customized
* @param spec the async specification to customize
*/
void customize(String name, McpClient.AsyncSpec spec);
}

View File

@@ -0,0 +1,44 @@
/*
* 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.mcp.customizer;
import io.modelcontextprotocol.client.McpClient;
/**
* Interface for customizing synchronous MCP client configurations.
* <p>
* This interface allows for customization of MCP client behavior through Spring's
* customizer pattern. Implementations can modify the client's configuration before it is
* used in the application.
* <p>
*
* @author Christian Tzolov
* @since 1.0.0
* @see io.modelcontextprotocol.client.McpClient.SyncSpec
*/
public interface McpSyncClientCustomizer {
/**
* Customizes a synchronous MCP client configuration.
* <p>
* This method is called for each sync MCP client being created, allowing for
* client-specific customizations based on the client's name and specification.
* @param name the name of the MCP client being customized
* @param spec the sync specification to customize
*/
void customize(String name, McpClient.SyncSpec spec);
}

View File

@@ -14,6 +14,26 @@
* limitations under the License.
*/
/**
* Core support for Model Context Protocol (MCP) integration in Spring AI.
* <p>
* This package provides the foundational classes and utilities for integrating MCP with
* Spring AI's tool system. It includes:
* <ul>
* <li>Tool callback implementations for both synchronous and asynchronous MCP
* operations</li>
* <li>Tool callback providers that discover and expose MCP tools</li>
* <li>Utility classes for converting between Spring AI and MCP tool representations</li>
* <li>Support for customizing MCP client behavior</li>
* </ul>
* <p>
* The classes in this package enable seamless integration between Spring AI applications
* and MCP servers, allowing language models to discover and invoke tools through a
* standardized protocol.
*
* @author Christian Tzolov
* @since 1.0.0
*/
@NonNullApi
@NonNullFields
package org.springframework.ai.mcp;

View File

@@ -1,2 +1,2 @@
org.springframework.aot.hint.RuntimeHintsRegistrar=\
org.springframework.ai.mcp.McpHints
org.springframework.ai.mcp.aot.McpHints

View File

@@ -33,7 +33,7 @@ import io.modelcontextprotocol.spec.McpSchema.ListToolsResult;
import io.modelcontextprotocol.spec.McpSchema.Tool;
@ExtendWith(MockitoExtension.class)
class McpToolCallbackProviderTests {
class SyncMcpToolCallbackProviderTests {
@Mock
private McpSyncClient mcpClient;

View File

@@ -31,7 +31,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class McpToolCallbackTests {
class SyncMcpToolCallbackTests {
@Mock
private McpSyncClient mcpClient;
@@ -45,7 +45,7 @@ class McpToolCallbackTests {
when(tool.name()).thenReturn("testTool");
when(tool.description()).thenReturn("Test tool description");
McpToolCallback callback = new McpToolCallback(mcpClient, tool);
SyncMcpToolCallback callback = new SyncMcpToolCallback(mcpClient, tool);
// Act
var toolDefinition = callback.getToolDefinition();
@@ -62,7 +62,7 @@ class McpToolCallbackTests {
CallToolResult callResult = mock(CallToolResult.class);
when(mcpClient.callTool(any(CallToolRequest.class))).thenReturn(callResult);
McpToolCallback callback = new McpToolCallback(mcpClient, tool);
SyncMcpToolCallback callback = new SyncMcpToolCallback(mcpClient, tool);
// Act
String response = callback.call("{\"param\":\"value\"}");

21
pom.xml
View File

@@ -34,7 +34,12 @@
<module>spring-ai-bom</module>
<module>spring-ai-core</module>
<module>spring-ai-test</module>
<module>spring-ai-spring-boot-autoconfigure</module>
<module>auto-configurations/spring-ai-mcp-client</module>
<module>auto-configurations/spring-ai-mcp-server</module>
<module>spring-ai-retry</module>
<module>spring-ai-spring-boot-docker-compose</module>
<module>spring-ai-spring-boot-testcontainers</module>
@@ -127,7 +132,11 @@
<module>spring-ai-spring-boot-starters/spring-ai-starter-zhipuai</module>
<module>spring-ai-spring-boot-starters/spring-ai-starter-moonshot</module>
<module>spring-ai-spring-boot-starters/spring-ai-starter-mcp</module>
<module>spring-ai-spring-boot-starters/spring-ai-starter-mcp-client</module>
<module>spring-ai-spring-boot-starters/spring-ai-starter-mcp-server</module>
<module>spring-ai-spring-boot-starters/spring-ai-starter-mcp-client-webflux</module>
<module>spring-ai-spring-boot-starters/spring-ai-starter-mcp-server-webflux</module>
<module>spring-ai-spring-boot-starters/spring-ai-starter-mcp-server-webmvc</module>
<module>spring-ai-integration-tests</module>
@@ -890,6 +899,16 @@
<role>lead</role>
</roles>
</developer>
<developer>
<id>tzolov</id>
<name>Christian Tzolov</name>
<email>christian tzolov at broadcom.com</email>
<organization>Broadcom</organization>
<organizationUrl>http://www.spring.io</organizationUrl>
<roles>
<role>lead</role>
</roles>
</developer>
</developers>
<reporting>

View File

@@ -310,6 +310,18 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-client-spring-boot-autoconfigure</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-spring-boot-autoconfigure</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Spring Boot Starters -->
<dependency>
<groupId>org.springframework.ai</groupId>
@@ -589,10 +601,35 @@
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-spring-boot-starter</artifactId>
<artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-client-webflux-spring-boot-starter</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-webflux-spring-boot-starter</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-webmvc-spring-boot-starter</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

Binary file not shown.

After

Width:  |  Height:  |  Size: 661 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 840 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 67 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 75 KiB

View File

@@ -95,10 +95,13 @@
* xref:api/prompt.adoc[]
* xref:api/structured-output-converter.adoc[Structured Output]
* xref:api/tools.adoc[Tool Calling]
* xref:api/model-context-protocol.adoc[Model Context Protocol (MCP)]
* xref:api/functions.adoc[Function Calling (Deprecated)]
** xref:api/function-callback.adoc[FunctionCallback API (Deprecated)]
** xref:api/tools-migration.adoc[Migrating to ToolCallback API]
* xref:api/mcp/mcp-overview.adoc[Model Context Protocol (MCP)]
** xref:api/mcp/mcp-client-boot-starter-docs.adoc[MCP Client Boot Starters]
** xref:api/mcp/mcp-server-boot-starter-docs.adoc[MCP Server Boot Starters]
** xref:api/mcp/mcp-helpers.adoc[MCP Utilities]
* xref:api/multimodality.adoc[Multimodality]
* xref:api/etl-pipeline.adoc[]
* xref:api/testing.adoc[AI Model Evaluation]
@@ -112,4 +115,3 @@
* Appendices
** xref:upgrade-notes.adoc[]

View File

@@ -0,0 +1,353 @@
= MCP Client Boot Starter
The Spring AI MCP (Model Context Protocol) Client Boot Starter provides auto-configuration for MCP client functionality in Spring Boot applications. It supports both synchronous and asynchronous client implementations with various transport options.
The MCP Client Boot Starter provides:
* Management of multiple client instances
* Automatic client initialization (if enabled)
* Support for multiple named transports
* Integration with Spring AI's tool execution framework
* Proper lifecycle management with automatic cleanup of resources when the application context is closed
* Customizable client creation through customizers
== Starters
=== Standard MCP Client
[source,xml]
----
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId>
</dependency>
----
The standard starter connects simultaneously to one or more MCP servers over `STDIO` (in-process) and/or `SSE` (remote) transports.
The SSE connection uses the HttpClient-based transport implementation.
Each connection to an MCP server creates a new MCP client instance.
You can choose either `SYNC` or `ASYNC` MCP clients (note: you cannot mix sync and async clients).
For production deployment, we recommend using the WebFlux-based SSE connection with the `spring-ai-mcp-client-webflux-spring-boot-starter`.
=== WebFlux Client
The WebFlux starter provides similar functionality to the standard starter but uses a WebFlux-based SSE transport implementation.
[source,xml]
----
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-client-webflux-spring-boot-starter</artifactId>
</dependency>
----
== Configuration Properties
=== Common Properties
The common properties are prefixed with `spring.ai.mcp.client`:
[cols="3,4,3"]
|===
|Property |Description |Default Value
|`enabled`
|Enable/disable the MCP client
|`true`
|`name`
|Name of the MCP client instance (used for compatibility checks)
|`spring-ai-mcp-client`
|`version`
|Version of the MCP client instance
|`1.0.0`
|`initialized`
|Whether to initialize clients on creation
|`true`
|`request-timeout`
|Timeout duration for MCP client requests
|`20s`
|`type`
|Client type (SYNC or ASYNC). All clients must be either sync or async; mixing is not supported
|`SYNC`
|`root-change-notification`
|Enable/disable root change notifications for all clients
|`true`
|===
=== Stdio Transport Properties
Properties for Standard I/O transport are prefixed with `spring.ai.mcp.client.stdio`:
[cols="3,4,3"]
|===
|Property |Description |Default Value
|`servers-configuration`
|Resource containing the MCP servers configuration in JSON format
|-
|`connections`
|Map of named stdio connection configurations
|-
|`connections.[name].command`
|The command to execute for the MCP server
|-
|`connections.[name].args`
|List of command arguments
|-
|`connections.[name].env`
|Map of environment variables for the server process
|-
|===
Example configuration:
[source,yaml]
----
spring:
ai:
mcp:
client:
stdio:
root-change-notification: true
connections:
server1:
command: /path/to/server
args:
- --port=8080
- --mode=production
env:
API_KEY: your-api-key
DEBUG: "true"
----
Alternatively, you can configure stdio connections using an external JSON file using the link:https://modelcontextprotocol.io/quickstart/user[Claude Desktop format]:
[source,yaml]
----
spring:
ai:
mcp:
client:
stdio:
servers-configuration: classpath:mcp-servers.json
----
The Claude Desktop format looks like this:
[source,json]
----
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"/Users/username/Desktop",
"/Users/username/Downloads"
]
}
}
}
----
Currently, the Claude Desktop format supports only STDIO connection types.
=== SSE Transport Properties
Properties for Server-Sent Events (SSE) transport are prefixed with `spring.ai.mcp.client.sse`:
[cols="2,4"]
|===
|Property |Description
|`connections`
|Map of named SSE connection configurations
|`connections.[name].url`
|URL endpoint for SSE communication with the MCP server
|===
Example configuration:
[source,yaml]
----
spring:
ai:
mcp:
client:
sse:
connections:
server1:
url: http://localhost:8080
server2:
url: http://otherserver:8081
----
== Features
=== Sync/Async Client Types
The starter supports two types of clients:
* Synchronous - default client type, suitable for traditional request-response patterns with blocking operations
* Asynchronous - suitable for reactive applications with non-blocking operations, configured using `spring.ai.mcp.client.type=ASYNC`
=== Client Customization
The auto-configuration supports client spec customization.
Implement the `McpSyncClientCustomizer` callback interface to customize the `McpClient.SyncSpec` spec for a named server connection.
Similarly, the `McpAsyncClientCustomizer` interface allows customizing the `McpClient.AsyncSpec` spec for asynchronous clients.
[tabs]
======
Sync::
+
[source,java]
----
@Component
public class CustomMcpSyncClientCustomizer implements McpSyncClientCustomizer {
@Override
public void customize(String name, McpClient.SyncSpec spec) {
// Customize the sync client configuration
spec.requestTimeout(Duration.ofSeconds(30));
// Adds a consumer to be notified when the available tools change. This allows the
// client to react to changes in the server's tool capabilities, such as tools
// being added or removed.
spec.toolsChangeConsumer((List<McpSchema.Tool> tools) -> {
// Handle tools change
});
// Adds a consumer to be notified when the available resources change. This allows the
// client to react to changes in the server's resource capabilities, such as resources
// being added or removed.
spec.resourcesChangeConsumer((List<McpSchema.Resource> resources) -> {
// Handle resources change
});
// Adds a consumer to be notified when the available prompts change. This allows the
// client to react to changes in the server's prompt capabilities, such as prompts
// being added or removed.
spec.promptsChangeConsumer((List<McpSchema.Prompt> prompts) -> {
// Handle prompts change
});
// Adds a consumer to be notified when logging messages are received from the server.
spec.loggingConsumer((McpSchema.LoggingMessageNotification log) -> {
// Handle log messages
});
// Sets a custom sampling handler for processing message creation requests.
spec.sampling((CreateMessageRequest messageRequest) -> {
// Handle sampling
CreateMessageResult result = ...
return result;
});
// Sets the root URIs that the server connecto this client can access.
// Roots define the base URIs for resources that the server can request.
// For example, a root might be "file://workspace" for accessing workspace files.
spec.roots(roots);
}
}
----
Async::
+
[source,java]
----
@Component
public class CustomMcpAsyncClientCustomizer implements McpAsyncClientCustomizer {
@Override
public void customize(String name, McpClient.AsyncSpec spec) {
// Customize the async client configuration
spec.requestTimeout(Duration.ofSeconds(30));
}
}
----
======
The MCP client auto-configuration automatically detects and applies any customizers found in the application context.
=== Transport Support
The auto-configuration supports multiple transport types:
* Standard I/O (Stdio) (activated by the `spring-ai-mcp-client-spring-boot-starter`)
* SSE HTTP (activated by the `spring-ai-mcp-client-spring-boot-starter`)
* SSE WebFlux (activated by the `spring-ai-starter-mcp-client-webflux`)
=== Integration with Spring AI
The starter automatically configures tool callbacks that integrate with Spring AI's tool execution framework, allowing MCP tools to be used as part of AI interactions.
== Usage Example
Add the appropriate starter dependency to your project and configure the client in `application.properties` or `application.yml`:
[source,yaml]
----
spring:
ai:
mcp:
client:
enabled: true
name: my-mcp-client
version: 1.0.0
request-timeout: 30s
type: SYNC # or ASYNC for reactive applications
sse:
connections:
server1:
url: http://localhost:8080
server2:
url: http://otherserver:8081
stdio:
root-change-notification: false
connections:
server1:
command: /path/to/server
args:
- --port=8080
- --mode=production
env:
API_KEY: your-api-key
DEBUG: "true"
----
The MCP client beans will be automatically configured and available for injection:
[source,java]
----
@Autowired
private List<McpSyncClient> mcpSyncClients; // For sync client
// OR
@Autowired
private List<McpAsyncClient> mcpAsyncClients; // For async client
----
Additionally, the registered MCP Tools with all MCP clients are provided as a list of ToolCallback instances:
[source,java]
----
@Autowired
private List<ToolCallback> toolCallbacks;
----
== Additional Resources
* link:https://docs.spring.io/spring-ai/reference/[Spring AI Documentation]
* link:https://modelcontextprotocol.github.io/specification/[Model Context Protocol Specification]
* link:https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.developing-auto-configuration[Spring Boot Auto-configuration]

View File

@@ -0,0 +1,156 @@
= MCP Utilities
:page-title: Spring AI MCP Utilities
The MCP utilities provide foundational support for integrating Model Context Protocol with Spring AI applications.
These utilities enable seamless communication between Spring AI's tool system and MCP servers, supporting both synchronous and asynchronous operations.
They are typically used for programmatic MCP Client and Server configuration and interaction.
For a more streamlined configuration, consider using the boot starters.
== ToolCallback Utility
=== Tool Callback Adapter
Adapts MCP tools to Spring AI's tool interface with both synchronous and asynchronous execution support.
[tabs]
======
Sync::
+
[source,java]
----
McpSyncClient mcpClient = // obtain MCP client
Tool mcpTool = // obtain MCP tool definition
ToolCallback callback = new SyncMcpToolCallback(mcpClient, mcpTool);
// Use the tool through Spring AI's interfaces
ToolDefinition definition = callback.getToolDefinition();
String result = callback.call("{\"param\": \"value\"}");
----
Async::
+
[source,java]
----
McpAsyncClient mcpClient = // obtain MCP client
Tool mcpTool = // obtain MCP tool definition
ToolCallback callback = new AsyncMcpToolCallback(mcpClient, mcpTool);
// Use the tool through Spring AI's interfaces
ToolDefinition definition = callback.getToolDefinition();
String result = callback.call("{\"param\": \"value\"}");
----
======
=== Tool Callback Providers
Discovers and provides MCP tools from MCP clients.
[tabs]
======
Sync::
+
[source,java]
----
McpSyncClient mcpClient = // obtain MCP client
ToolCallbackProvider provider = new SyncMcpToolCallbackProvider(mcpClient);
// Get all available tools
ToolCallback[] tools = provider.getToolCallbacks();
----
+
For multiple clients:
+
[source,java]
----
List<McpSyncClient> clients = // obtain list of clients
List<ToolCallback> callbacks = SyncMcpToolCallbackProvider.syncToolCallbacks(clients);
----
Async::
+
[source,java]
----
McpAsyncClient mcpClient = // obtain MCP client
ToolCallbackProvider provider = new AsyncMcpToolCallbackProvider(mcpClient);
// Get all available tools
ToolCallback[] tools = provider.getToolCallbacks();
----
+
For multiple clients:
+
[source,java]
----
List<McpAsyncClient> clients = // obtain list of clients
Flux<ToolCallback> callbacks = AsyncMcpToolCallbackProvider.asyncToolCallbacks(clients);
----
======
== McpToolUtils
=== ToolCallbacks to ToolRegistrations
Converting Spring AI tool callbacks to MCP tool registrations:
[tabs]
======
Sync::
+
[source,java]
----
List<ToolCallback> toolCallbacks = // obtain tool callbacks
List<SyncToolRegistration> syncToolRegs = McpToolUtils.toSyncToolRegistration(toolCallbacks);
----
+
then you can use the `McpServer.SyncSpec` to register the tool registrations:
+
[source,java]
----
McpServer.SyncSpec syncSpec = ...
syncSpec.tools(syncToolRegs);
----
Async::
+
[source,java]
----
List<ToolCallback> toolCallbacks = // obtain tool callbacks
List<AsyncToolRegistration> asyncToolRegs = McpToolUtils.toAsyncToolRegistration(toolCallbacks);
----
+
then you can use the `McpServer.AsyncSpec` to register the tool registrations:
+
[source,java]
----
McpServer.AsyncSpec asyncSpec = ...
asyncSpec.tools(asyncToolRegs);
----
======
=== MCP Clients to ToolCallbacks
Getting tool callbacks from MCP clients
[tabs]
======
Sync::
+
[source,java]
----
List<McpSyncClient> syncClients = // obtain sync clients
List<ToolCallback> syncCallbacks = McpToolUtils.getToolCallbacksFromSyncClients(syncClients);
----
Async::
+
[source,java]
----
List<McpAsyncClient> asyncClients = // obtain async clients
List<ToolCallback> asyncCallbacks = McpToolUtils.getToolCallbacksFromAsyncClients(asyncClients);
----
======
== Native Image Support
The `McpHints` class provides GraalVM native image hints for MCP schema classes.
This class automatically registers all necessary reflection hints for MCP schema classes when building native images.

View File

@@ -0,0 +1,90 @@
= Model Context Protocol (MCP)
The link:https://modelcontextprotocol.org/docs/concepts/architecture[Model Context Protocol] (MCP) is a standardized protocol that enables AI models to interact with external tools and resources in a structured way.
It supports multiple transport mechanisms for flexibility in different environments.
The link:https://modelcontextprotocol.github.io/sdk/java[Java MCP] provides a Java SDK implementation of the Model Context Protocol, enabling standardized interaction with AI models and tools through both synchronous and asynchronous communication.
== MCP Java SDK
The Java MCP implementation follows a three-layer architecture:
// [cols="2,10"]
|===
| |
^a| image::mcp/mcp-stack.svg[MCP Stack Architecture]
a| * *Client/Server Layer*: The McpClient handles client-side and the McpServer manages server-side protocol operations.
Both utilize McpSession for operations
* *Session Layer (McpSession)*: Manages communication patterns and state.
Uses the DefaultMcpSession implementation.
* *Transport Layer (McpTransport)*: Handles JSON-RPC message serialization/deserialization.
Supports multiple transport implementations.
|===
// [cols="10,2"]
|===
| MCP Client |
a| The MCP Client is a key component in the Model Context Protocol (MCP) architecture, responsible for establishing and managing connections with MCP servers. It implements the client-side of the protocol, handling:
* Protocol version negotiation to ensure compatibility with servers
* Capability negotiation to determine available features
* Message transport and JSON-RPC communication
* Tool discovery and execution
* Resource access and management
* Prompt system interactions
* Optional features like roots management and sampling support
* Synchronous and asynchronous operations
* Multiple transport options:
** Stdio-based transport for process-based communication
** Java HttpClient-based SSE client transport
** WebFlux SSE client transport for reactive HTTP streaming
^a| image::mcp/java-mcp-client-architecture.jpg[Java MCP Client Architecture, width=500]
|===
// [cols="10,2"]
|===
| MCP Server |
a| The MCP Server is a foundational component in the Model Context Protocol (MCP) architecture that provides tools, resources, and capabilities to clients. It implements the server-side of the protocol, responsible for:
* Exposing tools that clients can discover and execute
* Managing resources with URI-based access patterns
* Providing prompt templates and handling prompt requests
* Supporting capability negotiation with clients
* Implementing server-side protocol operations
* Managing concurrent client connections
* Providing structured logging and notifications
* Supporting both synchronous and asynchronous APIs for flexible integration
* Multiple transport options:
** Stdio-based transport for process-based communication
** Servlet-based SSE server transport
** WebFlux SSE server transport for reactive HTTP streaming
** WebMVC SSE server transport for servlet-based HTTP streaming
^a| image::mcp/java-mcp-server-architecture.jpg[Java MCP Server Architecture, width=600]
|===
For manual SDK implementation, refer to the link:https://modelcontextprotocol.github.io/sdk/java[MCP Java SDK documentation].
For simplified setup, use the Spring AI MCP Boot Starters below.
== Spring AI MCP Integration
The Spring AI MCP integration is provided through Spring Boot starters:
=== link:mcp-client-boot-starter-docs.html[Client Starters]
* `spring-ai-mcp-client-spring-boot-starter` - Core starter with STDIO and HTTP-based SSE support
* `spring-ai-mcp-client-webflux-spring-boot-starter` - WebFlux-based SSE transport
=== link:mcp-server-boot-starter-docs.html[Server Starters]
* `spring-ai-mcp-server-spring-boot-starter` - Core server with STDIO transport
* `spring-ai-mcp-server-webmvc-spring-boot-starter` - Spring MVC-based SSE transport
* `spring-ai-mcp-server-webflux-spring-boot-starter` - WebFlux-based SSE transport
== Additional Resources
* link:mcp-client-boot-starter-docs.html[MCP Client Boot Starters Documentation]
* link:mcp-server-boot-starter-docs.html[MCP Server Boot Starters Documentation]
* link:mcp-helpers.html[MCP Utilities Documentation]
* link:https://modelcontextprotocol.github.io/specification/[Model Context Protocol Specification]

View File

@@ -0,0 +1,218 @@
= MCP Server Boot Starter
The Spring AI MCP (Model Context Protocol) Server Boot Starter provides auto-configuration for setting up an MCP server in Spring Boot applications. It enables seamless integration of MCP server capabilities with Spring Boot's auto-configuration system.
The MCP Server Boot Starter offers:
* Automatic configuration of MCP server components
* Support for both synchronous and asynchronous operation modes
* Multiple transport layer options
* Flexible tool, resource, and prompt registration
* Change notification capabilities
== Starters
Choose one of the following starters based on your transport requirements:
=== Standard MCP Server
Full MCP Server features support with `STDIO` server transport.
[source,xml]
----
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId>
</dependency>
----
* Suitable for command-line and desktop tools
* No additional web dependencies required
The starter activates the `McpServerAutoConfiguration` auto-configuration responsible for:
* Configuring the basic server components
* Handling tool, resource, and prompt registrations
* Managing server capabilities and change notifications
* Providing both sync and async server implementations
=== WebMVC Server Transport
Full MCP Server features support with `SSE` (Server-Sent Events) server transport based on Spring MVC and an optional `STDIO` transport.
[source,xml]
----
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-webmvc-spring-boot-starter</artifactId>
</dependency>
----
The starter activates the `McpWebMvcServerAutoConfiguration` and `McpServerAutoConfiguration` auto-configurations to provide:
* HTTP-based transport using Spring MVC (`WebMvcSseServerTransport`)
* Automatically configured SSE endpoints
* Optional `STDIO` transport (enabled by setting `spring.ai.mcp.server.stdio=true`)
* Included `spring-boot-starter-web` and `mcp-spring-webmvc` dependencies
=== WebFlux Server Transport
Full MCP Server features support with `SSE` (Server-Sent Events) server transport based on Spring WebFlux and an optional `STDIO` transport.
[source,xml]
----
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-webflux-spring-boot-starter</artifactId>
</dependency>
----
The starter activates the `McpWebFluxServerAutoConfiguration` and `McpServerAutoConfiguration` auto-configurations to provide:
* Reactive transport using Spring WebFlux (`WebFluxSseServerTransport`)
* Automatically configured reactive SSE endpoints
* Optional `STDIO` transport (enabled by setting `spring.ai.mcp.server.stdio=true`)
* Included `spring-boot-starter-webflux` and `mcp-spring-webflux` dependencies
== Configuration Properties
All properties are prefixed with `spring.ai.mcp.server`:
[options="header"]
|===
|Property |Description |Default
|`enabled` |Enable/disable the MCP server |`true`
|`stdio` |Enable/disable stdio transport |`false`
|`name` |Server name for identification |`mcp-server`
|`version` |Server version |`1.0.0`
|`type` |Server type (SYNC/ASYNC) |`SYNC`
|`resource-change-notification` |Enable resource change notifications |`true`
|`tool-change-notification` |Enable tool change notifications |`true`
|`prompt-change-notification` |Enable prompt change notifications |`true`
|`sse-message-endpoint` |SSE endpoint path for web transport |`/mcp/message`
|===
== Sync/Async Server Types
* **Synchronous Server** - The default server type implemented using `McpSyncServer`.
It is designed for straightforward request-response patterns in your applications.
To enable this server type, set `spring.ai.mcp.server.type=SYNC` in your configuration.
When activated, it automatically handles the configuration of synchronous tool registrations.
* **Asynchronous Server** - The asynchronous server implementation uses `McpAsyncServer` and is optimized for non-blocking operations.
To enable this server type, configure your application with `spring.ai.mcp.server.type=ASYNC`.
This server type automatically sets up asynchronous tool registrations with built-in Project Reactor support.
== Transport Options
The MCP Server supports three transport mechanisms, each with its dedicated starter:
* Standard Input/Output (STDIO) - `spring-ai-mcp-server-spring-boot-starter`
* Spring MVC (Server-Sent Events) - `spring-ai-mcp-server-webmvc-spring-boot-starter`
* Spring WebFlux (Reactive SSE) - `spring-ai-mcp-server-webflux-spring-boot-starter`
== Features and Capabilities
=== Tools Registration
* Support for both sync and async tool execution
* Automatic tool registration through Spring beans
* Change notification support
* Tools are automatically converted to sync/async registrations based on server type
=== Resource Management
* Static and dynamic resource registration
* Optional change notifications
* Support for resource templates
* Automatic conversion between sync/async resource registrations
=== Prompt Templates
* Configurable prompt registration
* Change notification support
* Template versioning
* Automatic conversion between sync/async prompt registrations
=== Root Change Consumers
* Support for monitoring root changes
* Automatic conversion to async consumers for reactive applications
* Optional registration through Spring beans
== Usage Examples
=== Standard STDIO Server Configuration
[source,yaml]
----
# Using spring-ai-mcp-server-spring-boot-starter
spring:
ai:
mcp:
server:
name: stdio-mcp-server
version: 1.0.0
type: SYNC
----
=== WebMVC Server Configuration
[source,yaml]
----
# Using spring-ai-mcp-server-webmvc-spring-boot-starter
spring:
ai:
mcp:
server:
name: webmvc-mcp-server
version: 1.0.0
type: SYNC
sse-message-endpoint: /mcp/messages
----
=== WebFlux Server Configuration
[source,yaml]
----
# Using spring-ai-mcp-server-webflux-spring-boot-starter
spring:
ai:
mcp:
server:
name: webflux-mcp-server
version: 1.0.0
type: ASYNC # Recommended for reactive applications
sse-message-endpoint: /mcp/messages
----
=== Create Spring Boot application with MCP Server
[source,java]
----
@Service
public class WeatherService {
@Tool(description = "Get weather information by city name")
public String getBooks(String cityName) {
// Implementation
}
}
@SpringBootApplication
public class McpServerApplication {
private static final Logger logger = LoggerFactory.getLogger(McpServerApplication.class);
public static void main(String[] args) {
SpringApplication.run(McpServerApplication.class, args);
}
@Bean
public List<ToolCallback> tools(WeatherService weatherService) {
return ToolCallbacks.from(weatherService);
}
}
----
The auto-configuration will automatically register the toolcallbacs as MCP tools.
You can have multiple beans producing list of ToolCallbacks. The autoconfiguration will merge them.
== Additional Resources
* link:https://docs.spring.io/spring-ai/reference/[Spring AI Documentation]
* link:https://modelcontextprotocol.github.io/specification/[Model Context Protocol Specification]
* link:https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.developing-auto-configuration[Spring Boot Auto-configuration]

View File

@@ -1,117 +0,0 @@
[[MCP]]
= Model Context Protocol (MCP)
The link:https://modelcontextprotocol.io/introduction[Model Context Protocol (MCP)] is an open protocol that standardizes how applications provide context to Large Language Models (LLMs).
MCP provides an unified way to connect AI models to different data sources and tools, making integration seamless and consistent.
It helps you build agents and complex workflows on top of LLMs. LLMs frequently need to integrate with data and tools, and MCP provides:
- A growing list of pre-built integrations that your LLM can directly plug into
- The flexibility to switch between LLM providers and vendors
== Spring AI MCP
NOTE: Spring AI MCP is an experimental project and subject to change.
link:https://github.com/spring-projects-experimental/spring-ai-mcp[Spring AI MCP] is an experimental project that provides Java and Spring Framework integration for the Model Context Protocol.
It enables Spring AI applications to interact with different data sources and tools, through a standardized interface, supporting both synchronous and asynchronous communication patterns.
image::spring-ai-mcp-architecture.jpg[SpringAIMCP, 800]
The Spring AI MCP implements a modular architecture with the following components:
- Spring AI Application: Uses Spring AI framework to build Generative AI applications that want to access data through MCP
- Spring MCP Clients: Spring AI implementation of the MCP protocol that maintain 1:1 connections with servers
- MCP Servers: Lightweight programs that each expose specific capabilities through the standardized Model Context Protocol
- Local Data Sources: Your computer's files, databases, and services that MCP servers can securely access
- Remote Services: External systems available over the internet (e.g., through APIs) that MCP servers can connect to
The architecture supports a wide range of use cases, from simple file system access to complex multi-model AI interactions with database and internet connectivity.
== Getting Started
Add the SDK to your Maven project:
[tabs]
======
Maven::
+
[source,xml,indent=0,subs="verbatim,quotes"]
----
<dependency>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-ai-mcp</artifactId>
<version>0.2.0</version>
</dependency>
----
Gradle::
+
[source,groovy,indent=0,subs="verbatim,quotes"]
----
dependencies {
implementation 'org.springframework.experimental:spring-ai-mcp:0.2.0'
}
----
======
[NOTE]
====
The Spring AI MCP milestones are not available in the Maven Central Repository yet.
Please add tehe Spring Milestone Repository to your build file to access the Spring AI MCP artifacts:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone-local</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
----
====
The latter builds on top of mcp-core to provide some useful Spring AI abstractions, such as `McpFunctionCallback`.
Now create an `McpClient` to regester the MCP Brave server tools with your ChatClient and let the LLM call them:
[source,java]
----
// https://github.com/modelcontextprotocol/servers/tree/main/src/brave-search
var stdioParams = ServerParameters.builder("npx")
.args("-y", "@modelcontextprotocol/server-brave-search")
.addEnvVar("BRAVE_API_KEY", System.getenv("BRAVE_API_KEY"))
.build();
var mcpClient = McpClient.using(new StdioClientTransport(stdioParams)).sync();
var init = mcpClient.initialize();
var chatClient = chatClientBuilder
.defaultFunctions(mcpClient.listTools(null)
.tools()
.stream()
.map(tool -> new McpFunctionCallback(mcpClient, tool))
.toArray(McpFunctionCallback[]::new))
.build();
String response = chatClient
.prompt("Does Spring AI supports the Model Context Protocol? Please provide some references.")
.call().content();
----
== Example Demos
There is a growing link:https://github.com/modelcontextprotocol/servers[list of MCP Servers] that you can use with Spring AI MCP.
Explore these MCP examples in the link:https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol[spring-ai-examples/model-context-protocol] repository:
- link:https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/sqlite/simple[SQLite Simple] - Demonstrates LLM integration with a database
- link:https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/sqlite/chatbot[SQLite Chatbot] - Interactive chatbot with SQLite database interaction
- https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/filesystem[Filesystem] - Enables LLM interaction with local filesystem folders and files
- https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/brave[Brave] - Enables natural language interactions with Brave Search, allowing you to perform internet searches.

View File

@@ -20,9 +20,6 @@
<developerConnection>git@github.com:spring-projects/spring-ai.git</developerConnection>
</scm>
<properties>
</properties>
<dependencies>
<dependency>

View File

@@ -1,61 +0,0 @@
/*
* 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.stdio;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import io.modelcontextprotocol.client.transport.ServerParameters;
public class McpStdioConnection {
private String command;
private List<String> args = new ArrayList<>();
private Map<String, String> env;
public String getCommand() {
return this.command;
}
public void setCommand(String command) {
this.command = command;
}
public List<String> getArgs() {
return this.args;
}
public void setArgs(List<String> args) {
this.args = args;
}
public Map<String, String> getEnv() {
return this.env;
}
public void setEnv(Map<String, String> env) {
this.env = env;
}
public ServerParameters toServerParameters() {
return ServerParameters.builder(this.command).args(this.args).env(this.env).build();
}
}

View File

@@ -1,137 +0,0 @@
/*
* 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.stdio;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.client.McpSyncClient;
import io.modelcontextprotocol.client.transport.ServerParameters;
import io.modelcontextprotocol.client.transport.StdioClientTransport;
import io.modelcontextprotocol.spec.McpSchema;
import org.springframework.ai.mcp.McpSyncClientCustomizer;
import org.springframework.ai.mcp.McpToolCallback;
import org.springframework.ai.mcp.McpToolUtils;
import org.springframework.ai.tool.ToolCallback;
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;
/**
* Auto-configuration for Model Context Protocol (MCP) STDIO clients.
*
* <p>
* This configuration is responsible for setting up MCP clients that communicate with MCP
* servers through standard input/output (STDIO). It creates and configures
* {@link McpSyncClient} instances based on the provided configuration properties.
*
* <p>
* The configuration is conditionally enabled when:
* <ul>
* <li>Required classes ({@link McpSchema} and {@link McpSyncClient}) are present on the
* classpath</li>
* <li>The 'spring.ai.mcp.client.stdio.enabled' property is set to 'true'</li>
* </ul>
*
* <p>
* This auto-configuration provides:
* <ul>
* <li>A {@code List<McpSyncClient>} bean configured for STDIO communication</li>
* <li>A {@link McpSyncClientConfigurer} bean for customizing the MCP sync client
* configuration</li>
* <li>A {@code List<ToolCallback>} bean containing tool callbacks from the MCP
* clients</li>
* </ul>
*
* @author Christian Tzolov
* @since 1.0.0
* @see McpStdioClientProperties
* @see McpSyncClient
* @see McpToolCallback
*/
@AutoConfiguration
@ConditionalOnClass({ McpSchema.class, McpSyncClient.class })
@EnableConfigurationProperties(McpStdioClientProperties.class)
@ConditionalOnProperty(prefix = McpStdioClientProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true")
public class MpcStdioClientAutoConfiguration {
@Bean
public List<McpSyncClient> mcpSyncClients(McpSyncClientConfigurer mcpSyncClientConfigurer,
McpStdioClientProperties clientProperties) {
List<McpSyncClient> clients = new ArrayList<>();
for (Map.Entry<String, ServerParameters> serverParameters : clientProperties.toServerParameters().entrySet()) {
var transport = new StdioClientTransport(serverParameters.getValue());
McpSchema.Implementation clientInfo = new McpSchema.Implementation(serverParameters.getKey(),
clientProperties.getVersion());
McpClient.SyncSpec syncSpec = McpClient.sync(transport)
.clientInfo(clientInfo)
.requestTimeout(clientProperties.getRequestTimeout());
syncSpec = mcpSyncClientConfigurer.configure(serverParameters.getKey(), syncSpec);
var syncClient = syncSpec.build();
if (clientProperties.isInitialize()) {
syncClient.initialize();
}
clients.add(syncClient);
}
return clients;
}
@Bean
public List<ToolCallback> toolCallbacks(List<McpSyncClient> mcpClients) {
return McpToolUtils.getToolCallbacks(mcpClients);
}
public record ClosebleMcpSyncClients(List<McpSyncClient> clients) implements AutoCloseable {
@Override
public void close() {
this.clients.forEach(McpSyncClient::close);
}
}
@Bean
public ClosebleMcpSyncClients makeThemClosable(List<McpSyncClient> clients) {
return new ClosebleMcpSyncClients(clients);
}
@Bean
@ConditionalOnMissingBean
McpSyncClientConfigurer mcpSyncClientConfigurer(ObjectProvider<McpSyncClientCustomizer> customizerProvider) {
McpSyncClientConfigurer configurer = new McpSyncClientConfigurer();
configurer.setCustomizers(customizerProvider.orderedStream().toList());
return configurer;
}
}

View File

@@ -60,8 +60,3 @@ org.springframework.ai.autoconfigure.minimax.MiniMaxAutoConfiguration
org.springframework.ai.autoconfigure.vertexai.embedding.VertexAiEmbeddingAutoConfiguration
org.springframework.ai.autoconfigure.chat.memory.cassandra.CassandraChatMemoryAutoConfiguration
org.springframework.ai.autoconfigure.vectorstore.observation.VectorStoreObservationAutoConfiguration
org.springframework.ai.autoconfigure.mcp.server.MpcServerAutoConfiguration
org.springframework.ai.autoconfigure.mcp.server.MpcWebMvcServerAutoConfiguration
org.springframework.ai.autoconfigure.mcp.server.MpcWebFluxServerAutoConfiguration
org.springframework.ai.autoconfigure.mcp.client.stdio.MpcStdioClientAutoConfiguration

View File

@@ -1,93 +0,0 @@
/*
* 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.server;
import io.modelcontextprotocol.server.McpAsyncServer;
import io.modelcontextprotocol.server.McpSyncServer;
import io.modelcontextprotocol.server.transport.StdioServerTransport;
import io.modelcontextprotocol.spec.ServerMcpTransport;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import static org.assertj.core.api.Assertions.assertThat;
public class McpServerAutoConfigurationIT {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withPropertyValues("spring.ai.mcp.server.enabled=true")
.withConfiguration(AutoConfigurations.of(MpcServerAutoConfiguration.class));
@Test
void defaultConfiguration() {
this.contextRunner.run(context -> {
assertThat(context).hasSingleBean(McpSyncServer.class);
assertThat(context).hasSingleBean(ServerMcpTransport.class);
assertThat(context.getBean(ServerMcpTransport.class)).isInstanceOf(StdioServerTransport.class);
McpServerProperties properties = context.getBean(McpServerProperties.class);
assertThat(properties.getName()).isEqualTo("mcp-server");
assertThat(properties.getVersion()).isEqualTo("1.0.0");
assertThat(properties.getTransport()).isEqualTo(McpServerProperties.Transport.STDIO);
assertThat(properties.getType()).isEqualTo(McpServerProperties.ServerType.SYNC);
assertThat(properties.isToolChangeNotification()).isTrue();
assertThat(properties.isResourceChangeNotification()).isTrue();
assertThat(properties.isPromptChangeNotification()).isTrue();
});
}
@Test
void asyncConfiguration() {
this.contextRunner
.withPropertyValues("spring.ai.mcp.server.type=ASYNC", "spring.ai.mcp.server.name=test-server",
"spring.ai.mcp.server.version=2.0.0")
.run(context -> {
assertThat(context).hasSingleBean(McpAsyncServer.class);
assertThat(context).doesNotHaveBean(McpSyncServer.class);
McpServerProperties properties = context.getBean(McpServerProperties.class);
assertThat(properties.getName()).isEqualTo("test-server");
assertThat(properties.getVersion()).isEqualTo("2.0.0");
assertThat(properties.getType()).isEqualTo(McpServerProperties.ServerType.ASYNC);
});
}
@Test
void disabledConfiguration() {
this.contextRunner.withPropertyValues("spring.ai.mcp.server.enabled=false").run(context -> {
assertThat(context).doesNotHaveBean(McpSyncServer.class);
assertThat(context).doesNotHaveBean(McpAsyncServer.class);
assertThat(context).doesNotHaveBean(ServerMcpTransport.class);
});
}
@Test
void notificationConfiguration() {
this.contextRunner
.withPropertyValues("spring.ai.mcp.server.tool-change-notification=false",
"spring.ai.mcp.server.resource-change-notification=false",
"spring.ai.mcp.server.prompt-change-notification=false")
.run(context -> {
McpServerProperties properties = context.getBean(McpServerProperties.class);
assertThat(properties.isToolChangeNotification()).isFalse();
assertThat(properties.isResourceChangeNotification()).isFalse();
assertThat(properties.isPromptChangeNotification()).isFalse();
});
}
}

View File

@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2023-2024 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>spring-ai-mcp-client-webflux-spring-boot-starter</artifactId>
<packaging>jar</packaging>
<name>Spring AI Starter - MCP Client Webflux</name>
<description>Spring AI MCP Client WebFlux Auto Configuration</description>
<url>https://github.com/spring-projects/spring-ai</url>
<scm>
<url>https://github.com/spring-projects/spring-ai</url>
<connection>git://github.com/spring-projects/spring-ai.git</connection>
<developerConnection>git@github.com:spring-projects/spring-ai.git</developerConnection>
</scm>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-client-spring-boot-autoconfigure</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-spring-webflux</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -23,10 +23,10 @@
<version>1.0.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>spring-ai-mcp-spring-boot-starter</artifactId>
<artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId>
<packaging>jar</packaging>
<name>Spring AI Starter - MCP</name>
<description>Spring AI MCP Auto Configuration</description>
<name>Spring AI Starter - MCP Client</name>
<description>Spring AI MCP Client Auto Configuration</description>
<url>https://github.com/spring-projects/spring-ai</url>
<scm>
@@ -44,7 +44,7 @@
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-spring-boot-autoconfigure</artifactId>
<artifactId>spring-ai-mcp-client-spring-boot-autoconfigure</artifactId>
<version>${project.parent.version}</version>
</dependency>

View File

@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2023-2024 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>spring-ai-mcp-server-webflux-spring-boot-starter</artifactId>
<packaging>jar</packaging>
<name>Spring AI Starter - MCP Server Webflux</name>
<description>Spring AI MCP Server WebFlux Auto Configuration</description>
<url>https://github.com/spring-projects/spring-ai</url>
<scm>
<url>https://github.com/spring-projects/spring-ai</url>
<connection>git://github.com/spring-projects/spring-ai.git</connection>
<developerConnection>git@github.com:spring-projects/spring-ai.git</developerConnection>
</scm>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-spring-boot-autoconfigure</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-spring-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2023-2024 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>spring-ai-mcp-server-webmvc-spring-boot-starter</artifactId>
<packaging>jar</packaging>
<name>Spring AI Starter - MCP Server WebMvc</name>
<description>Spring AI MCP Server WebMvc Auto Configuration</description>
<url>https://github.com/spring-projects/spring-ai</url>
<scm>
<url>https://github.com/spring-projects/spring-ai</url>
<connection>git://github.com/spring-projects/spring-ai.git</connection>
<developerConnection>git@github.com:spring-projects/spring-ai.git</developerConnection>
</scm>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-spring-boot-autoconfigure</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2023-2024 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId>
<packaging>jar</packaging>
<name>Spring AI Starter - MCP Server</name>
<description>Spring AI MCP Server Auto Configuration</description>
<url>https://github.com/spring-projects/spring-ai</url>
<scm>
<url>https://github.com/spring-projects/spring-ai</url>
<connection>git://github.com/spring-projects/spring-ai.git</connection>
<developerConnection>git@github.com:spring-projects/spring-ai.git</developerConnection>
</scm>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-spring-boot-autoconfigure</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp</artifactId>
<version>${project.parent.version}</version>
</dependency>
</dependencies>
</project>