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 bcb5e3bd368..9e3d8e0f759 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 @@ -99,7 +99,8 @@ public WebFluxSseServerTransportProvider webFluxTransport(ObjectProvider webfluxMcpRouterFunction(WebFluxSseServerTransportProvider webFluxProvider) { + @ConditionalOnMissingBean(name = "webfluxSseServerRouterFunction") + public RouterFunction webfluxSseServerRouterFunction(WebFluxSseServerTransportProvider webFluxProvider) { return webFluxProvider.getRouterFunction(); } 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 13a9038fba0..c42cba64ec8 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 @@ -35,6 +35,7 @@ /** * @author Christian Tzolov + * @author Yanming Zhou */ @AutoConfiguration(before = McpServerStatelessAutoConfiguration.class) @ConditionalOnClass({ McpSchema.class }) @@ -60,6 +61,7 @@ public WebFluxStatelessServerTransport webFluxStatelessServerTransport( // Router function for stateless http transport used by Spring WebFlux to start an // HTTP server. @Bean + @ConditionalOnMissingBean(name = "webFluxStatelessServerRouterFunction") public RouterFunction webFluxStatelessServerRouterFunction( WebFluxStatelessServerTransport webFluxStatelessTransport) { return webFluxStatelessTransport.getRouterFunction(); 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 89b70e974b9..ade0fe91c8f 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 @@ -36,6 +36,7 @@ /** * @author Christian Tzolov + * @author Yanming Zhou */ @AutoConfiguration(before = McpServerAutoConfiguration.class) @ConditionalOnClass({ McpSchema.class }) @@ -62,6 +63,7 @@ public WebFluxStreamableServerTransportProvider webFluxStreamableServerTransport // Router function for streamable http transport used by Spring WebFlux to start an // HTTP server. @Bean + @ConditionalOnMissingBean(name = "webFluxStreamableServerRouterFunction") public RouterFunction webFluxStreamableServerRouterFunction( WebFluxStreamableServerTransportProvider webFluxProvider) { return webFluxProvider.getRouterFunction(); 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..bec86bec0ba 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 @@ -28,6 +28,8 @@ import org.springframework.web.reactive.function.server.RouterFunction; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockingDetails; class McpServerSseWebFluxAutoConfigurationIT { @@ -96,4 +98,30 @@ void serverBaseUrlConfiguration() { .isEqualTo("/test")); } + @Test + void routerFunctionIsCreatedFromProvider() { + this.contextRunner.run(context -> { + assertThat(context).hasSingleBean(RouterFunction.class); + assertThat(context).hasSingleBean(WebFluxSseServerTransportProvider.class); + + // Verify that the RouterFunction is created from the provider + WebFluxSseServerTransportProvider serverTransport = context + .getBean(WebFluxSseServerTransportProvider.class); + RouterFunction routerFunction = context.getBean(RouterFunction.class); + assertThat(routerFunction).isNotNull().isEqualTo(serverTransport.getRouterFunction()); + }); + } + + @Test + void routerFunctionIsCustom() { + this.contextRunner + .withBean("webfluxSseServerRouterFunction", RouterFunction.class, () -> mock(RouterFunction.class)) + .run(context -> { + assertThat(context).hasSingleBean(RouterFunction.class); + + RouterFunction routerFunction = context.getBean(RouterFunction.class); + assertThat(mockingDetails(routerFunction).isMock()).isTrue(); + }); + } + } 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 fb004c75b58..50ca43381f9 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 @@ -23,9 +23,13 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.web.reactive.function.server.RouterFunction; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockingDetails; class McpServerStatelessWebFluxAutoConfigurationIT { @@ -135,11 +139,24 @@ void routerFunctionIsCreatedFromProvider() { assertThat(context).hasSingleBean(WebFluxStatelessServerTransport.class); // Verify that the RouterFunction is created from the provider + WebFluxStatelessServerTransport serverTransport = context.getBean(WebFluxStatelessServerTransport.class); RouterFunction routerFunction = context.getBean(RouterFunction.class); - assertThat(routerFunction).isNotNull(); + assertThat(routerFunction).isNotNull().isEqualTo(serverTransport.getRouterFunction()); }); } + @Test + void routerFunctionIsCustom() { + this.contextRunner + .withBean("webFluxStatelessServerRouterFunction", RouterFunction.class, () -> mock(RouterFunction.class)) + .run(context -> { + assertThat(context).hasSingleBean(RouterFunction.class); + + RouterFunction routerFunction = context.getBean(RouterFunction.class); + assertThat(mockingDetails(routerFunction).isMock()).isTrue(); + }); + } + @Test void allPropertiesConfiguration() { this.contextRunner @@ -172,4 +189,15 @@ void enabledPropertyExplicitlyTrue() { }); } + @Configuration + private static class CustomRouterFunctionConfig { + + @Bean + public RouterFunction webFluxStatelessServerRouterFunction( + WebFluxStatelessServerTransport webFluxStatelessTransport) { + return mock(RouterFunction.class); + } + + } + } 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 aa4c319ff8f..a319d062c71 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 @@ -26,6 +26,8 @@ import org.springframework.web.reactive.function.server.RouterFunction; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockingDetails; class McpServerStreamableWebFluxAutoConfigurationIT { @@ -137,11 +139,25 @@ void routerFunctionIsCreatedFromProvider() { assertThat(context).hasSingleBean(WebFluxStreamableServerTransportProvider.class); // Verify that the RouterFunction is created from the provider + WebFluxStreamableServerTransportProvider serverTransport = context + .getBean(WebFluxStreamableServerTransportProvider.class); RouterFunction routerFunction = context.getBean(RouterFunction.class); - assertThat(routerFunction).isNotNull(); + assertThat(routerFunction).isNotNull().isEqualTo(serverTransport.getRouterFunction()); }); } + @Test + void routerFunctionIsCustom() { + this.contextRunner + .withBean("webFluxStreamableServerRouterFunction", RouterFunction.class, () -> mock(RouterFunction.class)) + .run(context -> { + assertThat(context).hasSingleBean(RouterFunction.class); + + RouterFunction routerFunction = context.getBean(RouterFunction.class); + assertThat(mockingDetails(routerFunction).isMock()).isTrue(); + }); + } + @Test void allPropertiesConfiguration() { this.contextRunner 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 459fa9dbb83..72f61bdedc9 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 @@ -90,7 +90,9 @@ public WebMvcSseServerTransportProvider webMvcSseServerTransportProvider( } @Bean - public RouterFunction mvcMcpRouterFunction(WebMvcSseServerTransportProvider transportProvider) { + @ConditionalOnMissingBean(name = "webMvcSseServerRouterFunction") + public RouterFunction webMvcSseServerRouterFunction( + WebMvcSseServerTransportProvider transportProvider) { return transportProvider.getRouterFunction(); } 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 0644de757ee..10d048887cf 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 @@ -32,9 +32,11 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.web.servlet.function.RouterFunction; +import org.springframework.web.servlet.function.ServerResponse; /** * @author Christian Tzolov + * @author Yanming Zhou */ @AutoConfiguration(before = McpServerStatelessAutoConfiguration.class) @ConditionalOnClass({ McpSchema.class }) @@ -60,7 +62,8 @@ public WebMvcStatelessServerTransport webMvcStatelessServerTransport( // Router function for stateless http transport used by Spring WebFlux to start an // HTTP server. @Bean - public RouterFunction webMvcStatelessServerRouterFunction( + @ConditionalOnMissingBean(name = "webMvcStatelessServerRouterFunction") + public RouterFunction webMvcStatelessServerRouterFunction( WebMvcStatelessServerTransport webMvcStatelessTransport) { return webMvcStatelessTransport.getRouterFunction(); } 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 33458131383..b91a9d9566f 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 @@ -33,9 +33,11 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.web.servlet.function.RouterFunction; +import org.springframework.web.servlet.function.ServerResponse; /** * @author Christian Tzolov + * @author Yanming Zhou */ @AutoConfiguration(before = McpServerAutoConfiguration.class) @ConditionalOnClass({ McpSchema.class }) @@ -62,7 +64,8 @@ public WebMvcStreamableServerTransportProvider webMvcStreamableServerTransportPr // Router function for streamable http transport used by Spring WebFlux to start an // HTTP server. @Bean - public RouterFunction webMvcStreamableServerRouterFunction( + @ConditionalOnMissingBean(name = "webMvcStreamableServerRouterFunction") + public RouterFunction webMvcStreamableServerRouterFunction( WebMvcStreamableServerTransportProvider webMvcProvider) { return webMvcProvider.getRouterFunction(); } 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..8857c96a72c 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 @@ -32,6 +32,8 @@ import org.springframework.web.servlet.function.RouterFunction; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockingDetails; class McpServerSseWebMvcAutoConfigurationIT { @@ -117,4 +119,29 @@ public ConfigurableEnvironment getEnvironment() { }); } + @Test + void routerFunctionIsCreatedFromProvider() { + this.contextRunner.run(context -> { + assertThat(context).hasSingleBean(RouterFunction.class); + assertThat(context).hasSingleBean(WebMvcSseServerTransportProvider.class); + + // Verify that the RouterFunction is created from the provider + WebMvcSseServerTransportProvider serverTransport = context.getBean(WebMvcSseServerTransportProvider.class); + RouterFunction routerFunction = context.getBean(RouterFunction.class); + assertThat(routerFunction).isNotNull().isEqualTo(serverTransport.getRouterFunction()); + }); + } + + @Test + void routerFunctionIsCustom() { + this.contextRunner + .withBean("webMvcSseServerRouterFunction", RouterFunction.class, () -> mock(RouterFunction.class)) + .run(context -> { + assertThat(context).hasSingleBean(RouterFunction.class); + + RouterFunction routerFunction = context.getBean(RouterFunction.class); + assertThat(mockingDetails(routerFunction).isMock()).isTrue(); + }); + } + } 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 d5a9402d134..5b82e51d104 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 @@ -26,6 +26,8 @@ import org.springframework.web.servlet.function.RouterFunction; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockingDetails; class McpServerStatelessWebMvcAutoConfigurationIT { @@ -135,11 +137,24 @@ void routerFunctionIsCreatedFromProvider() { assertThat(context).hasSingleBean(WebMvcStatelessServerTransport.class); // Verify that the RouterFunction is created from the provider + WebMvcStatelessServerTransport serverTransport = context.getBean(WebMvcStatelessServerTransport.class); RouterFunction routerFunction = context.getBean(RouterFunction.class); - assertThat(routerFunction).isNotNull(); + assertThat(routerFunction).isNotNull().isEqualTo(serverTransport.getRouterFunction()); }); } + @Test + void routerFunctionIsCustom() { + this.contextRunner + .withBean("webMvcStatelessServerRouterFunction", RouterFunction.class, () -> mock(RouterFunction.class)) + .run(context -> { + assertThat(context).hasSingleBean(RouterFunction.class); + + RouterFunction routerFunction = context.getBean(RouterFunction.class); + assertThat(mockingDetails(routerFunction).isMock()).isTrue(); + }); + } + @Test void allPropertiesConfiguration() { this.contextRunner 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 ff977a8ba70..e44d2b2c7a2 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 @@ -26,6 +26,8 @@ import org.springframework.web.servlet.function.RouterFunction; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockingDetails; class McpServerStreamableWebMvcAutoConfigurationIT { @@ -137,11 +139,25 @@ void routerFunctionIsCreatedFromProvider() { assertThat(context).hasSingleBean(WebMvcStreamableServerTransportProvider.class); // Verify that the RouterFunction is created from the provider + WebMvcStreamableServerTransportProvider serverTransportProvider = context + .getBean(WebMvcStreamableServerTransportProvider.class); RouterFunction routerFunction = context.getBean(RouterFunction.class); - assertThat(routerFunction).isNotNull(); + assertThat(routerFunction).isNotNull().isEqualTo(serverTransportProvider.getRouterFunction()); }); } + @Test + void routerFunctionIsCustom() { + this.contextRunner + .withBean("webMvcStreamableServerRouterFunction", RouterFunction.class, () -> mock(RouterFunction.class)) + .run(context -> { + assertThat(context).hasSingleBean(RouterFunction.class); + + RouterFunction routerFunction = context.getBean(RouterFunction.class); + assertThat(mockingDetails(routerFunction).isMock()).isTrue(); + }); + } + @Test void allPropertiesConfiguration() { this.contextRunner