From 8ad1b9ea8c57680451e0f3c9b0052dfc345903fb Mon Sep 17 00:00:00 2001 From: Josh Long <54473+joshlong@users.noreply.github.com> Date: Fri, 19 Sep 2025 15:01:29 +0200 Subject: [PATCH 1/6] wip --- ...verAnnotationScannerAutoConfiguration.java | 2 +- ...tractAnnotatedMethodBeanPostProcessor.java | 65 +++++++++++++++---- 2 files changed, 54 insertions(+), 13 deletions(-) diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/annotations/McpServerAnnotationScannerAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/annotations/McpServerAnnotationScannerAutoConfiguration.java index 26700c09019..f777bc96537 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/annotations/McpServerAnnotationScannerAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/annotations/McpServerAnnotationScannerAutoConfiguration.java @@ -54,7 +54,7 @@ public ServerMcpAnnotatedBeans serverAnnotatedBeanRegistry() { @Bean @ConditionalOnMissingBean - public ServerAnnotatedMethodBeanPostProcessor serverAnnotatedMethodBeanPostProcessor( + public static ServerAnnotatedMethodBeanPostProcessor serverAnnotatedMethodBeanPostProcessor( ServerMcpAnnotatedBeans serverMcpAnnotatedBeans, McpServerAnnotationScannerProperties properties) { return new ServerAnnotatedMethodBeanPostProcessor(serverMcpAnnotatedBeans, SERVER_MCP_ANNOTATIONS); } diff --git a/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AbstractAnnotatedMethodBeanPostProcessor.java b/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AbstractAnnotatedMethodBeanPostProcessor.java index 7e9ab4e9fae..06910dfc84d 100644 --- a/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AbstractAnnotatedMethodBeanPostProcessor.java +++ b/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AbstractAnnotatedMethodBeanPostProcessor.java @@ -16,21 +16,57 @@ package org.springframework.ai.mcp.annotation.spring.scan; -import java.lang.annotation.Annotation; -import java.util.HashSet; -import java.util.Set; - +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.aop.support.AopUtils; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.TypeReference; import org.springframework.beans.BeansException; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.log.LogAccessor; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + /** * @author Christian Tzolov + * @author Josh Long */ -public abstract class AbstractAnnotatedMethodBeanPostProcessor implements BeanPostProcessor { +public abstract class AbstractAnnotatedMethodBeanPostProcessor + implements BeanFactoryInitializationAotProcessor, BeanPostProcessor { + + private static final LogAccessor logger = new LogAccessor(AbstractAnnotatedMethodBeanPostProcessor.class); + + @Override + public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { + + List types = new ArrayList<>(); + + for (String beanName : beanFactory.getBeanDefinitionNames()) { + Class beanClass = beanFactory.getType(beanName); + Set> classes = scan(beanClass); + if (!classes.isEmpty()) { + types.add(TypeReference.of(beanClass.getName())); + } + } + return (generationContext, beanFactoryInitializationCode) -> { + RuntimeHints runtimeHints = generationContext.getRuntimeHints(); + for (TypeReference typeReference : types) { + runtimeHints.reflection().registerType(typeReference, MemberCategory.values()); + logger.info("registering " + typeReference.getName() + " for reflection"); + } + }; + } private final AbstractMcpAnnotatedBeans registry; @@ -50,6 +86,17 @@ public AbstractAnnotatedMethodBeanPostProcessor(AbstractMcpAnnotatedBeans regist public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { Class beanClass = AopUtils.getTargetClass(bean); // Handle proxied beans + Set> foundAnnotations = scan(beanClass); + + // Register the bean if it has any of our target annotations + if (!foundAnnotations.isEmpty()) { + this.registry.addMcpAnnotatedBean(bean, foundAnnotations); + } + + return bean; + } + + private Set> scan(Class beanClass) { Set> foundAnnotations = new HashSet<>(); // Scan all methods in the bean class @@ -60,13 +107,7 @@ public Object postProcessAfterInitialization(Object bean, String beanName) throw } }); }); - - // Register the bean if it has any of our target annotations - if (!foundAnnotations.isEmpty()) { - this.registry.addMcpAnnotatedBean(bean, foundAnnotations); - } - - return bean; + return foundAnnotations; } } From 6a5e6ccea7b292598fc08a8534683814b39e15f6 Mon Sep 17 00:00:00 2001 From: Josh Long <54473+joshlong@users.noreply.github.com> Date: Fri, 19 Sep 2025 19:00:44 +0200 Subject: [PATCH 2/6] works --- ...entAnnotationScannerAutoConfiguration.java | 40 ++++++++++-- ...verAnnotationScannerAutoConfiguration.java | 38 +++++++++-- ...SpecificationFactoryAutoConfiguration.java | 14 +++- ...SpecificationFactoryAutoConfiguration.java | 8 ++- .../springframework/ai/mcp/aot/McpHints.java | 47 ++------------ ...BeanFactoryInitializationAotProcessor.java | 47 ++++++++++++++ ...tractAnnotatedMethodBeanPostProcessor.java | 65 ++----------------- .../scan/AbstractMcpAnnotatedBeans.java | 2 +- .../spring/scan/AnnotatedMethodDiscovery.java | 32 +++++++++ .../ai/aot/AiRuntimeHints.java | 42 ++++++++++-- .../ai/aot/SpringAiCoreRuntimeHints.java | 28 ++++---- 11 files changed, 230 insertions(+), 133 deletions(-) create mode 100644 mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AbstractAnnotatedMethodBeanFactoryInitializationAotProcessor.java create mode 100644 mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AnnotatedMethodDiscovery.java diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/main/java/org/springframework/ai/mcp/client/common/autoconfigure/annotations/McpClientAnnotationScannerAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/main/java/org/springframework/ai/mcp/client/common/autoconfigure/annotations/McpClientAnnotationScannerAutoConfiguration.java index 0f4aa451b3a..38b93f47045 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/main/java/org/springframework/ai/mcp/client/common/autoconfigure/annotations/McpClientAnnotationScannerAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/main/java/org/springframework/ai/mcp/client/common/autoconfigure/annotations/McpClientAnnotationScannerAutoConfiguration.java @@ -16,36 +16,51 @@ package org.springframework.ai.mcp.client.common.autoconfigure.annotations; -import java.lang.annotation.Annotation; -import java.util.Set; - import org.springaicommunity.mcp.annotation.McpElicitation; import org.springaicommunity.mcp.annotation.McpLogging; import org.springaicommunity.mcp.annotation.McpProgress; import org.springaicommunity.mcp.annotation.McpSampling; - +import org.springframework.ai.mcp.annotation.spring.scan.AbstractAnnotatedMethodBeanFactoryInitializationAotProcessor; import org.springframework.ai.mcp.annotation.spring.scan.AbstractAnnotatedMethodBeanPostProcessor; import org.springframework.ai.mcp.annotation.spring.scan.AbstractMcpAnnotatedBeans; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ImportRuntimeHints; + +import java.lang.annotation.Annotation; +import java.util.Set; /** * @author Christian Tzolov + * @author Josh Long */ @AutoConfiguration @ConditionalOnClass(McpLogging.class) @ConditionalOnProperty(prefix = McpClientAnnotationScannerProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true) @EnableConfigurationProperties(McpClientAnnotationScannerProperties.class) +@ImportRuntimeHints(McpClientAnnotationScannerAutoConfiguration.AnnotationHints.class) public class McpClientAnnotationScannerAutoConfiguration { private static final Set> CLIENT_MCP_ANNOTATIONS = Set.of(McpLogging.class, McpSampling.class, McpElicitation.class, McpProgress.class); + static class AnnotationHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + CLIENT_MCP_ANNOTATIONS.forEach(an -> hints.reflection().registerType(an, MemberCategory.values())); + } + + } + @Bean @ConditionalOnMissingBean public ClientMcpAnnotatedBeans clientAnnotatedBeans() { @@ -54,15 +69,30 @@ public ClientMcpAnnotatedBeans clientAnnotatedBeans() { @Bean @ConditionalOnMissingBean - public ClientAnnotatedMethodBeanPostProcessor clientAnnotatedMethodBeanPostProcessor( + public static ClientAnnotatedMethodBeanPostProcessor clientAnnotatedMethodBeanPostProcessor( ClientMcpAnnotatedBeans clientMcpAnnotatedBeans, McpClientAnnotationScannerProperties properties) { return new ClientAnnotatedMethodBeanPostProcessor(clientMcpAnnotatedBeans, CLIENT_MCP_ANNOTATIONS); } + @Bean + static ClientAnnotatedBeanFactoryInitializationAotProcessor clientAnnotatedBeanFactoryInitializationAotProcessor() { + return new ClientAnnotatedBeanFactoryInitializationAotProcessor(CLIENT_MCP_ANNOTATIONS); + } + public static class ClientMcpAnnotatedBeans extends AbstractMcpAnnotatedBeans { } + public static class ClientAnnotatedBeanFactoryInitializationAotProcessor + extends AbstractAnnotatedMethodBeanFactoryInitializationAotProcessor { + + public ClientAnnotatedBeanFactoryInitializationAotProcessor( + Set> targetAnnotations) { + super(targetAnnotations); + } + + } + public static class ClientAnnotatedMethodBeanPostProcessor extends AbstractAnnotatedMethodBeanPostProcessor { public ClientAnnotatedMethodBeanPostProcessor(ClientMcpAnnotatedBeans clientMcpAnnotatedBeans, diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/annotations/McpServerAnnotationScannerAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/annotations/McpServerAnnotationScannerAutoConfiguration.java index f777bc96537..ea13f9f9435 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/annotations/McpServerAnnotationScannerAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/annotations/McpServerAnnotationScannerAutoConfiguration.java @@ -16,36 +16,51 @@ package org.springframework.ai.mcp.server.common.autoconfigure.annotations; -import java.lang.annotation.Annotation; -import java.util.Set; - import org.springaicommunity.mcp.annotation.McpComplete; import org.springaicommunity.mcp.annotation.McpPrompt; import org.springaicommunity.mcp.annotation.McpResource; import org.springaicommunity.mcp.annotation.McpTool; - +import org.springframework.ai.mcp.annotation.spring.scan.AbstractAnnotatedMethodBeanFactoryInitializationAotProcessor; import org.springframework.ai.mcp.annotation.spring.scan.AbstractAnnotatedMethodBeanPostProcessor; import org.springframework.ai.mcp.annotation.spring.scan.AbstractMcpAnnotatedBeans; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ImportRuntimeHints; + +import java.lang.annotation.Annotation; +import java.util.Set; /** * @author Christian Tzolov + * @author Josh Long */ @AutoConfiguration @ConditionalOnClass(McpTool.class) @ConditionalOnProperty(prefix = McpServerAnnotationScannerProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true) @EnableConfigurationProperties(McpServerAnnotationScannerProperties.class) +@ImportRuntimeHints(McpServerAnnotationScannerAutoConfiguration.AnnotationHints.class) public class McpServerAnnotationScannerAutoConfiguration { private static final Set> SERVER_MCP_ANNOTATIONS = Set.of(McpTool.class, McpResource.class, McpPrompt.class, McpComplete.class); + static class AnnotationHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + SERVER_MCP_ANNOTATIONS.forEach(an -> hints.reflection().registerType(an, MemberCategory.values())); + } + + } + @Bean @ConditionalOnMissingBean public ServerMcpAnnotatedBeans serverAnnotatedBeanRegistry() { @@ -59,10 +74,25 @@ public static ServerAnnotatedMethodBeanPostProcessor serverAnnotatedMethodBeanPo return new ServerAnnotatedMethodBeanPostProcessor(serverMcpAnnotatedBeans, SERVER_MCP_ANNOTATIONS); } + @Bean + public static ServerAnnotatedBeanFactoryInitializationAotProcessor serverAnnotatedBeanFactoryInitializationAotProcessor() { + return new ServerAnnotatedBeanFactoryInitializationAotProcessor(SERVER_MCP_ANNOTATIONS); + } + public static class ServerMcpAnnotatedBeans extends AbstractMcpAnnotatedBeans { } + public static class ServerAnnotatedBeanFactoryInitializationAotProcessor + extends AbstractAnnotatedMethodBeanFactoryInitializationAotProcessor { + + public ServerAnnotatedBeanFactoryInitializationAotProcessor( + Set> targetAnnotations) { + super(targetAnnotations); + } + + } + public static class ServerAnnotatedMethodBeanPostProcessor extends AbstractAnnotatedMethodBeanPostProcessor { public ServerAnnotatedMethodBeanPostProcessor(ServerMcpAnnotatedBeans serverMcpAnnotatedBeans, diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/annotations/McpServerSpecificationFactoryAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/annotations/McpServerSpecificationFactoryAutoConfiguration.java index f07b14ea256..a60071255f0 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/annotations/McpServerSpecificationFactoryAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/annotations/McpServerSpecificationFactoryAutoConfiguration.java @@ -54,8 +54,11 @@ static class SyncServerSpecificationConfiguration { @Bean public List resourceSpecs( ServerMcpAnnotatedBeans beansWithMcpMethodAnnotations) { - return SyncMcpAnnotationProviders + + List syncResourceSpecifications = SyncMcpAnnotationProviders .resourceSpecifications(beansWithMcpMethodAnnotations.getBeansByAnnotation(McpResource.class)); + System.out.println("SyncResourceSpecifications: " + syncResourceSpecifications); + return syncResourceSpecifications; } @Bean @@ -75,8 +78,13 @@ public List completionSpecs( @Bean public List toolSpecs( ServerMcpAnnotatedBeans beansWithMcpMethodAnnotations) { - return SyncMcpAnnotationProviders - .toolSpecifications(beansWithMcpMethodAnnotations.getBeansByAnnotation(McpTool.class)); + List beansByAnnotation = beansWithMcpMethodAnnotations.getBeansByAnnotation(McpTool.class); + System.out.println("beansByAnnotation: " + beansByAnnotation + "(" + beansByAnnotation.size() + ")" + "."); + List syncToolSpecifications = SyncMcpAnnotationProviders + .toolSpecifications(beansByAnnotation); + System.out.println("syncToolSpecifications: " + syncToolSpecifications + "(" + syncToolSpecifications.size() + + ")" + "."); + return syncToolSpecifications; } } diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/annotations/StatelessServerSpecificationFactoryAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/annotations/StatelessServerSpecificationFactoryAutoConfiguration.java index 47290ed653c..a34b1f4c5b0 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/annotations/StatelessServerSpecificationFactoryAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/annotations/StatelessServerSpecificationFactoryAutoConfiguration.java @@ -77,8 +77,12 @@ public List completionSp @Bean public List toolSpecs( ServerMcpAnnotatedBeans beansWithMcpMethodAnnotations) { - return SyncMcpAnnotationProviders - .statelessToolSpecifications(beansWithMcpMethodAnnotations.getBeansByAnnotation(McpTool.class)); + List beansByAnnotation = beansWithMcpMethodAnnotations.getBeansByAnnotation(McpTool.class); + System.out.println("MCPTool SyncToolSpecs: " + beansByAnnotation + "."); + List syncToolSpecifications = SyncMcpAnnotationProviders + .statelessToolSpecifications(beansByAnnotation); + System.out.println("SyncToolSpecs: " + syncToolSpecifications + "."); + return syncToolSpecifications; } } diff --git a/mcp/common/src/main/java/org/springframework/ai/mcp/aot/McpHints.java b/mcp/common/src/main/java/org/springframework/ai/mcp/aot/McpHints.java index 52f34fa2a7d..2e89c097bfa 100644 --- a/mcp/common/src/main/java/org/springframework/ai/mcp/aot/McpHints.java +++ b/mcp/common/src/main/java/org/springframework/ai/mcp/aot/McpHints.java @@ -16,20 +16,19 @@ package org.springframework.ai.mcp.aot; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; import java.util.Set; -import java.util.stream.Collectors; import io.modelcontextprotocol.spec.McpSchema; +import org.springframework.ai.aot.AiRuntimeHints; import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.aot.hint.TypeReference; import org.springframework.lang.Nullable; +import static org.springframework.ai.aot.AiRuntimeHints.findInnerClassesFor; + /** * Runtime hints registrar for Model Context Protocol (MCP) schema classes. *

@@ -65,45 +64,11 @@ public class McpHints implements RuntimeHintsRegistrar { public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { var mcs = MemberCategory.values(); - for (var tr : innerClasses(McpSchema.class)) { + Set typeReferences = AiRuntimeHints.findInnerClassesFor(McpSchema.class); + System.out.println("typeReferences: " + typeReferences + "(" + typeReferences.size() + ")"); + for (var tr : typeReferences) { hints.reflection().registerType(tr, mcs); } } - /** - * Discovers all inner classes of a given class. - *

- * This method recursively finds all nested classes (both declared and inherited) of - * the provided class and converts them to type references. - * @param clazz the class to find inner classes for - * @return a set of type references for all discovered inner classes - */ - private Set innerClasses(Class clazz) { - var indent = new HashSet(); - this.findNestedClasses(clazz, indent); - return indent.stream().map(TypeReference::of).collect(Collectors.toSet()); - } - - /** - * Recursively finds all nested classes of a given class. - *

- * This method: - *

    - *
  1. Collects both declared and inherited nested classes
  2. - *
  3. Recursively processes each nested class
  4. - *
  5. Adds the class names to the provided set
  6. - *
- * @param clazz the class to find nested classes for - * @param indent the set to collect class names in - */ - private void findNestedClasses(Class clazz, Set indent) { - var classes = new ArrayList>(); - classes.addAll(Arrays.asList(clazz.getDeclaredClasses())); - classes.addAll(Arrays.asList(clazz.getClasses())); - for (var nestedClass : classes) { - this.findNestedClasses(nestedClass, indent); - } - indent.addAll(classes.stream().map(Class::getName).toList()); - } - } diff --git a/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AbstractAnnotatedMethodBeanFactoryInitializationAotProcessor.java b/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AbstractAnnotatedMethodBeanFactoryInitializationAotProcessor.java new file mode 100644 index 00000000000..9ef6031178e --- /dev/null +++ b/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AbstractAnnotatedMethodBeanFactoryInitializationAotProcessor.java @@ -0,0 +1,47 @@ +package org.springframework.ai.mcp.annotation.spring.scan; + +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.core.log.LogAccessor; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * @author Josh Long + */ +public class AbstractAnnotatedMethodBeanFactoryInitializationAotProcessor extends AnnotatedMethodDiscovery + implements BeanFactoryInitializationAotProcessor { + + private static final LogAccessor logger = new LogAccessor(AbstractAnnotatedMethodBeanPostProcessor.class); + + public AbstractAnnotatedMethodBeanFactoryInitializationAotProcessor( + Set> targetAnnotations) { + super(targetAnnotations); + } + + @Override + public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { + List> types = new ArrayList<>(); + for (String beanName : beanFactory.getBeanDefinitionNames()) { + Class beanClass = beanFactory.getType(beanName); + Set> classes = this.scan(beanClass); + if (!classes.isEmpty()) { + types.add(beanClass); + } + } + return (generationContext, beanFactoryInitializationCode) -> { + RuntimeHints runtimeHints = generationContext.getRuntimeHints(); + for (Class typeReference : types) { + runtimeHints.reflection().registerType(typeReference, MemberCategory.values()); + logger.info("registering " + typeReference.getName() + " for reflection"); + } + }; + } + +} diff --git a/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AbstractAnnotatedMethodBeanPostProcessor.java b/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AbstractAnnotatedMethodBeanPostProcessor.java index 06910dfc84d..30b9a7af62f 100644 --- a/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AbstractAnnotatedMethodBeanPostProcessor.java +++ b/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AbstractAnnotatedMethodBeanPostProcessor.java @@ -16,98 +16,43 @@ package org.springframework.ai.mcp.annotation.spring.scan; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.aop.support.AopUtils; -import org.springframework.aot.hint.MemberCategory; -import org.springframework.aot.hint.RuntimeHints; -import org.springframework.aot.hint.TypeReference; import org.springframework.beans.BeansException; -import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; -import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.core.log.LogAccessor; import org.springframework.util.Assert; -import org.springframework.util.ReflectionUtils; import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; import java.util.Set; /** * @author Christian Tzolov * @author Josh Long */ -public abstract class AbstractAnnotatedMethodBeanPostProcessor - implements BeanFactoryInitializationAotProcessor, BeanPostProcessor { - - private static final LogAccessor logger = new LogAccessor(AbstractAnnotatedMethodBeanPostProcessor.class); - - @Override - public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { - - List types = new ArrayList<>(); - - for (String beanName : beanFactory.getBeanDefinitionNames()) { - Class beanClass = beanFactory.getType(beanName); - Set> classes = scan(beanClass); - if (!classes.isEmpty()) { - types.add(TypeReference.of(beanClass.getName())); - } - } - return (generationContext, beanFactoryInitializationCode) -> { - RuntimeHints runtimeHints = generationContext.getRuntimeHints(); - for (TypeReference typeReference : types) { - runtimeHints.reflection().registerType(typeReference, MemberCategory.values()); - logger.info("registering " + typeReference.getName() + " for reflection"); - } - }; - } +public abstract class AbstractAnnotatedMethodBeanPostProcessor extends AnnotatedMethodDiscovery + implements BeanPostProcessor { private final AbstractMcpAnnotatedBeans registry; - // Define the annotations to scan for - private final Set> targetAnnotations; - public AbstractAnnotatedMethodBeanPostProcessor(AbstractMcpAnnotatedBeans registry, Set> targetAnnotations) { + super(targetAnnotations); Assert.notNull(registry, "AnnotatedBeanRegistry must not be null"); Assert.notEmpty(targetAnnotations, "Target annotations must not be empty"); - this.registry = registry; - this.targetAnnotations = targetAnnotations; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { Class beanClass = AopUtils.getTargetClass(bean); // Handle proxied beans - Set> foundAnnotations = scan(beanClass); - // Register the bean if it has any of our target annotations if (!foundAnnotations.isEmpty()) { + System.out.println("registering " + beanName + " for " + beanClass.getName() + " with annotations " + + foundAnnotations.toString() + " in " + this.getClass().getName()); this.registry.addMcpAnnotatedBean(bean, foundAnnotations); } return bean; } - private Set> scan(Class beanClass) { - Set> foundAnnotations = new HashSet<>(); - - // Scan all methods in the bean class - ReflectionUtils.doWithMethods(beanClass, method -> { - this.targetAnnotations.forEach(annotationType -> { - if (AnnotationUtils.findAnnotation(method, annotationType) != null) { - foundAnnotations.add(annotationType); - } - }); - }); - return foundAnnotations; - } - } diff --git a/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AbstractMcpAnnotatedBeans.java b/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AbstractMcpAnnotatedBeans.java index d46ac06f399..3a61995e228 100644 --- a/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AbstractMcpAnnotatedBeans.java +++ b/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AbstractMcpAnnotatedBeans.java @@ -37,7 +37,7 @@ public abstract class AbstractMcpAnnotatedBeans { public void addMcpAnnotatedBean(Object bean, Set> annotations) { this.beansWithCustomAnnotations.add(bean); - + System.out.println("adding " + bean + " with annotations [" + annotations + ']'); annotations .forEach(annotationType -> this.beansByAnnotation.computeIfAbsent(annotationType, k -> new ArrayList<>()) .add(bean)); diff --git a/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AnnotatedMethodDiscovery.java b/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AnnotatedMethodDiscovery.java new file mode 100644 index 00000000000..1406846681b --- /dev/null +++ b/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AnnotatedMethodDiscovery.java @@ -0,0 +1,32 @@ +package org.springframework.ai.mcp.annotation.spring.scan; + +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.ReflectionUtils; + +import java.lang.annotation.Annotation; +import java.util.HashSet; +import java.util.Set; + +class AnnotatedMethodDiscovery { + + protected final Set> targetAnnotations; + + AnnotatedMethodDiscovery(Set> targetAnnotations) { + this.targetAnnotations = targetAnnotations; + } + + protected Set> scan(Class beanClass) { + Set> foundAnnotations = new HashSet<>(); + + // Scan all methods in the bean class + ReflectionUtils.doWithMethods(beanClass, method -> { + this.targetAnnotations.forEach(annotationType -> { + if (AnnotationUtils.findAnnotation(method, annotationType) != null) { + foundAnnotations.add(annotationType); + } + }); + }); + return foundAnnotations; + } + +} diff --git a/spring-ai-model/src/main/java/org/springframework/ai/aot/AiRuntimeHints.java b/spring-ai-model/src/main/java/org/springframework/ai/aot/AiRuntimeHints.java index e6b9b91c80e..1bc465a311f 100644 --- a/spring-ai-model/src/main/java/org/springframework/ai/aot/AiRuntimeHints.java +++ b/spring-ai-model/src/main/java/org/springframework/ai/aot/AiRuntimeHints.java @@ -17,10 +17,7 @@ package org.springframework.ai.aot; import java.lang.reflect.Executable; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; import com.fasterxml.jackson.annotation.JsonInclude; @@ -157,4 +154,41 @@ private static Set> discoverJacksonAnnotatedTypesFromRootType(Class return jsonTypes; } + + /** + * Discovers all inner classes of a given class. + *

+ * This method recursively finds all nested classes (both declared and inherited) of + * the provided class and converts them to type references. + * @param clazz the class to find inner classes for + * @return a set of type references for all discovered inner classes + */ + public static Set findInnerClassesFor(Class clazz) { + var indent = new HashSet(); + findNestedClasses(clazz, indent); + return indent.stream().map(TypeReference::of).collect(Collectors.toSet()); + } + + /** + * Recursively finds all nested classes of a given class. + *

+ * This method: + *

    + *
  1. Collects both declared and inherited nested classes
  2. + *
  3. Recursively processes each nested class
  4. + *
  5. Adds the class names to the provided set
  6. + *
+ * @param clazz the class to find nested classes for + * @param indent the set to collect class names in + */ + private static void findNestedClasses(Class clazz, Set indent) { + var classes = new ArrayList>(); + classes.addAll(Arrays.asList(clazz.getDeclaredClasses())); + classes.addAll(Arrays.asList(clazz.getClasses())); + for (var nestedClass : classes) { + findNestedClasses(nestedClass, indent); + } + indent.addAll(classes.stream().map(Class::getName).toList()); + } + } diff --git a/spring-ai-model/src/main/java/org/springframework/ai/aot/SpringAiCoreRuntimeHints.java b/spring-ai-model/src/main/java/org/springframework/ai/aot/SpringAiCoreRuntimeHints.java index b7b93ac4757..74f35e7878b 100644 --- a/spring-ai-model/src/main/java/org/springframework/ai/aot/SpringAiCoreRuntimeHints.java +++ b/spring-ai-model/src/main/java/org/springframework/ai/aot/SpringAiCoreRuntimeHints.java @@ -16,38 +16,40 @@ package org.springframework.ai.aot; -import java.util.Set; - -import org.springframework.ai.chat.messages.AbstractMessage; -import org.springframework.ai.chat.messages.AssistantMessage; -import org.springframework.ai.chat.messages.Message; -import org.springframework.ai.chat.messages.MessageType; -import org.springframework.ai.chat.messages.SystemMessage; -import org.springframework.ai.chat.messages.ToolResponseMessage; -import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.messages.*; +import org.springframework.ai.content.Content; +import org.springframework.ai.content.MediaContent; import org.springframework.ai.tool.ToolCallback; import org.springframework.ai.tool.definition.ToolDefinition; +import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.core.io.ClassPathResource; import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; +import java.util.Set; + public class SpringAiCoreRuntimeHints implements RuntimeHintsRegistrar { @Override public void registerHints(@NonNull RuntimeHints hints, @Nullable ClassLoader classLoader) { - + // var chatTypes = Set.of(AbstractMessage.class, AssistantMessage.class, ToolResponseMessage.class, Message.class, - MessageType.class, UserMessage.class, SystemMessage.class); + AssistantMessage.ToolCall.class, MessageType.class, UserMessage.class, SystemMessage.class, + Content.class, MediaContent.class); + + var memberCategories = MemberCategory.values(); for (var c : chatTypes) { - hints.reflection().registerType(c); + hints.reflection().registerType(c, memberCategories); + System.out.println("registering " + c); } // Register tool-related types for reflection var toolTypes = Set.of(ToolCallback.class, ToolDefinition.class); for (var c : toolTypes) { - hints.reflection().registerType(c); + hints.reflection().registerType(c, memberCategories); + System.out.println("registering " + c); } for (var r : Set.of("embedding/embedding-model-dimensions.properties")) { From dcef32ad0378129adfd8e21d04d173b08bf536d5 Mon Sep 17 00:00:00 2001 From: Josh Long <54473+joshlong@users.noreply.github.com> Date: Fri, 19 Sep 2025 19:12:43 +0200 Subject: [PATCH 3/6] up --- .../ai/aot/AiRuntimeHints.java | 1 - .../ai/aot/SpringAiCoreRuntimeHints.java | 19 ++++++++----------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/spring-ai-model/src/main/java/org/springframework/ai/aot/AiRuntimeHints.java b/spring-ai-model/src/main/java/org/springframework/ai/aot/AiRuntimeHints.java index 1bc465a311f..6ea41dbc19f 100644 --- a/spring-ai-model/src/main/java/org/springframework/ai/aot/AiRuntimeHints.java +++ b/spring-ai-model/src/main/java/org/springframework/ai/aot/AiRuntimeHints.java @@ -154,7 +154,6 @@ private static Set> discoverJacksonAnnotatedTypesFromRootType(Class return jsonTypes; } - /** * Discovers all inner classes of a given class. *

diff --git a/spring-ai-model/src/main/java/org/springframework/ai/aot/SpringAiCoreRuntimeHints.java b/spring-ai-model/src/main/java/org/springframework/ai/aot/SpringAiCoreRuntimeHints.java index 74f35e7878b..dedb08e492a 100644 --- a/spring-ai-model/src/main/java/org/springframework/ai/aot/SpringAiCoreRuntimeHints.java +++ b/spring-ai-model/src/main/java/org/springframework/ai/aot/SpringAiCoreRuntimeHints.java @@ -24,6 +24,7 @@ import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.aot.hint.TypeReference; import org.springframework.core.io.ClassPathResource; import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; @@ -34,22 +35,18 @@ public class SpringAiCoreRuntimeHints implements RuntimeHintsRegistrar { @Override public void registerHints(@NonNull RuntimeHints hints, @Nullable ClassLoader classLoader) { - // + var chatTypes = Set.of(AbstractMessage.class, AssistantMessage.class, ToolResponseMessage.class, Message.class, - AssistantMessage.ToolCall.class, MessageType.class, UserMessage.class, SystemMessage.class, - Content.class, MediaContent.class); + ToolCallback.class, ToolDefinition.class, AssistantMessage.ToolCall.class, MessageType.class, + UserMessage.class, SystemMessage.class, Content.class, MediaContent.class); var memberCategories = MemberCategory.values(); - for (var c : chatTypes) { - hints.reflection().registerType(c, memberCategories); - System.out.println("registering " + c); - } - // Register tool-related types for reflection - var toolTypes = Set.of(ToolCallback.class, ToolDefinition.class); - for (var c : toolTypes) { + for (var c : chatTypes) { hints.reflection().registerType(c, memberCategories); - System.out.println("registering " + c); + var innerClassesFor = AiRuntimeHints.findInnerClassesFor(c); + for (var cc : innerClassesFor) + hints.reflection().registerType(cc, memberCategories); } for (var r : Set.of("embedding/embedding-model-dimensions.properties")) { From 5ab9b18add985311ba0fb86da2cc401a9de0e00e Mon Sep 17 00:00:00 2001 From: Christian Tzolov Date: Fri, 19 Sep 2025 19:25:05 +0200 Subject: [PATCH 4/6] Fix checkstyle issues Signed-off-by: Christian Tzolov --- .../springframework/ai/aot/AiRuntimeHints.java | 6 +++++- .../ai/aot/SpringAiCoreRuntimeHints.java | 16 +++++++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/spring-ai-model/src/main/java/org/springframework/ai/aot/AiRuntimeHints.java b/spring-ai-model/src/main/java/org/springframework/ai/aot/AiRuntimeHints.java index 6ea41dbc19f..07fbad5d7e3 100644 --- a/spring-ai-model/src/main/java/org/springframework/ai/aot/AiRuntimeHints.java +++ b/spring-ai-model/src/main/java/org/springframework/ai/aot/AiRuntimeHints.java @@ -17,7 +17,11 @@ package org.springframework.ai.aot; import java.lang.reflect.Executable; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; import java.util.stream.Collectors; import com.fasterxml.jackson.annotation.JsonInclude; diff --git a/spring-ai-model/src/main/java/org/springframework/ai/aot/SpringAiCoreRuntimeHints.java b/spring-ai-model/src/main/java/org/springframework/ai/aot/SpringAiCoreRuntimeHints.java index dedb08e492a..9210e1819d8 100644 --- a/spring-ai-model/src/main/java/org/springframework/ai/aot/SpringAiCoreRuntimeHints.java +++ b/spring-ai-model/src/main/java/org/springframework/ai/aot/SpringAiCoreRuntimeHints.java @@ -16,7 +16,15 @@ package org.springframework.ai.aot; -import org.springframework.ai.chat.messages.*; +import java.util.Set; + +import org.springframework.ai.chat.messages.AbstractMessage; +import org.springframework.ai.chat.messages.AssistantMessage; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.MessageType; +import org.springframework.ai.chat.messages.SystemMessage; +import org.springframework.ai.chat.messages.ToolResponseMessage; +import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.content.Content; import org.springframework.ai.content.MediaContent; import org.springframework.ai.tool.ToolCallback; @@ -24,13 +32,10 @@ import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; -import org.springframework.aot.hint.TypeReference; import org.springframework.core.io.ClassPathResource; import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; -import java.util.Set; - public class SpringAiCoreRuntimeHints implements RuntimeHintsRegistrar { @Override @@ -45,8 +50,9 @@ public void registerHints(@NonNull RuntimeHints hints, @Nullable ClassLoader cla for (var c : chatTypes) { hints.reflection().registerType(c, memberCategories); var innerClassesFor = AiRuntimeHints.findInnerClassesFor(c); - for (var cc : innerClassesFor) + for (var cc : innerClassesFor) { hints.reflection().registerType(cc, memberCategories); + } } for (var r : Set.of("embedding/embedding-model-dimensions.properties")) { From eb0384a7126793790c877f94ea6e410b056dc161 Mon Sep 17 00:00:00 2001 From: Christian Tzolov Date: Fri, 19 Sep 2025 19:52:10 +0200 Subject: [PATCH 5/6] checkstyle fixes Signed-off-by: Christian Tzolov --- ...entAnnotationScannerAutoConfiguration.java | 25 +++++++++--------- ...verAnnotationScannerAutoConfiguration.java | 25 +++++++++--------- .../springframework/ai/mcp/aot/McpHints.java | 3 --- ...BeanFactoryInitializationAotProcessor.java | 26 +++++++++++++++---- ...tractAnnotatedMethodBeanPostProcessor.java | 6 ++--- .../spring/scan/AnnotatedMethodDiscovery.java | 22 +++++++++++++--- 6 files changed, 69 insertions(+), 38 deletions(-) diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/main/java/org/springframework/ai/mcp/client/common/autoconfigure/annotations/McpClientAnnotationScannerAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/main/java/org/springframework/ai/mcp/client/common/autoconfigure/annotations/McpClientAnnotationScannerAutoConfiguration.java index 38b93f47045..f2b8632a013 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/main/java/org/springframework/ai/mcp/client/common/autoconfigure/annotations/McpClientAnnotationScannerAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/main/java/org/springframework/ai/mcp/client/common/autoconfigure/annotations/McpClientAnnotationScannerAutoConfiguration.java @@ -16,10 +16,14 @@ package org.springframework.ai.mcp.client.common.autoconfigure.annotations; +import java.lang.annotation.Annotation; +import java.util.Set; + import org.springaicommunity.mcp.annotation.McpElicitation; import org.springaicommunity.mcp.annotation.McpLogging; import org.springaicommunity.mcp.annotation.McpProgress; import org.springaicommunity.mcp.annotation.McpSampling; + import org.springframework.ai.mcp.annotation.spring.scan.AbstractAnnotatedMethodBeanFactoryInitializationAotProcessor; import org.springframework.ai.mcp.annotation.spring.scan.AbstractAnnotatedMethodBeanPostProcessor; import org.springframework.ai.mcp.annotation.spring.scan.AbstractMcpAnnotatedBeans; @@ -34,9 +38,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ImportRuntimeHints; -import java.lang.annotation.Annotation; -import java.util.Set; - /** * @author Christian Tzolov * @author Josh Long @@ -52,15 +53,6 @@ public class McpClientAnnotationScannerAutoConfiguration { private static final Set> CLIENT_MCP_ANNOTATIONS = Set.of(McpLogging.class, McpSampling.class, McpElicitation.class, McpProgress.class); - static class AnnotationHints implements RuntimeHintsRegistrar { - - @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { - CLIENT_MCP_ANNOTATIONS.forEach(an -> hints.reflection().registerType(an, MemberCategory.values())); - } - - } - @Bean @ConditionalOnMissingBean public ClientMcpAnnotatedBeans clientAnnotatedBeans() { @@ -102,4 +94,13 @@ public ClientAnnotatedMethodBeanPostProcessor(ClientMcpAnnotatedBeans clientMcpA } + static class AnnotationHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + CLIENT_MCP_ANNOTATIONS.forEach(an -> hints.reflection().registerType(an, MemberCategory.values())); + } + + } + } diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/annotations/McpServerAnnotationScannerAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/annotations/McpServerAnnotationScannerAutoConfiguration.java index ea13f9f9435..3f6746601b0 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/annotations/McpServerAnnotationScannerAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/annotations/McpServerAnnotationScannerAutoConfiguration.java @@ -16,10 +16,14 @@ package org.springframework.ai.mcp.server.common.autoconfigure.annotations; +import java.lang.annotation.Annotation; +import java.util.Set; + import org.springaicommunity.mcp.annotation.McpComplete; import org.springaicommunity.mcp.annotation.McpPrompt; import org.springaicommunity.mcp.annotation.McpResource; import org.springaicommunity.mcp.annotation.McpTool; + import org.springframework.ai.mcp.annotation.spring.scan.AbstractAnnotatedMethodBeanFactoryInitializationAotProcessor; import org.springframework.ai.mcp.annotation.spring.scan.AbstractAnnotatedMethodBeanPostProcessor; import org.springframework.ai.mcp.annotation.spring.scan.AbstractMcpAnnotatedBeans; @@ -34,9 +38,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ImportRuntimeHints; -import java.lang.annotation.Annotation; -import java.util.Set; - /** * @author Christian Tzolov * @author Josh Long @@ -52,15 +53,6 @@ public class McpServerAnnotationScannerAutoConfiguration { private static final Set> SERVER_MCP_ANNOTATIONS = Set.of(McpTool.class, McpResource.class, McpPrompt.class, McpComplete.class); - static class AnnotationHints implements RuntimeHintsRegistrar { - - @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { - SERVER_MCP_ANNOTATIONS.forEach(an -> hints.reflection().registerType(an, MemberCategory.values())); - } - - } - @Bean @ConditionalOnMissingBean public ServerMcpAnnotatedBeans serverAnnotatedBeanRegistry() { @@ -102,4 +94,13 @@ public ServerAnnotatedMethodBeanPostProcessor(ServerMcpAnnotatedBeans serverMcpA } + static class AnnotationHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + SERVER_MCP_ANNOTATIONS.forEach(an -> hints.reflection().registerType(an, MemberCategory.values())); + } + + } + } diff --git a/mcp/common/src/main/java/org/springframework/ai/mcp/aot/McpHints.java b/mcp/common/src/main/java/org/springframework/ai/mcp/aot/McpHints.java index 2e89c097bfa..5e1815feda8 100644 --- a/mcp/common/src/main/java/org/springframework/ai/mcp/aot/McpHints.java +++ b/mcp/common/src/main/java/org/springframework/ai/mcp/aot/McpHints.java @@ -27,8 +27,6 @@ import org.springframework.aot.hint.TypeReference; import org.springframework.lang.Nullable; -import static org.springframework.ai.aot.AiRuntimeHints.findInnerClassesFor; - /** * Runtime hints registrar for Model Context Protocol (MCP) schema classes. *

@@ -65,7 +63,6 @@ public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) var mcs = MemberCategory.values(); Set typeReferences = AiRuntimeHints.findInnerClassesFor(McpSchema.class); - System.out.println("typeReferences: " + typeReferences + "(" + typeReferences.size() + ")"); for (var tr : typeReferences) { hints.reflection().registerType(tr, mcs); } diff --git a/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AbstractAnnotatedMethodBeanFactoryInitializationAotProcessor.java b/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AbstractAnnotatedMethodBeanFactoryInitializationAotProcessor.java index 9ef6031178e..093a38ea618 100644 --- a/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AbstractAnnotatedMethodBeanFactoryInitializationAotProcessor.java +++ b/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AbstractAnnotatedMethodBeanFactoryInitializationAotProcessor.java @@ -1,5 +1,26 @@ +/* + * Copyright 2025-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.springframework.ai.mcp.annotation.spring.scan; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; @@ -7,11 +28,6 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.core.log.LogAccessor; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - /** * @author Josh Long */ diff --git a/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AbstractAnnotatedMethodBeanPostProcessor.java b/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AbstractAnnotatedMethodBeanPostProcessor.java index 30b9a7af62f..23c654b6c93 100644 --- a/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AbstractAnnotatedMethodBeanPostProcessor.java +++ b/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AbstractAnnotatedMethodBeanPostProcessor.java @@ -16,14 +16,14 @@ package org.springframework.ai.mcp.annotation.spring.scan; +import java.lang.annotation.Annotation; +import java.util.Set; + import org.springframework.aop.support.AopUtils; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.util.Assert; -import java.lang.annotation.Annotation; -import java.util.Set; - /** * @author Christian Tzolov * @author Josh Long diff --git a/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AnnotatedMethodDiscovery.java b/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AnnotatedMethodDiscovery.java index 1406846681b..233c1f037b8 100644 --- a/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AnnotatedMethodDiscovery.java +++ b/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AnnotatedMethodDiscovery.java @@ -1,12 +1,28 @@ -package org.springframework.ai.mcp.annotation.spring.scan; +/* + * Copyright 2025-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.util.ReflectionUtils; +package org.springframework.ai.mcp.annotation.spring.scan; import java.lang.annotation.Annotation; import java.util.HashSet; import java.util.Set; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.ReflectionUtils; + class AnnotatedMethodDiscovery { protected final Set> targetAnnotations; From 7a05d254e077cfde5abec880e543fa2ec928e183 Mon Sep 17 00:00:00 2001 From: Christian Tzolov Date: Fri, 19 Sep 2025 20:01:03 +0200 Subject: [PATCH 6/6] remove sysout Signed-off-by: Christian Tzolov --- .../SseHttpClientTransportAutoConfigurationIT.java | 2 -- .../StreamableHttpHttpClientTransportAutoConfigurationIT.java | 2 -- .../autoconfigure/SseWebFluxTransportAutoConfigurationIT.java | 2 -- .../StreamableHttpHttpClientTransportAutoConfigurationIT.java | 2 -- .../McpServerSpecificationFactoryAutoConfiguration.java | 4 ---- .../StatelessServerSpecificationFactoryAutoConfiguration.java | 2 -- .../spring/scan/AbstractAnnotatedMethodBeanPostProcessor.java | 2 -- .../mcp/annotation/spring/scan/AbstractMcpAnnotatedBeans.java | 1 - 8 files changed, 17 deletions(-) diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-httpclient/src/test/java/org/springframework/ai/mcp/client/autoconfigure/SseHttpClientTransportAutoConfigurationIT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-httpclient/src/test/java/org/springframework/ai/mcp/client/autoconfigure/SseHttpClientTransportAutoConfigurationIT.java index 6d58a163d62..7dae305197e 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-httpclient/src/test/java/org/springframework/ai/mcp/client/autoconfigure/SseHttpClientTransportAutoConfigurationIT.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-httpclient/src/test/java/org/springframework/ai/mcp/client/autoconfigure/SseHttpClientTransportAutoConfigurationIT.java @@ -94,8 +94,6 @@ void streamableHttpTest() { mcpClient.ping(); - System.out.println("mcpClient = " + mcpClient.getServerInfo()); - ListToolsResult toolsResult = mcpClient.listTools(); assertThat(toolsResult).isNotNull(); diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-httpclient/src/test/java/org/springframework/ai/mcp/client/autoconfigure/StreamableHttpHttpClientTransportAutoConfigurationIT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-httpclient/src/test/java/org/springframework/ai/mcp/client/autoconfigure/StreamableHttpHttpClientTransportAutoConfigurationIT.java index 491e7302e77..230605fdc46 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-httpclient/src/test/java/org/springframework/ai/mcp/client/autoconfigure/StreamableHttpHttpClientTransportAutoConfigurationIT.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-httpclient/src/test/java/org/springframework/ai/mcp/client/autoconfigure/StreamableHttpHttpClientTransportAutoConfigurationIT.java @@ -95,8 +95,6 @@ void streamableHttpTest() { mcpClient.ping(); - System.out.println("mcpClient = " + mcpClient.getServerInfo()); - ListToolsResult toolsResult = mcpClient.listTools(); assertThat(toolsResult).isNotNull(); diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-webflux/src/test/java/org/springframework/ai/mcp/client/webflux/autoconfigure/SseWebFluxTransportAutoConfigurationIT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-webflux/src/test/java/org/springframework/ai/mcp/client/webflux/autoconfigure/SseWebFluxTransportAutoConfigurationIT.java index b168033676e..744b494cf4d 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-webflux/src/test/java/org/springframework/ai/mcp/client/webflux/autoconfigure/SseWebFluxTransportAutoConfigurationIT.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-webflux/src/test/java/org/springframework/ai/mcp/client/webflux/autoconfigure/SseWebFluxTransportAutoConfigurationIT.java @@ -81,8 +81,6 @@ void streamableHttpTest() { mcpClient.ping(); - System.out.println("mcpClient = " + mcpClient.getServerInfo()); - ListToolsResult toolsResult = mcpClient.listTools(); assertThat(toolsResult).isNotNull(); diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-webflux/src/test/java/org/springframework/ai/mcp/client/webflux/autoconfigure/StreamableHttpHttpClientTransportAutoConfigurationIT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-webflux/src/test/java/org/springframework/ai/mcp/client/webflux/autoconfigure/StreamableHttpHttpClientTransportAutoConfigurationIT.java index 4251449eace..257df12a97b 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-webflux/src/test/java/org/springframework/ai/mcp/client/webflux/autoconfigure/StreamableHttpHttpClientTransportAutoConfigurationIT.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-webflux/src/test/java/org/springframework/ai/mcp/client/webflux/autoconfigure/StreamableHttpHttpClientTransportAutoConfigurationIT.java @@ -82,8 +82,6 @@ void streamableHttpTest() { mcpClient.ping(); - System.out.println("mcpClient = " + mcpClient.getServerInfo()); - ListToolsResult toolsResult = mcpClient.listTools(); assertThat(toolsResult).isNotNull(); diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/annotations/McpServerSpecificationFactoryAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/annotations/McpServerSpecificationFactoryAutoConfiguration.java index a60071255f0..67bbcf65a69 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/annotations/McpServerSpecificationFactoryAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/annotations/McpServerSpecificationFactoryAutoConfiguration.java @@ -57,7 +57,6 @@ public List resourceSpecs( List syncResourceSpecifications = SyncMcpAnnotationProviders .resourceSpecifications(beansWithMcpMethodAnnotations.getBeansByAnnotation(McpResource.class)); - System.out.println("SyncResourceSpecifications: " + syncResourceSpecifications); return syncResourceSpecifications; } @@ -79,11 +78,8 @@ public List completionSpecs( public List toolSpecs( ServerMcpAnnotatedBeans beansWithMcpMethodAnnotations) { List beansByAnnotation = beansWithMcpMethodAnnotations.getBeansByAnnotation(McpTool.class); - System.out.println("beansByAnnotation: " + beansByAnnotation + "(" + beansByAnnotation.size() + ")" + "."); List syncToolSpecifications = SyncMcpAnnotationProviders .toolSpecifications(beansByAnnotation); - System.out.println("syncToolSpecifications: " + syncToolSpecifications + "(" + syncToolSpecifications.size() - + ")" + "."); return syncToolSpecifications; } diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/annotations/StatelessServerSpecificationFactoryAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/annotations/StatelessServerSpecificationFactoryAutoConfiguration.java index a34b1f4c5b0..7a04b72186c 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/annotations/StatelessServerSpecificationFactoryAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/annotations/StatelessServerSpecificationFactoryAutoConfiguration.java @@ -78,10 +78,8 @@ public List completionSp public List toolSpecs( ServerMcpAnnotatedBeans beansWithMcpMethodAnnotations) { List beansByAnnotation = beansWithMcpMethodAnnotations.getBeansByAnnotation(McpTool.class); - System.out.println("MCPTool SyncToolSpecs: " + beansByAnnotation + "."); List syncToolSpecifications = SyncMcpAnnotationProviders .statelessToolSpecifications(beansByAnnotation); - System.out.println("SyncToolSpecs: " + syncToolSpecifications + "."); return syncToolSpecifications; } diff --git a/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AbstractAnnotatedMethodBeanPostProcessor.java b/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AbstractAnnotatedMethodBeanPostProcessor.java index 23c654b6c93..1e49687b788 100644 --- a/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AbstractAnnotatedMethodBeanPostProcessor.java +++ b/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AbstractAnnotatedMethodBeanPostProcessor.java @@ -47,8 +47,6 @@ public Object postProcessAfterInitialization(Object bean, String beanName) throw Set> foundAnnotations = scan(beanClass); // Register the bean if it has any of our target annotations if (!foundAnnotations.isEmpty()) { - System.out.println("registering " + beanName + " for " + beanClass.getName() + " with annotations " - + foundAnnotations.toString() + " in " + this.getClass().getName()); this.registry.addMcpAnnotatedBean(bean, foundAnnotations); } diff --git a/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AbstractMcpAnnotatedBeans.java b/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AbstractMcpAnnotatedBeans.java index 3a61995e228..429a716f300 100644 --- a/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AbstractMcpAnnotatedBeans.java +++ b/mcp/mcp-annotations-spring/src/main/java/org/springframework/ai/mcp/annotation/spring/scan/AbstractMcpAnnotatedBeans.java @@ -37,7 +37,6 @@ public abstract class AbstractMcpAnnotatedBeans { public void addMcpAnnotatedBean(Object bean, Set> annotations) { this.beansWithCustomAnnotations.add(bean); - System.out.println("adding " + bean + " with annotations [" + annotations + ']'); annotations .forEach(annotationType -> this.beansByAnnotation.computeIfAbsent(annotationType, k -> new ArrayList<>()) .add(bean));