I am using Spring AI WebMvc MCP Server to build my MCP server, but I now want to start multiple MCP servers within a single project. However, my attempts have failed. Please provide a solution.

Expected Behavior I now want to start multiple MCP servers within a single project.

Current Behavior

The current situation is that only one MCP server can be started, and attempting to start multiple servers results in an error.

Context

version springboot 3.5.0 spring-ai-mcp 1.0.0

package org.example.mcpserverdemo;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.server.McpServer;
import io.modelcontextprotocol.server.McpSyncServer;
import io.modelcontextprotocol.server.transport.WebMvcSseServerTransportProvider;
import io.modelcontextprotocol.spec.McpSchema;
import org.springframework.ai.mcp.McpToolUtils;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.ServerResponse;

/**
 * <p>
 *
 * </p>
 *
 * @author kunqi.fc
 * @since 2025/6/12
 **/
@Configuration
public class McpServerConfig {

    @Bean("t1")
    public WebMvcSseServerTransportProvider webMvcSseServerTransportProvider1() {
        return  new WebMvcSseServerTransportProvider(new ObjectMapper(), "/mcp/message","/sse");
    }

    @Bean
    public RouterFunction<ServerResponse> mvcMcpRouterFunction1(@Qualifier("t1") WebMvcSseServerTransportProvider transportProvider) {
        return transportProvider.getRouterFunction();
    }

    @Bean("t2")
    public WebMvcSseServerTransportProvider webMvcSseServerTransportProvider2() {
        return  new WebMvcSseServerTransportProvider(new ObjectMapper(), "/mcp/message","/sse2");
    }

    @Bean
    public RouterFunction<ServerResponse> mvcMcpRouterFunction2(@Qualifier("t2")WebMvcSseServerTransportProvider transportProvider) {
        return transportProvider.getRouterFunction();
    }

    @Bean("weather-mcp-server_instance1")
    public McpSyncServer mcpServer(ToolCallbackProvider provider,@Qualifier("t1") WebMvcSseServerTransportProvider transportProvider) { // @formatter:off

        // Configure server capabilities with resource support
        var capabilities = McpSchema.ServerCapabilities.builder()
                .tools(true) // Tool support with list changes notifications
                .logging() // Logging support
                .build();

        // Create the server with both tool and resource capabilities
        // Add @Tools

        return McpServer.sync(transportProvider)
                .serverInfo("MCP Demo Weather Server1", "1.0.1")
                .capabilities(capabilities)
                .tools(McpToolUtils.toSyncToolSpecifications(provider.getToolCallbacks())) // Add @Tools
                .build(); // @formatter:on
    } // @formatter:on


    @Bean(name = "weather-mcp-server_instance2")
    public McpSyncServer mcpServer2(ToolCallbackProvider provider,@Qualifier("t2") WebMvcSseServerTransportProvider transportProvider) { // @formatter:off

        // Configure server capabilities with resource support
        var capabilities = McpSchema.ServerCapabilities.builder()
                .tools(true) // Tool support with list changes notifications
                .logging() // Logging support
                .build();

        // Create the server with both tool and resource capabilities
        // Add @Tools

        return McpServer.sync(transportProvider)
                .serverInfo("MCP Demo Weather Server2", "1.0.2")
                .capabilities(capabilities)
                .tools(McpToolUtils.toSyncToolSpecifications(provider.getToolCallbacks())) // Add @Tools
                .build(); // @formatter:on
    } // @formatter:on


    @Bean
    public ToolCallbackProvider weatherTools(WeatherService weatherService) {
        return MethodToolCallbackProvider.builder().toolObjects(weatherService).build();
    }
}

errormssage: 2025-06-12T11:14:54.713+08:00 INFO 57942 --- [mcp_server_demo] [ main] .s.b.a.l.ConditionEvaluationReportLogger :

Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled. 2025-06-12T11:14:54.720+08:00 ERROR 57942 --- [mcp_server_demo] [ main] o.s.b.d.LoggingFailureAnalysisReporter :


APPLICATION FAILED TO START


Description:

Parameter 0 of method mcpSyncServer in org.springframework.ai.mcp.server.autoconfigure.McpServerAutoConfiguration required a single bean, but 2 were found: - t1: defined by method 'webMvcSseServerTransportProvider1' in class path resource [org/example/mcpserverdemo/McpServerConfig.class] - t2: defined by method 'webMvcSseServerTransportProvider2' in class path resource [org/example/mcpserverdemo/McpServerConfig.class]

This may be due to missing parameter name information

Action:

Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed

Ensure that your compiler is configured to use the '-parameters' flag. You may need to update both your build tool settings as well as your IDE. (See https://github.com/spring-projects/spring-framework/wiki/Spring-Framework-6.1-Release-Notes#parameter-name-retention)

Image

Comment From: Susuper2019

I have the same requirement. After I configured @SpringBootApplication(exclude = {McpServerAutoConfiguration.class}), both servers can start successfully, but they are unable to load their respective tools.

Comment From: Susuper2019

Using @SpringBootApplication(exclude = {McpServerAutoConfiguration.class}) is the correct approach, and I’ve got it running successfully now.

I have the same requirement. After I configured @SpringBootApplication(exclude = {McpServerAutoConfiguration.class}), both servers can start successfully, but they are unable to load their respective tools.我也有同样的要求。配置 @SpringBootApplication(exclude = {McpServerAutoConfiguration.class}) 后,两台服务器都可以成功启动,但它们无法加载各自的工具。

Comment From: sunyuhan1998

In fact, your current approach is already equivalent to using the native MCP Java sdk directly, and I don't think you need to introduce Spring AI's MCP Server Starter at all, so that this issue is good to go.

Comment From: quaff

I now want to start multiple MCP servers within a single project.

Curiously, why would you want to do in that way?

Comment From: Susuper2019

I now want to start multiple MCP servers within a single project.现在,我想在一个项目中启动多个 MCP 服务器。

Curiously, why would you want to do in that way?奇怪的是,你为什么要这样做呢?

We have multiple backend services, each paired with its own MCP server and agent. This design offers two main benefits: First, it minimizes resource usage, as a single server application can host multiple MCP servers. Second, the tools defined by each backend service may be similar — for example, tools for starting or stopping rules and business logic. If all tools were placed under a single MCP server, the client might mistakenly invoke the wrong tool.

Comment From: quaff

First, it minimizes resource usage, as a single server application can host multiple MCP servers.

Yes, but it increase complexity, it's a tradeoff.

Second, the tools defined by each backend service may be similar — for example, tools for starting or stopping rules and business logic.

You should extract common code as library for share.

Comment From: gakkiyomi

In fact, your current approach is already equivalent to using the native MCP Java sdk directly, and I don't think you need to introduce Spring AI's MCP Server Starter at all, so that this issue is good to go.

How to bind servlet to embbding webserver when using native SDK? I am not very good at springboot

Comment From: gakkiyomi

Using @SpringBootApplication(exclude = {McpServerAutoConfiguration.class}) is the correct approach, and I’ve got it running successfully now.

I have the same requirement. After I configured @SpringBootApplication(exclude = {McpServerAutoConfiguration.class}), both servers can start successfully, but they are unable to load their respective tools.我也有同样的要求。配置 @SpringBootApplication(exclude = {McpServerAutoConfiguration.class}) 后,两台服务器都可以成功启动,但它们无法加载各自的工具。

so, how to solve this: 《not load their respective tools.》

Comment From: Susuper2019

Using @SpringBootApplication(exclude = {McpServerAutoConfiguration.class}) is the correct approach, and I’ve got it running successfully now.使用 @SpringBootApplication(exclude = {McpServerAutoConfiguration.class}) 是正确的方法,我现在已经成功运行了它。

I have the same requirement. After I configured @SpringBootApplication(exclude = {McpServerAutoConfiguration.class}), both servers can start successfully, but they are unable to load their respective tools.我也有同样的要求。配置 @SpringBootApplication(exclude = {McpServerAutoConfiguration.class}) 后,两台服务器都可以成功启动,但它们无法加载各自的工具。我也有同样的要求。在我配置了 @SpringBootApplication(exclude = {McpServerAutoConfiguration.class}) 后,两个服务器都可以成功启动,但它们无法加载各自的工具。配置 @SpringBootApplication(exclude = {McpServerAutoConfiguration.class}) 后,两台服务器都可以成功启动,但它们无法加载各自的工具。

so, how to solve this: 《not load their respective tools.》那么,如何解决这个问题: 《不加载各自的工具》

I initially wrote the wrong URL, but after correcting it, the tools were successfully loaded

Comment From: gakkiyomi

Using @SpringBootApplication(exclude = {McpServerAutoConfiguration.class}) is the correct approach, and I’ve got it running successfully now.使用 @SpringBootApplication(exclude = {McpServerAutoConfiguration.class}) 是正确的方法,我现在已经成功运行了它。

I have the same requirement. After I configured @SpringBootApplication(exclude = {McpServerAutoConfiguration.class}), both servers can start successfully, but they are unable to load their respective tools.我也有同样的要求。配置 @SpringBootApplication(exclude = {McpServerAutoConfiguration.class}) 后,两台服务器都可以成功启动,但它们无法加载各自的工具。我也有同样的要求。在我配置了 @SpringBootApplication(exclude = {McpServerAutoConfiguration.class}) 后,两个服务器都可以成功启动,但它们无法加载各自的工具。配置 @SpringBootApplication(exclude = {McpServerAutoConfiguration.class}) 后,两台服务器都可以成功启动,但它们无法加载各自的工具。

so, how to solve this: 《not load their respective tools.》那么,如何解决这个问题: 《不加载各自的工具》

I initially wrote the wrong URL, but after correcting it, the tools were successfully loaded

Can you share your code with me?

Comment From: sunyuhan1998

How to bind servlet to embbding webserver when using native SDK? I am not very good at springboot

I noticed that you are using WebMvcSseServerTransportProvider, so I'm assuming that you expect to use a WebMvc web server, in this scenario you should use spring-boot-starter-web, which can quickly help you build a web server: https://docs.spring.io/spring-boot/tutorial/first-application/index.html

Comment From: FishingDanDan

Using @SpringBootApplication(exclude = {McpServerAutoConfiguration.class}) is the correct approach, and I’ve got it running successfully now.使用 @SpringBootApplication(exclude = {McpServerAutoConfiguration.class}) 是正确的方法,我现在已经成功运行了它。

I have the same requirement. After I configured @SpringBootApplication(exclude = {McpServerAutoConfiguration.class}), both servers can start successfully, but they are unable to load their respective tools.我也有同样的要求。配置 @SpringBootApplication(exclude = {McpServerAutoConfiguration.class}) 后,两台服务器都可以成功启动,但它们无法加载各自的工具。我也有同样的要求。在我配置了 @SpringBootApplication(exclude = {McpServerAutoConfiguration.class}) 后,两个服务器都可以成功启动,但它们无法加载各自的工具。配置 @SpringBootApplication(exclude = {McpServerAutoConfiguration.class}) 后,两台服务器都可以成功启动,但它们无法加载各自的工具。

so, how to solve this: 《not load their respective tools.》那么,如何解决这个问题: 《不加载各自的工具》

I initially wrote the wrong URL, but after correcting it, the tools were successfully loaded

Can you share your code with me?

How did you solve it in the end, buddy

Comment From: FishingDanDan

Using @SpringBootApplication(exclude = {McpServerAutoConfiguration.class}) is the correct approach, and I’ve got it running successfully now.使用 @SpringBootApplication(exclude = {McpServerAutoConfiguration.class}) 是正确的方法,我现在已经成功运行了它。

I have the same requirement. After I configured @SpringBootApplication(exclude = {McpServerAutoConfiguration.class}), both servers can start successfully, but they are unable to load their respective tools.我也有同样的要求。配置 @SpringBootApplication(exclude = {McpServerAutoConfiguration.class}) 后,两台服务器都可以成功启动,但它们无法加载各自的工具。我也有同样的要求。在我配置了 @SpringBootApplication(exclude = {McpServerAutoConfiguration.class}) 后,两个服务器都可以成功启动,但它们无法加载各自的工具。配置 @SpringBootApplication(exclude = {McpServerAutoConfiguration.class}) 后,两台服务器都可以成功启动,但它们无法加载各自的工具。

so, how to solve this: 《not load their respective tools.》那么,如何解决这个问题: 《不加载各自的工具》

I initially wrote the wrong URL, but after correcting it, the tools were successfully loaded

Can you share your code with me?

How did you solve it in the end, buddy

我解决了,感谢。最重要的还是复写McpWebMvcServerAutoConfiguration,可能由于我之前配置了context-path,你上面的代码没有携带serverProperties.getBaseUrl()。

完整的代码如下:

package com.example.mcpdemo.config;

import com.example.mcpdemo.service.CqmsService;
import com.example.mcpdemo.service.WeatherService;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.server.McpServer;
import io.modelcontextprotocol.server.McpSyncServer;
import io.modelcontextprotocol.server.transport.WebMvcSseServerTransportProvider;
import io.modelcontextprotocol.spec.McpSchema;
import org.springframework.ai.mcp.McpToolUtils;
import org.springframework.ai.mcp.server.autoconfigure.McpServerProperties;
import org.springframework.ai.support.ToolCallbacks;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.ServerResponse;

@Configuration
@EnableConfigurationProperties(McpServerProperties.class)
public class McpServerConfig {

    @Autowired
    private CqmsService cqmsService;

    @Autowired
    private WeatherService weatherService;

    private final ObjectMapper objectMapper = new ObjectMapper();

    private McpSyncServer getMcpSyncServer(WebMvcSseServerTransportProvider provider, String name, String version, ToolCallback... toolCallbacks) {
        var capabilities = McpSchema.ServerCapabilities.builder()
                .tools(true)
                .logging()
                .build();

        return McpServer.sync(provider)
                .serverInfo(name, version)
                .capabilities(capabilities)
                .tools(McpToolUtils.toSyncToolSpecifications(toolCallbacks))
                .build();
    }


    @Bean(name = "cqmsTransportProvider")
    public WebMvcSseServerTransportProvider cqmsTransportProvider(McpServerProperties serverProperties) {
        return new WebMvcSseServerTransportProvider(objectMapper, serverProperties.getBaseUrl(), "/cqms/mcp/message", "/cqms/sse");
    }

    @Bean
    public RouterFunction<?> cqmsMcpRouterFunction(@Qualifier("cqmsTransportProvider") WebMvcSseServerTransportProvider sseServerTransportProvider) {
        return sseServerTransportProvider.getRouterFunction();
    }

    @Bean("cqms-mcp-server")
    public McpSyncServer newsMcpServer(@Qualifier("cqmsTransportProvider") WebMvcSseServerTransportProvider newsTransportProvider) {
        return getMcpSyncServer(newsTransportProvider, "MCP Cqms Server", "1.0.0", ToolCallbacks.from(cqmsService));
    }


    @Bean(name = "weatherTransportProvider")
    public WebMvcSseServerTransportProvider weatherTransportProvider(McpServerProperties serverProperties) {
        return new WebMvcSseServerTransportProvider(objectMapper, serverProperties.getBaseUrl(), "/weather/mcp/message", "/weather/sse");
    }

    @Bean
    public RouterFunction<ServerResponse> weatherMcpRouterFunction(@Qualifier("weatherTransportProvider") WebMvcSseServerTransportProvider sseServerTransportProvider) {
        return sseServerTransportProvider.getRouterFunction();
    }

    @Bean("weather-mcp-server")
    public McpSyncServer weatherMcpServer(@Qualifier("weatherTransportProvider") WebMvcSseServerTransportProvider newsTransportProvider) {
        return getMcpSyncServer(newsTransportProvider, "MCP Weather Server", "1.0.0", ToolCallbacks.from(weatherService));
    }

}
spring.application.name=mcp-demo
server.port=9090
server.servlet.context-path=/mcp-demo
#
spring.ai.mcp.server.base-url=${server.servlet.context-path}
spring.ai.mcp.server.capabilities.completion=false
spring.ai.mcp.server.capabilities.prompt=false

这样我的两个mcpServer路径分别是: http://localhost:9090/mcp-demo/weather/sse http://localhost:9090/mcp-demo/cqms/sse

Comment From: lihuagang03

In fact, your current approach is already equivalent to using the native MCP Java sdk directly, and I don't think you need to introduce Spring AI's MCP Server Starter at all, so that this issue is good to go.

There is actually such a real demand. RouterFunction<?> is a Spring-Boot framework-level function. @sunyuhan1998

Comment From: lihuagang03

I now want to start multiple MCP servers within a single project.

Curiously, why would you want to do in that way?

The demand is wrapping all HTTP REST APIs to MCP Tools, MCP Servers are group by application name. @quaff

Comment From: lihuagang03

最重要的还是复写McpWebMvcServerAutoConfiguration

Good, correct. 正解 @FishingDanDan