Skip to content

Commit 68ea4c0

Browse files
committed
refactor: Migrate MCP test to use autoconfiguration and add ChatClient integration
Replace manual MCP annotation providers with autoconfiguration beans. Add ChatClient integration test with Anthropic model in sampling handler. Signed-off-by: Christian Tzolov <[email protected]>
1 parent 6189d79 commit 68ea4c0

File tree

2 files changed

+62
-53
lines changed

2 files changed

+62
-53
lines changed

auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/pom.xml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,27 @@
9595
<scope>test</scope>
9696
</dependency>
9797

98+
<dependency>
99+
<groupId>org.springframework.ai</groupId>
100+
<artifactId>spring-ai-autoconfigure-model-anthropic</artifactId>
101+
<version>${project.parent.version}</version>
102+
<scope>test</scope>
103+
</dependency>
104+
105+
<dependency>
106+
<groupId>org.springframework.ai</groupId>
107+
<artifactId>spring-ai-anthropic</artifactId>
108+
<version>${project.parent.version}</version>
109+
<scope>test</scope>
110+
</dependency>
111+
112+
<dependency>
113+
<groupId>org.springframework.ai</groupId>
114+
<artifactId>spring-ai-autoconfigure-model-chat-client</artifactId>
115+
<version>${project.parent.version}</version>
116+
<scope>test</scope>
117+
</dependency>
118+
98119
</dependencies>
99120

100121
</project>

auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableMcpAnnotationsManualIT.java

Lines changed: 41 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727

2828
import com.fasterxml.jackson.databind.ObjectMapper;
2929
import io.modelcontextprotocol.client.McpSyncClient;
30-
import io.modelcontextprotocol.server.McpServerFeatures;
3130
import io.modelcontextprotocol.server.McpSyncServer;
3231
import io.modelcontextprotocol.server.McpSyncServerExchange;
3332
import io.modelcontextprotocol.server.transport.WebFluxStreamableServerTransportProvider;
@@ -54,6 +53,7 @@
5453
import net.javacrumbs.jsonunit.assertj.JsonAssertions;
5554
import net.javacrumbs.jsonunit.core.Option;
5655
import org.junit.jupiter.api.Test;
56+
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
5757
import org.slf4j.Logger;
5858
import org.slf4j.LoggerFactory;
5959
import org.springaicommunity.mcp.annotation.McpArg;
@@ -68,46 +68,56 @@
6868
import org.springaicommunity.mcp.annotation.McpSampling;
6969
import org.springaicommunity.mcp.annotation.McpTool;
7070
import org.springaicommunity.mcp.annotation.McpToolParam;
71-
import org.springaicommunity.mcp.method.elicitation.SyncElicitationSpecification;
72-
import org.springaicommunity.mcp.method.logging.SyncLoggingSpecification;
73-
import org.springaicommunity.mcp.method.progress.SyncProgressSpecification;
74-
import org.springaicommunity.mcp.method.sampling.SyncSamplingSpecification;
7571
import reactor.netty.DisposableServer;
7672
import reactor.netty.http.server.HttpServer;
7773

78-
import org.springframework.ai.mcp.annotation.spring.SyncMcpAnnotationProviders;
74+
import org.springframework.ai.chat.client.ChatClient;
7975
import org.springframework.ai.mcp.client.common.autoconfigure.McpClientAutoConfiguration;
8076
import org.springframework.ai.mcp.client.common.autoconfigure.McpToolCallbackAutoConfiguration;
77+
import org.springframework.ai.mcp.client.common.autoconfigure.annotations.McpClientAnnotationScannerAutoConfiguration;
78+
import org.springframework.ai.mcp.client.common.autoconfigure.annotations.McpClientSpecificationFactoryAutoConfiguration;
8179
import org.springframework.ai.mcp.client.webflux.autoconfigure.StreamableHttpWebFluxTransportAutoConfiguration;
8280
import org.springframework.ai.mcp.server.common.autoconfigure.McpServerAutoConfiguration;
8381
import org.springframework.ai.mcp.server.common.autoconfigure.ToolCallbackConverterAutoConfiguration;
82+
import org.springframework.ai.mcp.server.common.autoconfigure.annotations.McpServerAnnotationScannerAutoConfiguration;
83+
import org.springframework.ai.mcp.server.common.autoconfigure.annotations.McpServerSpecificationFactoryAutoConfiguration;
8484
import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties;
8585
import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerStreamableHttpProperties;
86+
import org.springframework.ai.model.anthropic.autoconfigure.AnthropicChatAutoConfiguration;
87+
import org.springframework.ai.model.chat.client.autoconfigure.ChatClientAutoConfiguration;
8688
import org.springframework.beans.factory.ObjectProvider;
8789
import org.springframework.boot.autoconfigure.AutoConfigurations;
8890
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
8991
import org.springframework.context.ApplicationContext;
9092
import org.springframework.context.annotation.Bean;
93+
import org.springframework.context.annotation.Lazy;
9194
import org.springframework.core.ResolvableType;
9295
import org.springframework.http.server.reactive.HttpHandler;
9396
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
97+
import org.springframework.stereotype.Service;
9498
import org.springframework.test.util.TestSocketUtils;
9599
import org.springframework.web.reactive.function.server.RouterFunction;
96100
import org.springframework.web.reactive.function.server.RouterFunctions;
97101

98102
import static org.assertj.core.api.Assertions.assertThat;
99103
import static org.assertj.core.api.InstanceOfAssertFactories.map;
100104

105+
@EnabledIfEnvironmentVariable(named = "ANTHROPIC_API_KEY", matches = ".+")
101106
public class StreamableMcpAnnotationsManualIT {
102107

103108
private final ApplicationContextRunner serverContextRunner = new ApplicationContextRunner()
104109
.withPropertyValues("spring.ai.mcp.server.protocol=STREAMABLE")
105-
.withConfiguration(AutoConfigurations.of(McpServerAutoConfiguration.class,
110+
.withConfiguration(AutoConfigurations.of(McpServerAnnotationScannerAutoConfiguration.class,
111+
McpServerSpecificationFactoryAutoConfiguration.class, McpServerAutoConfiguration.class,
106112
ToolCallbackConverterAutoConfiguration.class, McpServerStreamableHttpWebFluxAutoConfiguration.class));
107113

108114
private final ApplicationContextRunner clientApplicationContext = new ApplicationContextRunner()
109115
.withConfiguration(AutoConfigurations.of(McpToolCallbackAutoConfiguration.class,
110-
McpClientAutoConfiguration.class, StreamableHttpWebFluxTransportAutoConfiguration.class));
116+
McpClientAutoConfiguration.class, StreamableHttpWebFluxTransportAutoConfiguration.class,
117+
// MCP Annotations
118+
McpClientAnnotationScannerAutoConfiguration.class, McpClientSpecificationFactoryAutoConfiguration.class,
119+
// Anthropic ChatClient Builder
120+
AnthropicChatAutoConfiguration.class, ChatClientAutoConfiguration.class));
111121

112122
@Test
113123
void clientServerCapabilities() {
@@ -141,6 +151,7 @@ void clientServerCapabilities() {
141151

142152
this.clientApplicationContext.withUserConfiguration(TestMcpClientConfiguration.class)
143153
.withPropertyValues(// @formatter:off
154+
"spring.ai.anthropic.api-key=" + System.getenv("ANTHROPIC_API_KEY"),
144155
"spring.ai.mcp.client.streamable-http.connections.server1.url=http://localhost:" + serverPort,
145156
// "spring.ai.mcp.client.request-timeout=20m",
146157
"spring.ai.mcp.client.initialized=false") // @formatter:on
@@ -306,28 +317,6 @@ public McpServerHandlers serverSideSpecProviders() {
306317
return new McpServerHandlers();
307318
}
308319

309-
@Bean
310-
public List<McpServerFeatures.SyncToolSpecification> myTools(McpServerHandlers serverSideSpecProviders) {
311-
return SyncMcpAnnotationProviders.toolSpecifications(List.of(serverSideSpecProviders));
312-
}
313-
314-
@Bean
315-
public List<McpServerFeatures.SyncResourceSpecification> myResources(
316-
McpServerHandlers serverSideSpecProviders) {
317-
return SyncMcpAnnotationProviders.resourceSpecifications(List.of(serverSideSpecProviders));
318-
}
319-
320-
@Bean
321-
public List<McpServerFeatures.SyncPromptSpecification> myPrompts(McpServerHandlers serverSideSpecProviders) {
322-
return SyncMcpAnnotationProviders.promptSpecifications(List.of(serverSideSpecProviders));
323-
}
324-
325-
@Bean
326-
public List<McpServerFeatures.SyncCompletionSpecification> myCompletions(
327-
McpServerHandlers serverSideSpecProviders) {
328-
return SyncMcpAnnotationProviders.completeSpecifications(List.of(serverSideSpecProviders));
329-
}
330-
331320
public static class McpServerHandlers {
332321

333322
@McpTool(description = "Test tool", name = "tool1")
@@ -449,28 +438,9 @@ public TestContext testContext() {
449438
}
450439

451440
@Bean
452-
public McpClientHandlers mcpClientHandlers(TestContext testContext) {
453-
return new McpClientHandlers(testContext);
454-
}
455-
456-
@Bean
457-
List<SyncLoggingSpecification> loggingSpecs(McpClientHandlers clientMcpHandlers) {
458-
return SyncMcpAnnotationProviders.loggingSpecifications(List.of(clientMcpHandlers));
459-
}
460-
461-
@Bean
462-
List<SyncSamplingSpecification> samplingSpecs(McpClientHandlers clientMcpHandlers) {
463-
return SyncMcpAnnotationProviders.samplingSpecifications(List.of(clientMcpHandlers));
464-
}
465-
466-
@Bean
467-
List<SyncElicitationSpecification> elicitationSpecs(McpClientHandlers clientMcpHandlers) {
468-
return SyncMcpAnnotationProviders.elicitationSpecifications(List.of(clientMcpHandlers));
469-
}
470-
471-
@Bean
472-
List<SyncProgressSpecification> progressSpecs(McpClientHandlers clientMcpHandlers) {
473-
return SyncMcpAnnotationProviders.progressSpecifications(List.of(clientMcpHandlers));
441+
public McpClientHandlers mcpClientHandlers(TestContext testContext,
442+
ObjectProvider<ChatClient.Builder> chatClientBuilderProvider) {
443+
return new McpClientHandlers(testContext, chatClientBuilderProvider);
474444
}
475445

476446
public static class TestContext {
@@ -489,8 +459,21 @@ public static class McpClientHandlers {
489459

490460
private TestContext testContext;
491461

492-
public McpClientHandlers(TestContext testContext) {
462+
private final ObjectProvider<ChatClient.Builder> chatClientBuilderProvider;
463+
464+
private AtomicReference<ChatClient> chatClientRef = new AtomicReference<>();
465+
466+
private ChatClient chatClient() {
467+
if (this.chatClientRef.get() == null) {
468+
this.chatClientRef.compareAndSet(null, this.chatClientBuilderProvider.getIfAvailable().build());
469+
}
470+
return this.chatClientRef.get();
471+
}
472+
473+
public McpClientHandlers(TestContext testContext,
474+
ObjectProvider<ChatClient.Builder> chatClientBuilderProvider) {
493475
this.testContext = testContext;
476+
this.chatClientBuilderProvider = chatClientBuilderProvider;
494477
}
495478

496479
@McpProgress(clients = "server1")
@@ -515,6 +498,11 @@ public CreateMessageResult samplingHandler(CreateMessageRequest llmRequest) {
515498
String userPrompt = ((McpSchema.TextContent) llmRequest.messages().get(0).content()).text();
516499
String modelHint = llmRequest.modelPreferences().hints().get(0).name();
517500

501+
// String joke =
502+
// this.chatClientBuilderProvider.getIfAvailable().build().prompt("Tell me
503+
// a joke").call().content();
504+
String joke = this.chatClient().prompt("Tell me a joke").call().content();
505+
logger.info("Received joke from chat client: {}", joke);
518506
return CreateMessageResult.builder()
519507
.content(new McpSchema.TextContent("Response " + userPrompt + " with model hint " + modelHint))
520508
.build();

0 commit comments

Comments
 (0)