Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?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-parent</artifactId>
<version>1.1.0-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<artifactId>spring-ai-autoconfigure-mcp-server-common</artifactId>
<packaging>jar</packaging>
<name>Spring AI MCP Server Common Auto Configuration for STDIO, SSE and Streamable-HTTP</name>
<description>Spring AI MCP Server Common Auto Configuration for STDIO, SSE and Streamable-HTTP</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>[email protected]:spring-projects/spring-ai.git</developerConnection>
</scm>

<dependencies>

<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-autoconfigure-mcp-server-properties</artifactId>
<version>${project.parent.version}</version>
</dependency>

<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>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure-processor</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>net.javacrumbs.json-unit</groupId>
<artifactId>json-unit-assertj</artifactId>
<version>${json-unit-assertj.version}</version>
<scope>test</scope>
</dependency>

</dependencies>

</project>
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,26 @@
* limitations under the License.
*/

package org.springframework.ai.mcp.server.autoconfigure;
package org.springframework.ai.mcp.server.common.autoconfigure;

import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;

import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerChangeNotificationProperties;
import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties;
import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerStreamableHttpProperties;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
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.context.annotation.Conditional;
import org.springframework.core.env.Environment;
import org.springframework.core.log.LogAccessor;
import org.springframework.util.CollectionUtils;
Expand All @@ -54,70 +58,56 @@
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.McpSchema.Implementation;
import io.modelcontextprotocol.spec.McpServerTransportProvider;
import io.modelcontextprotocol.spec.McpServerTransportProviderBase;
import io.modelcontextprotocol.spec.McpStreamableServerTransportProvider;
import reactor.core.publisher.Mono;

/**
* {@link EnableAutoConfiguration Auto-configuration} for the Model Context Protocol (MCP)
* Server.
* <p>
* This configuration class sets up the core MCP server components with support for both
* synchronous and asynchronous operation modes. The server type is controlled through the
* {@code spring.ai.mcp.server.type} property, defaulting to SYNC mode.
* <p>
* Core features and capabilities include:
* <ul>
* <li>Tools: Extensible tool registration system supporting both sync and async
* execution</li>
* <li>Resources: Static and dynamic resource management with optional change
* notifications</li>
* <li>Prompts: Configurable prompt templates with change notification support</li>
* <li>Transport: Flexible transport layer with built-in support for:
* <ul>
* <li>STDIO (default): Standard input/output based communication</li>
* <li>WebMvc: HTTP-based transport when Spring MVC is available</li>
* <li>WebFlux: Reactive transport when Spring WebFlux is available</li>
* </ul>
* </li>
* </ul>
* <p>
* The configuration is activated when:
* <ul>
* <li>The required MCP classes ({@link McpSchema} and {@link McpSyncServer}) are on the
* classpath</li>
* <li>The {@code spring.ai.mcp.server.enabled} property is true (default)</li>
* </ul>
* <p>
* Server configuration is managed through {@link McpServerProperties} with support for:
* <ul>
* <li>Server identification (name, version)</li>
* <li>Transport selection</li>
* <li>Change notification settings for tools, resources, and prompts</li>
* <li>Sync/Async operation mode selection</li>
* </ul>
* <p>
* WebMvc transport support is provided separately by
* {@link McpWebMvcServerAutoConfiguration}.
*
* @author Christian Tzolov
* @since 1.0.0
* @see McpServerProperties
* @see McpWebMvcServerAutoConfiguration
* @see McpWebFluxServerAutoConfiguration
* @see ToolCallback
*/
@AutoConfiguration(after = { ToolCallbackConverterAutoConfiguration.class, McpWebMvcServerAutoConfiguration.class,
McpWebFluxServerAutoConfiguration.class })
@AutoConfiguration(afterName = {
"org.springframework.ai.mcp.server.common.autoconfigure.ToolCallbackConverterAutoConfiguration",
"org.springframework.ai.mcp.server.autoconfigure.McpServerSseWebFluxAutoConfiguration",
"org.springframework.ai.mcp.server.autoconfigure.McpServerSseWebMvcAutoConfiguration",
"org.springframework.ai.mcp.server.streamable.webflux.autoconfigure.McpServerStreamableHttpWebMvcAutoConfiguration",
"org.springframework.ai.mcp.server.streamable.webflux.autoconfigure.McpServerStreamableHttpWebFluxAutoConfiguration" })
@ConditionalOnClass({ McpSchema.class, McpSyncServer.class })
@EnableConfigurationProperties(McpServerProperties.class)
@EnableConfigurationProperties({ McpServerProperties.class, McpServerChangeNotificationProperties.class })
@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
matchIfMissing = true)
public class McpServerAutoConfiguration {

private static final LogAccessor logger = new LogAccessor(McpServerAutoConfiguration.class);

public static class EnabledNonStatlessServerCondition extends AllNestedConditions {

public EnabledNonStatlessServerCondition() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}

@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
matchIfMissing = true)
static class McpServerEnabledCondition {

}

@ConditionalOnProperty(prefix = McpServerStreamableHttpProperties.CONFIG_PREFIX, name = "stateless",
havingValue = "false", matchIfMissing = true)
static class StatelessEnabledCondition {

}

}

@Bean
@ConditionalOnMissingBean
public McpServerTransportProvider stdioServerTransport() {
public McpServerTransportProviderBase stdioServerTransport() {
return new StdioServerTransportProvider();
}

Expand All @@ -130,8 +120,9 @@ public McpSchema.ServerCapabilities.Builder capabilitiesBuilder() {
@Bean
@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "type", havingValue = "SYNC",
matchIfMissing = true)
public McpSyncServer mcpSyncServer(McpServerTransportProvider transportProvider,
public McpSyncServer mcpSyncServer(McpServerTransportProviderBase transportProvider,
McpSchema.ServerCapabilities.Builder capabilitiesBuilder, McpServerProperties serverProperties,
McpServerChangeNotificationProperties changeNotificationProperties,
ObjectProvider<List<SyncToolSpecification>> tools,
ObjectProvider<List<SyncResourceSpecification>> resources,
ObjectProvider<List<SyncPromptSpecification>> prompts,
Expand All @@ -143,12 +134,20 @@ public McpSyncServer mcpSyncServer(McpServerTransportProvider transportProvider,
serverProperties.getVersion());

// Create the server with both tool and resource capabilities
SyncSpecification serverBuilder = McpServer.sync(transportProvider).serverInfo(serverInfo);
SyncSpecification<?> serverBuilder;
if (transportProvider instanceof McpStreamableServerTransportProvider) {
serverBuilder = McpServer.sync((McpStreamableServerTransportProvider) transportProvider);
}
else {
serverBuilder = McpServer.sync((McpServerTransportProvider) transportProvider);
}
serverBuilder.serverInfo(serverInfo);

// Tools
if (serverProperties.getCapabilities().isTool()) {
logger.info("Enable tools capabilities, notification: " + serverProperties.isToolChangeNotification());
capabilitiesBuilder.tools(serverProperties.isToolChangeNotification());
logger.info("Enable tools capabilities, notification: "
+ changeNotificationProperties.isToolChangeNotification());
capabilitiesBuilder.tools(changeNotificationProperties.isToolChangeNotification());

List<SyncToolSpecification> toolSpecifications = new ArrayList<>(
tools.stream().flatMap(List::stream).toList());
Expand All @@ -161,9 +160,9 @@ public McpSyncServer mcpSyncServer(McpServerTransportProvider transportProvider,

// Resources
if (serverProperties.getCapabilities().isResource()) {
logger.info(
"Enable resources capabilities, notification: " + serverProperties.isResourceChangeNotification());
capabilitiesBuilder.resources(false, serverProperties.isResourceChangeNotification());
logger.info("Enable resources capabilities, notification: "
+ changeNotificationProperties.isResourceChangeNotification());
capabilitiesBuilder.resources(false, changeNotificationProperties.isResourceChangeNotification());

List<SyncResourceSpecification> resourceSpecifications = resources.stream().flatMap(List::stream).toList();
if (!CollectionUtils.isEmpty(resourceSpecifications)) {
Expand All @@ -174,8 +173,9 @@ public McpSyncServer mcpSyncServer(McpServerTransportProvider transportProvider,

// Prompts
if (serverProperties.getCapabilities().isPrompt()) {
logger.info("Enable prompts capabilities, notification: " + serverProperties.isPromptChangeNotification());
capabilitiesBuilder.prompts(serverProperties.isPromptChangeNotification());
logger.info("Enable prompts capabilities, notification: "
+ changeNotificationProperties.isPromptChangeNotification());
capabilitiesBuilder.prompts(changeNotificationProperties.isPromptChangeNotification());

List<SyncPromptSpecification> promptSpecifications = prompts.stream().flatMap(List::stream).toList();
if (!CollectionUtils.isEmpty(promptSpecifications)) {
Expand Down Expand Up @@ -219,8 +219,9 @@ public McpSyncServer mcpSyncServer(McpServerTransportProvider transportProvider,

@Bean
@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "type", havingValue = "ASYNC")
public McpAsyncServer mcpAsyncServer(McpServerTransportProvider transportProvider,
public McpAsyncServer mcpAsyncServer(McpServerTransportProviderBase transportProvider,
McpSchema.ServerCapabilities.Builder capabilitiesBuilder, McpServerProperties serverProperties,
McpServerChangeNotificationProperties changeNotificationProperties,
ObjectProvider<List<AsyncToolSpecification>> tools,
ObjectProvider<List<AsyncResourceSpecification>> resources,
ObjectProvider<List<AsyncPromptSpecification>> prompts,
Expand All @@ -231,15 +232,23 @@ public McpAsyncServer mcpAsyncServer(McpServerTransportProvider transportProvide
serverProperties.getVersion());

// Create the server with both tool and resource capabilities
AsyncSpecification serverBuilder = McpServer.async(transportProvider).serverInfo(serverInfo);
AsyncSpecification<?> serverBuilder;
if (transportProvider instanceof McpStreamableServerTransportProvider) {
serverBuilder = McpServer.async((McpStreamableServerTransportProvider) transportProvider);
}
else {
serverBuilder = McpServer.async((McpServerTransportProvider) transportProvider);
}
serverBuilder.serverInfo(serverInfo);

// Tools
if (serverProperties.getCapabilities().isTool()) {
List<AsyncToolSpecification> toolSpecifications = new ArrayList<>(
tools.stream().flatMap(List::stream).toList());

logger.info("Enable tools capabilities, notification: " + serverProperties.isToolChangeNotification());
capabilitiesBuilder.tools(serverProperties.isToolChangeNotification());
logger.info("Enable tools capabilities, notification: "
+ changeNotificationProperties.isToolChangeNotification());
capabilitiesBuilder.tools(changeNotificationProperties.isToolChangeNotification());

if (!CollectionUtils.isEmpty(toolSpecifications)) {
serverBuilder.tools(toolSpecifications);
Expand All @@ -249,9 +258,9 @@ public McpAsyncServer mcpAsyncServer(McpServerTransportProvider transportProvide

// Resources
if (serverProperties.getCapabilities().isResource()) {
logger.info(
"Enable resources capabilities, notification: " + serverProperties.isResourceChangeNotification());
capabilitiesBuilder.resources(false, serverProperties.isResourceChangeNotification());
logger.info("Enable resources capabilities, notification: "
+ changeNotificationProperties.isResourceChangeNotification());
capabilitiesBuilder.resources(false, changeNotificationProperties.isResourceChangeNotification());

List<AsyncResourceSpecification> resourceSpecifications = resources.stream().flatMap(List::stream).toList();
if (!CollectionUtils.isEmpty(resourceSpecifications)) {
Expand All @@ -262,8 +271,9 @@ public McpAsyncServer mcpAsyncServer(McpServerTransportProvider transportProvide

// Prompts
if (serverProperties.getCapabilities().isPrompt()) {
logger.info("Enable prompts capabilities, notification: " + serverProperties.isPromptChangeNotification());
capabilitiesBuilder.prompts(serverProperties.isPromptChangeNotification());
logger.info("Enable prompts capabilities, notification: "
+ changeNotificationProperties.isPromptChangeNotification());
capabilitiesBuilder.prompts(changeNotificationProperties.isPromptChangeNotification());
List<AsyncPromptSpecification> promptSpecifications = prompts.stream().flatMap(List::stream).toList();

if (!CollectionUtils.isEmpty(promptSpecifications)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@
* limitations under the License.
*/

package org.springframework.ai.mcp.server.autoconfigure;
package org.springframework.ai.mcp.server.common.autoconfigure;

import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties;
import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;

Expand Down
Loading