Skip to content

Commit 3aca3ae

Browse files
committed
refactor: Restructure MCP server auto-configuration modules for better modularity
This commit reorganizes the Spring AI MCP (Model Context Protocol) server auto-configuration modules to improve separation of concerns and modularity: **Module Restructuring:** - Rename spring-ai-autoconfigure-mcp-server → spring-ai-autoconfigure-mcp-server-common , spring-ai-autoconfigure-mcp-server-sse - Rename spring-ai-autoconfigure-mcp-streamable-server-* → spring-ai-autoconfigure-mcp-server-streamable-* - Rename spring-ai-autoconfigure-mcp-stateless-server-* → spring-ai-autoconfigure-mcp-server-stateless-* **New Common Modules:** - Extract spring-ai-autoconfigure-mcp-server-properties for shared configuration properties - Create spring-ai-autoconfigure-mcp-server-common for shared auto-configuration logic between STDIO, SSE and Streamable-HTTP servers **Key Changes:** - Move McpServerProperties and related properties classes to dedicated properties module shared with all server modules. - Extract McpServerChangeNotificationProperties and McpServerStreamableHttpProperties - Refactor auto-configuration classes to use common base configurations - Update package names from *.autoconfigure to *.common.autoconfigure.properties - Consolidate tool callback converter logic into common module **Configuration Updates:** - Separate SSE-specific properties from general server properties - Improve conditional configuration with new condition classes - Update dependency references across all affected modules and starters - Maintain backward compatibility for existing property configurations **Impact:** - Better separation between different MCP server transport mechanisms (SSE, Streamable HTTP, Stateless) - Reduced code duplication across auto-configuration modules - Cleaner dependency management and module boundaries - Improved maintainability and extensibility for future MCP server features All existing functionality is preserved while providing a more modular and maintainable architecture for MCP server auto-configuration.
1 parent ab826e4 commit 3aca3ae

File tree

64 files changed

+839
-2363
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+839
-2363
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xmlns="http://maven.apache.org/POM/4.0.0"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
<parent>
7+
<groupId>org.springframework.ai</groupId>
8+
<artifactId>spring-ai-parent</artifactId>
9+
<version>1.1.0-SNAPSHOT</version>
10+
<relativePath>../../../pom.xml</relativePath>
11+
</parent>
12+
<artifactId>spring-ai-autoconfigure-mcp-server-common</artifactId>
13+
<packaging>jar</packaging>
14+
<name>Spring AI MCP Server Common Auto Configuration for STDIO, SSE and Streamable-HTTP</name>
15+
<description>Spring AI MCP Server Common Auto Configuration for STDIO, SSE and Streamable-HTTP</description>
16+
<url>https://github.com/spring-projects/spring-ai</url>
17+
18+
<scm>
19+
<url>https://github.com/spring-projects/spring-ai</url>
20+
<connection>git://github.com/spring-projects/spring-ai.git</connection>
21+
<developerConnection>[email protected]:spring-projects/spring-ai.git</developerConnection>
22+
</scm>
23+
24+
<dependencies>
25+
26+
<dependency>
27+
<groupId>org.springframework.ai</groupId>
28+
<artifactId>spring-ai-autoconfigure-mcp-server-properties</artifactId>
29+
<version>${project.parent.version}</version>
30+
</dependency>
31+
32+
<dependency>
33+
<groupId>org.springframework.boot</groupId>
34+
<artifactId>spring-boot-starter</artifactId>
35+
</dependency>
36+
37+
<dependency>
38+
<groupId>org.springframework.ai</groupId>
39+
<artifactId>spring-ai-mcp</artifactId>
40+
<version>${project.parent.version}</version>
41+
<optional>true</optional>
42+
</dependency>
43+
44+
<dependency>
45+
<groupId>org.springframework</groupId>
46+
<artifactId>spring-web</artifactId>
47+
<optional>true</optional>
48+
</dependency>
49+
50+
<dependency>
51+
<groupId>org.springframework.boot</groupId>
52+
<artifactId>spring-boot-configuration-processor</artifactId>
53+
<optional>true</optional>
54+
</dependency>
55+
56+
<dependency>
57+
<groupId>org.springframework.boot</groupId>
58+
<artifactId>spring-boot-autoconfigure-processor</artifactId>
59+
<optional>true</optional>
60+
</dependency>
61+
62+
<!-- test dependencies -->
63+
64+
<dependency>
65+
<groupId>org.springframework.ai</groupId>
66+
<artifactId>spring-ai-test</artifactId>
67+
<version>${project.parent.version}</version>
68+
<scope>test</scope>
69+
</dependency>
70+
71+
<dependency>
72+
<groupId>net.javacrumbs.json-unit</groupId>
73+
<artifactId>json-unit-assertj</artifactId>
74+
<version>${json-unit-assertj.version}</version>
75+
<scope>test</scope>
76+
</dependency>
77+
78+
</dependencies>
79+
80+
</project>
Lines changed: 74 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,26 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.ai.mcp.server.autoconfigure;
17+
package org.springframework.ai.mcp.server.common.autoconfigure;
1818

1919
import java.util.ArrayList;
2020
import java.util.List;
2121
import java.util.function.BiConsumer;
2222
import java.util.function.BiFunction;
2323

24-
import org.springframework.ai.tool.ToolCallback;
24+
import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerChangeNotificationProperties;
25+
import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties;
26+
import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerStreamableHttpProperties;
2527
import org.springframework.beans.factory.ObjectProvider;
2628
import org.springframework.boot.autoconfigure.AutoConfiguration;
2729
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
30+
import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
2831
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2932
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
3033
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
3134
import org.springframework.boot.context.properties.EnableConfigurationProperties;
3235
import org.springframework.context.annotation.Bean;
36+
import org.springframework.context.annotation.Conditional;
3337
import org.springframework.core.env.Environment;
3438
import org.springframework.core.log.LogAccessor;
3539
import org.springframework.util.CollectionUtils;
@@ -54,70 +58,56 @@
5458
import io.modelcontextprotocol.spec.McpSchema;
5559
import io.modelcontextprotocol.spec.McpSchema.Implementation;
5660
import io.modelcontextprotocol.spec.McpServerTransportProvider;
61+
import io.modelcontextprotocol.spec.McpServerTransportProviderBase;
62+
import io.modelcontextprotocol.spec.McpStreamableServerTransportProvider;
5763
import reactor.core.publisher.Mono;
5864

5965
/**
6066
* {@link EnableAutoConfiguration Auto-configuration} for the Model Context Protocol (MCP)
6167
* Server.
6268
* <p>
63-
* This configuration class sets up the core MCP server components with support for both
64-
* synchronous and asynchronous operation modes. The server type is controlled through the
65-
* {@code spring.ai.mcp.server.type} property, defaulting to SYNC mode.
66-
* <p>
67-
* Core features and capabilities include:
68-
* <ul>
69-
* <li>Tools: Extensible tool registration system supporting both sync and async
70-
* execution</li>
71-
* <li>Resources: Static and dynamic resource management with optional change
72-
* notifications</li>
73-
* <li>Prompts: Configurable prompt templates with change notification support</li>
74-
* <li>Transport: Flexible transport layer with built-in support for:
75-
* <ul>
76-
* <li>STDIO (default): Standard input/output based communication</li>
77-
* <li>WebMvc: HTTP-based transport when Spring MVC is available</li>
78-
* <li>WebFlux: Reactive transport when Spring WebFlux is available</li>
79-
* </ul>
80-
* </li>
81-
* </ul>
82-
* <p>
83-
* The configuration is activated when:
84-
* <ul>
85-
* <li>The required MCP classes ({@link McpSchema} and {@link McpSyncServer}) are on the
86-
* classpath</li>
87-
* <li>The {@code spring.ai.mcp.server.enabled} property is true (default)</li>
88-
* </ul>
89-
* <p>
90-
* Server configuration is managed through {@link McpServerProperties} with support for:
91-
* <ul>
92-
* <li>Server identification (name, version)</li>
93-
* <li>Transport selection</li>
94-
* <li>Change notification settings for tools, resources, and prompts</li>
95-
* <li>Sync/Async operation mode selection</li>
96-
* </ul>
97-
* <p>
98-
* WebMvc transport support is provided separately by
99-
* {@link McpWebMvcServerAutoConfiguration}.
10069
*
10170
* @author Christian Tzolov
10271
* @since 1.0.0
10372
* @see McpServerProperties
104-
* @see McpWebMvcServerAutoConfiguration
105-
* @see McpWebFluxServerAutoConfiguration
106-
* @see ToolCallback
10773
*/
108-
@AutoConfiguration(after = { ToolCallbackConverterAutoConfiguration.class, McpWebMvcServerAutoConfiguration.class,
109-
McpWebFluxServerAutoConfiguration.class })
74+
@AutoConfiguration(afterName = {
75+
"org.springframework.ai.mcp.server.common.autoconfigure.ToolCallbackConverterAutoConfiguration",
76+
"org.springframework.ai.mcp.server.autoconfigure.McpServerSseWebFluxAutoConfiguration",
77+
"org.springframework.ai.mcp.server.autoconfigure.McpServerSseWebMvcAutoConfiguration",
78+
"org.springframework.ai.mcp.server.streamable.webflux.autoconfigure.McpServerStreamableHttpWebMvcAutoConfiguration",
79+
"org.springframework.ai.mcp.server.streamable.webflux.autoconfigure.McpServerStreamableHttpWebFluxAutoConfiguration" })
11080
@ConditionalOnClass({ McpSchema.class, McpSyncServer.class })
111-
@EnableConfigurationProperties(McpServerProperties.class)
81+
@EnableConfigurationProperties({ McpServerProperties.class, McpServerChangeNotificationProperties.class })
11282
@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
11383
matchIfMissing = true)
11484
public class McpServerAutoConfiguration {
11585

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

88+
public static class EnabledNonStatlessServerCondition extends AllNestedConditions {
89+
90+
public EnabledNonStatlessServerCondition() {
91+
super(ConfigurationPhase.PARSE_CONFIGURATION);
92+
}
93+
94+
@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
95+
matchIfMissing = true)
96+
static class McpServerEnabledCondition {
97+
98+
}
99+
100+
@ConditionalOnProperty(prefix = McpServerStreamableHttpProperties.CONFIG_PREFIX, name = "stateless",
101+
havingValue = "false", matchIfMissing = true)
102+
static class StatelessEnabledCondition {
103+
104+
}
105+
106+
}
107+
118108
@Bean
119109
@ConditionalOnMissingBean
120-
public McpServerTransportProvider stdioServerTransport() {
110+
public McpServerTransportProviderBase stdioServerTransport() {
121111
return new StdioServerTransportProvider();
122112
}
123113

@@ -130,8 +120,9 @@ public McpSchema.ServerCapabilities.Builder capabilitiesBuilder() {
130120
@Bean
131121
@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "type", havingValue = "SYNC",
132122
matchIfMissing = true)
133-
public McpSyncServer mcpSyncServer(McpServerTransportProvider transportProvider,
123+
public McpSyncServer mcpSyncServer(McpServerTransportProviderBase transportProvider,
134124
McpSchema.ServerCapabilities.Builder capabilitiesBuilder, McpServerProperties serverProperties,
125+
McpServerChangeNotificationProperties changeNotificationProperties,
135126
ObjectProvider<List<SyncToolSpecification>> tools,
136127
ObjectProvider<List<SyncResourceSpecification>> resources,
137128
ObjectProvider<List<SyncPromptSpecification>> prompts,
@@ -143,12 +134,20 @@ public McpSyncServer mcpSyncServer(McpServerTransportProvider transportProvider,
143134
serverProperties.getVersion());
144135

145136
// Create the server with both tool and resource capabilities
146-
SyncSpecification serverBuilder = McpServer.sync(transportProvider).serverInfo(serverInfo);
137+
SyncSpecification<?> serverBuilder;
138+
if (transportProvider instanceof McpStreamableServerTransportProvider) {
139+
serverBuilder = McpServer.sync((McpStreamableServerTransportProvider) transportProvider);
140+
}
141+
else {
142+
serverBuilder = McpServer.sync((McpServerTransportProvider) transportProvider);
143+
}
144+
serverBuilder.serverInfo(serverInfo);
147145

148146
// Tools
149147
if (serverProperties.getCapabilities().isTool()) {
150-
logger.info("Enable tools capabilities, notification: " + serverProperties.isToolChangeNotification());
151-
capabilitiesBuilder.tools(serverProperties.isToolChangeNotification());
148+
logger.info("Enable tools capabilities, notification: "
149+
+ changeNotificationProperties.isToolChangeNotification());
150+
capabilitiesBuilder.tools(changeNotificationProperties.isToolChangeNotification());
152151

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

162161
// Resources
163162
if (serverProperties.getCapabilities().isResource()) {
164-
logger.info(
165-
"Enable resources capabilities, notification: " + serverProperties.isResourceChangeNotification());
166-
capabilitiesBuilder.resources(false, serverProperties.isResourceChangeNotification());
163+
logger.info("Enable resources capabilities, notification: "
164+
+ changeNotificationProperties.isResourceChangeNotification());
165+
capabilitiesBuilder.resources(false, changeNotificationProperties.isResourceChangeNotification());
167166

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

175174
// Prompts
176175
if (serverProperties.getCapabilities().isPrompt()) {
177-
logger.info("Enable prompts capabilities, notification: " + serverProperties.isPromptChangeNotification());
178-
capabilitiesBuilder.prompts(serverProperties.isPromptChangeNotification());
176+
logger.info("Enable prompts capabilities, notification: "
177+
+ changeNotificationProperties.isPromptChangeNotification());
178+
capabilitiesBuilder.prompts(changeNotificationProperties.isPromptChangeNotification());
179179

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

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

233234
// Create the server with both tool and resource capabilities
234-
AsyncSpecification serverBuilder = McpServer.async(transportProvider).serverInfo(serverInfo);
235+
AsyncSpecification<?> serverBuilder;
236+
if (transportProvider instanceof McpStreamableServerTransportProvider) {
237+
serverBuilder = McpServer.async((McpStreamableServerTransportProvider) transportProvider);
238+
}
239+
else {
240+
serverBuilder = McpServer.async((McpServerTransportProvider) transportProvider);
241+
}
242+
serverBuilder.serverInfo(serverInfo);
235243

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

241-
logger.info("Enable tools capabilities, notification: " + serverProperties.isToolChangeNotification());
242-
capabilitiesBuilder.tools(serverProperties.isToolChangeNotification());
249+
logger.info("Enable tools capabilities, notification: "
250+
+ changeNotificationProperties.isToolChangeNotification());
251+
capabilitiesBuilder.tools(changeNotificationProperties.isToolChangeNotification());
243252

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

250259
// Resources
251260
if (serverProperties.getCapabilities().isResource()) {
252-
logger.info(
253-
"Enable resources capabilities, notification: " + serverProperties.isResourceChangeNotification());
254-
capabilitiesBuilder.resources(false, serverProperties.isResourceChangeNotification());
261+
logger.info("Enable resources capabilities, notification: "
262+
+ changeNotificationProperties.isResourceChangeNotification());
263+
capabilitiesBuilder.resources(false, changeNotificationProperties.isResourceChangeNotification());
255264

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

263272
// Prompts
264273
if (serverProperties.getCapabilities().isPrompt()) {
265-
logger.info("Enable prompts capabilities, notification: " + serverProperties.isPromptChangeNotification());
266-
capabilitiesBuilder.prompts(serverProperties.isPromptChangeNotification());
274+
logger.info("Enable prompts capabilities, notification: "
275+
+ changeNotificationProperties.isPromptChangeNotification());
276+
capabilitiesBuilder.prompts(changeNotificationProperties.isPromptChangeNotification());
267277
List<AsyncPromptSpecification> promptSpecifications = prompts.stream().flatMap(List::stream).toList();
268278

269279
if (!CollectionUtils.isEmpty(promptSpecifications)) {
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.ai.mcp.server.autoconfigure;
17+
package org.springframework.ai.mcp.server.common.autoconfigure;
1818

19+
import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties;
1920
import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
2021
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
2122

0 commit comments

Comments
 (0)