Skip to content

Add streamable support to spring-ai-starter-mcp-server-webflux #4053

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,11 @@
<scope>test</scope>
</dependency>


<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* 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.autoconfigure;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.server.transport.HttpServletStreamableServerTransportProvider;
import io.modelcontextprotocol.spec.McpServerTransportProvider;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;

/**
* {@link AutoConfiguration Auto-configuration} for MCP HttpServlet Streamable Server
* Transport.
* <p>
* This configuration class sets up the HttpServlet-specific Streamable transport
* components for the MCP server, providing HTTP streaming communication through standard
* Java Servlet API. It is activated when:
* <ul>
* <li>The HttpServletStreamableServerTransportProvider class is on the classpath (from
* mcp-server-http-servlet dependency)</li>
* <li>The MCP server is enabled and STDIO transport is disabled</li>
* <li>The transport type is set to STREAMABLE</li>
* <li>No other MCP server transport provider is available</li>
* </ul>
* <p>
* The configuration provides:
* <ul>
* <li>A HttpServletStreamableServerTransportProvider bean for handling HTTP streaming
* communication</li>
* </ul>
* <p>
* This configuration has the lowest priority and will only be activated if no WebFlux or
* WebMVC streamable transports are available.
* <p>
* Required dependencies: <pre>{@code
* <dependency>
* <groupId>io.modelcontextprotocol.sdk</groupId>
* <artifactId>mcp-server-http-servlet</artifactId>
* </dependency>
* }</pre>
*
* @author yinh
* @since 1.0.1
* @see McpServerProperties
* @see HttpServletStreamableServerTransportProvider
*/
@AutoConfiguration(
after = { McpWebFluxStreamableServerAutoConfiguration.class, McpWebMvcStreamableServerAutoConfiguration.class })
@ConditionalOnClass({ HttpServletStreamableServerTransportProvider.class })
@ConditionalOnMissingBean(McpServerTransportProvider.class)
@Conditional(McpServerStreamableTransportCondition.class)
public class McpHttpServletStreamableServerAutoConfiguration {

@Bean
@ConditionalOnMissingBean
public HttpServletStreamableServerTransportProvider httpServletStreamableTransport(
ObjectProvider<ObjectMapper> objectMapperProvider, McpServerProperties serverProperties) {

ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new);

// Create an HttpServlet Streamable transport provider using the builder pattern
return HttpServletStreamableServerTransportProvider.builder()
.mcpEndpoint(serverProperties.getSseMessageEndpoint())
.objectMapper(objectMapper)
.build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.McpSchema.Implementation;
import io.modelcontextprotocol.spec.McpServerTransportProvider;
import io.modelcontextprotocol.spec.McpServerTransportProviderBase;
import io.modelcontextprotocol.spec.McpStreamableServerTransportProvider;
import reactor.core.publisher.Mono;

import org.springframework.ai.mcp.McpToolUtils;
Expand Down Expand Up @@ -93,7 +95,7 @@
* Server configuration is managed through {@link McpServerProperties} with support for:
* <ul>
* <li>Server identification (name, version)</li>
* <li>Transport selection</li>
* <li>Transport selection (STDIO, SSE, STREAMABLE)</li>
* <li>Change notification settings for tools, resources, and prompts</li>
* <li>Sync/Async operation mode selection</li>
* </ul>
Expand All @@ -106,9 +108,14 @@
* @see McpServerProperties
* @see McpWebMvcServerAutoConfiguration
* @see McpWebFluxServerAutoConfiguration
* @see McpWebMvcStreamableServerAutoConfiguration
* @see McpWebFluxStreamableServerAutoConfiguration
* @see McpHttpServletStreamableServerAutoConfiguration
* @see ToolCallback
*/
@AutoConfiguration(after = { McpWebMvcServerAutoConfiguration.class, McpWebFluxServerAutoConfiguration.class })
@AutoConfiguration(after = { McpWebMvcServerAutoConfiguration.class, McpWebFluxServerAutoConfiguration.class,
McpWebMvcStreamableServerAutoConfiguration.class, McpWebFluxStreamableServerAutoConfiguration.class,
McpHttpServletStreamableServerAutoConfiguration.class })
@ConditionalOnClass({ McpSchema.class, McpSyncServer.class })
@EnableConfigurationProperties(McpServerProperties.class)
@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
Expand Down Expand Up @@ -170,7 +177,7 @@ private List<McpServerFeatures.SyncToolSpecification> toSyncToolSpecifications(L
@Bean
@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "type", havingValue = "SYNC",
matchIfMissing = true)
public McpSyncServer mcpSyncServer(McpServerTransportProvider transportProvider,
public McpSyncServer mcpSyncServer(McpServerTransportProviderBase transportProvider,
McpSchema.ServerCapabilities.Builder capabilitiesBuilder, McpServerProperties serverProperties,
ObjectProvider<List<SyncToolSpecification>> tools,
ObjectProvider<List<SyncResourceSpecification>> resources,
Expand All @@ -183,7 +190,14 @@ public McpSyncServer mcpSyncServer(McpServerTransportProvider transportProvider,
serverProperties.getVersion());

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

// Tools
if (serverProperties.getCapabilities().isTool()) {
Expand Down Expand Up @@ -300,7 +314,7 @@ private List<McpServerFeatures.AsyncToolSpecification> toAsyncToolSpecification(

@Bean
@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "type", havingValue = "ASYNC")
public McpAsyncServer mcpAsyncServer(McpServerTransportProvider transportProvider,
public McpAsyncServer mcpAsyncServer(McpServerTransportProviderBase transportProvider,
McpSchema.ServerCapabilities.Builder capabilitiesBuilder, McpServerProperties serverProperties,
ObjectProvider<List<AsyncToolSpecification>> tools,
ObjectProvider<List<AsyncResourceSpecification>> resources,
Expand All @@ -313,7 +327,14 @@ public McpAsyncServer mcpAsyncServer(McpServerTransportProvider transportProvide
serverProperties.getVersion());

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

// Tools
if (serverProperties.getCapabilities().isTool()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,18 @@ public class McpServerProperties {
*/
private boolean stdio = false;

/**
* The transport type for web-based communication.
* <p>
* Supported transport types:
* <ul>
* <li>SSE - Server-Sent Events transport (default)</li>
* <li>STREAMABLE - Streamable HTTP transport</li>
* </ul>
* Only relevant when stdio is disabled.
*/
private TransportType transportType = TransportType.SSE;

/**
* The name of the MCP server instance.
* <p>
Expand Down Expand Up @@ -170,6 +182,23 @@ public enum ServerType {

}

/**
* Transport types supported by the MCP server for web communication.
*/
public enum TransportType {

/**
* Server-Sent Events transport
*/
SSE,

/**
* Streamable HTTP transport
*/
STREAMABLE

}

/**
* (Optional) response MIME type per tool name.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.springframework.ai.mcp.server.autoconfigure;

import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;

/**
* This class defines a condition met when the MCP server is enabled, STDIO transport is
* disabled, and STREAMABLE transport type is selected.
*
* @since 1.1.0
* @author yinh
*/
public class McpServerStreamableTransportCondition extends AllNestedConditions {

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

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

}

@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "stdio", havingValue = "false",
matchIfMissing = true)
static class StdioDisabledCondition {

}

@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "transport-type",
havingValue = "STREAMABLE")
static class StreamableTransportCondition {

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* 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.autoconfigure;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.server.transport.WebFluxStreamableServerTransportProvider;
import io.modelcontextprotocol.spec.McpServerTransportProvider;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.web.reactive.function.server.RouterFunction;

/**
* {@link AutoConfiguration Auto-configuration} for MCP WebFlux Streamable Server
* Transport.
* <p>
* This configuration class sets up the WebFlux-specific Streamable transport components
* for the MCP server, providing reactive HTTP streaming communication through Spring
* WebFlux. It is activated when:
* <ul>
* <li>The WebFluxStreamableServerTransportProvider class is on the classpath (from
* mcp-spring-webflux dependency)</li>
* <li>Spring WebFlux's RouterFunction class is available (from
* spring-boot-starter-webflux)</li>
* <li>The MCP server is enabled and STDIO transport is disabled</li>
* <li>The transport type is set to STREAMABLE</li>
* </ul>
* <p>
* The configuration provides:
* <ul>
* <li>A WebFluxStreamableServerTransportProvider bean for handling reactive HTTP
* streaming communication</li>
* <li>A RouterFunction bean that sets up the reactive streaming endpoint</li>
* </ul>
* <p>
* Required dependencies: <pre>{@code
* <dependency>
* <groupId>io.modelcontextprotocol.sdk</groupId>
* <artifactId>mcp-spring-webflux</artifactId>
* </dependency>
* <dependency>
* <groupId>org.springframework.boot</groupId>
* <artifactId>spring-boot-starter-webflux</artifactId>
* </dependency>
* }</pre>
*
* @author yinh
* @since 1.0.1
* @see McpServerProperties
* @see WebFluxStreamableServerTransportProvider
*/
@AutoConfiguration
@ConditionalOnClass({ WebFluxStreamableServerTransportProvider.class, RouterFunction.class })
@ConditionalOnMissingBean(McpServerTransportProvider.class)
@Conditional(McpServerStreamableTransportCondition.class)
public class McpWebFluxStreamableServerAutoConfiguration {

@Bean
@ConditionalOnMissingBean
public WebFluxStreamableServerTransportProvider webFluxStreamableTransport(
ObjectProvider<ObjectMapper> objectMapperProvider, McpServerProperties serverProperties) {

ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new);

// 使用builder模式创建WebFlux Streamable传输提供者
return WebFluxStreamableServerTransportProvider.builder()
.messageEndpoint(serverProperties.getSseMessageEndpoint())
.objectMapper(objectMapper)
.build();
}

// Router function for Streamable transport used by Spring WebFlux to start an HTTP
// server.
@Bean
public RouterFunction<?> webfluxMcpStreamableRouterFunction(
WebFluxStreamableServerTransportProvider webFluxStreamableProvider) {
return webFluxStreamableProvider.getRouterFunction();
}

}
Loading