27
27
28
28
import com .fasterxml .jackson .databind .ObjectMapper ;
29
29
import io .modelcontextprotocol .client .McpSyncClient ;
30
- import io .modelcontextprotocol .server .McpServerFeatures ;
31
30
import io .modelcontextprotocol .server .McpSyncServer ;
32
31
import io .modelcontextprotocol .server .McpSyncServerExchange ;
33
32
import io .modelcontextprotocol .server .transport .WebFluxStreamableServerTransportProvider ;
54
53
import net .javacrumbs .jsonunit .assertj .JsonAssertions ;
55
54
import net .javacrumbs .jsonunit .core .Option ;
56
55
import org .junit .jupiter .api .Test ;
56
+ import org .junit .jupiter .api .condition .EnabledIfEnvironmentVariable ;
57
57
import org .slf4j .Logger ;
58
58
import org .slf4j .LoggerFactory ;
59
59
import org .springaicommunity .mcp .annotation .McpArg ;
68
68
import org .springaicommunity .mcp .annotation .McpSampling ;
69
69
import org .springaicommunity .mcp .annotation .McpTool ;
70
70
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 ;
75
71
import reactor .netty .DisposableServer ;
76
72
import reactor .netty .http .server .HttpServer ;
77
73
78
- import org .springframework .ai .mcp . annotation . spring . SyncMcpAnnotationProviders ;
74
+ import org .springframework .ai .chat . client . ChatClient ;
79
75
import org .springframework .ai .mcp .client .common .autoconfigure .McpClientAutoConfiguration ;
80
76
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 ;
81
79
import org .springframework .ai .mcp .client .webflux .autoconfigure .StreamableHttpWebFluxTransportAutoConfiguration ;
82
80
import org .springframework .ai .mcp .server .common .autoconfigure .McpServerAutoConfiguration ;
83
81
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 ;
84
84
import org .springframework .ai .mcp .server .common .autoconfigure .properties .McpServerProperties ;
85
85
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 ;
86
88
import org .springframework .beans .factory .ObjectProvider ;
87
89
import org .springframework .boot .autoconfigure .AutoConfigurations ;
88
90
import org .springframework .boot .test .context .runner .ApplicationContextRunner ;
98
100
import static org .assertj .core .api .Assertions .assertThat ;
99
101
import static org .assertj .core .api .InstanceOfAssertFactories .map ;
100
102
103
+ @ EnabledIfEnvironmentVariable (named = "ANTHROPIC_API_KEY" , matches = ".+" )
101
104
public class StreamableMcpAnnotationsManualIT {
102
105
103
106
private final ApplicationContextRunner serverContextRunner = new ApplicationContextRunner ()
104
107
.withPropertyValues ("spring.ai.mcp.server.protocol=STREAMABLE" )
105
- .withConfiguration (AutoConfigurations .of (McpServerAutoConfiguration .class ,
108
+ .withConfiguration (AutoConfigurations .of (McpServerAnnotationScannerAutoConfiguration .class ,
109
+ McpServerSpecificationFactoryAutoConfiguration .class , McpServerAutoConfiguration .class ,
106
110
ToolCallbackConverterAutoConfiguration .class , McpServerStreamableHttpWebFluxAutoConfiguration .class ));
107
111
108
112
private final ApplicationContextRunner clientApplicationContext = new ApplicationContextRunner ()
109
113
.withConfiguration (AutoConfigurations .of (McpToolCallbackAutoConfiguration .class ,
110
- McpClientAutoConfiguration .class , StreamableHttpWebFluxTransportAutoConfiguration .class ));
114
+ McpClientAutoConfiguration .class , StreamableHttpWebFluxTransportAutoConfiguration .class ,
115
+ // MCP Annotations
116
+ McpClientAnnotationScannerAutoConfiguration .class , McpClientSpecificationFactoryAutoConfiguration .class ,
117
+ // Anthropic ChatClient Builder
118
+ AnthropicChatAutoConfiguration .class , ChatClientAutoConfiguration .class ));
111
119
112
120
@ Test
113
121
void clientServerCapabilities () {
@@ -141,6 +149,7 @@ void clientServerCapabilities() {
141
149
142
150
this .clientApplicationContext .withUserConfiguration (TestMcpClientConfiguration .class )
143
151
.withPropertyValues (// @formatter:off
152
+ "spring.ai.anthropic.api-key=" + System .getenv ("ANTHROPIC_API_KEY" ),
144
153
"spring.ai.mcp.client.streamable-http.connections.server1.url=http://localhost:" + serverPort ,
145
154
// "spring.ai.mcp.client.request-timeout=20m",
146
155
"spring.ai.mcp.client.initialized=false" ) // @formatter:on
@@ -306,28 +315,6 @@ public McpServerHandlers serverSideSpecProviders() {
306
315
return new McpServerHandlers ();
307
316
}
308
317
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
-
331
318
public static class McpServerHandlers {
332
319
333
320
@ McpTool (description = "Test tool" , name = "tool1" )
@@ -449,28 +436,9 @@ public TestContext testContext() {
449
436
}
450
437
451
438
@ 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 ));
439
+ public McpClientHandlers mcpClientHandlers (TestContext testContext ,
440
+ ObjectProvider <ChatClient .Builder > chatClientBuilderProvider ) {
441
+ return new McpClientHandlers (testContext , chatClientBuilderProvider );
474
442
}
475
443
476
444
public static class TestContext {
@@ -489,8 +457,21 @@ public static class McpClientHandlers {
489
457
490
458
private TestContext testContext ;
491
459
492
- public McpClientHandlers (TestContext testContext ) {
460
+ private final ObjectProvider <ChatClient .Builder > chatClientBuilderProvider ;
461
+
462
+ private AtomicReference <ChatClient > chatClientRef = new AtomicReference <>();
463
+
464
+ private ChatClient chatClient () {
465
+ if (this .chatClientRef .get () == null ) {
466
+ this .chatClientRef .compareAndSet (null , this .chatClientBuilderProvider .getIfAvailable ().build ());
467
+ }
468
+ return this .chatClientRef .get ();
469
+ }
470
+
471
+ public McpClientHandlers (TestContext testContext ,
472
+ ObjectProvider <ChatClient .Builder > chatClientBuilderProvider ) {
493
473
this .testContext = testContext ;
474
+ this .chatClientBuilderProvider = chatClientBuilderProvider ;
494
475
}
495
476
496
477
@ McpProgress (clients = "server1" )
@@ -515,6 +496,11 @@ public CreateMessageResult samplingHandler(CreateMessageRequest llmRequest) {
515
496
String userPrompt = ((McpSchema .TextContent ) llmRequest .messages ().get (0 ).content ()).text ();
516
497
String modelHint = llmRequest .modelPreferences ().hints ().get (0 ).name ();
517
498
499
+ // String joke =
500
+ // this.chatClientBuilderProvider.getIfAvailable().build().prompt("Tell me
501
+ // a joke").call().content();
502
+ String joke = this .chatClient ().prompt ("Tell me a joke" ).call ().content ();
503
+ logger .info ("Received joke from chat client: {}" , joke );
518
504
return CreateMessageResult .builder ()
519
505
.content (new McpSchema .TextContent ("Response " + userPrompt + " with model hint " + modelHint ))
520
506
.build ();
0 commit comments