From d1d56705e0ea5d5b01e9f1e3ef601ccb5ee40e8f Mon Sep 17 00:00:00 2001 From: yinh Date: Fri, 22 Aug 2025 15:20:14 +0800 Subject: [PATCH 1/3] refactor: consolidate MCP server configuration properties into unified structure Replace 4 separate configuration classes with unified McpServerProperties: - Consolidate all MCP server config under spring.ai.mcp.server prefix - Add nested classes for protocol-specific settings (SSE, Streamable, Stateless) - Update configuration paths and test cases accordingly - Follow Spring Boot ServerProperties pattern This eliminates configuration fragmentation and provides single entry point for users. Signed-off-by: yinh --- .../McpServerAutoConfiguration.java | 31 ++- ...McpServerChangeNotificationProperties.java | 79 ------ .../properties/McpServerProperties.java | 232 ++++++++++++++++++ .../properties/McpServerSseProperties.java | 87 ------- .../McpServerStreamableHttpProperties.java | 69 ------ .../McpServerAutoConfigurationIT.java | 76 +++--- ...lCallbackConverterAutoConfigurationIT.java | 2 +- ...lCallbackConverterAutoConfigurationIT.java | 2 +- .../McpServerSseWebFluxAutoConfiguration.java | 14 +- ...rverStatelessWebFluxAutoConfiguration.java | 9 +- ...treamableHttpWebFluxAutoConfiguration.java | 12 +- ...cpServerSseWebFluxAutoConfigurationIT.java | 20 +- ...erStatelessWebFluxAutoConfigurationIT.java | 33 +-- ...rStreamableWebFluxAutoConfigurationIT.java | 28 +-- .../StatelessWebClientWebFluxServerIT.java | 8 +- .../StreamableWebClientWebFluxServerIT.java | 10 +- .../McpServerSseWebMvcAutoConfiguration.java | 14 +- ...erverStatelessWebMvcAutoConfiguration.java | 9 +- ...StreamableHttpWebMvcAutoConfiguration.java | 12 +- ...McpServerSseWebMvcAutoConfigurationIT.java | 26 +- ...verStatelessWebMvcAutoConfigurationIT.java | 35 +-- ...erStreamableWebMvcAutoConfigurationIT.java | 28 +-- 22 files changed, 383 insertions(+), 453 deletions(-) delete mode 100644 auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/properties/McpServerChangeNotificationProperties.java delete mode 100644 auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/properties/McpServerSseProperties.java delete mode 100644 auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/properties/McpServerStreamableHttpProperties.java diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerAutoConfiguration.java index c280cc684a7..abb69331ccb 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerAutoConfiguration.java @@ -44,7 +44,6 @@ import io.modelcontextprotocol.spec.McpStreamableServerTransportProvider; import reactor.core.publisher.Mono; -import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerChangeNotificationProperties; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; @@ -78,7 +77,7 @@ "org.springframework.ai.mcp.server.autoconfigure.McpServerStreamableHttpWebMvcAutoConfiguration", "org.springframework.ai.mcp.server.autoconfigure.McpServerStreamableHttpWebFluxAutoConfiguration" }) @ConditionalOnClass({ McpSchema.class }) -@EnableConfigurationProperties({ McpServerProperties.class, McpServerChangeNotificationProperties.class }) +@EnableConfigurationProperties({ McpServerProperties.class }) @ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true) @Conditional(McpServerAutoConfiguration.NonStatlessServerCondition.class) @@ -103,7 +102,6 @@ public McpSchema.ServerCapabilities.Builder capabilitiesBuilder() { matchIfMissing = true) public McpSyncServer mcpSyncServer(McpServerTransportProviderBase transportProvider, McpSchema.ServerCapabilities.Builder capabilitiesBuilder, McpServerProperties serverProperties, - McpServerChangeNotificationProperties changeNotificationProperties, ObjectProvider> tools, ObjectProvider> resources, ObjectProvider> prompts, @@ -127,8 +125,8 @@ public McpSyncServer mcpSyncServer(McpServerTransportProviderBase transportProvi // Tools if (serverProperties.getCapabilities().isTool()) { logger.info("Enable tools capabilities, notification: " - + changeNotificationProperties.isToolChangeNotification()); - capabilitiesBuilder.tools(changeNotificationProperties.isToolChangeNotification()); + + serverProperties.getToolChangeNotification().isToolChangeNotification()); + capabilitiesBuilder.tools(serverProperties.getToolChangeNotification().isToolChangeNotification()); List toolSpecifications = new ArrayList<>( tools.stream().flatMap(List::stream).toList()); @@ -142,8 +140,9 @@ public McpSyncServer mcpSyncServer(McpServerTransportProviderBase transportProvi // Resources if (serverProperties.getCapabilities().isResource()) { logger.info("Enable resources capabilities, notification: " - + changeNotificationProperties.isResourceChangeNotification()); - capabilitiesBuilder.resources(false, changeNotificationProperties.isResourceChangeNotification()); + + serverProperties.getToolChangeNotification().isResourceChangeNotification()); + capabilitiesBuilder.resources(false, + serverProperties.getToolChangeNotification().isResourceChangeNotification()); List resourceSpecifications = resources.stream().flatMap(List::stream).toList(); if (!CollectionUtils.isEmpty(resourceSpecifications)) { @@ -155,8 +154,8 @@ public McpSyncServer mcpSyncServer(McpServerTransportProviderBase transportProvi // Prompts if (serverProperties.getCapabilities().isPrompt()) { logger.info("Enable prompts capabilities, notification: " - + changeNotificationProperties.isPromptChangeNotification()); - capabilitiesBuilder.prompts(changeNotificationProperties.isPromptChangeNotification()); + + serverProperties.getToolChangeNotification().isPromptChangeNotification()); + capabilitiesBuilder.prompts(serverProperties.getToolChangeNotification().isPromptChangeNotification()); List promptSpecifications = prompts.stream().flatMap(List::stream).toList(); if (!CollectionUtils.isEmpty(promptSpecifications)) { @@ -202,7 +201,6 @@ public McpSyncServer mcpSyncServer(McpServerTransportProviderBase transportProvi @ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "type", havingValue = "ASYNC") public McpAsyncServer mcpAsyncServer(McpServerTransportProviderBase transportProvider, McpSchema.ServerCapabilities.Builder capabilitiesBuilder, McpServerProperties serverProperties, - McpServerChangeNotificationProperties changeNotificationProperties, ObjectProvider> tools, ObjectProvider> resources, ObjectProvider> prompts, @@ -228,8 +226,8 @@ public McpAsyncServer mcpAsyncServer(McpServerTransportProviderBase transportPro tools.stream().flatMap(List::stream).toList()); logger.info("Enable tools capabilities, notification: " - + changeNotificationProperties.isToolChangeNotification()); - capabilitiesBuilder.tools(changeNotificationProperties.isToolChangeNotification()); + + serverProperties.getToolChangeNotification().isToolChangeNotification()); + capabilitiesBuilder.tools(serverProperties.getToolChangeNotification().isToolChangeNotification()); if (!CollectionUtils.isEmpty(toolSpecifications)) { serverBuilder.tools(toolSpecifications); @@ -240,8 +238,9 @@ public McpAsyncServer mcpAsyncServer(McpServerTransportProviderBase transportPro // Resources if (serverProperties.getCapabilities().isResource()) { logger.info("Enable resources capabilities, notification: " - + changeNotificationProperties.isResourceChangeNotification()); - capabilitiesBuilder.resources(false, changeNotificationProperties.isResourceChangeNotification()); + + serverProperties.getToolChangeNotification().isResourceChangeNotification()); + capabilitiesBuilder.resources(false, + serverProperties.getToolChangeNotification().isResourceChangeNotification()); List resourceSpecifications = resources.stream().flatMap(List::stream).toList(); if (!CollectionUtils.isEmpty(resourceSpecifications)) { @@ -253,8 +252,8 @@ public McpAsyncServer mcpAsyncServer(McpServerTransportProviderBase transportPro // Prompts if (serverProperties.getCapabilities().isPrompt()) { logger.info("Enable prompts capabilities, notification: " - + changeNotificationProperties.isPromptChangeNotification()); - capabilitiesBuilder.prompts(changeNotificationProperties.isPromptChangeNotification()); + + serverProperties.getToolChangeNotification().isPromptChangeNotification()); + capabilitiesBuilder.prompts(serverProperties.getToolChangeNotification().isPromptChangeNotification()); List promptSpecifications = prompts.stream().flatMap(List::stream).toList(); if (!CollectionUtils.isEmpty(promptSpecifications)) { diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/properties/McpServerChangeNotificationProperties.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/properties/McpServerChangeNotificationProperties.java deleted file mode 100644 index 4afc54a2248..00000000000 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/properties/McpServerChangeNotificationProperties.java +++ /dev/null @@ -1,79 +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.server.common.autoconfigure.properties; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * @author Christian Tzolov - */ -@ConfigurationProperties(McpServerChangeNotificationProperties.CONFIG_PREFIX) -public class McpServerChangeNotificationProperties { - - public static final String CONFIG_PREFIX = "spring.ai.mcp.server"; - - /** - * Enable/disable notifications for resource changes. Only relevant for MCP servers - * with resource capabilities. - *

- * When enabled, the server will notify clients when resources are added, updated, or - * removed. - */ - private boolean resourceChangeNotification = true; - - /** - * Enable/disable notifications for tool changes. Only relevant for MCP servers with - * tool capabilities. - *

- * When enabled, the server will notify clients when tools are registered or - * unregistered. - */ - private boolean toolChangeNotification = true; - - /** - * Enable/disable notifications for prompt changes. Only relevant for MCP servers with - * prompt capabilities. - *

- * When enabled, the server will notify clients when prompt templates are modified. - */ - private boolean promptChangeNotification = true; - - public boolean isResourceChangeNotification() { - return this.resourceChangeNotification; - } - - public void setResourceChangeNotification(boolean resourceChangeNotification) { - this.resourceChangeNotification = resourceChangeNotification; - } - - public boolean isToolChangeNotification() { - return this.toolChangeNotification; - } - - public void setToolChangeNotification(boolean toolChangeNotification) { - this.toolChangeNotification = toolChangeNotification; - } - - public boolean isPromptChangeNotification() { - return this.promptChangeNotification; - } - - public void setPromptChangeNotification(boolean promptChangeNotification) { - this.promptChangeNotification = promptChangeNotification; - } - -} diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/properties/McpServerProperties.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/properties/McpServerProperties.java index 9ea863999c0..9ad771fac79 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/properties/McpServerProperties.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/properties/McpServerProperties.java @@ -95,6 +95,14 @@ public class McpServerProperties { private ServerProtocol protocol = ServerProtocol.SSE; + private Sse sse = new Sse(); + + private Streamable streamable = new Streamable(); + + private Stateless stateless = new Stateless(); + + private ToolChangeNotification toolChangeNotification = new ToolChangeNotification(); + /** * Sets the duration to wait for server responses before timing out requests. This * timeout applies to all requests made through the client, including tool calls, @@ -115,6 +123,10 @@ public Capabilities getCapabilities() { return this.capabilities; } + public void setCapabilities(Capabilities capabilities) { + this.capabilities = capabilities; + } + public enum ServerProtocol { SSE, STREAMABLE, STATELESS @@ -207,6 +219,38 @@ public void setProtocol(ServerProtocol serverMode) { this.protocol = serverMode; } + public Sse getSse() { + return sse; + } + + public void setSse(Sse sse) { + this.sse = sse; + } + + public Streamable getStreamable() { + return streamable; + } + + public void setStreamable(Streamable streamable) { + this.streamable = streamable; + } + + public Stateless getStateless() { + return stateless; + } + + public void setStateless(Stateless stateless) { + this.stateless = stateless; + } + + public ToolChangeNotification getToolChangeNotification() { + return toolChangeNotification; + } + + public void setToolChangeNotification(ToolChangeNotification toolChangeNotification) { + this.toolChangeNotification = toolChangeNotification; + } + public static class Capabilities { private boolean resource = true; @@ -251,4 +295,192 @@ public void setCompletion(boolean completion) { } + /** + * Server-Sent Events (SSE) protocol specific configuration. SSE uses two separate + * endpoints: one for establishing SSE connections and another for message posting. + */ + public static class Sse { + + /** + * Base URL for the SSE transport. + */ + private String baseUrl = ""; + + /** + * An SSE endpoint, for clients to establish a connection and receive messages + * from the server + */ + private String sseEndpoint = "/sse"; + + /** + * A regular HTTP POST endpoint for clients to send messages to the server. + */ + private String sseMessageEndpoint = "/mcp/message"; + + /** + * The duration to keep the connection alive. Disabled by default. + */ + private Duration keepAliveInterval; + + public String getBaseUrl() { + return this.baseUrl; + } + + public void setBaseUrl(String baseUrl) { + Assert.notNull(baseUrl, "Base URL must not be null"); + this.baseUrl = baseUrl; + } + + public String getSseEndpoint() { + return this.sseEndpoint; + } + + public void setSseEndpoint(String eventEndpoint) { + Assert.hasText(eventEndpoint, "event endpoint must not be empty"); + this.sseEndpoint = eventEndpoint; + } + + public String getSseMessageEndpoint() { + return this.sseMessageEndpoint; + } + + public void setSseMessageEndpoint(String sseMessageEndpoint) { + Assert.hasText(sseMessageEndpoint, "SSE message endpoint must not be empty"); + this.sseMessageEndpoint = sseMessageEndpoint; + } + + public Duration getKeepAliveInterval() { + return this.keepAliveInterval; + } + + public void setKeepAliveInterval(Duration keepAliveInterval) { + this.keepAliveInterval = keepAliveInterval; + } + + } + + /** + * Streamable HTTP protocol specific configuration. Streamable uses a single endpoint + * for bidirectional communication with streaming support. + */ + public static class Streamable { + + /** + * The MCP endpoint path for streamable HTTP communication. + */ + private String mcpEndpoint = "/mcp"; + + /** + * The duration to keep the connection alive. + */ + private Duration keepAliveInterval; + + private boolean disallowDelete; + + public String getMcpEndpoint() { + return this.mcpEndpoint; + } + + public void setMcpEndpoint(String mcpEndpoint) { + Assert.hasText(mcpEndpoint, "MCP endpoint must not be empty"); + this.mcpEndpoint = mcpEndpoint; + } + + public void setKeepAliveInterval(Duration keepAliveInterval) { + Assert.notNull(keepAliveInterval, "Keep-alive interval must not be null"); + this.keepAliveInterval = keepAliveInterval; + } + + public Duration getKeepAliveInterval() { + return this.keepAliveInterval; + } + + public boolean isDisallowDelete() { + return this.disallowDelete; + } + + public void setDisallowDelete(boolean disallowDelete) { + this.disallowDelete = disallowDelete; + } + + } + + /** + * Stateless HTTP protocol specific configuration. Stateless uses a single endpoint + * for request-response communication without maintaining connection state. + */ + public static class Stateless { + + /** + * The MCP endpoint path for stateless HTTP communication. + */ + private String mcpEndpoint = "/mcp"; + + // Getters and setters + public String getMcpEndpoint() { + return this.mcpEndpoint; + } + + public void setMcpEndpoint(String mcpEndpoint) { + Assert.hasText(mcpEndpoint, "MCP endpoint must not be empty"); + this.mcpEndpoint = mcpEndpoint; + } + + } + + public static class ToolChangeNotification { + + /** + * Enable/disable notifications for resource changes. Only relevant for MCP + * servers with resource capabilities. + *

+ * When enabled, the server will notify clients when resources are added, updated, + * or removed. + */ + private boolean resourceChangeNotification = true; + + /** + * Enable/disable notifications for tool changes. Only relevant for MCP servers + * with tool capabilities. + *

+ * When enabled, the server will notify clients when tools are registered or + * unregistered. + */ + private boolean toolChangeNotification = true; + + /** + * Enable/disable notifications for prompt changes. Only relevant for MCP servers + * with prompt capabilities. + *

+ * When enabled, the server will notify clients when prompt templates are + * modified. + */ + private boolean promptChangeNotification = true; + + public boolean isResourceChangeNotification() { + return this.resourceChangeNotification; + } + + public void setResourceChangeNotification(boolean resourceChangeNotification) { + this.resourceChangeNotification = resourceChangeNotification; + } + + public boolean isToolChangeNotification() { + return this.toolChangeNotification; + } + + public void setToolChangeNotification(boolean toolChangeNotification) { + this.toolChangeNotification = toolChangeNotification; + } + + public boolean isPromptChangeNotification() { + return this.promptChangeNotification; + } + + public void setPromptChangeNotification(boolean promptChangeNotification) { + this.promptChangeNotification = promptChangeNotification; + } + + } + } diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/properties/McpServerSseProperties.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/properties/McpServerSseProperties.java deleted file mode 100644 index 2aff7b023a2..00000000000 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/properties/McpServerSseProperties.java +++ /dev/null @@ -1,87 +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.server.common.autoconfigure.properties; - -import java.time.Duration; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.util.Assert; - -/** - * @author Christian Tzolov - */ -@ConfigurationProperties(McpServerSseProperties.CONFIG_PREFIX) -public class McpServerSseProperties { - - public static final String CONFIG_PREFIX = "spring.ai.mcp.server"; - - /** - */ - private String baseUrl = ""; - - /** - * An SSE endpoint, for clients to establish a connection and receive messages from - * the server - */ - private String sseEndpoint = "/sse"; - - /** - * A regular HTTP POST endpoint for clients to send messages to the server. - */ - private String sseMessageEndpoint = "/mcp/message"; - - /** - * The duration to keep the connection alive. Disabled by default. - */ - private Duration keepAliveInterval; - - public String getBaseUrl() { - return this.baseUrl; - } - - public void setBaseUrl(String baseUrl) { - Assert.notNull(baseUrl, "Base URL must not be null"); - this.baseUrl = baseUrl; - } - - public String getSseEndpoint() { - return this.sseEndpoint; - } - - public void setSseEndpoint(String sseEndpoint) { - Assert.hasText(sseEndpoint, "SSE endpoint must not be empty"); - this.sseEndpoint = sseEndpoint; - } - - public String getSseMessageEndpoint() { - return this.sseMessageEndpoint; - } - - public void setSseMessageEndpoint(String sseMessageEndpoint) { - Assert.hasText(sseMessageEndpoint, "SSE message endpoint must not be empty"); - this.sseMessageEndpoint = sseMessageEndpoint; - } - - public Duration getKeepAliveInterval() { - return this.keepAliveInterval; - } - - public void setKeepAliveInterval(Duration keepAliveInterval) { - this.keepAliveInterval = keepAliveInterval; - } - -} diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/properties/McpServerStreamableHttpProperties.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/properties/McpServerStreamableHttpProperties.java deleted file mode 100644 index 1227d7652fd..00000000000 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/properties/McpServerStreamableHttpProperties.java +++ /dev/null @@ -1,69 +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.server.common.autoconfigure.properties; - -import java.time.Duration; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.util.Assert; - -/** - * @author Christian Tzolov - */ -@ConfigurationProperties(McpServerStreamableHttpProperties.CONFIG_PREFIX) -public class McpServerStreamableHttpProperties { - - public static final String CONFIG_PREFIX = "spring.ai.mcp.server.streamable-http"; - - /** - */ - private String mcpEndpoint = "/mcp"; - - /** - * The duration to keep the connection alive. - */ - private Duration keepAliveInterval; - - private boolean disallowDelete; - - public String getMcpEndpoint() { - return this.mcpEndpoint; - } - - public void setMcpEndpoint(String mcpEndpoint) { - Assert.hasText(mcpEndpoint, "MCP endpoint must not be empty"); - this.mcpEndpoint = mcpEndpoint; - } - - public void setKeepAliveInterval(Duration keepAliveInterval) { - Assert.notNull(keepAliveInterval, "Keep-alive interval must not be null"); - this.keepAliveInterval = keepAliveInterval; - } - - public Duration getKeepAliveInterval() { - return this.keepAliveInterval; - } - - public boolean isDisallowDelete() { - return this.disallowDelete; - } - - public void setDisallowDelete(boolean disallowDelete) { - this.disallowDelete = disallowDelete; - } - -} diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/test/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerAutoConfigurationIT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/test/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerAutoConfigurationIT.java index 67695a246f1..df9ed021504 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/test/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerAutoConfigurationIT.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/test/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerAutoConfigurationIT.java @@ -16,33 +16,17 @@ package org.springframework.ai.mcp.server.common.autoconfigure; -import java.util.List; -import java.util.function.BiConsumer; -import java.util.function.BiFunction; - import com.fasterxml.jackson.core.type.TypeReference; import io.modelcontextprotocol.client.McpSyncClient; -import io.modelcontextprotocol.server.McpAsyncServer; -import io.modelcontextprotocol.server.McpAsyncServerExchange; -import io.modelcontextprotocol.server.McpServerFeatures; -import io.modelcontextprotocol.server.McpServerFeatures.AsyncCompletionSpecification; -import io.modelcontextprotocol.server.McpServerFeatures.AsyncToolSpecification; -import io.modelcontextprotocol.server.McpServerFeatures.SyncCompletionSpecification; -import io.modelcontextprotocol.server.McpServerFeatures.SyncPromptSpecification; -import io.modelcontextprotocol.server.McpServerFeatures.SyncResourceSpecification; -import io.modelcontextprotocol.server.McpServerFeatures.SyncToolSpecification; -import io.modelcontextprotocol.server.McpSyncServer; -import io.modelcontextprotocol.server.McpSyncServerExchange; +import io.modelcontextprotocol.server.*; +import io.modelcontextprotocol.server.McpServerFeatures.*; import io.modelcontextprotocol.server.transport.StdioServerTransportProvider; import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.McpServerTransport; import io.modelcontextprotocol.spec.McpServerTransportProvider; import org.junit.jupiter.api.Test; import org.mockito.Mockito; -import reactor.core.publisher.Mono; - import org.springframework.ai.mcp.SyncMcpToolCallback; -import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerChangeNotificationProperties; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties; import org.springframework.ai.tool.ToolCallback; import org.springframework.ai.tool.ToolCallbackProvider; @@ -50,6 +34,11 @@ 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.BiConsumer; +import java.util.function.BiFunction; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; @@ -79,11 +68,15 @@ void defaultConfiguration() { assertThat(properties.getCapabilities().isPrompt()).isTrue(); assertThat(properties.getCapabilities().isCompletion()).isTrue(); - McpServerChangeNotificationProperties changeNotificationProperties = context - .getBean(McpServerChangeNotificationProperties.class); - assertThat(changeNotificationProperties.isToolChangeNotification()).isTrue(); - assertThat(changeNotificationProperties.isResourceChangeNotification()).isTrue(); - assertThat(changeNotificationProperties.isPromptChangeNotification()).isTrue(); + // Check change notifications + assertThat(properties.getToolChangeNotification().isToolChangeNotification()).isTrue(); + assertThat(properties.getToolChangeNotification().isResourceChangeNotification()).isTrue(); + assertThat(properties.getToolChangeNotification().isPromptChangeNotification()).isTrue(); + + // Check default nested configurations exist + assertThat(properties.getSse()).isNotNull(); + assertThat(properties.getStreamable()).isNotNull(); + assertThat(properties.getStateless()).isNotNull(); }); } @@ -107,6 +100,27 @@ void asyncConfiguration() { }); } + @Test + void protocolSwitchingConfiguration() { + // Test SSE protocol (default) + this.contextRunner.withPropertyValues("spring.ai.mcp.server.protocol=SSE").run(context -> { + McpServerProperties properties = context.getBean(McpServerProperties.class); + assertThat(properties.getProtocol()).isEqualTo(McpServerProperties.ServerProtocol.SSE); + }); + + // Test STREAMABLE protocol + this.contextRunner.withPropertyValues("spring.ai.mcp.server.protocol=STREAMABLE").run(context -> { + McpServerProperties properties = context.getBean(McpServerProperties.class); + assertThat(properties.getProtocol()).isEqualTo(McpServerProperties.ServerProtocol.STREAMABLE); + }); + + // Test STATELESS protocol + this.contextRunner.withPropertyValues("spring.ai.mcp.server.protocol=STATELESS").run(context -> { + McpServerProperties properties = context.getBean(McpServerProperties.class); + assertThat(properties.getProtocol()).isEqualTo(McpServerProperties.ServerProtocol.STATELESS); + }); + } + @Test void syncServerInstructionsConfiguration() { this.contextRunner.withPropertyValues("spring.ai.mcp.server.instructions=Sync Server Instructions") @@ -133,10 +147,9 @@ void serverNotificationConfiguration() { .withPropertyValues("spring.ai.mcp.server.tool-change-notification=false", "spring.ai.mcp.server.resource-change-notification=false") .run(context -> { - McpServerChangeNotificationProperties changeNotificationProperties = context - .getBean(McpServerChangeNotificationProperties.class); - assertThat(changeNotificationProperties.isToolChangeNotification()).isFalse(); - assertThat(changeNotificationProperties.isResourceChangeNotification()).isFalse(); + McpServerProperties properties = context.getBean(McpServerProperties.class); + assertThat(properties.getToolChangeNotification().isToolChangeNotification()).isFalse(); + assertThat(properties.getToolChangeNotification().isResourceChangeNotification()).isFalse(); }); } @@ -166,11 +179,10 @@ void notificationConfiguration() { "spring.ai.mcp.server.resource-change-notification=false", "spring.ai.mcp.server.prompt-change-notification=false") .run(context -> { - McpServerChangeNotificationProperties changeNotificationProperties = context - .getBean(McpServerChangeNotificationProperties.class); - assertThat(changeNotificationProperties.isToolChangeNotification()).isFalse(); - assertThat(changeNotificationProperties.isResourceChangeNotification()).isFalse(); - assertThat(changeNotificationProperties.isPromptChangeNotification()).isFalse(); + McpServerProperties properties = context.getBean(McpServerProperties.class); + assertThat(properties.getToolChangeNotification().isToolChangeNotification()).isFalse(); + assertThat(properties.getToolChangeNotification().isResourceChangeNotification()).isFalse(); + assertThat(properties.getToolChangeNotification().isPromptChangeNotification()).isFalse(); }); } diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/test/java/org/springframework/ai/mcp/server/common/autoconfigure/StatelessToolCallbackConverterAutoConfigurationIT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/test/java/org/springframework/ai/mcp/server/common/autoconfigure/StatelessToolCallbackConverterAutoConfigurationIT.java index 043e384e64c..a9f4fb704e0 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/test/java/org/springframework/ai/mcp/server/common/autoconfigure/StatelessToolCallbackConverterAutoConfigurationIT.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/test/java/org/springframework/ai/mcp/server/common/autoconfigure/StatelessToolCallbackConverterAutoConfigurationIT.java @@ -39,7 +39,7 @@ /** * Integration tests for {@link StatelessToolCallbackConverterAutoConfiguration} and - * {@link ToolCallbackConverterCondition}. + * {@link StatelessToolCallbackConverterAutoConfiguration.ToolCallbackConverterCondition}. * * @author Christian Tzolov */ diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/test/java/org/springframework/ai/mcp/server/common/autoconfigure/ToolCallbackConverterAutoConfigurationIT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/test/java/org/springframework/ai/mcp/server/common/autoconfigure/ToolCallbackConverterAutoConfigurationIT.java index 942132e02d6..f0443e2f3db 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/test/java/org/springframework/ai/mcp/server/common/autoconfigure/ToolCallbackConverterAutoConfigurationIT.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/test/java/org/springframework/ai/mcp/server/common/autoconfigure/ToolCallbackConverterAutoConfigurationIT.java @@ -39,7 +39,7 @@ /** * Integration tests for {@link ToolCallbackConverterAutoConfiguration} and - * {@link ToolCallbackConverterCondition}. + * {@link ToolCallbackConverterAutoConfiguration.ToolCallbackConverterCondition}. * * @author Christian Tzolov */ diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfiguration.java index b41563b7a42..b60ac93be32 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfiguration.java @@ -19,11 +19,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.modelcontextprotocol.server.transport.WebFluxSseServerTransportProvider; import io.modelcontextprotocol.spec.McpServerTransportProvider; - import org.springframework.ai.mcp.server.common.autoconfigure.McpServerAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStdioDisabledCondition; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties; -import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerSseProperties; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -74,7 +72,7 @@ * @see WebFluxSseServerTransportProvider */ @AutoConfiguration(before = McpServerAutoConfiguration.class) -@EnableConfigurationProperties({ McpServerSseProperties.class }) +@EnableConfigurationProperties({ McpServerProperties.class }) @ConditionalOnClass({ WebFluxSseServerTransportProvider.class }) @ConditionalOnMissingBean(McpServerTransportProvider.class) @Conditional({ McpServerStdioDisabledCondition.class, McpServerAutoConfiguration.EnabledSseServerCondition.class }) @@ -83,16 +81,16 @@ public class McpServerSseWebFluxAutoConfiguration { @Bean @ConditionalOnMissingBean public WebFluxSseServerTransportProvider webFluxTransport(ObjectProvider objectMapperProvider, - McpServerSseProperties serverProperties) { + McpServerProperties serverProperties) { ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new); return WebFluxSseServerTransportProvider.builder() .objectMapper(objectMapper) - .basePath(serverProperties.getBaseUrl()) - .messageEndpoint(serverProperties.getSseMessageEndpoint()) - .sseEndpoint(serverProperties.getSseEndpoint()) - .keepAliveInterval(serverProperties.getKeepAliveInterval()) + .basePath(serverProperties.getSse().getBaseUrl()) + .messageEndpoint(serverProperties.getSse().getSseMessageEndpoint()) + .sseEndpoint(serverProperties.getSse().getSseEndpoint()) + .keepAliveInterval(serverProperties.getSse().getKeepAliveInterval()) .build(); } diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebFluxAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebFluxAutoConfiguration.java index fa7385aa660..65b78160fff 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebFluxAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebFluxAutoConfiguration.java @@ -19,10 +19,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.modelcontextprotocol.server.transport.WebFluxStatelessServerTransport; import io.modelcontextprotocol.spec.McpSchema; - import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStatelessAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStdioDisabledCondition; -import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerStreamableHttpProperties; +import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -37,7 +36,7 @@ */ @AutoConfiguration(before = McpServerStatelessAutoConfiguration.class) @ConditionalOnClass({ McpSchema.class }) -@EnableConfigurationProperties(McpServerStreamableHttpProperties.class) +@EnableConfigurationProperties(McpServerProperties.class) @Conditional({ McpServerStdioDisabledCondition.class, McpServerStatelessAutoConfiguration.EnabledStatelessServerCondition.class }) public class McpServerStatelessWebFluxAutoConfiguration { @@ -45,13 +44,13 @@ public class McpServerStatelessWebFluxAutoConfiguration { @Bean @ConditionalOnMissingBean public WebFluxStatelessServerTransport webFluxStatelessServerTransport( - ObjectProvider objectMapperProvider, McpServerStreamableHttpProperties serverProperties) { + ObjectProvider objectMapperProvider, McpServerProperties serverProperties) { ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new); return WebFluxStatelessServerTransport.builder() .objectMapper(objectMapper) - .messageEndpoint(serverProperties.getMcpEndpoint()) + .messageEndpoint(serverProperties.getStateless().getMcpEndpoint()) // .disallowDelete(serverProperties.isDisallowDelete()) .build(); } diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebFluxAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebFluxAutoConfiguration.java index 49844127fb6..16981904bc6 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebFluxAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebFluxAutoConfiguration.java @@ -19,11 +19,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.modelcontextprotocol.server.transport.WebFluxStreamableServerTransportProvider; import io.modelcontextprotocol.spec.McpSchema; - import org.springframework.ai.mcp.server.common.autoconfigure.McpServerAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStdioDisabledCondition; 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.condition.ConditionalOnClass; @@ -38,7 +36,7 @@ */ @AutoConfiguration(before = McpServerAutoConfiguration.class) @ConditionalOnClass({ McpSchema.class }) -@EnableConfigurationProperties({ McpServerProperties.class, McpServerStreamableHttpProperties.class }) +@EnableConfigurationProperties({ McpServerProperties.class }) @Conditional({ McpServerStdioDisabledCondition.class, McpServerAutoConfiguration.EnabledStreamableServerCondition.class }) public class McpServerStreamableHttpWebFluxAutoConfiguration { @@ -46,15 +44,15 @@ public class McpServerStreamableHttpWebFluxAutoConfiguration { @Bean @ConditionalOnMissingBean public WebFluxStreamableServerTransportProvider webFluxStreamableServerTransportProvider( - ObjectProvider objectMapperProvider, McpServerStreamableHttpProperties serverProperties) { + ObjectProvider objectMapperProvider, McpServerProperties serverProperties) { ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new); return WebFluxStreamableServerTransportProvider.builder() .objectMapper(objectMapper) - .messageEndpoint(serverProperties.getMcpEndpoint()) - .keepAliveInterval(serverProperties.getKeepAliveInterval()) - .disallowDelete(serverProperties.isDisallowDelete()) + .messageEndpoint(serverProperties.getStreamable().getMcpEndpoint()) + .keepAliveInterval(serverProperties.getStreamable().getKeepAliveInterval()) + .disallowDelete(serverProperties.getStreamable().isDisallowDelete()) .build(); } diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfigurationIT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfigurationIT.java index 20d679c2f1e..2b49c4c593e 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfigurationIT.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfigurationIT.java @@ -22,7 +22,7 @@ import org.junit.jupiter.api.Test; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerAutoConfiguration; -import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerSseProperties; +import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.web.reactive.function.server.RouterFunction; @@ -40,11 +40,11 @@ void defaultConfiguration() { assertThat(context).hasSingleBean(WebFluxSseServerTransportProvider.class); assertThat(context).hasSingleBean(RouterFunction.class); - McpServerSseProperties sseProperties = context.getBean(McpServerSseProperties.class); - assertThat(sseProperties.getBaseUrl()).isEqualTo(""); - assertThat(sseProperties.getSseEndpoint()).isEqualTo("/sse"); - assertThat(sseProperties.getSseMessageEndpoint()).isEqualTo("/mcp/message"); - assertThat(sseProperties.getKeepAliveInterval()).isNull(); + McpServerProperties sseProperties = context.getBean(McpServerProperties.class); + assertThat(sseProperties.getSse().getBaseUrl()).isEqualTo(""); + assertThat(sseProperties.getSse().getSseEndpoint()).isEqualTo("/sse"); + assertThat(sseProperties.getSse().getSseMessageEndpoint()).isEqualTo("/mcp/message"); + assertThat(sseProperties.getSse().getKeepAliveInterval()).isNull(); }); } @@ -56,10 +56,10 @@ void endpointConfiguration() { "spring.ai.mcp.server.sse-endpoint=/events", "spring.ai.mcp.server.sse-message-endpoint=/api/mcp/message") .run(context -> { - McpServerSseProperties sseProperties = context.getBean(McpServerSseProperties.class); - assertThat(sseProperties.getBaseUrl()).isEqualTo("http://localhost:8080"); - assertThat(sseProperties.getSseEndpoint()).isEqualTo("/events"); - assertThat(sseProperties.getSseMessageEndpoint()).isEqualTo("/api/mcp/message"); + McpServerProperties sseProperties = context.getBean(McpServerProperties.class); + assertThat(sseProperties.getSse().getBaseUrl()).isEqualTo("http://localhost:8080"); + assertThat(sseProperties.getSse().getSseEndpoint()).isEqualTo("/events"); + assertThat(sseProperties.getSse().getSseMessageEndpoint()).isEqualTo("/api/mcp/message"); // Verify the server is configured with the endpoints McpSyncServer server = context.getBean(McpSyncServer.class); diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebFluxAutoConfigurationIT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebFluxAutoConfigurationIT.java index 40770bb4c57..9befbef0e90 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebFluxAutoConfigurationIT.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebFluxAutoConfigurationIT.java @@ -58,38 +58,11 @@ void serverDisableConfiguration() { @Test void serverBaseUrlConfiguration() { - this.contextRunner.withPropertyValues("spring.ai.mcp.server.streamable-http.mcpEndpoint=/test") + this.contextRunner.withPropertyValues("spring.ai.mcp.server.stateless.mcpEndpoint=/test") .run(context -> assertThat(context.getBean(WebFluxStatelessServerTransport.class)).extracting("mcpEndpoint") .isEqualTo("/test")); } - @Test - void keepAliveIntervalConfiguration() { - this.contextRunner.withPropertyValues("spring.ai.mcp.server.streamable-http.keep-alive-interval=PT30S") - .run(context -> { - assertThat(context).hasSingleBean(WebFluxStatelessServerTransport.class); - assertThat(context).hasSingleBean(RouterFunction.class); - }); - } - - @Test - void disallowDeleteConfiguration() { - this.contextRunner.withPropertyValues("spring.ai.mcp.server.streamable-http.disallow-delete=true") - .run(context -> { - assertThat(context).hasSingleBean(WebFluxStatelessServerTransport.class); - assertThat(context).hasSingleBean(RouterFunction.class); - }); - } - - @Test - void disallowDeleteFalseConfiguration() { - this.contextRunner.withPropertyValues("spring.ai.mcp.server.streamable-http.disallow-delete=false") - .run(context -> { - assertThat(context).hasSingleBean(WebFluxStatelessServerTransport.class); - assertThat(context).hasSingleBean(RouterFunction.class); - }); - } - @Test void customObjectMapperIsUsed() { ObjectMapper customObjectMapper = new ObjectMapper(); @@ -141,9 +114,7 @@ void routerFunctionIsCreatedFromProvider() { @Test void allPropertiesConfiguration() { - this.contextRunner - .withPropertyValues("spring.ai.mcp.server.streamable-http.mcpEndpoint=/custom-endpoint", - "spring.ai.mcp.server.streamable-http.disallow-delete=true") + this.contextRunner.withPropertyValues("spring.ai.mcp.server.streamable.mcpEndpoint=/custom-endpoint") .run(context -> { WebFluxStatelessServerTransport provider = context.getBean(WebFluxStatelessServerTransport.class); assertThat(provider).extracting("mcpEndpoint").isEqualTo("/custom-endpoint"); diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableWebFluxAutoConfigurationIT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableWebFluxAutoConfigurationIT.java index 9e3c33f8d81..0d4b915135c 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableWebFluxAutoConfigurationIT.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableWebFluxAutoConfigurationIT.java @@ -58,7 +58,7 @@ void serverDisableConfiguration() { @Test void serverBaseUrlConfiguration() { - this.contextRunner.withPropertyValues("spring.ai.mcp.server.streamable-http.mcpEndpoint=/test") + this.contextRunner.withPropertyValues("spring.ai.mcp.server.streamable.mcpEndpoint=/test") .run(context -> assertThat(context.getBean(WebFluxStreamableServerTransportProvider.class)) .extracting("mcpEndpoint") .isEqualTo("/test")); @@ -66,7 +66,7 @@ void serverBaseUrlConfiguration() { @Test void keepAliveIntervalConfiguration() { - this.contextRunner.withPropertyValues("spring.ai.mcp.server.streamable-http.keep-alive-interval=PT30S") + this.contextRunner.withPropertyValues("spring.ai.mcp.server.streamable.keep-alive-interval=PT30S") .run(context -> { assertThat(context).hasSingleBean(WebFluxStreamableServerTransportProvider.class); assertThat(context).hasSingleBean(RouterFunction.class); @@ -75,20 +75,18 @@ void keepAliveIntervalConfiguration() { @Test void disallowDeleteConfiguration() { - this.contextRunner.withPropertyValues("spring.ai.mcp.server.streamable-http.disallow-delete=true") - .run(context -> { - assertThat(context).hasSingleBean(WebFluxStreamableServerTransportProvider.class); - assertThat(context).hasSingleBean(RouterFunction.class); - }); + this.contextRunner.withPropertyValues("spring.ai.mcp.server.streamable.disallow-delete=true").run(context -> { + assertThat(context).hasSingleBean(WebFluxStreamableServerTransportProvider.class); + assertThat(context).hasSingleBean(RouterFunction.class); + }); } @Test void disallowDeleteFalseConfiguration() { - this.contextRunner.withPropertyValues("spring.ai.mcp.server.streamable-http.disallow-delete=false") - .run(context -> { - assertThat(context).hasSingleBean(WebFluxStreamableServerTransportProvider.class); - assertThat(context).hasSingleBean(RouterFunction.class); - }); + this.contextRunner.withPropertyValues("spring.ai.mcp.server.streamable.disallow-delete=false").run(context -> { + assertThat(context).hasSingleBean(WebFluxStreamableServerTransportProvider.class); + assertThat(context).hasSingleBean(RouterFunction.class); + }); } @Test @@ -144,9 +142,9 @@ void routerFunctionIsCreatedFromProvider() { @Test void allPropertiesConfiguration() { this.contextRunner - .withPropertyValues("spring.ai.mcp.server.streamable-http.mcpEndpoint=/custom-endpoint", - "spring.ai.mcp.server.streamable-http.keep-alive-interval=PT45S", - "spring.ai.mcp.server.streamable-http.disallow-delete=true") + .withPropertyValues("spring.ai.mcp.server.streamable.mcpEndpoint=/custom-endpoint", + "spring.ai.mcp.server.streamable.keep-alive-interval=PT45S", + "spring.ai.mcp.server.streamable.disallow-delete=true") .run(context -> { WebFluxStreamableServerTransportProvider provider = context .getBean(WebFluxStreamableServerTransportProvider.class); diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StatelessWebClientWebFluxServerIT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StatelessWebClientWebFluxServerIT.java index c7038fc8740..11b17cb3cbf 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StatelessWebClientWebFluxServerIT.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StatelessWebClientWebFluxServerIT.java @@ -88,9 +88,8 @@ void clientServerCapabilities() { this.serverContextRunner.withUserConfiguration(TestMcpServerConfiguration.class) .withPropertyValues(// @formatter:off - "spring.ai.mcp.server.streamable-http.mcp-endpoint=/mcp", + "spring.ai.mcp.server.stateless.mcp-endpoint=/mcp", "spring.ai.mcp.server.name=test-mcp-server", - "spring.ai.mcp.server.streamable-http.keep-alive-interval=1s", "spring.ai.mcp.server.version=1.0.0") // @formatter:on .run(serverContext -> { // Verify all required beans are present @@ -103,10 +102,7 @@ void clientServerCapabilities() { assertThat(properties.getName()).isEqualTo("test-mcp-server"); assertThat(properties.getVersion()).isEqualTo("1.0.0"); - McpServerStreamableHttpProperties streamableHttpProperties = serverContext - .getBean(McpServerStreamableHttpProperties.class); - assertThat(streamableHttpProperties.getMcpEndpoint()).isEqualTo("/mcp"); - assertThat(streamableHttpProperties.getKeepAliveInterval()).isEqualTo(Duration.ofSeconds(1)); + assertThat(properties.getStateless().getMcpEndpoint()).isEqualTo("/mcp"); var httpServer = startHttpServer(serverContext, serverPort); diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableWebClientWebFluxServerIT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableWebClientWebFluxServerIT.java index 88cf2bda007..e9ee28eb713 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableWebClientWebFluxServerIT.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableWebClientWebFluxServerIT.java @@ -104,8 +104,8 @@ void clientServerCapabilities() { .withPropertyValues(// @formatter:off "spring.ai.mcp.server.name=test-mcp-server", "spring.ai.mcp.server.version=1.0.0", - "spring.ai.mcp.server.streamable-http.keep-alive-interval=1s", - "spring.ai.mcp.server.streamable-http.mcp-endpoint=/mcp") // @formatter:on + "spring.ai.mcp.server.streamable.keep-alive-interval=1s", + "spring.ai.mcp.server.streamable.mcp-endpoint=/mcp") // @formatter:on .run(serverContext -> { // Verify all required beans are present assertThat(serverContext).hasSingleBean(WebFluxStreamableServerTransportProvider.class); @@ -117,10 +117,8 @@ void clientServerCapabilities() { assertThat(properties.getName()).isEqualTo("test-mcp-server"); assertThat(properties.getVersion()).isEqualTo("1.0.0"); - McpServerStreamableHttpProperties streamableHttpProperties = serverContext - .getBean(McpServerStreamableHttpProperties.class); - assertThat(streamableHttpProperties.getMcpEndpoint()).isEqualTo("/mcp"); - assertThat(streamableHttpProperties.getKeepAliveInterval()).isEqualTo(Duration.ofSeconds(1)); + assertThat(properties.getStreamable().getMcpEndpoint()).isEqualTo("/mcp"); + assertThat(properties.getStreamable().getKeepAliveInterval()).isEqualTo(Duration.ofSeconds(1)); var httpServer = startHttpServer(serverContext, serverPort); diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebMvcAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebMvcAutoConfiguration.java index bc3db979813..613de8b627a 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebMvcAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebMvcAutoConfiguration.java @@ -19,11 +19,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.modelcontextprotocol.server.transport.WebMvcSseServerTransportProvider; import io.modelcontextprotocol.spec.McpServerTransportProvider; - import org.springframework.ai.mcp.server.common.autoconfigure.McpServerAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStdioDisabledCondition; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties; -import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerSseProperties; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -67,7 +65,7 @@ * @see WebMvcSseServerTransportProvider */ @AutoConfiguration(before = McpServerAutoConfiguration.class) -@EnableConfigurationProperties({ McpServerSseProperties.class }) +@EnableConfigurationProperties({ McpServerProperties.class }) @ConditionalOnClass({ WebMvcSseServerTransportProvider.class }) @ConditionalOnMissingBean(McpServerTransportProvider.class) @Conditional({ McpServerStdioDisabledCondition.class, McpServerAutoConfiguration.EnabledSseServerCondition.class }) @@ -76,16 +74,16 @@ public class McpServerSseWebMvcAutoConfiguration { @Bean @ConditionalOnMissingBean public WebMvcSseServerTransportProvider webMvcSseServerTransportProvider( - ObjectProvider objectMapperProvider, McpServerSseProperties serverProperties) { + ObjectProvider objectMapperProvider, McpServerProperties serverProperties) { ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new); return WebMvcSseServerTransportProvider.builder() .objectMapper(objectMapper) - .baseUrl(serverProperties.getBaseUrl()) - .sseEndpoint(serverProperties.getSseEndpoint()) - .messageEndpoint(serverProperties.getSseMessageEndpoint()) - .keepAliveInterval(serverProperties.getKeepAliveInterval()) + .baseUrl(serverProperties.getSse().getBaseUrl()) + .sseEndpoint(serverProperties.getSse().getSseEndpoint()) + .messageEndpoint(serverProperties.getSse().getSseMessageEndpoint()) + .keepAliveInterval(serverProperties.getSse().getKeepAliveInterval()) .build(); } diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebMvcAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebMvcAutoConfiguration.java index f411c930b80..9b51b0d6048 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebMvcAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebMvcAutoConfiguration.java @@ -19,10 +19,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.modelcontextprotocol.server.transport.WebMvcStatelessServerTransport; import io.modelcontextprotocol.spec.McpSchema; - import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStatelessAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStdioDisabledCondition; -import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerStreamableHttpProperties; +import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -37,7 +36,7 @@ */ @AutoConfiguration(before = McpServerStatelessAutoConfiguration.class) @ConditionalOnClass({ McpSchema.class }) -@EnableConfigurationProperties(McpServerStreamableHttpProperties.class) +@EnableConfigurationProperties(McpServerProperties.class) @Conditional({ McpServerStdioDisabledCondition.class, McpServerStatelessAutoConfiguration.EnabledStatelessServerCondition.class }) public class McpServerStatelessWebMvcAutoConfiguration { @@ -45,13 +44,13 @@ public class McpServerStatelessWebMvcAutoConfiguration { @Bean @ConditionalOnMissingBean public WebMvcStatelessServerTransport webMvcStatelessServerTransport( - ObjectProvider objectMapperProvider, McpServerStreamableHttpProperties serverProperties) { + ObjectProvider objectMapperProvider, McpServerProperties serverProperties) { ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new); return WebMvcStatelessServerTransport.builder() .objectMapper(objectMapper) - .messageEndpoint(serverProperties.getMcpEndpoint()) + .messageEndpoint(serverProperties.getStateless().getMcpEndpoint()) // .disallowDelete(serverProperties.isDisallowDelete()) .build(); } diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebMvcAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebMvcAutoConfiguration.java index db9757aa6a5..2b77dc382ab 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebMvcAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebMvcAutoConfiguration.java @@ -19,11 +19,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.modelcontextprotocol.server.transport.WebMvcStreamableServerTransportProvider; import io.modelcontextprotocol.spec.McpSchema; - import org.springframework.ai.mcp.server.common.autoconfigure.McpServerAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStdioDisabledCondition; 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.condition.ConditionalOnClass; @@ -38,7 +36,7 @@ */ @AutoConfiguration(before = McpServerAutoConfiguration.class) @ConditionalOnClass({ McpSchema.class }) -@EnableConfigurationProperties({ McpServerProperties.class, McpServerStreamableHttpProperties.class }) +@EnableConfigurationProperties({ McpServerProperties.class }) @Conditional({ McpServerStdioDisabledCondition.class, McpServerAutoConfiguration.EnabledStreamableServerCondition.class }) public class McpServerStreamableHttpWebMvcAutoConfiguration { @@ -46,15 +44,15 @@ public class McpServerStreamableHttpWebMvcAutoConfiguration { @Bean @ConditionalOnMissingBean public WebMvcStreamableServerTransportProvider webMvcStreamableServerTransportProvider( - ObjectProvider objectMapperProvider, McpServerStreamableHttpProperties serverProperties) { + ObjectProvider objectMapperProvider, McpServerProperties serverProperties) { ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new); return WebMvcStreamableServerTransportProvider.builder() .objectMapper(objectMapper) - .mcpEndpoint(serverProperties.getMcpEndpoint()) - .keepAliveInterval(serverProperties.getKeepAliveInterval()) - .disallowDelete(serverProperties.isDisallowDelete()) + .mcpEndpoint(serverProperties.getStreamable().getMcpEndpoint()) + .keepAliveInterval(serverProperties.getStreamable().getKeepAliveInterval()) + .disallowDelete(serverProperties.getStreamable().isDisallowDelete()) .build(); } diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebMvcAutoConfigurationIT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebMvcAutoConfigurationIT.java index f292fb06fa9..7f5f554de20 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebMvcAutoConfigurationIT.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebMvcAutoConfigurationIT.java @@ -22,7 +22,7 @@ import org.junit.jupiter.api.Test; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerAutoConfiguration; -import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerSseProperties; +import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -44,11 +44,11 @@ void defaultConfiguration() { assertThat(context).hasSingleBean(WebMvcSseServerTransportProvider.class); assertThat(context).hasSingleBean(RouterFunction.class); - McpServerSseProperties sseProperties = context.getBean(McpServerSseProperties.class); - assertThat(sseProperties.getBaseUrl()).isEqualTo(""); - assertThat(sseProperties.getSseEndpoint()).isEqualTo("/sse"); - assertThat(sseProperties.getSseMessageEndpoint()).isEqualTo("/mcp/message"); - assertThat(sseProperties.getKeepAliveInterval()).isNull(); + McpServerProperties serverProperties = context.getBean(McpServerProperties.class); + assertThat(serverProperties.getSse().getBaseUrl()).isEqualTo(""); + assertThat(serverProperties.getSse().getSseEndpoint()).isEqualTo("/sse"); + assertThat(serverProperties.getSse().getSseMessageEndpoint()).isEqualTo("/mcp/message"); + assertThat(serverProperties.getSse().getKeepAliveInterval()).isNull(); }); } @@ -57,13 +57,13 @@ void defaultConfiguration() { void endpointConfiguration() { this.contextRunner .withPropertyValues("spring.ai.mcp.server.base-url=http://localhost:8080", - "spring.ai.mcp.server.sse-endpoint=/events", - "spring.ai.mcp.server.sse-message-endpoint=/api/mcp/message") + "spring.ai.mcp.server.sse.sse-endpoint=/events", + "spring.ai.mcp.server.sse.sse-message-endpoint=/api/mcp/message") .run(context -> { - McpServerSseProperties sseProperties = context.getBean(McpServerSseProperties.class); - assertThat(sseProperties.getBaseUrl()).isEqualTo("http://localhost:8080"); - assertThat(sseProperties.getSseEndpoint()).isEqualTo("/events"); - assertThat(sseProperties.getSseMessageEndpoint()).isEqualTo("/api/mcp/message"); + McpServerProperties sseProperties = context.getBean(McpServerProperties.class); + assertThat(sseProperties.getSse().getBaseUrl()).isEqualTo("http://localhost:8080"); + assertThat(sseProperties.getSse().getSseEndpoint()).isEqualTo("/events"); + assertThat(sseProperties.getSse().getSseMessageEndpoint()).isEqualTo("/api/mcp/message"); // Verify the server is configured with the endpoints McpSyncServer server = context.getBean(McpSyncServer.class); @@ -95,7 +95,7 @@ void serverDisableConfiguration() { @Test void serverBaseUrlConfiguration() { - this.contextRunner.withPropertyValues("spring.ai.mcp.server.base-url=/test") + this.contextRunner.withPropertyValues("spring.ai.mcp.server.sse.base-url=/test") .run(context -> assertThat(context.getBean(WebMvcSseServerTransportProvider.class)).extracting("baseUrl") .isEqualTo("/test")); } diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebMvcAutoConfigurationIT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebMvcAutoConfigurationIT.java index 7f79b9e4eb2..c54bd6fc156 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebMvcAutoConfigurationIT.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebMvcAutoConfigurationIT.java @@ -58,38 +58,11 @@ void serverDisableConfiguration() { @Test void serverBaseUrlConfiguration() { - this.contextRunner.withPropertyValues("spring.ai.mcp.server.streamable-http.mcpEndpoint=/test") + this.contextRunner.withPropertyValues("spring.ai.mcp.server.stateless.mcpEndpoint=/test") .run(context -> assertThat(context.getBean(WebMvcStatelessServerTransport.class)).extracting("mcpEndpoint") .isEqualTo("/test")); } - @Test - void keepAliveIntervalConfiguration() { - this.contextRunner.withPropertyValues("spring.ai.mcp.server.streamable-http.keep-alive-interval=PT30S") - .run(context -> { - assertThat(context).hasSingleBean(WebMvcStatelessServerTransport.class); - assertThat(context).hasSingleBean(RouterFunction.class); - }); - } - - @Test - void disallowDeleteConfiguration() { - this.contextRunner.withPropertyValues("spring.ai.mcp.server.streamable-http.disallow-delete=true") - .run(context -> { - assertThat(context).hasSingleBean(WebMvcStatelessServerTransport.class); - assertThat(context).hasSingleBean(RouterFunction.class); - }); - } - - @Test - void disallowDeleteFalseConfiguration() { - this.contextRunner.withPropertyValues("spring.ai.mcp.server.streamable-http.disallow-delete=false") - .run(context -> { - assertThat(context).hasSingleBean(WebMvcStatelessServerTransport.class); - assertThat(context).hasSingleBean(RouterFunction.class); - }); - } - @Test void customObjectMapperIsUsed() { ObjectMapper customObjectMapper = new ObjectMapper(); @@ -141,9 +114,7 @@ void routerFunctionIsCreatedFromProvider() { @Test void allPropertiesConfiguration() { - this.contextRunner - .withPropertyValues("spring.ai.mcp.server.streamable-http.mcpEndpoint=/custom-endpoint", - "spring.ai.mcp.server.streamable-http.disallow-delete=true") + this.contextRunner.withPropertyValues("spring.ai.mcp.server.stateless.mcpEndpoint=/custom-endpoint") .run(context -> { WebMvcStatelessServerTransport provider = context.getBean(WebMvcStatelessServerTransport.class); assertThat(provider).extracting("mcpEndpoint").isEqualTo("/custom-endpoint"); @@ -165,7 +136,7 @@ void enabledPropertyDefaultsToTrue() { @Test void enabledPropertyExplicitlyTrue() { - this.contextRunner.withPropertyValues("spring.ai.mcp.server.streamable-http.enabled=true").run(context -> { + this.contextRunner.withPropertyValues("spring.ai.mcp.server.enabled=true").run(context -> { assertThat(context).hasSingleBean(WebMvcStatelessServerTransport.class); assertThat(context).hasSingleBean(RouterFunction.class); }); diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableWebMvcAutoConfigurationIT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableWebMvcAutoConfigurationIT.java index fb9d0d2d4c8..4b1f98a407f 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableWebMvcAutoConfigurationIT.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableWebMvcAutoConfigurationIT.java @@ -58,7 +58,7 @@ void serverDisableConfiguration() { @Test void serverBaseUrlConfiguration() { - this.contextRunner.withPropertyValues("spring.ai.mcp.server.streamable-http.mcpEndpoint=/test") + this.contextRunner.withPropertyValues("spring.ai.mcp.server.streamable.mcpEndpoint=/test") .run(context -> assertThat(context.getBean(WebMvcStreamableServerTransportProvider.class)) .extracting("mcpEndpoint") .isEqualTo("/test")); @@ -66,7 +66,7 @@ void serverBaseUrlConfiguration() { @Test void keepAliveIntervalConfiguration() { - this.contextRunner.withPropertyValues("spring.ai.mcp.server.streamable-http.keep-alive-interval=PT30S") + this.contextRunner.withPropertyValues("spring.ai.mcp.server.streamable.keep-alive-interval=PT30S") .run(context -> { assertThat(context).hasSingleBean(WebMvcStreamableServerTransportProvider.class); assertThat(context).hasSingleBean(RouterFunction.class); @@ -75,20 +75,18 @@ void keepAliveIntervalConfiguration() { @Test void disallowDeleteConfiguration() { - this.contextRunner.withPropertyValues("spring.ai.mcp.server.streamable-http.disallow-delete=true") - .run(context -> { - assertThat(context).hasSingleBean(WebMvcStreamableServerTransportProvider.class); - assertThat(context).hasSingleBean(RouterFunction.class); - }); + this.contextRunner.withPropertyValues("spring.ai.mcp.server.streamable.disallow-delete=true").run(context -> { + assertThat(context).hasSingleBean(WebMvcStreamableServerTransportProvider.class); + assertThat(context).hasSingleBean(RouterFunction.class); + }); } @Test void disallowDeleteFalseConfiguration() { - this.contextRunner.withPropertyValues("spring.ai.mcp.server.streamable-http.disallow-delete=false") - .run(context -> { - assertThat(context).hasSingleBean(WebMvcStreamableServerTransportProvider.class); - assertThat(context).hasSingleBean(RouterFunction.class); - }); + this.contextRunner.withPropertyValues("spring.ai.mcp.server.streamable.disallow-delete=false").run(context -> { + assertThat(context).hasSingleBean(WebMvcStreamableServerTransportProvider.class); + assertThat(context).hasSingleBean(RouterFunction.class); + }); } @Test @@ -144,9 +142,9 @@ void routerFunctionIsCreatedFromProvider() { @Test void allPropertiesConfiguration() { this.contextRunner - .withPropertyValues("spring.ai.mcp.server.streamable-http.mcpEndpoint=/custom-endpoint", - "spring.ai.mcp.server.streamable-http.keep-alive-interval=PT45S", - "spring.ai.mcp.server.streamable-http.disallow-delete=true") + .withPropertyValues("spring.ai.mcp.server.streamable.mcpEndpoint=/custom-endpoint", + "spring.ai.mcp.server.streamable.keep-alive-interval=PT45S", + "spring.ai.mcp.server.streamable.disallow-delete=true") .run(context -> { WebMvcStreamableServerTransportProvider provider = context .getBean(WebMvcStreamableServerTransportProvider.class); From c274f6fbfb22390c007ba6189befc89e58168958 Mon Sep 17 00:00:00 2001 From: yinh Date: Sat, 23 Aug 2025 07:40:29 +0800 Subject: [PATCH 2/3] fix(mcp): update tests to use callHandler instead of deprecated call method - Replace deprecated call() method with callHandler() in ToolUtilsTests and McpToolUtils - Fix NullPointerException in sync/async tool specification tests - Update MCP API usage to match current implementation Resolves test failures in spring-ai-mcp module. Signed-off-by: yinh --- .../springframework/ai/mcp/McpToolUtils.java | 6 ++--- .../ai/mcp/ToolUtilsTests.java | 23 +++++++++++++++---- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/mcp/common/src/main/java/org/springframework/ai/mcp/McpToolUtils.java b/mcp/common/src/main/java/org/springframework/ai/mcp/McpToolUtils.java index 9fd6b9e4a54..d5eb0ad8e2b 100644 --- a/mcp/common/src/main/java/org/springframework/ai/mcp/McpToolUtils.java +++ b/mcp/common/src/main/java/org/springframework/ai/mcp/McpToolUtils.java @@ -317,9 +317,9 @@ public static McpServerFeatures.AsyncToolSpecification toAsyncToolSpecification( McpServerFeatures.SyncToolSpecification syncToolSpecification = toSyncToolSpecification(toolCallback, mimeType); - return new AsyncToolSpecification(syncToolSpecification.tool(), - (exchange, map) -> Mono - .fromCallable(() -> syncToolSpecification.call().apply(new McpSyncServerExchange(exchange), map)) + return new AsyncToolSpecification(syncToolSpecification.tool(), null, + (exchange, request) -> Mono.fromCallable( + () -> syncToolSpecification.callHandler().apply(new McpSyncServerExchange(exchange), request)) .subscribeOn(Schedulers.boundedElastic())); } diff --git a/mcp/common/src/test/java/org/springframework/ai/mcp/ToolUtilsTests.java b/mcp/common/src/test/java/org/springframework/ai/mcp/ToolUtilsTests.java index 6dcac5c376c..d55f143f38d 100644 --- a/mcp/common/src/test/java/org/springframework/ai/mcp/ToolUtilsTests.java +++ b/mcp/common/src/test/java/org/springframework/ai/mcp/ToolUtilsTests.java @@ -26,6 +26,7 @@ import io.modelcontextprotocol.server.McpServerFeatures.AsyncToolSpecification; import io.modelcontextprotocol.server.McpServerFeatures.SyncToolSpecification; import io.modelcontextprotocol.server.McpSyncServerExchange; +import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.McpSchema.CallToolResult; import io.modelcontextprotocol.spec.McpSchema.Implementation; import io.modelcontextprotocol.spec.McpSchema.ListToolsResult; @@ -226,7 +227,10 @@ void toSyncToolSpecificationShouldConvertSingleCallback() { assertThat(toolSpecification).isNotNull(); assertThat(toolSpecification.tool().name()).isEqualTo("test"); - CallToolResult result = toolSpecification.call().apply(mock(McpSyncServerExchange.class), Map.of()); + McpSchema.CallToolRequest mockRequest = mock(McpSchema.CallToolRequest.class); + when(mockRequest.arguments()).thenReturn(Map.of()); + + CallToolResult result = toolSpecification.callHandler().apply(mock(McpSyncServerExchange.class), mockRequest); TextContent content = (TextContent) result.content().get(0); assertThat(content.text()).isEqualTo("success"); assertThat(result.isError()).isFalse(); @@ -239,7 +243,11 @@ void toSyncToolSpecificationShouldHandleError() { SyncToolSpecification toolSpecification = McpToolUtils.toSyncToolSpecification(callback); assertThat(toolSpecification).isNotNull(); - CallToolResult result = toolSpecification.call().apply(mock(McpSyncServerExchange.class), Map.of()); + + McpSchema.CallToolRequest mockRequest = mock(McpSchema.CallToolRequest.class); + when(mockRequest.arguments()).thenReturn(Map.of()); + + CallToolResult result = toolSpecification.callHandler().apply(mock(McpSyncServerExchange.class), mockRequest); TextContent content = (TextContent) result.content().get(0); assertThat(content.text()).isEqualTo("error"); assertThat(result.isError()).isTrue(); @@ -267,7 +275,10 @@ void toAsyncToolSpecificationShouldConvertSingleCallback() { assertThat(toolSpecification).isNotNull(); assertThat(toolSpecification.tool().name()).isEqualTo("test"); - StepVerifier.create(toolSpecification.call().apply(mock(McpAsyncServerExchange.class), Map.of())) + McpSchema.CallToolRequest mockRequest = mock(McpSchema.CallToolRequest.class); + when(mockRequest.arguments()).thenReturn(Map.of()); + + StepVerifier.create(toolSpecification.callHandler().apply(mock(McpAsyncServerExchange.class), mockRequest)) .assertNext(result -> { TextContent content = (TextContent) result.content().get(0); assertThat(content.text()).isEqualTo("success"); @@ -283,7 +294,11 @@ void toAsyncToolSpecificationShouldHandleError() { AsyncToolSpecification toolSpecification = McpToolUtils.toAsyncToolSpecification(callback); assertThat(toolSpecification).isNotNull(); - StepVerifier.create(toolSpecification.call().apply(mock(McpAsyncServerExchange.class), Map.of())) + + McpSchema.CallToolRequest mockRequest = mock(McpSchema.CallToolRequest.class); + when(mockRequest.arguments()).thenReturn(Map.of()); + + StepVerifier.create(toolSpecification.callHandler().apply(mock(McpAsyncServerExchange.class), mockRequest)) .assertNext(result -> { TextContent content = (TextContent) result.content().get(0); assertThat(content.text()).isEqualTo("error"); From 624db56c45ec55bdcacb50b08fe296f7ed615b17 Mon Sep 17 00:00:00 2001 From: yinh Date: Mon, 1 Sep 2025 10:29:46 +0800 Subject: [PATCH 3/3] feat: Conflict resolution Signed-off-by: yinh --- .../properties/McpServerProperties.java | 8 ++-- .../McpServerAutoConfigurationIT.java | 38 +++++++++++-------- .../McpServerSseWebFluxAutoConfiguration.java | 1 + ...rverStatelessWebFluxAutoConfiguration.java | 1 + ...treamableHttpWebFluxAutoConfiguration.java | 1 + .../StatelessWebClientWebFluxServerIT.java | 2 - .../StreamableMcpAnnotationsIT.java | 13 +++---- .../StreamableMcpAnnotationsManualIT.java | 13 +++---- .../StreamableWebClientWebFluxServerIT.java | 1 - .../McpServerSseWebMvcAutoConfiguration.java | 1 + ...erverStatelessWebMvcAutoConfiguration.java | 1 + ...StreamableHttpWebMvcAutoConfiguration.java | 1 + 12 files changed, 42 insertions(+), 39 deletions(-) diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/properties/McpServerProperties.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/properties/McpServerProperties.java index 9ad771fac79..6723dee1d37 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/properties/McpServerProperties.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/properties/McpServerProperties.java @@ -220,7 +220,7 @@ public void setProtocol(ServerProtocol serverMode) { } public Sse getSse() { - return sse; + return this.sse; } public void setSse(Sse sse) { @@ -228,7 +228,7 @@ public void setSse(Sse sse) { } public Streamable getStreamable() { - return streamable; + return this.streamable; } public void setStreamable(Streamable streamable) { @@ -236,7 +236,7 @@ public void setStreamable(Streamable streamable) { } public Stateless getStateless() { - return stateless; + return this.stateless; } public void setStateless(Stateless stateless) { @@ -244,7 +244,7 @@ public void setStateless(Stateless stateless) { } public ToolChangeNotification getToolChangeNotification() { - return toolChangeNotification; + return this.toolChangeNotification; } public void setToolChangeNotification(ToolChangeNotification toolChangeNotification) { diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/test/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerAutoConfigurationIT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/test/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerAutoConfigurationIT.java index df9ed021504..6c2edb57587 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/test/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerAutoConfigurationIT.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/test/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerAutoConfigurationIT.java @@ -16,16 +16,25 @@ package org.springframework.ai.mcp.server.common.autoconfigure; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; + import com.fasterxml.jackson.core.type.TypeReference; import io.modelcontextprotocol.client.McpSyncClient; -import io.modelcontextprotocol.server.*; -import io.modelcontextprotocol.server.McpServerFeatures.*; +import io.modelcontextprotocol.server.McpAsyncServer; +import io.modelcontextprotocol.server.McpAsyncServerExchange; +import io.modelcontextprotocol.server.McpServerFeatures; +import io.modelcontextprotocol.server.McpSyncServer; +import io.modelcontextprotocol.server.McpSyncServerExchange; import io.modelcontextprotocol.server.transport.StdioServerTransportProvider; import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.McpServerTransport; import io.modelcontextprotocol.spec.McpServerTransportProvider; import org.junit.jupiter.api.Test; import org.mockito.Mockito; +import reactor.core.publisher.Mono; + import org.springframework.ai.mcp.SyncMcpToolCallback; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties; import org.springframework.ai.tool.ToolCallback; @@ -34,11 +43,6 @@ 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.BiConsumer; -import java.util.function.BiFunction; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; @@ -206,7 +210,7 @@ void serverCapabilitiesConfiguration() { @Test void toolSpecificationConfiguration() { this.contextRunner.withUserConfiguration(TestToolConfiguration.class).run(context -> { - List tools = context.getBean("syncTools", List.class); + List tools = context.getBean("syncTools", List.class); assertThat(tools).hasSize(1); }); } @@ -253,7 +257,7 @@ void asyncToolSpecificationConfiguration() { this.contextRunner.withPropertyValues("spring.ai.mcp.server.type=ASYNC") .withUserConfiguration(TestToolConfiguration.class) .run(context -> { - List tools = context.getBean("asyncTools", List.class); + List tools = context.getBean("asyncTools", List.class); assertThat(tools).hasSize(1); }); } @@ -312,7 +316,7 @@ void toolResponseMimeTypeConfiguration() { assertThat(properties.getToolResponseMimeType()).containsEntry("test-tool", "application/json"); // Verify the MIME type is applied to the tool specifications - List tools = context.getBean("syncTools", List.class); + List tools = context.getBean("syncTools", List.class); assertThat(tools).hasSize(1); // The server should be properly configured with the tool @@ -336,7 +340,8 @@ void requestTimeoutConfiguration() { @Test void completionSpecificationConfiguration() { this.contextRunner.withUserConfiguration(TestCompletionConfiguration.class).run(context -> { - List completions = context.getBean("testCompletions", List.class); + List completions = context.getBean("testCompletions", + List.class); assertThat(completions).hasSize(1); }); } @@ -346,7 +351,8 @@ void asyncCompletionSpecificationConfiguration() { this.contextRunner.withPropertyValues("spring.ai.mcp.server.type=ASYNC") .withUserConfiguration(TestAsyncCompletionConfiguration.class) .run(context -> { - List completions = context.getBean("testAsyncCompletions", List.class); + List completions = context + .getBean("testAsyncCompletions", List.class); assertThat(completions).hasSize(1); }); } @@ -361,7 +367,7 @@ void toolCallbackProviderConfiguration() { static class TestResourceConfiguration { @Bean - List testResources() { + List testResources() { return List.of(); } @@ -371,7 +377,7 @@ List testResources() { static class TestPromptConfiguration { @Bean - List testPrompts() { + List testPrompts() { return List.of(); } @@ -435,7 +441,7 @@ ToolCallbackProvider testToolCallbackProvider() { static class TestCompletionConfiguration { @Bean - List testCompletions() { + List testCompletions() { BiFunction completionHandler = ( exchange, request) -> new McpSchema.CompleteResult( @@ -451,7 +457,7 @@ List testCompletions() { static class TestAsyncCompletionConfiguration { @Bean - List testAsyncCompletions() { + List testAsyncCompletions() { BiFunction> completionHandler = ( exchange, request) -> Mono.just(new McpSchema.CompleteResult( new McpSchema.CompleteResult.CompleteCompletion(List.of(), 0, false))); diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfiguration.java index b60ac93be32..57ba22eecd6 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfiguration.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.modelcontextprotocol.server.transport.WebFluxSseServerTransportProvider; import io.modelcontextprotocol.spec.McpServerTransportProvider; + import org.springframework.ai.mcp.server.common.autoconfigure.McpServerAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStdioDisabledCondition; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties; diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebFluxAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebFluxAutoConfiguration.java index 65b78160fff..daad7c657b5 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebFluxAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebFluxAutoConfiguration.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.modelcontextprotocol.server.transport.WebFluxStatelessServerTransport; import io.modelcontextprotocol.spec.McpSchema; + import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStatelessAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStdioDisabledCondition; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties; diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebFluxAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebFluxAutoConfiguration.java index 16981904bc6..969fadafed7 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebFluxAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebFluxAutoConfiguration.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.modelcontextprotocol.server.transport.WebFluxStreamableServerTransportProvider; import io.modelcontextprotocol.spec.McpSchema; + import org.springframework.ai.mcp.server.common.autoconfigure.McpServerAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStdioDisabledCondition; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties; diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StatelessWebClientWebFluxServerIT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StatelessWebClientWebFluxServerIT.java index 11b17cb3cbf..278badd8078 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StatelessWebClientWebFluxServerIT.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StatelessWebClientWebFluxServerIT.java @@ -16,7 +16,6 @@ package org.springframework.ai.mcp.server.autoconfigure; -import java.time.Duration; import java.util.List; import java.util.Map; @@ -53,7 +52,6 @@ import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStatelessAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.StatelessToolCallbackConverterAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties; -import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerStreamableHttpProperties; import org.springframework.ai.tool.function.FunctionToolCallback; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfigurations; diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableMcpAnnotationsIT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableMcpAnnotationsIT.java index 541470fc89a..3ca43be0df0 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableMcpAnnotationsIT.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableMcpAnnotationsIT.java @@ -80,7 +80,6 @@ import org.springframework.ai.mcp.server.common.autoconfigure.annotations.McpServerAnnotationScannerAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.annotations.McpServerSpecificationFactoryAutoConfiguration; 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.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -121,9 +120,9 @@ void clientServerCapabilities() { // "spring.ai.mcp.server.type=ASYNC", // "spring.ai.mcp.server.protocol=SSE", "spring.ai.mcp.server.version=1.0.0", - "spring.ai.mcp.server.streamable-http.keep-alive-interval=1s", + "spring.ai.mcp.server.streamable.keep-alive-interval=1s", // "spring.ai.mcp.server.requestTimeout=1m", - "spring.ai.mcp.server.streamable-http.mcp-endpoint=/mcp") // @formatter:on + "spring.ai.mcp.server.streamable.mcp-endpoint=/mcp") // @formatter:on .run(serverContext -> { // Verify all required beans are present assertThat(serverContext).hasSingleBean(WebFluxStreamableServerTransportProvider.class); @@ -135,16 +134,14 @@ void clientServerCapabilities() { assertThat(properties.getName()).isEqualTo("test-mcp-server"); assertThat(properties.getVersion()).isEqualTo("1.0.0"); - McpServerStreamableHttpProperties streamableHttpProperties = serverContext - .getBean(McpServerStreamableHttpProperties.class); - assertThat(streamableHttpProperties.getMcpEndpoint()).isEqualTo("/mcp"); - assertThat(streamableHttpProperties.getKeepAliveInterval()).isEqualTo(Duration.ofSeconds(1)); + assertThat(properties.getStreamable().getMcpEndpoint()).isEqualTo("/mcp"); + assertThat(properties.getStreamable().getKeepAliveInterval()).isEqualTo(Duration.ofSeconds(1)); var httpServer = startHttpServer(serverContext, serverPort); this.clientApplicationContext.withUserConfiguration(TestMcpClientConfiguration.class) .withPropertyValues(// @formatter:off - "spring.ai.mcp.client.streamable-http.connections.server1.url=http://localhost:" + serverPort, + "spring.ai.mcp.client.streamable.connections.server1.url=http://localhost:" + serverPort, // "spring.ai.mcp.client.sse.connections.server1.url=http://localhost:" + serverPort, // "spring.ai.mcp.client.request-timeout=20m", "spring.ai.mcp.client.initialized=false") // @formatter:on diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableMcpAnnotationsManualIT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableMcpAnnotationsManualIT.java index 5904183f6ea..1e5c8c55291 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableMcpAnnotationsManualIT.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableMcpAnnotationsManualIT.java @@ -82,7 +82,6 @@ import org.springframework.ai.mcp.server.common.autoconfigure.McpServerAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.ToolCallbackConverterAutoConfiguration; 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.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -117,9 +116,9 @@ void clientServerCapabilities() { .withPropertyValues(// @formatter:off "spring.ai.mcp.server.name=test-mcp-server", "spring.ai.mcp.server.version=1.0.0", - "spring.ai.mcp.server.streamable-http.keep-alive-interval=1s", + "spring.ai.mcp.server.streamable.keep-alive-interval=1s", // "spring.ai.mcp.server.requestTimeout=1m", - "spring.ai.mcp.server.streamable-http.mcp-endpoint=/mcp") // @formatter:on + "spring.ai.mcp.server.streamable.mcp-endpoint=/mcp") // @formatter:on .run(serverContext -> { // Verify all required beans are present assertThat(serverContext).hasSingleBean(WebFluxStreamableServerTransportProvider.class); @@ -131,16 +130,14 @@ void clientServerCapabilities() { assertThat(properties.getName()).isEqualTo("test-mcp-server"); assertThat(properties.getVersion()).isEqualTo("1.0.0"); - McpServerStreamableHttpProperties streamableHttpProperties = serverContext - .getBean(McpServerStreamableHttpProperties.class); - assertThat(streamableHttpProperties.getMcpEndpoint()).isEqualTo("/mcp"); - assertThat(streamableHttpProperties.getKeepAliveInterval()).isEqualTo(Duration.ofSeconds(1)); + assertThat(properties.getStreamable().getMcpEndpoint()).isEqualTo("/mcp"); + assertThat(properties.getStreamable().getKeepAliveInterval()).isEqualTo(Duration.ofSeconds(1)); var httpServer = startHttpServer(serverContext, serverPort); this.clientApplicationContext.withUserConfiguration(TestMcpClientConfiguration.class) .withPropertyValues(// @formatter:off - "spring.ai.mcp.client.streamable-http.connections.server1.url=http://localhost:" + serverPort, + "spring.ai.mcp.client.streamable.connections.server1.url=http://localhost:" + serverPort, // "spring.ai.mcp.client.request-timeout=20m", "spring.ai.mcp.client.initialized=false") // @formatter:on .run(clientContext -> { diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableWebClientWebFluxServerIT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableWebClientWebFluxServerIT.java index e9ee28eb713..3aefa6fc19f 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableWebClientWebFluxServerIT.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableWebClientWebFluxServerIT.java @@ -67,7 +67,6 @@ import org.springframework.ai.mcp.server.common.autoconfigure.McpServerAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.ToolCallbackConverterAutoConfiguration; 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.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebMvcAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebMvcAutoConfiguration.java index 613de8b627a..6b799474a74 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebMvcAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebMvcAutoConfiguration.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.modelcontextprotocol.server.transport.WebMvcSseServerTransportProvider; import io.modelcontextprotocol.spec.McpServerTransportProvider; + import org.springframework.ai.mcp.server.common.autoconfigure.McpServerAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStdioDisabledCondition; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties; diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebMvcAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebMvcAutoConfiguration.java index 9b51b0d6048..91302234d98 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebMvcAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebMvcAutoConfiguration.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.modelcontextprotocol.server.transport.WebMvcStatelessServerTransport; import io.modelcontextprotocol.spec.McpSchema; + import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStatelessAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStdioDisabledCondition; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties; diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebMvcAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebMvcAutoConfiguration.java index 2b77dc382ab..11b1fb432d2 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebMvcAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebMvcAutoConfiguration.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.modelcontextprotocol.server.transport.WebMvcStreamableServerTransportProvider; import io.modelcontextprotocol.spec.McpSchema; + import org.springframework.ai.mcp.server.common.autoconfigure.McpServerAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStdioDisabledCondition; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties;