diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/McpProviderUtils.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/McpProviderUtils.java index 0daaec5..a1dfade 100644 --- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/McpProviderUtils.java +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/McpProviderUtils.java @@ -21,11 +21,15 @@ import java.util.regex.Pattern; import org.reactivestreams.Publisher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; public class McpProviderUtils { + private static final Logger logger = LoggerFactory.getLogger(McpProviderUtils.class); + private static final Pattern URI_VARIABLE_PATTERN = Pattern.compile("\\{([^/]+?)\\}"); public static boolean isUriTemplate(String uri) { @@ -40,4 +44,28 @@ public static boolean isUriTemplate(String uri) { .isAssignableFrom(method.getReturnType()) && !Flux.class.isAssignableFrom(method.getReturnType()) && !Publisher.class.isAssignableFrom(method.getReturnType()); + public static Predicate filterNonReactiveReturnTypeMethod() { + return method -> { + if (isReactiveReturnType.test(method)) { + return true; + } + logger.info( + "Sync providers doesn't support reactive return types. Skipping method {} with reactive return type {}", + method, method.getReturnType()); + return false; + }; + } + + public static Predicate filterReactiveReturnTypeMethod() { + return method -> { + if (isNotReactiveReturnType.test(method)) { + return true; + } + logger.info( + "Sync providers doesn't support reactive return types. Skipping method {} with reactive return type {}", + method, method.getReturnType()); + return false; + }; + } + } diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/prompt/AsyncMcpPromptListChangedProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/prompt/AsyncMcpPromptListChangedProvider.java index c965fb5..8b6db0c 100644 --- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/prompt/AsyncMcpPromptListChangedProvider.java +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/prompt/AsyncMcpPromptListChangedProvider.java @@ -21,12 +21,12 @@ import java.util.function.Function; import java.util.stream.Stream; -import org.springaicommunity.mcp.annotation.McpPromptListChanged; -import org.springaicommunity.mcp.method.changed.prompt.AsyncPromptListChangedSpecification; -import org.springaicommunity.mcp.method.changed.prompt.AsyncMcpPromptListChangedMethodCallback; - import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.util.Assert; +import org.springaicommunity.mcp.annotation.McpPromptListChanged; +import org.springaicommunity.mcp.method.changed.prompt.AsyncMcpPromptListChangedMethodCallback; +import org.springaicommunity.mcp.method.changed.prompt.AsyncPromptListChangedSpecification; +import org.springaicommunity.mcp.provider.McpProviderUtils; import reactor.core.publisher.Mono; /** @@ -82,8 +82,7 @@ public List getPromptListChangedSpecificati .stream() .map(consumerObject -> Stream.of(doGetClassMethods(consumerObject)) .filter(method -> method.isAnnotationPresent(McpPromptListChanged.class)) - .filter(method -> method.getReturnType() == void.class - || Mono.class.isAssignableFrom(method.getReturnType())) + .filter(McpProviderUtils.filterNonReactiveReturnTypeMethod()) .sorted((m1, m2) -> m1.getName().compareTo(m2.getName())) .map(mcpPromptListChangedConsumerMethod -> { var promptListChangedAnnotation = mcpPromptListChangedConsumerMethod diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/prompt/SyncMcpPromptListChangedProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/prompt/SyncMcpPromptListChangedProvider.java index b4cfce4..9d7d079 100644 --- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/prompt/SyncMcpPromptListChangedProvider.java +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/prompt/SyncMcpPromptListChangedProvider.java @@ -21,13 +21,12 @@ import java.util.function.Consumer; import java.util.stream.Stream; -import org.springaicommunity.mcp.annotation.McpPromptListChanged; -import org.springaicommunity.mcp.method.changed.prompt.SyncPromptListChangedSpecification; -import org.springaicommunity.mcp.method.changed.prompt.SyncMcpPromptListChangedMethodCallback; - import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.util.Assert; -import reactor.core.publisher.Mono; +import org.springaicommunity.mcp.annotation.McpPromptListChanged; +import org.springaicommunity.mcp.method.changed.prompt.SyncMcpPromptListChangedMethodCallback; +import org.springaicommunity.mcp.method.changed.prompt.SyncPromptListChangedSpecification; +import org.springaicommunity.mcp.provider.McpProviderUtils; /** * Provider for synchronous prompt list changed consumer callbacks. @@ -81,7 +80,7 @@ public List getPromptListChangedSpecificatio .stream() .map(consumerObject -> Stream.of(doGetClassMethods(consumerObject)) .filter(method -> method.isAnnotationPresent(McpPromptListChanged.class)) - .filter(method -> !Mono.class.isAssignableFrom(method.getReturnType())) + .filter(McpProviderUtils.filterReactiveReturnTypeMethod()) .sorted((m1, m2) -> m1.getName().compareTo(m2.getName())) .map(mcpPromptListChangedConsumerMethod -> { var promptListChangedAnnotation = mcpPromptListChangedConsumerMethod diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/resource/AsyncMcpResourceListChangedProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/resource/AsyncMcpResourceListChangedProvider.java index 9796a8c..c647058 100644 --- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/resource/AsyncMcpResourceListChangedProvider.java +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/resource/AsyncMcpResourceListChangedProvider.java @@ -21,12 +21,12 @@ import java.util.function.Function; import java.util.stream.Stream; -import org.springaicommunity.mcp.annotation.McpResourceListChanged; -import org.springaicommunity.mcp.method.changed.resource.AsyncResourceListChangedSpecification; -import org.springaicommunity.mcp.method.changed.resource.AsyncMcpResourceListChangedMethodCallback; - import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.util.Assert; +import org.springaicommunity.mcp.annotation.McpResourceListChanged; +import org.springaicommunity.mcp.method.changed.resource.AsyncMcpResourceListChangedMethodCallback; +import org.springaicommunity.mcp.method.changed.resource.AsyncResourceListChangedSpecification; +import org.springaicommunity.mcp.provider.McpProviderUtils; import reactor.core.publisher.Mono; /** @@ -82,8 +82,7 @@ public List getResourceListChangedSpecifi .stream() .map(consumerObject -> Stream.of(doGetClassMethods(consumerObject)) .filter(method -> method.isAnnotationPresent(McpResourceListChanged.class)) - .filter(method -> method.getReturnType() == void.class - || Mono.class.isAssignableFrom(method.getReturnType())) + .filter(McpProviderUtils.filterNonReactiveReturnTypeMethod()) .sorted((m1, m2) -> m1.getName().compareTo(m2.getName())) .map(mcpResourceListChangedConsumerMethod -> { var resourceListChangedAnnotation = mcpResourceListChangedConsumerMethod diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/resource/SyncMcpResourceListChangedProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/resource/SyncMcpResourceListChangedProvider.java index cf0ca76..48119f1 100644 --- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/resource/SyncMcpResourceListChangedProvider.java +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/resource/SyncMcpResourceListChangedProvider.java @@ -21,13 +21,12 @@ import java.util.function.Consumer; import java.util.stream.Stream; -import org.springaicommunity.mcp.annotation.McpResourceListChanged; -import org.springaicommunity.mcp.method.changed.resource.SyncResourceListChangedSpecification; -import org.springaicommunity.mcp.method.changed.resource.SyncMcpResourceListChangedMethodCallback; - import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.util.Assert; -import reactor.core.publisher.Mono; +import org.springaicommunity.mcp.annotation.McpResourceListChanged; +import org.springaicommunity.mcp.method.changed.resource.SyncMcpResourceListChangedMethodCallback; +import org.springaicommunity.mcp.method.changed.resource.SyncResourceListChangedSpecification; +import org.springaicommunity.mcp.provider.McpProviderUtils; /** * Provider for synchronous resource list changed consumer callbacks. @@ -81,7 +80,7 @@ public List getResourceListChangedSpecific .stream() .map(consumerObject -> Stream.of(doGetClassMethods(consumerObject)) .filter(method -> method.isAnnotationPresent(McpResourceListChanged.class)) - .filter(method -> !Mono.class.isAssignableFrom(method.getReturnType())) + .filter(McpProviderUtils.filterReactiveReturnTypeMethod()) .sorted((m1, m2) -> m1.getName().compareTo(m2.getName())) .map(mcpResourceListChangedConsumerMethod -> { var resourceListChangedAnnotation = mcpResourceListChangedConsumerMethod diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/tool/AsyncMcpToolListChangedProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/tool/AsyncMcpToolListChangedProvider.java index 0b6fe5a..bcccce6 100644 --- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/tool/AsyncMcpToolListChangedProvider.java +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/tool/AsyncMcpToolListChangedProvider.java @@ -21,12 +21,12 @@ import java.util.function.Function; import java.util.stream.Stream; -import org.springaicommunity.mcp.annotation.McpToolListChanged; -import org.springaicommunity.mcp.method.changed.tool.AsyncToolListChangedSpecification; -import org.springaicommunity.mcp.method.changed.tool.AsyncMcpToolListChangedMethodCallback; - import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.util.Assert; +import org.springaicommunity.mcp.annotation.McpToolListChanged; +import org.springaicommunity.mcp.method.changed.tool.AsyncMcpToolListChangedMethodCallback; +import org.springaicommunity.mcp.method.changed.tool.AsyncToolListChangedSpecification; +import org.springaicommunity.mcp.provider.McpProviderUtils; import reactor.core.publisher.Mono; /** @@ -81,8 +81,7 @@ public List getToolListChangedSpecifications( List toolListChangedConsumers = this.toolListChangedConsumerObjects.stream() .map(consumerObject -> Stream.of(doGetClassMethods(consumerObject)) .filter(method -> method.isAnnotationPresent(McpToolListChanged.class)) - .filter(method -> method.getReturnType() == void.class - || Mono.class.isAssignableFrom(method.getReturnType())) + .filter(McpProviderUtils.filterNonReactiveReturnTypeMethod()) .sorted((m1, m2) -> m1.getName().compareTo(m2.getName())) .map(mcpToolListChangedConsumerMethod -> { var toolListChangedAnnotation = mcpToolListChangedConsumerMethod diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/tool/SyncMcpToolListChangedProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/tool/SyncMcpToolListChangedProvider.java index 1a9ef48..33a2933 100644 --- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/tool/SyncMcpToolListChangedProvider.java +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/tool/SyncMcpToolListChangedProvider.java @@ -21,13 +21,12 @@ import java.util.function.Consumer; import java.util.stream.Stream; -import org.springaicommunity.mcp.annotation.McpToolListChanged; -import org.springaicommunity.mcp.method.changed.tool.SyncToolListChangedSpecification; -import org.springaicommunity.mcp.method.changed.tool.SyncMcpToolListChangedMethodCallback; - import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.util.Assert; -import reactor.core.publisher.Mono; +import org.springaicommunity.mcp.annotation.McpToolListChanged; +import org.springaicommunity.mcp.method.changed.tool.SyncMcpToolListChangedMethodCallback; +import org.springaicommunity.mcp.method.changed.tool.SyncToolListChangedSpecification; +import org.springaicommunity.mcp.provider.McpProviderUtils; /** * Provider for synchronous tool list changed consumer callbacks. @@ -80,7 +79,7 @@ public List getToolListChangedSpecifications() List toolListChangedConsumers = this.toolListChangedConsumerObjects.stream() .map(consumerObject -> Stream.of(doGetClassMethods(consumerObject)) .filter(method -> method.isAnnotationPresent(McpToolListChanged.class)) - .filter(method -> !Mono.class.isAssignableFrom(method.getReturnType())) + .filter(McpProviderUtils.filterReactiveReturnTypeMethod()) .sorted((m1, m2) -> m1.getName().compareTo(m2.getName())) .map(mcpToolListChangedConsumerMethod -> { var toolListChangedAnnotation = mcpToolListChangedConsumerMethod diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/complete/AsyncMcpCompleteProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/complete/AsyncMcpCompleteProvider.java index 74809fe..ccc8c05 100644 --- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/complete/AsyncMcpCompleteProvider.java +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/complete/AsyncMcpCompleteProvider.java @@ -23,14 +23,12 @@ import io.modelcontextprotocol.server.McpAsyncServerExchange; import io.modelcontextprotocol.server.McpServerFeatures.AsyncCompletionSpecification; import io.modelcontextprotocol.util.Assert; -import org.reactivestreams.Publisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springaicommunity.mcp.adapter.CompleteAdapter; import org.springaicommunity.mcp.annotation.McpComplete; import org.springaicommunity.mcp.method.complete.AsyncMcpCompleteMethodCallback; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; +import org.springaicommunity.mcp.provider.McpProviderUtils; /** * Provider for asynchronous MCP complete methods. @@ -66,9 +64,7 @@ public List getCompleteSpecifications() { List asyncCompleteSpecification = this.completeObjects.stream() .map(completeObject -> Stream.of(doGetClassMethods(completeObject)) .filter(method -> method.isAnnotationPresent(McpComplete.class)) - .filter(method -> Mono.class.isAssignableFrom(method.getReturnType()) - || Flux.class.isAssignableFrom(method.getReturnType()) - || Publisher.class.isAssignableFrom(method.getReturnType())) + .filter(McpProviderUtils.filterNonReactiveReturnTypeMethod()) .sorted((m1, m2) -> m1.getName().compareTo(m2.getName())) .map(mcpCompleteMethod -> { var completeAnnotation = mcpCompleteMethod.getAnnotation(McpComplete.class); diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/complete/AsyncStatelessMcpCompleteProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/complete/AsyncStatelessMcpCompleteProvider.java index 2cbe89b..d34c113 100644 --- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/complete/AsyncStatelessMcpCompleteProvider.java +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/complete/AsyncStatelessMcpCompleteProvider.java @@ -26,13 +26,12 @@ import io.modelcontextprotocol.spec.McpSchema.CompleteRequest; import io.modelcontextprotocol.spec.McpSchema.CompleteResult; import io.modelcontextprotocol.util.Assert; -import org.reactivestreams.Publisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springaicommunity.mcp.adapter.CompleteAdapter; import org.springaicommunity.mcp.annotation.McpComplete; import org.springaicommunity.mcp.method.complete.AsyncStatelessMcpCompleteMethodCallback; -import reactor.core.publisher.Flux; +import org.springaicommunity.mcp.provider.McpProviderUtils; import reactor.core.publisher.Mono; /** @@ -69,9 +68,7 @@ public List getCompleteSpecifications() { List completeSpecs = this.completeObjects.stream() .map(completeObject -> Stream.of(doGetClassMethods(completeObject)) .filter(method -> method.isAnnotationPresent(McpComplete.class)) - .filter(method -> Mono.class.isAssignableFrom(method.getReturnType()) - || Flux.class.isAssignableFrom(method.getReturnType()) - || Publisher.class.isAssignableFrom(method.getReturnType())) + .filter(McpProviderUtils.filterNonReactiveReturnTypeMethod()) .sorted((m1, m2) -> m1.getName().compareTo(m2.getName())) .map(mcpCompleteMethod -> { var completeAnnotation = mcpCompleteMethod.getAnnotation(McpComplete.class); diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/complete/SyncMcpCompleteProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/complete/SyncMcpCompleteProvider.java index d865492..b84848e 100644 --- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/complete/SyncMcpCompleteProvider.java +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/complete/SyncMcpCompleteProvider.java @@ -25,7 +25,7 @@ import org.springaicommunity.mcp.adapter.CompleteAdapter; import org.springaicommunity.mcp.annotation.McpComplete; import org.springaicommunity.mcp.method.complete.SyncMcpCompleteMethodCallback; -import reactor.core.publisher.Mono; +import org.springaicommunity.mcp.provider.McpProviderUtils; /** */ @@ -43,7 +43,7 @@ public List getCompleteSpecifications() { List syncCompleteSpecification = this.completeObjects.stream() .map(completeObject -> Stream.of(doGetClassMethods(completeObject)) .filter(method -> method.isAnnotationPresent(McpComplete.class)) - .filter(method -> !Mono.class.isAssignableFrom(method.getReturnType())) + .filter(McpProviderUtils.filterReactiveReturnTypeMethod()) .sorted((m1, m2) -> m1.getName().compareTo(m2.getName())) .map(mcpCompleteMethod -> { var completeAnnotation = mcpCompleteMethod.getAnnotation(McpComplete.class); diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/complete/SyncStatelessMcpCompleteProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/complete/SyncStatelessMcpCompleteProvider.java index 8d61a3f..cce8307 100644 --- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/complete/SyncStatelessMcpCompleteProvider.java +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/complete/SyncStatelessMcpCompleteProvider.java @@ -31,7 +31,7 @@ import org.springaicommunity.mcp.adapter.CompleteAdapter; import org.springaicommunity.mcp.annotation.McpComplete; import org.springaicommunity.mcp.method.complete.SyncStatelessMcpCompleteMethodCallback; -import reactor.core.publisher.Mono; +import org.springaicommunity.mcp.provider.McpProviderUtils; /** * Provider for synchronous stateless MCP complete methods. @@ -67,7 +67,7 @@ public List getCompleteSpecifications() { List completeSpecs = this.completeObjects.stream() .map(completeObject -> Stream.of(doGetClassMethods(completeObject)) .filter(method -> method.isAnnotationPresent(McpComplete.class)) - .filter(method -> !Mono.class.isAssignableFrom(method.getReturnType())) + .filter(McpProviderUtils.filterReactiveReturnTypeMethod()) .sorted((m1, m2) -> m1.getName().compareTo(m2.getName())) .map(mcpCompleteMethod -> { var completeAnnotation = mcpCompleteMethod.getAnnotation(McpComplete.class); diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/elicitation/AsyncMcpElicitationProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/elicitation/AsyncMcpElicitationProvider.java index 47fd8e7..0f58af7 100644 --- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/elicitation/AsyncMcpElicitationProvider.java +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/elicitation/AsyncMcpElicitationProvider.java @@ -26,7 +26,7 @@ import org.springaicommunity.mcp.annotation.McpElicitation; import org.springaicommunity.mcp.method.elicitation.AsyncElicitationSpecification; import org.springaicommunity.mcp.method.elicitation.AsyncMcpElicitationMethodCallback; - +import org.springaicommunity.mcp.provider.McpProviderUtils; import io.modelcontextprotocol.spec.McpSchema.ElicitRequest; import io.modelcontextprotocol.spec.McpSchema.ElicitResult; import io.modelcontextprotocol.util.Assert; @@ -89,8 +89,7 @@ public List getElicitationSpecifications() { .filter(method -> method.isAnnotationPresent(McpElicitation.class)) .filter(method -> method.getParameterCount() == 1 && ElicitRequest.class.isAssignableFrom(method.getParameterTypes()[0])) - .filter(method -> Mono.class.isAssignableFrom(method.getReturnType()) - || ElicitResult.class.isAssignableFrom(method.getReturnType())) + .filter(McpProviderUtils.filterNonReactiveReturnTypeMethod()) .sorted((m1, m2) -> m1.getName().compareTo(m2.getName())) .map(mcpElicitationMethod -> { var elicitationAnnotation = mcpElicitationMethod.getAnnotation(McpElicitation.class); diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/elicitation/SyncMcpElicitationProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/elicitation/SyncMcpElicitationProvider.java index 78cfece..9a11a4d 100644 --- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/elicitation/SyncMcpElicitationProvider.java +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/elicitation/SyncMcpElicitationProvider.java @@ -21,16 +21,15 @@ import java.util.function.Function; import java.util.stream.Stream; +import io.modelcontextprotocol.spec.McpSchema.ElicitRequest; +import io.modelcontextprotocol.spec.McpSchema.ElicitResult; +import io.modelcontextprotocol.util.Assert; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springaicommunity.mcp.annotation.McpElicitation; import org.springaicommunity.mcp.method.elicitation.SyncElicitationSpecification; import org.springaicommunity.mcp.method.elicitation.SyncMcpElicitationMethodCallback; - -import io.modelcontextprotocol.spec.McpSchema.ElicitRequest; -import io.modelcontextprotocol.spec.McpSchema.ElicitResult; -import io.modelcontextprotocol.util.Assert; -import reactor.core.publisher.Mono; +import org.springaicommunity.mcp.provider.McpProviderUtils; /** * Provider for synchronous elicitation callbacks. @@ -87,7 +86,7 @@ public List getElicitationSpecifications() { List elicitationHandlers = this.elicitationObjects.stream() .map(elicitationObject -> Stream.of(doGetClassMethods(elicitationObject)) .filter(method -> method.isAnnotationPresent(McpElicitation.class)) - .filter(method -> !Mono.class.isAssignableFrom(method.getReturnType())) + .filter(McpProviderUtils.filterReactiveReturnTypeMethod()) .filter(method -> ElicitResult.class.isAssignableFrom(method.getReturnType())) .filter(method -> method.getParameterCount() == 1 && ElicitRequest.class.isAssignableFrom(method.getParameterTypes()[0])) diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/logging/AsyncMcpLoggingProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/logging/AsyncMcpLoggingProvider.java index 7324349..c8aff18 100644 --- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/logging/AsyncMcpLoggingProvider.java +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/logging/AsyncMcpLoggingProvider.java @@ -24,7 +24,7 @@ import org.springaicommunity.mcp.annotation.McpLogging; import org.springaicommunity.mcp.method.logging.AsyncLoggingSpecification; import org.springaicommunity.mcp.method.logging.AsyncMcpLoggingMethodCallback; - +import org.springaicommunity.mcp.provider.McpProviderUtils; import io.modelcontextprotocol.spec.McpSchema.LoggingMessageNotification; import io.modelcontextprotocol.util.Assert; import reactor.core.publisher.Mono; @@ -80,6 +80,7 @@ public List getLoggingSpecifications() { List loggingConsumers = this.loggingConsumerObjects.stream() .map(consumerObject -> Stream.of(this.doGetClassMethods(consumerObject)) .filter(method -> method.isAnnotationPresent(McpLogging.class)) + .filter(McpProviderUtils.filterNonReactiveReturnTypeMethod()) .sorted((m1, m2) -> m1.getName().compareTo(m2.getName())) .map(mcpLoggingConsumerMethod -> { var loggingConsumerAnnotation = mcpLoggingConsumerMethod.getAnnotation(McpLogging.class); diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/logging/SyncMcpLogginProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/logging/SyncMcpLogginProvider.java index 471cd83..3fa9698 100644 --- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/logging/SyncMcpLogginProvider.java +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/logging/SyncMcpLogginProvider.java @@ -21,13 +21,12 @@ import java.util.function.Consumer; import java.util.stream.Stream; +import io.modelcontextprotocol.spec.McpSchema.LoggingMessageNotification; +import io.modelcontextprotocol.util.Assert; import org.springaicommunity.mcp.annotation.McpLogging; import org.springaicommunity.mcp.method.logging.SyncLoggingSpecification; import org.springaicommunity.mcp.method.logging.SyncMcpLoggingMethodCallback; - -import io.modelcontextprotocol.spec.McpSchema.LoggingMessageNotification; -import io.modelcontextprotocol.util.Assert; -import reactor.core.publisher.Mono; +import org.springaicommunity.mcp.provider.McpProviderUtils; /** * Provider for synchronous logging consumer callbacks. @@ -82,7 +81,7 @@ public List getLoggingSpecifications() { List loggingConsumers = this.loggingConsumerObjects.stream() .map(consumerObject -> Stream.of(doGetClassMethods(consumerObject)) .filter(method -> method.isAnnotationPresent(McpLogging.class)) - .filter(method -> !Mono.class.isAssignableFrom(method.getReturnType())) + .filter(McpProviderUtils.filterReactiveReturnTypeMethod()) .sorted((m1, m2) -> m1.getName().compareTo(m2.getName())) .map(mcpLoggingConsumerMethod -> { var loggingConsumerAnnotation = mcpLoggingConsumerMethod.getAnnotation(McpLogging.class); diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/logging/SyncMcpLoggingProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/logging/SyncMcpLoggingProvider.java index cfc77bb..a2cdb52 100644 --- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/logging/SyncMcpLoggingProvider.java +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/logging/SyncMcpLoggingProvider.java @@ -21,13 +21,12 @@ import java.util.function.Consumer; import java.util.stream.Stream; +import io.modelcontextprotocol.spec.McpSchema.LoggingMessageNotification; +import io.modelcontextprotocol.util.Assert; import org.springaicommunity.mcp.annotation.McpLogging; import org.springaicommunity.mcp.method.logging.SyncLoggingSpecification; import org.springaicommunity.mcp.method.logging.SyncMcpLoggingMethodCallback; - -import io.modelcontextprotocol.spec.McpSchema.LoggingMessageNotification; -import io.modelcontextprotocol.util.Assert; -import reactor.core.publisher.Mono; +import org.springaicommunity.mcp.provider.McpProviderUtils; /** * Provider for synchronous logging consumer callbacks. @@ -80,7 +79,7 @@ public List getLoggingSpecifications() { List loggingConsumers = this.loggingConsumerObjects.stream() .map(consumerObject -> Stream.of(doGetClassMethods(consumerObject)) .filter(method -> method.isAnnotationPresent(McpLogging.class)) - .filter(method -> !Mono.class.isAssignableFrom(method.getReturnType())) + .filter(McpProviderUtils.filterReactiveReturnTypeMethod()) .sorted((m1, m2) -> m1.getName().compareTo(m2.getName())) .map(mcpLoggingConsumerMethod -> { var loggingConsumerAnnotation = mcpLoggingConsumerMethod.getAnnotation(McpLogging.class); diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/progress/AsyncMcpProgressProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/progress/AsyncMcpProgressProvider.java index 3805d1a..8c55abc 100644 --- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/progress/AsyncMcpProgressProvider.java +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/progress/AsyncMcpProgressProvider.java @@ -25,6 +25,7 @@ import org.springaicommunity.mcp.annotation.McpProgress; import org.springaicommunity.mcp.method.progress.AsyncProgressSpecification; +import org.springaicommunity.mcp.provider.McpProviderUtils; import org.springaicommunity.mcp.method.progress.AsyncMcpProgressMethodCallback; import io.modelcontextprotocol.spec.McpSchema.ProgressNotification; @@ -80,12 +81,8 @@ public List getProgressSpecifications() { List progressHandlers = this.progressObjects.stream() .map(progressObject -> Stream.of(doGetClassMethods(progressObject)) .filter(method -> method.isAnnotationPresent(McpProgress.class)) + .filter(McpProviderUtils.filterNonReactiveReturnTypeMethod()) .filter(method -> { - // For async callbacks, only Mono is valid - Class returnType = method.getReturnType(); - if (!Mono.class.isAssignableFrom(returnType)) { - return false; - } // Check if it's specifically Mono Type genericReturnType = method.getGenericReturnType(); if (genericReturnType instanceof ParameterizedType) { diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/progress/SyncMcpProgressProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/progress/SyncMcpProgressProvider.java index 8285970..4e220e6 100644 --- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/progress/SyncMcpProgressProvider.java +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/progress/SyncMcpProgressProvider.java @@ -21,12 +21,11 @@ import java.util.function.Consumer; import java.util.stream.Stream; +import io.modelcontextprotocol.spec.McpSchema.ProgressNotification; import org.springaicommunity.mcp.annotation.McpProgress; -import org.springaicommunity.mcp.method.progress.SyncProgressSpecification; import org.springaicommunity.mcp.method.progress.SyncMcpProgressMethodCallback; - -import io.modelcontextprotocol.spec.McpSchema.ProgressNotification; -import reactor.core.publisher.Mono; +import org.springaicommunity.mcp.method.progress.SyncProgressSpecification; +import org.springaicommunity.mcp.provider.McpProviderUtils; /** * Provider for synchronous progress callbacks. @@ -78,7 +77,7 @@ public List getProgressSpecifications() { List progressConsumers = this.progressObjects.stream() .map(progressObject -> Stream.of(doGetClassMethods(progressObject)) .filter(method -> method.isAnnotationPresent(McpProgress.class)) - .filter(method -> !Mono.class.isAssignableFrom(method.getReturnType())) + .filter(McpProviderUtils.filterReactiveReturnTypeMethod()) .filter(method -> method.getReturnType() == void.class) // Only void // return type is // valid for sync diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/prompt/AsyncMcpPromptProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/prompt/AsyncMcpPromptProvider.java index 6bb5d5b..00f7239 100644 --- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/prompt/AsyncMcpPromptProvider.java +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/prompt/AsyncMcpPromptProvider.java @@ -26,13 +26,12 @@ import io.modelcontextprotocol.spec.McpSchema.GetPromptRequest; import io.modelcontextprotocol.spec.McpSchema.GetPromptResult; import io.modelcontextprotocol.util.Assert; -import org.reactivestreams.Publisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springaicommunity.mcp.adapter.PromptAdapter; import org.springaicommunity.mcp.annotation.McpPrompt; import org.springaicommunity.mcp.method.prompt.AsyncMcpPromptMethodCallback; -import reactor.core.publisher.Flux; +import org.springaicommunity.mcp.provider.McpProviderUtils; import reactor.core.publisher.Mono; /** @@ -69,9 +68,7 @@ public List getPromptSpecifications() { List promptSpecs = this.promptObjects.stream() .map(promptObject -> Stream.of(doGetClassMethods(promptObject)) .filter(method -> method.isAnnotationPresent(McpPrompt.class)) - .filter(method -> Mono.class.isAssignableFrom(method.getReturnType()) - || Flux.class.isAssignableFrom(method.getReturnType()) - || Publisher.class.isAssignableFrom(method.getReturnType())) + .filter(McpProviderUtils.filterNonReactiveReturnTypeMethod()) .sorted((m1, m2) -> m1.getName().compareTo(m2.getName())) .map(mcpPromptMethod -> { var promptAnnotation = mcpPromptMethod.getAnnotation(McpPrompt.class); diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/prompt/AsyncStatelessMcpPromptProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/prompt/AsyncStatelessMcpPromptProvider.java index 4a9bb11..458e55b 100644 --- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/prompt/AsyncStatelessMcpPromptProvider.java +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/prompt/AsyncStatelessMcpPromptProvider.java @@ -21,18 +21,17 @@ import java.util.function.BiFunction; import java.util.stream.Stream; -import io.modelcontextprotocol.server.McpStatelessServerFeatures.AsyncPromptSpecification; import io.modelcontextprotocol.common.McpTransportContext; +import io.modelcontextprotocol.server.McpStatelessServerFeatures.AsyncPromptSpecification; import io.modelcontextprotocol.spec.McpSchema.GetPromptRequest; import io.modelcontextprotocol.spec.McpSchema.GetPromptResult; import io.modelcontextprotocol.util.Assert; -import org.reactivestreams.Publisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springaicommunity.mcp.adapter.PromptAdapter; import org.springaicommunity.mcp.annotation.McpPrompt; import org.springaicommunity.mcp.method.prompt.AsyncStatelessMcpPromptMethodCallback; -import reactor.core.publisher.Flux; +import org.springaicommunity.mcp.provider.McpProviderUtils; import reactor.core.publisher.Mono; /** @@ -69,9 +68,7 @@ public List getPromptSpecifications() { List promptSpecs = this.promptObjects.stream() .map(promptObject -> Stream.of(doGetClassMethods(promptObject)) .filter(method -> method.isAnnotationPresent(McpPrompt.class)) - .filter(method -> Mono.class.isAssignableFrom(method.getReturnType()) - || Flux.class.isAssignableFrom(method.getReturnType()) - || Publisher.class.isAssignableFrom(method.getReturnType())) + .filter(McpProviderUtils.filterNonReactiveReturnTypeMethod()) .sorted((m1, m2) -> m1.getName().compareTo(m2.getName())) .map(mcpPromptMethod -> { var promptAnnotation = mcpPromptMethod.getAnnotation(McpPrompt.class); diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/prompt/SyncMcpPromptProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/prompt/SyncMcpPromptProvider.java index f2bb7b7..edf2048 100644 --- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/prompt/SyncMcpPromptProvider.java +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/prompt/SyncMcpPromptProvider.java @@ -25,6 +25,7 @@ import org.springaicommunity.mcp.adapter.PromptAdapter; import org.springaicommunity.mcp.annotation.McpPrompt; import org.springaicommunity.mcp.method.prompt.SyncMcpPromptMethodCallback; +import org.springaicommunity.mcp.provider.McpProviderUtils; import reactor.core.publisher.Mono; /** @@ -43,7 +44,7 @@ public List getPromptSpecifications() { List syncPromptSpecification = this.promptObjects.stream() .map(resourceObject -> Stream.of(doGetClassMethods(resourceObject)) .filter(method -> method.isAnnotationPresent(McpPrompt.class)) - .filter(method -> !Mono.class.isAssignableFrom(method.getReturnType())) + .filter(McpProviderUtils.filterReactiveReturnTypeMethod()) .sorted((m1, m2) -> m1.getName().compareTo(m2.getName())) .map(mcpPromptMethod -> { var promptAnnotation = mcpPromptMethod.getAnnotation(McpPrompt.class); diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/prompt/SyncStatelessMcpPromptProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/prompt/SyncStatelessMcpPromptProvider.java index 6a44163..1a7f1bc 100644 --- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/prompt/SyncStatelessMcpPromptProvider.java +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/prompt/SyncStatelessMcpPromptProvider.java @@ -21,8 +21,8 @@ import java.util.function.BiFunction; import java.util.stream.Stream; -import io.modelcontextprotocol.server.McpStatelessServerFeatures.SyncPromptSpecification; import io.modelcontextprotocol.common.McpTransportContext; +import io.modelcontextprotocol.server.McpStatelessServerFeatures.SyncPromptSpecification; import io.modelcontextprotocol.spec.McpSchema.GetPromptRequest; import io.modelcontextprotocol.spec.McpSchema.GetPromptResult; import io.modelcontextprotocol.util.Assert; @@ -31,7 +31,7 @@ import org.springaicommunity.mcp.adapter.PromptAdapter; import org.springaicommunity.mcp.annotation.McpPrompt; import org.springaicommunity.mcp.method.prompt.SyncStatelessMcpPromptMethodCallback; -import reactor.core.publisher.Mono; +import org.springaicommunity.mcp.provider.McpProviderUtils; /** * Provider for synchronous stateless MCP prompt methods. @@ -67,7 +67,7 @@ public List getPromptSpecifications() { List promptSpecs = this.promptObjects.stream() .map(promptObject -> Stream.of(doGetClassMethods(promptObject)) .filter(method -> method.isAnnotationPresent(McpPrompt.class)) - .filter(method -> !Mono.class.isAssignableFrom(method.getReturnType())) + .filter(McpProviderUtils.filterReactiveReturnTypeMethod()) .sorted((m1, m2) -> m1.getName().compareTo(m2.getName())) .map(mcpPromptMethod -> { var promptAnnotation = mcpPromptMethod.getAnnotation(McpPrompt.class); diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/resource/AsyncMcpResourceProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/resource/AsyncMcpResourceProvider.java index e7e515b..da4e9c9 100644 --- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/resource/AsyncMcpResourceProvider.java +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/resource/AsyncMcpResourceProvider.java @@ -29,13 +29,11 @@ import io.modelcontextprotocol.spec.McpSchema.ReadResourceRequest; import io.modelcontextprotocol.spec.McpSchema.ReadResourceResult; import io.modelcontextprotocol.util.Assert; -import org.reactivestreams.Publisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springaicommunity.mcp.annotation.McpResource; import org.springaicommunity.mcp.method.resource.AsyncMcpResourceMethodCallback; import org.springaicommunity.mcp.provider.McpProviderUtils; -import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; /** @@ -72,9 +70,7 @@ public List getResourceSpecifications() { List resourceSpecs = this.resourceObjects.stream() .map(resourceObject -> Stream.of(doGetClassMethods(resourceObject)) .filter(method -> method.isAnnotationPresent(McpResource.class)) - .filter(method -> Mono.class.isAssignableFrom(method.getReturnType()) - || Flux.class.isAssignableFrom(method.getReturnType()) - || Publisher.class.isAssignableFrom(method.getReturnType())) + .filter(McpProviderUtils.filterNonReactiveReturnTypeMethod()) .sorted((m1, m2) -> m1.getName().compareTo(m2.getName())) .map(mcpResourceMethod -> { @@ -125,9 +121,7 @@ public List getResourceTemplateSpecification List resourceSpecs = this.resourceObjects.stream() .map(resourceObject -> Stream.of(doGetClassMethods(resourceObject)) .filter(method -> method.isAnnotationPresent(McpResource.class)) - .filter(method -> Mono.class.isAssignableFrom(method.getReturnType()) - || Flux.class.isAssignableFrom(method.getReturnType()) - || Publisher.class.isAssignableFrom(method.getReturnType())) + .filter(McpProviderUtils.filterNonReactiveReturnTypeMethod()) .sorted((m1, m2) -> m1.getName().compareTo(m2.getName())) .map(mcpResourceMethod -> { diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/resource/AsyncStatelessMcpResourceProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/resource/AsyncStatelessMcpResourceProvider.java index 59bafe8..56495b3 100644 --- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/resource/AsyncStatelessMcpResourceProvider.java +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/resource/AsyncStatelessMcpResourceProvider.java @@ -22,20 +22,18 @@ import java.util.function.BiFunction; import java.util.stream.Stream; -import org.reactivestreams.Publisher; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springaicommunity.mcp.annotation.McpResource; -import org.springaicommunity.mcp.method.resource.AsyncStatelessMcpResourceMethodCallback; -import org.springaicommunity.mcp.provider.McpProviderUtils; +import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.server.McpStatelessServerFeatures.AsyncResourceSpecification; import io.modelcontextprotocol.server.McpStatelessServerFeatures.AsyncResourceTemplateSpecification; -import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.McpSchema.ReadResourceRequest; import io.modelcontextprotocol.spec.McpSchema.ReadResourceResult; import io.modelcontextprotocol.util.Assert; -import reactor.core.publisher.Flux; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springaicommunity.mcp.annotation.McpResource; +import org.springaicommunity.mcp.method.resource.AsyncStatelessMcpResourceMethodCallback; +import org.springaicommunity.mcp.provider.McpProviderUtils; import reactor.core.publisher.Mono; /** @@ -72,9 +70,7 @@ public List getResourceSpecifications() { List resourceSpecs = this.resourceObjects.stream() .map(resourceObject -> Stream.of(doGetClassMethods(resourceObject)) .filter(method -> method.isAnnotationPresent(McpResource.class)) - .filter(method -> Mono.class.isAssignableFrom(method.getReturnType()) - || Flux.class.isAssignableFrom(method.getReturnType()) - || Publisher.class.isAssignableFrom(method.getReturnType())) + .filter(McpProviderUtils.filterNonReactiveReturnTypeMethod()) .sorted((m1, m2) -> m1.getName().compareTo(m2.getName())) .map(mcpResourceMethod -> { @@ -125,9 +121,7 @@ public List getResourceTemplateSpecification List resourceSpecs = this.resourceObjects.stream() .map(resourceObject -> Stream.of(doGetClassMethods(resourceObject)) .filter(method -> method.isAnnotationPresent(McpResource.class)) - .filter(method -> Mono.class.isAssignableFrom(method.getReturnType()) - || Flux.class.isAssignableFrom(method.getReturnType()) - || Publisher.class.isAssignableFrom(method.getReturnType())) + .filter(McpProviderUtils.filterNonReactiveReturnTypeMethod()) .sorted((m1, m2) -> m1.getName().compareTo(m2.getName())) .map(mcpResourceMethod -> { diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/resource/SyncMcpResourceProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/resource/SyncMcpResourceProvider.java index 0c5d972..a56d63c 100644 --- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/resource/SyncMcpResourceProvider.java +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/resource/SyncMcpResourceProvider.java @@ -21,14 +21,13 @@ import java.util.Objects; import java.util.stream.Stream; -import org.springaicommunity.mcp.annotation.McpResource; -import org.springaicommunity.mcp.method.resource.SyncMcpResourceMethodCallback; -import org.springaicommunity.mcp.provider.McpProviderUtils; import io.modelcontextprotocol.server.McpServerFeatures.SyncResourceSpecification; import io.modelcontextprotocol.server.McpServerFeatures.SyncResourceTemplateSpecification; import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.util.Assert; -import reactor.core.publisher.Mono; +import org.springaicommunity.mcp.annotation.McpResource; +import org.springaicommunity.mcp.method.resource.SyncMcpResourceMethodCallback; +import org.springaicommunity.mcp.provider.McpProviderUtils; /** */ @@ -46,7 +45,7 @@ public List getResourceSpecifications() { List methodCallbacks = this.resourceObjects.stream() .map(resourceObject -> Stream.of(this.doGetClassMethods(resourceObject)) .filter(resourceMethod -> resourceMethod.isAnnotationPresent(McpResource.class)) - .filter(method -> !Mono.class.isAssignableFrom(method.getReturnType())) + .filter(McpProviderUtils.filterReactiveReturnTypeMethod()) .sorted((m1, m2) -> m1.getName().compareTo(m2.getName())) .map(mcpResourceMethod -> { var resourceAnnotation = mcpResourceMethod.getAnnotation(McpResource.class); @@ -89,7 +88,7 @@ public List getResourceTemplateSpecifications List methodCallbacks = this.resourceObjects.stream() .map(resourceObject -> Stream.of(this.doGetClassMethods(resourceObject)) .filter(resourceMethod -> resourceMethod.isAnnotationPresent(McpResource.class)) - .filter(method -> !Mono.class.isAssignableFrom(method.getReturnType())) + .filter(McpProviderUtils.filterReactiveReturnTypeMethod()) .sorted((m1, m2) -> m1.getName().compareTo(m2.getName())) .map(mcpResourceMethod -> { var resourceAnnotation = mcpResourceMethod.getAnnotation(McpResource.class); diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/resource/SyncStatelessMcpResourceProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/resource/SyncStatelessMcpResourceProvider.java index cfd3cb9..a464422 100644 --- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/resource/SyncStatelessMcpResourceProvider.java +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/resource/SyncStatelessMcpResourceProvider.java @@ -22,19 +22,18 @@ import java.util.function.BiFunction; import java.util.stream.Stream; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springaicommunity.mcp.annotation.McpResource; -import org.springaicommunity.mcp.method.resource.SyncStatelessMcpResourceMethodCallback; -import org.springaicommunity.mcp.provider.McpProviderUtils; -import io.modelcontextprotocol.server.McpStatelessServerFeatures.SyncResourceTemplateSpecification; -import io.modelcontextprotocol.server.McpStatelessServerFeatures.SyncResourceSpecification; import io.modelcontextprotocol.common.McpTransportContext; +import io.modelcontextprotocol.server.McpStatelessServerFeatures.SyncResourceSpecification; +import io.modelcontextprotocol.server.McpStatelessServerFeatures.SyncResourceTemplateSpecification; import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.McpSchema.ReadResourceRequest; import io.modelcontextprotocol.spec.McpSchema.ReadResourceResult; import io.modelcontextprotocol.util.Assert; -import reactor.core.publisher.Mono; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springaicommunity.mcp.annotation.McpResource; +import org.springaicommunity.mcp.method.resource.SyncStatelessMcpResourceMethodCallback; +import org.springaicommunity.mcp.provider.McpProviderUtils; /** * Provider for synchronous stateless MCP resource methods. @@ -70,7 +69,7 @@ public List getResourceSpecifications() { List resourceSpecs = this.resourceObjects.stream() .map(resourceObject -> Stream.of(doGetClassMethods(resourceObject)) .filter(method -> method.isAnnotationPresent(McpResource.class)) - .filter(method -> !Mono.class.isAssignableFrom(method.getReturnType())) + .filter(McpProviderUtils.filterReactiveReturnTypeMethod()) .sorted((m1, m2) -> m1.getName().compareTo(m2.getName())) .map(mcpResourceMethod -> { @@ -121,7 +120,7 @@ public List getResourceTemplateSpecifications List resourceSpecs = this.resourceObjects.stream() .map(resourceObject -> Stream.of(doGetClassMethods(resourceObject)) .filter(method -> method.isAnnotationPresent(McpResource.class)) - .filter(method -> !Mono.class.isAssignableFrom(method.getReturnType())) + .filter(McpProviderUtils.filterReactiveReturnTypeMethod()) .sorted((m1, m2) -> m1.getName().compareTo(m2.getName())) .map(mcpResourceMethod -> { diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/sampling/AsyncMcpSamplingProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/sampling/AsyncMcpSamplingProvider.java index 11fcf9b..5611cec 100644 --- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/sampling/AsyncMcpSamplingProvider.java +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/sampling/AsyncMcpSamplingProvider.java @@ -21,15 +21,15 @@ import java.util.function.Function; import java.util.stream.Stream; +import io.modelcontextprotocol.spec.McpSchema.CreateMessageRequest; +import io.modelcontextprotocol.spec.McpSchema.CreateMessageResult; +import io.modelcontextprotocol.util.Assert; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springaicommunity.mcp.annotation.McpSampling; import org.springaicommunity.mcp.method.sampling.AsyncMcpSamplingMethodCallback; import org.springaicommunity.mcp.method.sampling.AsyncSamplingSpecification; - -import io.modelcontextprotocol.spec.McpSchema.CreateMessageRequest; -import io.modelcontextprotocol.spec.McpSchema.CreateMessageResult; -import io.modelcontextprotocol.util.Assert; +import org.springaicommunity.mcp.provider.McpProviderUtils; import reactor.core.publisher.Mono; /** @@ -89,8 +89,7 @@ public List getSamplingSpecifictions() { .filter(method -> method.isAnnotationPresent(McpSampling.class)) .filter(method -> method.getParameterCount() == 1 && CreateMessageRequest.class.isAssignableFrom(method.getParameterTypes()[0])) - .filter(method -> Mono.class.isAssignableFrom(method.getReturnType()) - || CreateMessageResult.class.isAssignableFrom(method.getReturnType())) + .filter(McpProviderUtils.filterNonReactiveReturnTypeMethod()) .sorted((m1, m2) -> m1.getName().compareTo(m2.getName())) .map(mcpSamplingMethod -> { var samplingAnnotation = mcpSamplingMethod.getAnnotation(McpSampling.class); diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/sampling/SyncMcpSamplingProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/sampling/SyncMcpSamplingProvider.java index 8b76a92..95b02f9 100644 --- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/sampling/SyncMcpSamplingProvider.java +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/sampling/SyncMcpSamplingProvider.java @@ -21,16 +21,15 @@ import java.util.function.Function; import java.util.stream.Stream; +import io.modelcontextprotocol.spec.McpSchema.CreateMessageRequest; +import io.modelcontextprotocol.spec.McpSchema.CreateMessageResult; +import io.modelcontextprotocol.util.Assert; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springaicommunity.mcp.annotation.McpSampling; import org.springaicommunity.mcp.method.sampling.SyncMcpSamplingMethodCallback; import org.springaicommunity.mcp.method.sampling.SyncSamplingSpecification; - -import io.modelcontextprotocol.spec.McpSchema.CreateMessageRequest; -import io.modelcontextprotocol.spec.McpSchema.CreateMessageResult; -import io.modelcontextprotocol.util.Assert; -import reactor.core.publisher.Mono; +import org.springaicommunity.mcp.provider.McpProviderUtils; /** * Provider for synchronous sampling callbacks. @@ -87,7 +86,7 @@ public List getSamplingSpecifications() { List samplingHandlers = this.samplingObjects.stream() .map(samplingObject -> Stream.of(doGetClassMethods(samplingObject)) .filter(method -> method.isAnnotationPresent(McpSampling.class)) - .filter(method -> !Mono.class.isAssignableFrom(method.getReturnType())) + .filter(McpProviderUtils.filterReactiveReturnTypeMethod()) .filter(method -> CreateMessageResult.class.isAssignableFrom(method.getReturnType())) .filter(method -> method.getParameterCount() == 1 && CreateMessageRequest.class.isAssignableFrom(method.getParameterTypes()[0])) diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/tool/AsyncMcpToolProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/tool/AsyncMcpToolProvider.java index 15f749e..5231c08 100644 --- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/tool/AsyncMcpToolProvider.java +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/tool/AsyncMcpToolProvider.java @@ -63,7 +63,7 @@ public List getToolSpecifications() { List toolSpecs = this.toolObjects.stream() .map(toolObject -> Stream.of(this.doGetClassMethods(toolObject)) .filter(method -> method.isAnnotationPresent(McpTool.class)) - .filter(McpProviderUtils.isReactiveReturnType) + .filter(McpProviderUtils.filterNonReactiveReturnTypeMethod()) .sorted((m1, m2) -> m1.getName().compareTo(m2.getName())) .map(mcpToolMethod -> { diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/tool/AsyncStatelessMcpToolProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/tool/AsyncStatelessMcpToolProvider.java index a1b5274..3da8cc8 100644 --- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/tool/AsyncStatelessMcpToolProvider.java +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/tool/AsyncStatelessMcpToolProvider.java @@ -21,7 +21,6 @@ import java.util.stream.Stream; import io.modelcontextprotocol.common.McpTransportContext; -import io.modelcontextprotocol.json.McpJsonMapper; import io.modelcontextprotocol.server.McpStatelessServerFeatures.AsyncToolSpecification; import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.McpSchema.CallToolRequest; @@ -68,7 +67,7 @@ public List getToolSpecifications() { List toolSpecs = this.toolObjects.stream() .map(toolObject -> Stream.of(doGetClassMethods(toolObject)) .filter(method -> method.isAnnotationPresent(McpTool.class)) - .filter(McpProviderUtils.isReactiveReturnType) + .filter(McpProviderUtils.filterNonReactiveReturnTypeMethod()) .sorted((m1, m2) -> m1.getName().compareTo(m2.getName())) .map(mcpToolMethod -> { diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/tool/SyncMcpToolProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/tool/SyncMcpToolProvider.java index ba5ff86..d08e32c 100644 --- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/tool/SyncMcpToolProvider.java +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/tool/SyncMcpToolProvider.java @@ -61,7 +61,7 @@ public List getToolSpecifications() { List toolSpecs = this.toolObjects.stream() .map(toolObject -> Stream.of(this.doGetClassMethods(toolObject)) .filter(method -> method.isAnnotationPresent(McpTool.class)) - .filter(McpProviderUtils.isNotReactiveReturnType) + .filter(McpProviderUtils.filterReactiveReturnTypeMethod()) .sorted((m1, m2) -> m1.getName().compareTo(m2.getName())) .map(mcpToolMethod -> { diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/tool/SyncStatelessMcpToolProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/tool/SyncStatelessMcpToolProvider.java index 2ce6e63..1dc1640 100644 --- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/tool/SyncStatelessMcpToolProvider.java +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/tool/SyncStatelessMcpToolProvider.java @@ -21,7 +21,6 @@ import java.util.stream.Stream; import io.modelcontextprotocol.common.McpTransportContext; -import io.modelcontextprotocol.json.McpJsonMapper; import io.modelcontextprotocol.server.McpStatelessServerFeatures.SyncToolSpecification; import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.McpSchema.CallToolRequest; @@ -65,7 +64,7 @@ public List getToolSpecifications() { List toolSpecs = this.toolObjects.stream() .map(toolObject -> Stream.of(this.doGetClassMethods(toolObject)) .filter(method -> method.isAnnotationPresent(McpTool.class)) - .filter(McpProviderUtils.isNotReactiveReturnType) + .filter(McpProviderUtils.filterReactiveReturnTypeMethod()) .sorted((m1, m2) -> m1.getName().compareTo(m2.getName())) .map(mcpToolMethod -> { diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/tool/CallToolRequestSupportTests.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/tool/CallToolRequestSupportTests.java index 6af8f0d..51ff39c 100644 --- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/tool/CallToolRequestSupportTests.java +++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/tool/CallToolRequestSupportTests.java @@ -31,7 +31,7 @@ import org.springaicommunity.mcp.annotation.McpToolParam; import org.springaicommunity.mcp.method.tool.utils.JsonSchemaGenerator; import org.springaicommunity.mcp.provider.tool.SyncMcpToolProvider; - +import reactor.core.publisher.Mono; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.JsonNode; @@ -179,6 +179,14 @@ public TestResult structuredOutputTool(CallToolRequest request) { return new TestResult(input != null ? input : "default", 42); } + /** + * Simple reactive tool for negative testing + */ + @McpTool(name = "reactive-tool", description = "Hello World Reactive Tool") + public Mono simpleReactive(CallToolRequest request) { + return Mono.just("Hello World"); + } + } public static class TestResult { diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/prompt/AsyncMcpPromptListChangedProviderTests.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/prompt/AsyncMcpPromptListChangedProviderTests.java index dbe8390..e4bbe9a 100644 --- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/prompt/AsyncMcpPromptListChangedProviderTests.java +++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/prompt/AsyncMcpPromptListChangedProviderTests.java @@ -72,9 +72,9 @@ void testGetPromptListChangedSpecifications() { .map(AsyncPromptListChangedSpecification::promptListChangeHandler) .toList(); - // Should find 3 annotated methods (2 Mono + 1 void) - assertThat(consumers).hasSize(3); - assertThat(specifications).hasSize(3); + // Should find 2 annotated methods (2 Mono) + assertThat(consumers).hasSize(2); + assertThat(specifications).hasSize(2); // Test the first consumer StepVerifier.create(consumers.get(0).apply(TEST_PROMPTS)).verifyComplete(); @@ -92,7 +92,7 @@ void testGetPromptListChangedSpecifications() { assertThat(handler.lastUpdatedPrompts).isEqualTo(TEST_PROMPTS); // Test the third consumer (void method) - StepVerifier.create(consumers.get(2).apply(TEST_PROMPTS)).verifyComplete(); + StepVerifier.create(consumers.get(1).apply(TEST_PROMPTS)).verifyComplete(); // Verify that the method was called assertThat(handler.lastUpdatedPrompts).isEqualTo(TEST_PROMPTS); @@ -106,12 +106,12 @@ void testClientIdSpecifications() { List specifications = provider.getPromptListChangedSpecifications(); // Should find 3 specifications - assertThat(specifications).hasSize(3); + assertThat(specifications).hasSize(2); // Check client IDs List clientIds = specifications.stream().map(spec -> spec.clients()).flatMap(Stream::of).toList(); - assertThat(clientIds).containsExactlyInAnyOrder("my-client-id", "test-client", "my-client-id"); + assertThat(clientIds).containsExactlyInAnyOrder("my-client-id", "test-client"); } @Test @@ -137,8 +137,8 @@ void testMultipleObjects() { .map(AsyncPromptListChangedSpecification::promptListChangeHandler) .toList(); - // Should find 6 annotated methods (3 from each handler) - assertThat(consumers).hasSize(6); + // Should find 4 annotated methods (2 from each handler) + assertThat(consumers).hasSize(4); } @Test @@ -169,7 +169,7 @@ void testNonAnnotatedMethodsIgnored() { List specifications = provider.getPromptListChangedSpecifications(); // Should only find annotated methods, not the non-annotated one - assertThat(specifications).hasSize(3); + assertThat(specifications).hasSize(2); } /** @@ -234,7 +234,7 @@ void testMixedValidAndInvalidMethods() { List specifications = provider.getPromptListChangedSpecifications(); // Should find only the 2 valid methods (Mono and void) - assertThat(specifications).hasSize(2); + assertThat(specifications).hasSize(1); // Test that the valid methods work Function, Mono> consumer = specifications.get(0).promptListChangeHandler(); diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/resource/AsyncMcpResourceListChangedProviderTests.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/resource/AsyncMcpResourceListChangedProviderTests.java index 7512674..8f32bcd 100644 --- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/resource/AsyncMcpResourceListChangedProviderTests.java +++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/resource/AsyncMcpResourceListChangedProviderTests.java @@ -4,20 +4,19 @@ package org.springaicommunity.mcp.provider.changed.resource; -import static org.assertj.core.api.Assertions.assertThat; - import java.util.List; import java.util.function.Function; import java.util.stream.Stream; +import io.modelcontextprotocol.spec.McpSchema; import org.junit.jupiter.api.Test; import org.springaicommunity.mcp.annotation.McpResourceListChanged; import org.springaicommunity.mcp.method.changed.resource.AsyncResourceListChangedSpecification; - -import io.modelcontextprotocol.spec.McpSchema; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +import static org.assertj.core.api.Assertions.assertThat; + /** * Tests for {@link AsyncMcpResourceListChangedProvider}. * @@ -82,9 +81,9 @@ void testGetResourceListChangedSpecifications() { .map(AsyncResourceListChangedSpecification::resourceListChangeHandler) .toList(); - // Should find 3 annotated methods (2 Mono + 1 void) - assertThat(consumers).hasSize(3); - assertThat(specifications).hasSize(3); + // Should find 2 annotated methods (2 Mono. Ignores the void method) + assertThat(consumers).hasSize(2); + assertThat(specifications).hasSize(2); // Test the first consumer StepVerifier.create(consumers.get(0).apply(TEST_RESOURCES)).verifyComplete(); @@ -96,13 +95,13 @@ void testGetResourceListChangedSpecifications() { assertThat(handler.lastUpdatedResources.get(1).name()).isEqualTo("test-resource-2"); // Test the second consumer - StepVerifier.create(consumers.get(1).apply(TEST_RESOURCES)).verifyComplete(); + StepVerifier.create(consumers.get(0).apply(TEST_RESOURCES)).verifyComplete(); // Verify that the method was called assertThat(handler.lastUpdatedResources).isEqualTo(TEST_RESOURCES); // Test the third consumer (void method) - StepVerifier.create(consumers.get(2).apply(TEST_RESOURCES)).verifyComplete(); + StepVerifier.create(consumers.get(1).apply(TEST_RESOURCES)).verifyComplete(); // Verify that the method was called assertThat(handler.lastUpdatedResources).isEqualTo(TEST_RESOURCES); @@ -116,12 +115,12 @@ void testClientIdSpecifications() { List specifications = provider.getResourceListChangedSpecifications(); // Should find 3 specifications - assertThat(specifications).hasSize(3); + assertThat(specifications).hasSize(2); // Check client IDs List clientIds = specifications.stream().map(spec -> spec.clients()).flatMap(Stream::of).toList(); - assertThat(clientIds).containsExactlyInAnyOrder("client1", "test-client", "client1"); + assertThat(clientIds).containsExactlyInAnyOrder("client1", "test-client"); } @Test @@ -148,8 +147,9 @@ void testMultipleObjects() { .map(AsyncResourceListChangedSpecification::resourceListChangeHandler) .toList(); - // Should find 6 annotated methods (3 from each handler) - assertThat(consumers).hasSize(6); + // Should find 4 annotated methods (2 from each handler) drops the non-reactive + // ones + assertThat(consumers).hasSize(4); } @Test @@ -179,8 +179,9 @@ void testNonAnnotatedMethodsIgnored() { List specifications = provider.getResourceListChangedSpecifications(); - // Should only find annotated methods, not the non-annotated one - assertThat(specifications).hasSize(3); + // Should only find annotated methods, not the non-annotated one and drops the + // non-reactive ones + assertThat(specifications).hasSize(2); } /** @@ -244,8 +245,8 @@ void testMixedValidAndInvalidMethods() { List specifications = provider.getResourceListChangedSpecifications(); - // Should find only the 2 valid methods (Mono and void) - assertThat(specifications).hasSize(2); + // Should find only 1 valid method (Mono and drop the non-reactive void) + assertThat(specifications).hasSize(1); // Test that the valid methods work Function, Mono> consumer = specifications.get(0).resourceListChangeHandler(); diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/tool/AsyncMcpToolListChangedProviderTests.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/tool/AsyncMcpToolListChangedProviderTests.java index 061f162..3985a35 100644 --- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/tool/AsyncMcpToolListChangedProviderTests.java +++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/tool/AsyncMcpToolListChangedProviderTests.java @@ -80,9 +80,9 @@ void testGetToolListChangedSpecifications() { .map(AsyncToolListChangedSpecification::toolListChangeHandler) .toList(); - // Should find 3 annotated methods (2 Mono + 1 void) - assertThat(consumers).hasSize(3); - assertThat(specifications).hasSize(3); + // Should find 2 annotated methods (2 Mono. Ignores the void method) + assertThat(consumers).hasSize(2); + assertThat(specifications).hasSize(2); // Test the first consumer StepVerifier.create(consumers.get(0).apply(TEST_TOOLS)).verifyComplete(); @@ -99,9 +99,6 @@ void testGetToolListChangedSpecifications() { // Verify that the method was called assertThat(handler.lastUpdatedTools).isEqualTo(TEST_TOOLS); - // Test the third consumer (void method) - StepVerifier.create(consumers.get(2).apply(TEST_TOOLS)).verifyComplete(); - // Verify that the method was called assertThat(handler.lastUpdatedTools).isEqualTo(TEST_TOOLS); } @@ -113,13 +110,13 @@ void testClientIdSpecifications() { List specifications = provider.getToolListChangedSpecifications(); - // Should find 3 specifications - assertThat(specifications).hasSize(3); + // Should find 2 specifications. Ignore the non-reactive method + assertThat(specifications).hasSize(2); // Check client IDs List clientIds = specifications.stream().map(spec -> spec.clients()).flatMap(Stream::of).toList(); - assertThat(clientIds).containsExactlyInAnyOrder("client1", "test-client", "client1"); + assertThat(clientIds).containsExactlyInAnyOrder("client1", "test-client"); } @Test @@ -145,8 +142,8 @@ void testMultipleObjects() { .map(AsyncToolListChangedSpecification::toolListChangeHandler) .toList(); - // Should find 6 annotated methods (3 from each handler) - assertThat(consumers).hasSize(6); + // Should find 4 annotated methods (2 from each handler) + assertThat(consumers).hasSize(4); } @Test @@ -176,8 +173,9 @@ void testNonAnnotatedMethodsIgnored() { List specifications = provider.getToolListChangedSpecifications(); - // Should only find annotated methods, not the non-annotated one - assertThat(specifications).hasSize(3); + // Should only find annotated methods, not the non-annotated one and ignore the + // non-reactive one + assertThat(specifications).hasSize(2); } /** @@ -222,6 +220,7 @@ public Mono validMethod(List updatedTools) { }); } + // ignored since it does not return Mono @McpToolListChanged(clients = "client1") public void validVoidMethod(List updatedTools) { this.lastUpdatedTools = updatedTools; @@ -241,8 +240,8 @@ void testMixedValidAndInvalidMethods() { List specifications = provider.getToolListChangedSpecifications(); - // Should find only the 2 valid methods (Mono and void) - assertThat(specifications).hasSize(2); + // Should find only the 1 valid methods (one Mono) + assertThat(specifications).hasSize(1); // Test that the valid methods work Function, Mono> consumer = specifications.get(0).toolListChangeHandler(); diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/elicitation/AsyncMcpElicitationProviderTests.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/elicitation/AsyncMcpElicitationProviderTests.java index 6df1383..360fb6c 100644 --- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/elicitation/AsyncMcpElicitationProviderTests.java +++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/elicitation/AsyncMcpElicitationProviderTests.java @@ -4,23 +4,22 @@ package org.springaicommunity.mcp.provider.elicitation; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - import java.util.List; import java.util.Map; import java.util.function.Function; +import io.modelcontextprotocol.spec.McpSchema.ElicitRequest; +import io.modelcontextprotocol.spec.McpSchema.ElicitResult; import org.junit.jupiter.api.Test; import org.springaicommunity.mcp.annotation.McpElicitation; import org.springaicommunity.mcp.method.elicitation.AsyncElicitationSpecification; - -import io.modelcontextprotocol.spec.McpSchema.ElicitRequest; -import io.modelcontextprotocol.spec.McpSchema.ElicitResult; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + /** * Tests for {@link AsyncMcpElicitationProvider}. * @@ -51,20 +50,7 @@ public void testGetElicitationHandler() { @Test public void testGetElicitationHandlerWithSyncMethod() { var provider = new AsyncMcpElicitationProvider(List.of(new SyncElicitationHandler())); - AsyncElicitationSpecification specification = provider.getElicitationSpecifications().get(0); - Function> handler = specification.elicitationHandler(); - - assertNotNull(handler); - - ElicitRequest request = new ElicitRequest("Please provide your name", - Map.of("type", "object", "properties", Map.of("name", Map.of("type", "string")))); - Mono result = handler.apply(request); - - StepVerifier.create(result).assertNext(elicitResult -> { - assertEquals(ElicitResult.Action.ACCEPT, elicitResult.action()); - assertNotNull(elicitResult.content()); - assertEquals("Sync Test User", elicitResult.content().get("name")); - }).verifyComplete(); + assertThat(provider.getElicitationSpecifications()).isEmpty(); } public static class TestElicitationHandler { diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/logging/AsyncMcpLoggingProviderTests.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/logging/AsyncMcpLoggingProviderTests.java index 7bf2e1a..d001377 100644 --- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/logging/AsyncMcpLoggingProviderTests.java +++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/logging/AsyncMcpLoggingProviderTests.java @@ -54,6 +54,7 @@ public Mono handleLoggingMessageWithParams(LoggingLevel level, String logg }); } + // This should be filtered out since it does not return Mono @McpLogging(clients = "test-client") public void handleLoggingMessageVoid(LoggingMessageNotification notification) { this.lastNotification = notification; @@ -132,8 +133,8 @@ void testMultipleObjects() { .map(AsyncLoggingSpecification::loggingHandler) .toList(); - // Should find 6 annotated methods (3 from each handler) - assertThat(consumers).hasSize(6); + // Should find 4 annotated methods (2 from each handler) + assertThat(consumers).hasSize(4); } } diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/sampling/AsyncMcpSamplingProviderTests.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/sampling/AsyncMcpSamplingProviderTests.java index 1f928f1..be2df84 100644 --- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/sampling/AsyncMcpSamplingProviderTests.java +++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/sampling/AsyncMcpSamplingProviderTests.java @@ -4,25 +4,22 @@ package org.springaicommunity.mcp.provider.sampling; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import java.util.Collections; import java.util.List; import java.util.function.Function; +import io.modelcontextprotocol.spec.McpSchema.CreateMessageRequest; +import io.modelcontextprotocol.spec.McpSchema.CreateMessageResult; +import io.modelcontextprotocol.spec.McpSchema.TextContent; import org.junit.jupiter.api.Test; import org.springaicommunity.mcp.annotation.McpSampling; -import org.springaicommunity.mcp.method.sampling.AsyncMcpSamplingMethodCallbackExample; import org.springaicommunity.mcp.method.sampling.AsyncSamplingSpecification; import org.springaicommunity.mcp.method.sampling.SamlingTestHelper; - -import io.modelcontextprotocol.spec.McpSchema.CreateMessageRequest; -import io.modelcontextprotocol.spec.McpSchema.CreateMessageResult; -import io.modelcontextprotocol.spec.McpSchema.TextContent; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + /** * Tests for {@link AsyncMcpSamplingProvider}. * @@ -78,12 +75,12 @@ void testDirectResultMethod() { class DirectResultOnly { @McpSampling(clients = "test-client") - public CreateMessageResult handleDirectSamplingRequest(CreateMessageRequest request) { - return CreateMessageResult.builder() + public Mono handleDirectSamplingRequest(CreateMessageRequest request) { + return Mono.just(CreateMessageResult.builder() .role(io.modelcontextprotocol.spec.McpSchema.Role.ASSISTANT) .content(new TextContent("This is a direct response to the sampling request")) .model("test-model") - .build(); + .build()); } }