diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/pom.xml b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/pom.xml
index 822927ba052..c5844c26a25 100644
--- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/pom.xml
+++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/pom.xml
@@ -95,6 +95,27 @@
test
+
+ org.springframework.ai
+ spring-ai-autoconfigure-model-anthropic
+ ${project.parent.version}
+ test
+
+
+
+ org.springframework.ai
+ spring-ai-anthropic
+ ${project.parent.version}
+ test
+
+
+
+ org.springframework.ai
+ spring-ai-autoconfigure-model-chat-client
+ ${project.parent.version}
+ test
+
+
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 bbb4d374a09..41c9f65586d 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
@@ -27,7 +27,6 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.client.McpSyncClient;
-import io.modelcontextprotocol.server.McpServerFeatures;
import io.modelcontextprotocol.server.McpSyncServer;
import io.modelcontextprotocol.server.McpSyncServerExchange;
import io.modelcontextprotocol.server.transport.WebFluxStreamableServerTransportProvider;
@@ -54,6 +53,7 @@
import net.javacrumbs.jsonunit.assertj.JsonAssertions;
import net.javacrumbs.jsonunit.core.Option;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springaicommunity.mcp.annotation.McpArg;
@@ -68,21 +68,23 @@
import org.springaicommunity.mcp.annotation.McpSampling;
import org.springaicommunity.mcp.annotation.McpTool;
import org.springaicommunity.mcp.annotation.McpToolParam;
-import org.springaicommunity.mcp.method.elicitation.SyncElicitationSpecification;
-import org.springaicommunity.mcp.method.logging.SyncLoggingSpecification;
-import org.springaicommunity.mcp.method.progress.SyncProgressSpecification;
-import org.springaicommunity.mcp.method.sampling.SyncSamplingSpecification;
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
-import org.springframework.ai.mcp.annotation.spring.SyncMcpAnnotationProviders;
+import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.mcp.client.common.autoconfigure.McpClientAutoConfiguration;
import org.springframework.ai.mcp.client.common.autoconfigure.McpToolCallbackAutoConfiguration;
+import org.springframework.ai.mcp.client.common.autoconfigure.annotations.McpClientAnnotationScannerAutoConfiguration;
+import org.springframework.ai.mcp.client.common.autoconfigure.annotations.McpClientSpecificationFactoryAutoConfiguration;
import org.springframework.ai.mcp.client.webflux.autoconfigure.StreamableHttpWebFluxTransportAutoConfiguration;
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.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.ai.model.anthropic.autoconfigure.AnthropicChatAutoConfiguration;
+import org.springframework.ai.model.chat.client.autoconfigure.ChatClientAutoConfiguration;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@@ -98,16 +100,22 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.InstanceOfAssertFactories.map;
+@EnabledIfEnvironmentVariable(named = "ANTHROPIC_API_KEY", matches = ".+")
public class StreamableMcpAnnotationsManualIT {
private final ApplicationContextRunner serverContextRunner = new ApplicationContextRunner()
.withPropertyValues("spring.ai.mcp.server.protocol=STREAMABLE")
- .withConfiguration(AutoConfigurations.of(McpServerAutoConfiguration.class,
+ .withConfiguration(AutoConfigurations.of(McpServerAnnotationScannerAutoConfiguration.class,
+ McpServerSpecificationFactoryAutoConfiguration.class, McpServerAutoConfiguration.class,
ToolCallbackConverterAutoConfiguration.class, McpServerStreamableHttpWebFluxAutoConfiguration.class));
private final ApplicationContextRunner clientApplicationContext = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(McpToolCallbackAutoConfiguration.class,
- McpClientAutoConfiguration.class, StreamableHttpWebFluxTransportAutoConfiguration.class));
+ McpClientAutoConfiguration.class, StreamableHttpWebFluxTransportAutoConfiguration.class,
+ // MCP Annotations
+ McpClientAnnotationScannerAutoConfiguration.class, McpClientSpecificationFactoryAutoConfiguration.class,
+ // Anthropic ChatClient Builder
+ AnthropicChatAutoConfiguration.class, ChatClientAutoConfiguration.class));
@Test
void clientServerCapabilities() {
@@ -141,6 +149,7 @@ void clientServerCapabilities() {
this.clientApplicationContext.withUserConfiguration(TestMcpClientConfiguration.class)
.withPropertyValues(// @formatter:off
+ "spring.ai.anthropic.api-key=" + System.getenv("ANTHROPIC_API_KEY"),
"spring.ai.mcp.client.streamable-http.connections.server1.url=http://localhost:" + serverPort,
// "spring.ai.mcp.client.request-timeout=20m",
"spring.ai.mcp.client.initialized=false") // @formatter:on
@@ -306,28 +315,6 @@ public McpServerHandlers serverSideSpecProviders() {
return new McpServerHandlers();
}
- @Bean
- public List myTools(McpServerHandlers serverSideSpecProviders) {
- return SyncMcpAnnotationProviders.toolSpecifications(List.of(serverSideSpecProviders));
- }
-
- @Bean
- public List myResources(
- McpServerHandlers serverSideSpecProviders) {
- return SyncMcpAnnotationProviders.resourceSpecifications(List.of(serverSideSpecProviders));
- }
-
- @Bean
- public List myPrompts(McpServerHandlers serverSideSpecProviders) {
- return SyncMcpAnnotationProviders.promptSpecifications(List.of(serverSideSpecProviders));
- }
-
- @Bean
- public List myCompletions(
- McpServerHandlers serverSideSpecProviders) {
- return SyncMcpAnnotationProviders.completeSpecifications(List.of(serverSideSpecProviders));
- }
-
public static class McpServerHandlers {
@McpTool(description = "Test tool", name = "tool1")
@@ -449,28 +436,9 @@ public TestContext testContext() {
}
@Bean
- public McpClientHandlers mcpClientHandlers(TestContext testContext) {
- return new McpClientHandlers(testContext);
- }
-
- @Bean
- List loggingSpecs(McpClientHandlers clientMcpHandlers) {
- return SyncMcpAnnotationProviders.loggingSpecifications(List.of(clientMcpHandlers));
- }
-
- @Bean
- List samplingSpecs(McpClientHandlers clientMcpHandlers) {
- return SyncMcpAnnotationProviders.samplingSpecifications(List.of(clientMcpHandlers));
- }
-
- @Bean
- List elicitationSpecs(McpClientHandlers clientMcpHandlers) {
- return SyncMcpAnnotationProviders.elicitationSpecifications(List.of(clientMcpHandlers));
- }
-
- @Bean
- List progressSpecs(McpClientHandlers clientMcpHandlers) {
- return SyncMcpAnnotationProviders.progressSpecifications(List.of(clientMcpHandlers));
+ public McpClientHandlers mcpClientHandlers(TestContext testContext,
+ ObjectProvider chatClientBuilderProvider) {
+ return new McpClientHandlers(testContext, chatClientBuilderProvider);
}
public static class TestContext {
@@ -489,8 +457,21 @@ public static class McpClientHandlers {
private TestContext testContext;
- public McpClientHandlers(TestContext testContext) {
+ private final ObjectProvider chatClientBuilderProvider;
+
+ private AtomicReference chatClientRef = new AtomicReference<>();
+
+ private ChatClient chatClient() {
+ if (this.chatClientRef.get() == null) {
+ this.chatClientRef.compareAndSet(null, this.chatClientBuilderProvider.getIfAvailable().build());
+ }
+ return this.chatClientRef.get();
+ }
+
+ public McpClientHandlers(TestContext testContext,
+ ObjectProvider chatClientBuilderProvider) {
this.testContext = testContext;
+ this.chatClientBuilderProvider = chatClientBuilderProvider;
}
@McpProgress(clients = "server1")
@@ -515,6 +496,11 @@ public CreateMessageResult samplingHandler(CreateMessageRequest llmRequest) {
String userPrompt = ((McpSchema.TextContent) llmRequest.messages().get(0).content()).text();
String modelHint = llmRequest.modelPreferences().hints().get(0).name();
+ // String joke =
+ // this.chatClientBuilderProvider.getIfAvailable().build().prompt("Tell me
+ // a joke").call().content();
+ String joke = this.chatClient().prompt("Tell me a joke").call().content();
+ logger.info("Received joke from chat client: {}", joke);
return CreateMessageResult.builder()
.content(new McpSchema.TextContent("Response " + userPrompt + " with model hint " + modelHint))
.build();