Skip to content

Commit 0fdb911

Browse files
jiafu1115ilayaperumalg
authored andcommitted
Fix auto-configuration to include scanning for @McpToolListChanged and similar annotations
Signed-off-by: stroller <fujian1115@gmail.com>
1 parent 3e6084c commit 0fdb911

File tree

3 files changed

+172
-1
lines changed

3 files changed

+172
-1
lines changed

auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/main/java/org/springframework/ai/mcp/client/common/autoconfigure/annotations/McpClientAnnotationScannerAutoConfiguration.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@
2222
import org.springaicommunity.mcp.annotation.McpElicitation;
2323
import org.springaicommunity.mcp.annotation.McpLogging;
2424
import org.springaicommunity.mcp.annotation.McpProgress;
25+
import org.springaicommunity.mcp.annotation.McpPromptListChanged;
26+
import org.springaicommunity.mcp.annotation.McpResourceListChanged;
2527
import org.springaicommunity.mcp.annotation.McpSampling;
28+
import org.springaicommunity.mcp.annotation.McpToolListChanged;
2629

2730
import org.springframework.ai.mcp.annotation.spring.scan.AbstractAnnotatedMethodBeanFactoryInitializationAotProcessor;
2831
import org.springframework.ai.mcp.annotation.spring.scan.AbstractAnnotatedMethodBeanPostProcessor;
@@ -41,6 +44,7 @@
4144
/**
4245
* @author Christian Tzolov
4346
* @author Josh Long
47+
* @author Fu Jian
4448
*/
4549
@AutoConfiguration
4650
@ConditionalOnClass(McpLogging.class)
@@ -51,7 +55,8 @@
5155
public class McpClientAnnotationScannerAutoConfiguration {
5256

5357
private static final Set<Class<? extends Annotation>> CLIENT_MCP_ANNOTATIONS = Set.of(McpLogging.class,
54-
McpSampling.class, McpElicitation.class, McpProgress.class);
58+
McpSampling.class, McpElicitation.class, McpProgress.class, McpToolListChanged.class,
59+
McpResourceListChanged.class, McpPromptListChanged.class);
5560

5661
@Bean
5762
@ConditionalOnMissingBean

auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/main/java/org/springframework/ai/mcp/client/common/autoconfigure/annotations/McpClientSpecificationFactoryAutoConfiguration.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,16 @@
2121
import org.springaicommunity.mcp.annotation.McpElicitation;
2222
import org.springaicommunity.mcp.annotation.McpLogging;
2323
import org.springaicommunity.mcp.annotation.McpProgress;
24+
import org.springaicommunity.mcp.annotation.McpPromptListChanged;
25+
import org.springaicommunity.mcp.annotation.McpResourceListChanged;
2426
import org.springaicommunity.mcp.annotation.McpSampling;
27+
import org.springaicommunity.mcp.annotation.McpToolListChanged;
28+
import org.springaicommunity.mcp.method.changed.prompt.AsyncPromptListChangedSpecification;
29+
import org.springaicommunity.mcp.method.changed.prompt.SyncPromptListChangedSpecification;
30+
import org.springaicommunity.mcp.method.changed.resource.AsyncResourceListChangedSpecification;
31+
import org.springaicommunity.mcp.method.changed.resource.SyncResourceListChangedSpecification;
32+
import org.springaicommunity.mcp.method.changed.tool.AsyncToolListChangedSpecification;
33+
import org.springaicommunity.mcp.method.changed.tool.SyncToolListChangedSpecification;
2534
import org.springaicommunity.mcp.method.elicitation.AsyncElicitationSpecification;
2635
import org.springaicommunity.mcp.method.elicitation.SyncElicitationSpecification;
2736
import org.springaicommunity.mcp.method.logging.AsyncLoggingSpecification;
@@ -43,6 +52,7 @@
4352

4453
/**
4554
* @author Christian Tzolov
55+
* @author Fu Jian
4656
*/
4757
@AutoConfiguration(after = McpClientAnnotationScannerAutoConfiguration.class)
4858
@ConditionalOnClass(McpLogging.class)
@@ -79,6 +89,27 @@ List<SyncProgressSpecification> progressSpecs(ClientMcpAnnotatedBeans beansWithM
7989
.progressSpecifications(beansWithMcpMethodAnnotations.getBeansByAnnotation(McpProgress.class));
8090
}
8191

92+
@Bean
93+
List<SyncToolListChangedSpecification> syncToolListChangedSpecs(
94+
ClientMcpAnnotatedBeans beansWithMcpMethodAnnotations) {
95+
return SyncMcpAnnotationProviders.toolListChangedSpecifications(
96+
beansWithMcpMethodAnnotations.getBeansByAnnotation(McpToolListChanged.class));
97+
}
98+
99+
@Bean
100+
List<SyncResourceListChangedSpecification> syncResourceListChangedSpecs(
101+
ClientMcpAnnotatedBeans beansWithMcpMethodAnnotations) {
102+
return SyncMcpAnnotationProviders.resourceListChangedSpecifications(
103+
beansWithMcpMethodAnnotations.getBeansByAnnotation(McpResourceListChanged.class));
104+
}
105+
106+
@Bean
107+
List<SyncPromptListChangedSpecification> syncPromptListChangedSpecs(
108+
ClientMcpAnnotatedBeans beansWithMcpMethodAnnotations) {
109+
return SyncMcpAnnotationProviders.promptListChangedSpecifications(
110+
beansWithMcpMethodAnnotations.getBeansByAnnotation(McpPromptListChanged.class));
111+
}
112+
82113
}
83114

84115
@Configuration(proxyBeanMethods = false)
@@ -105,6 +136,22 @@ List<AsyncProgressSpecification> progressSpecs(ClientMcpAnnotatedBeans beanRegis
105136
return AsyncMcpAnnotationProviders.progressSpecifications(beanRegistry.getAllAnnotatedBeans());
106137
}
107138

139+
@Bean
140+
List<AsyncToolListChangedSpecification> asyncToolListChangedSpecs(ClientMcpAnnotatedBeans beanRegistry) {
141+
return AsyncMcpAnnotationProviders.toolListChangedSpecifications(beanRegistry.getAllAnnotatedBeans());
142+
}
143+
144+
@Bean
145+
List<AsyncResourceListChangedSpecification> asyncResourceListChangedSpecs(
146+
ClientMcpAnnotatedBeans beanRegistry) {
147+
return AsyncMcpAnnotationProviders.resourceListChangedSpecifications(beanRegistry.getAllAnnotatedBeans());
148+
}
149+
150+
@Bean
151+
List<AsyncPromptListChangedSpecification> asyncPromptListChangedSpecs(ClientMcpAnnotatedBeans beanRegistry) {
152+
return AsyncMcpAnnotationProviders.promptListChangedSpecifications(beanRegistry.getAllAnnotatedBeans());
153+
}
154+
108155
}
109156

110157
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
* Copyright 2025-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.ai.mcp.client.common.autoconfigure.annotations;
18+
19+
import java.util.List;
20+
21+
import io.modelcontextprotocol.spec.McpSchema;
22+
import org.junit.jupiter.params.ParameterizedTest;
23+
import org.junit.jupiter.params.provider.ValueSource;
24+
import org.springaicommunity.mcp.annotation.McpPromptListChanged;
25+
import org.springaicommunity.mcp.annotation.McpResourceListChanged;
26+
import org.springaicommunity.mcp.annotation.McpToolListChanged;
27+
28+
import org.springframework.boot.autoconfigure.AutoConfigurations;
29+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
30+
import org.springframework.context.annotation.Bean;
31+
import org.springframework.context.annotation.Configuration;
32+
33+
import static org.assertj.core.api.Assertions.assertThat;
34+
35+
/**
36+
* Integration tests for MCP client list-changed annotations scanning.
37+
*
38+
* <p>
39+
* This test validates that the annotation scanner correctly identifies and processes
40+
* {@code @McpToolListChanged}, {@code @McpResourceListChanged}, and
41+
* {@code @McpPromptListChanged} annotations.
42+
*
43+
* @author Fu Jian
44+
*/
45+
public class McpClientListChangedAnnotationsScanningIT {
46+
47+
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
48+
.withConfiguration(AutoConfigurations.of(McpClientAnnotationScannerAutoConfiguration.class,
49+
McpClientSpecificationFactoryAutoConfiguration.class));
50+
51+
@ParameterizedTest
52+
@ValueSource(strings = { "SYNC", "ASYNC" })
53+
void shouldScanAllThreeListChangedAnnotations(String clientType) {
54+
String prefix = clientType.toLowerCase();
55+
56+
this.contextRunner.withUserConfiguration(AllListChangedConfiguration.class)
57+
.withPropertyValues("spring.ai.mcp.client.type=" + clientType)
58+
.run(context -> {
59+
// Verify all three annotations were scanned
60+
McpClientAnnotationScannerAutoConfiguration.ClientMcpAnnotatedBeans annotatedBeans = context
61+
.getBean(McpClientAnnotationScannerAutoConfiguration.ClientMcpAnnotatedBeans.class);
62+
assertThat(annotatedBeans.getBeansByAnnotation(McpToolListChanged.class)).hasSize(1);
63+
assertThat(annotatedBeans.getBeansByAnnotation(McpResourceListChanged.class)).hasSize(1);
64+
assertThat(annotatedBeans.getBeansByAnnotation(McpPromptListChanged.class)).hasSize(1);
65+
66+
// Verify all three specification beans were created
67+
assertThat(context).hasBean(prefix + "ToolListChangedSpecs");
68+
assertThat(context).hasBean(prefix + "ResourceListChangedSpecs");
69+
assertThat(context).hasBean(prefix + "PromptListChangedSpecs");
70+
});
71+
}
72+
73+
@ParameterizedTest
74+
@ValueSource(strings = { "SYNC", "ASYNC" })
75+
void shouldNotScanAnnotationsWhenScannerDisabled(String clientType) {
76+
String prefix = clientType.toLowerCase();
77+
78+
this.contextRunner.withUserConfiguration(AllListChangedConfiguration.class)
79+
.withPropertyValues("spring.ai.mcp.client.type=" + clientType,
80+
"spring.ai.mcp.client.annotation-scanner.enabled=false")
81+
.run(context -> {
82+
// Verify scanner beans were not created
83+
assertThat(context).doesNotHaveBean(McpClientAnnotationScannerAutoConfiguration.class);
84+
assertThat(context).doesNotHaveBean(prefix + "ToolListChangedSpecs");
85+
assertThat(context).doesNotHaveBean(prefix + "ResourceListChangedSpecs");
86+
assertThat(context).doesNotHaveBean(prefix + "PromptListChangedSpecs");
87+
});
88+
}
89+
90+
@Configuration
91+
static class AllListChangedConfiguration {
92+
93+
@Bean
94+
TestListChangedHandlers testHandlers() {
95+
return new TestListChangedHandlers();
96+
}
97+
98+
}
99+
100+
static class TestListChangedHandlers {
101+
102+
@McpToolListChanged(clients = "test-client")
103+
public void onToolListChanged(List<McpSchema.Tool> updatedTools) {
104+
// Test handler for tool list changes
105+
}
106+
107+
@McpResourceListChanged(clients = "test-client")
108+
public void onResourceListChanged(List<McpSchema.Resource> updatedResources) {
109+
// Test handler for resource list changes
110+
}
111+
112+
@McpPromptListChanged(clients = "test-client")
113+
public void onPromptListChanged(List<McpSchema.Prompt> updatedPrompts) {
114+
// Test handler for prompt list changes
115+
}
116+
117+
}
118+
119+
}

0 commit comments

Comments
 (0)