2727
2828import com .fasterxml .jackson .databind .ObjectMapper ;
2929import io .modelcontextprotocol .client .McpSyncClient ;
30- import io .modelcontextprotocol .server .McpServerFeatures ;
3130import io .modelcontextprotocol .server .McpSyncServer ;
3231import io .modelcontextprotocol .server .McpSyncServerExchange ;
3332import io .modelcontextprotocol .server .transport .WebFluxStreamableServerTransportProvider ;
5453import net .javacrumbs .jsonunit .assertj .JsonAssertions ;
5554import net .javacrumbs .jsonunit .core .Option ;
5655import org .junit .jupiter .api .Test ;
56+ import org .junit .jupiter .api .condition .EnabledIfEnvironmentVariable ;
5757import org .slf4j .Logger ;
5858import org .slf4j .LoggerFactory ;
5959import org .springaicommunity .mcp .annotation .McpArg ;
6868import org .springaicommunity .mcp .annotation .McpSampling ;
6969import org .springaicommunity .mcp .annotation .McpTool ;
7070import 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 ;
7571import reactor .netty .DisposableServer ;
7672import reactor .netty .http .server .HttpServer ;
7773
78- import org .springframework .ai .mcp . annotation . spring . SyncMcpAnnotationProviders ;
74+ import org .springframework .ai .chat . client . ChatClient ;
7975import org .springframework .ai .mcp .client .common .autoconfigure .McpClientAutoConfiguration ;
8076import 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 ;
8179import org .springframework .ai .mcp .client .webflux .autoconfigure .StreamableHttpWebFluxTransportAutoConfiguration ;
8280import org .springframework .ai .mcp .server .common .autoconfigure .McpServerAutoConfiguration ;
8381import 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 ;
8484import org .springframework .ai .mcp .server .common .autoconfigure .properties .McpServerProperties ;
8585import 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 ;
8688import org .springframework .beans .factory .ObjectProvider ;
8789import org .springframework .boot .autoconfigure .AutoConfigurations ;
8890import org .springframework .boot .test .context .runner .ApplicationContextRunner ;
98100import static org .assertj .core .api .Assertions .assertThat ;
99101import static org .assertj .core .api .InstanceOfAssertFactories .map ;
100102
103+ @ EnabledIfEnvironmentVariable (named = "ANTHROPIC_API_KEY" , matches = ".+" )
101104public class StreamableMcpAnnotationsManualIT {
102105
103106 private final ApplicationContextRunner serverContextRunner = new ApplicationContextRunner ()
104107 .withPropertyValues ("spring.ai.mcp.server.protocol=STREAMABLE" )
105- .withConfiguration (AutoConfigurations .of (McpServerAutoConfiguration .class ,
108+ .withConfiguration (AutoConfigurations .of (McpServerAnnotationScannerAutoConfiguration .class ,
109+ McpServerSpecificationFactoryAutoConfiguration .class , McpServerAutoConfiguration .class ,
106110 ToolCallbackConverterAutoConfiguration .class , McpServerStreamableHttpWebFluxAutoConfiguration .class ));
107111
108112 private final ApplicationContextRunner clientApplicationContext = new ApplicationContextRunner ()
109113 .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 ));
111119
112120 @ Test
113121 void clientServerCapabilities () {
@@ -141,6 +149,7 @@ void clientServerCapabilities() {
141149
142150 this .clientApplicationContext .withUserConfiguration (TestMcpClientConfiguration .class )
143151 .withPropertyValues (// @formatter:off
152+ "spring.ai.anthropic.api-key=" + System .getenv ("ANTHROPIC_API_KEY" ),
144153 "spring.ai.mcp.client.streamable-http.connections.server1.url=http://localhost:" + serverPort ,
145154 // "spring.ai.mcp.client.request-timeout=20m",
146155 "spring.ai.mcp.client.initialized=false" ) // @formatter:on
@@ -306,28 +315,6 @@ public McpServerHandlers serverSideSpecProviders() {
306315 return new McpServerHandlers ();
307316 }
308317
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-
331318 public static class McpServerHandlers {
332319
333320 @ McpTool (description = "Test tool" , name = "tool1" )
@@ -449,28 +436,9 @@ public TestContext testContext() {
449436 }
450437
451438 @ 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 );
474442 }
475443
476444 public static class TestContext {
@@ -489,8 +457,21 @@ public static class McpClientHandlers {
489457
490458 private TestContext testContext ;
491459
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 ) {
493473 this .testContext = testContext ;
474+ this .chatClientBuilderProvider = chatClientBuilderProvider ;
494475 }
495476
496477 @ McpProgress (clients = "server1" )
@@ -515,6 +496,11 @@ public CreateMessageResult samplingHandler(CreateMessageRequest llmRequest) {
515496 String userPrompt = ((McpSchema .TextContent ) llmRequest .messages ().get (0 ).content ()).text ();
516497 String modelHint = llmRequest .modelPreferences ().hints ().get (0 ).name ();
517498
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 );
518504 return CreateMessageResult .builder ()
519505 .content (new McpSchema .TextContent ("Response " + userPrompt + " with model hint " + modelHint ))
520506 .build ();
0 commit comments