Skip to content

Commit 1176e6b

Browse files
authored
refactor: extract reactive/imperative return type filtering to utility methods (spring-ai-community#66)
- Add filterNonReactiveReturnTypeMethod() and filterReactiveReturnTypeMethod() to McpProviderUtils for consistent return type validation - Add logging to warn when methods with incompatible return types are skipped - Replace inline return type checks across all provider classes with utility methods - Standardize import organization (group by external/internal packages) - Update tests to reflect filtering of non-reactive methods in async providers This refactoring improves code maintainability by centralizing the logic for determining whether a method has a reactive or non-reactive return type, and provides better visibility through logging when methods are filtered out. Affected providers: - Complete (Async/Sync, Stateless/Stateful) - Elicitation (Async/Sync) - Logging (Async/Sync) - Progress (Async/Sync) - Prompt (Async/Sync, Stateless/Stateful) - Resource (Async/Sync, Stateless/Stateful) - Sampling (Async/Sync) - Tool (Async/Sync, Stateless/Stateful) - Changed listeners (Prompt/Resource/Tool, Async/Sync) Signed-off-by: Christian Tzolov <[email protected]>
1 parent e5f3e68 commit 1176e6b

File tree

39 files changed

+207
-230
lines changed

39 files changed

+207
-230
lines changed

mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/McpProviderUtils.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,15 @@
2121
import java.util.regex.Pattern;
2222

2323
import org.reactivestreams.Publisher;
24+
import org.slf4j.Logger;
25+
import org.slf4j.LoggerFactory;
2426
import reactor.core.publisher.Flux;
2527
import reactor.core.publisher.Mono;
2628

2729
public class McpProviderUtils {
2830

31+
private static final Logger logger = LoggerFactory.getLogger(McpProviderUtils.class);
32+
2933
private static final Pattern URI_VARIABLE_PATTERN = Pattern.compile("\\{([^/]+?)\\}");
3034

3135
public static boolean isUriTemplate(String uri) {
@@ -40,4 +44,28 @@ public static boolean isUriTemplate(String uri) {
4044
.isAssignableFrom(method.getReturnType()) && !Flux.class.isAssignableFrom(method.getReturnType())
4145
&& !Publisher.class.isAssignableFrom(method.getReturnType());
4246

47+
public static Predicate<Method> filterNonReactiveReturnTypeMethod() {
48+
return method -> {
49+
if (isReactiveReturnType.test(method)) {
50+
return true;
51+
}
52+
logger.info(
53+
"Sync providers doesn't support reactive return types. Skipping method {} with reactive return type {}",
54+
method, method.getReturnType());
55+
return false;
56+
};
57+
}
58+
59+
public static Predicate<Method> filterReactiveReturnTypeMethod() {
60+
return method -> {
61+
if (isNotReactiveReturnType.test(method)) {
62+
return true;
63+
}
64+
logger.info(
65+
"Sync providers doesn't support reactive return types. Skipping method {} with reactive return type {}",
66+
method, method.getReturnType());
67+
return false;
68+
};
69+
}
70+
4371
}

mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/prompt/AsyncMcpPromptListChangedProvider.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@
2121
import java.util.function.Function;
2222
import java.util.stream.Stream;
2323

24-
import org.springaicommunity.mcp.annotation.McpPromptListChanged;
25-
import org.springaicommunity.mcp.method.changed.prompt.AsyncPromptListChangedSpecification;
26-
import org.springaicommunity.mcp.method.changed.prompt.AsyncMcpPromptListChangedMethodCallback;
27-
2824
import io.modelcontextprotocol.spec.McpSchema;
2925
import io.modelcontextprotocol.util.Assert;
26+
import org.springaicommunity.mcp.annotation.McpPromptListChanged;
27+
import org.springaicommunity.mcp.method.changed.prompt.AsyncMcpPromptListChangedMethodCallback;
28+
import org.springaicommunity.mcp.method.changed.prompt.AsyncPromptListChangedSpecification;
29+
import org.springaicommunity.mcp.provider.McpProviderUtils;
3030
import reactor.core.publisher.Mono;
3131

3232
/**
@@ -82,8 +82,7 @@ public List<AsyncPromptListChangedSpecification> getPromptListChangedSpecificati
8282
.stream()
8383
.map(consumerObject -> Stream.of(doGetClassMethods(consumerObject))
8484
.filter(method -> method.isAnnotationPresent(McpPromptListChanged.class))
85-
.filter(method -> method.getReturnType() == void.class
86-
|| Mono.class.isAssignableFrom(method.getReturnType()))
85+
.filter(McpProviderUtils.filterNonReactiveReturnTypeMethod())
8786
.sorted((m1, m2) -> m1.getName().compareTo(m2.getName()))
8887
.map(mcpPromptListChangedConsumerMethod -> {
8988
var promptListChangedAnnotation = mcpPromptListChangedConsumerMethod

mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/prompt/SyncMcpPromptListChangedProvider.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,12 @@
2121
import java.util.function.Consumer;
2222
import java.util.stream.Stream;
2323

24-
import org.springaicommunity.mcp.annotation.McpPromptListChanged;
25-
import org.springaicommunity.mcp.method.changed.prompt.SyncPromptListChangedSpecification;
26-
import org.springaicommunity.mcp.method.changed.prompt.SyncMcpPromptListChangedMethodCallback;
27-
2824
import io.modelcontextprotocol.spec.McpSchema;
2925
import io.modelcontextprotocol.util.Assert;
30-
import reactor.core.publisher.Mono;
26+
import org.springaicommunity.mcp.annotation.McpPromptListChanged;
27+
import org.springaicommunity.mcp.method.changed.prompt.SyncMcpPromptListChangedMethodCallback;
28+
import org.springaicommunity.mcp.method.changed.prompt.SyncPromptListChangedSpecification;
29+
import org.springaicommunity.mcp.provider.McpProviderUtils;
3130

3231
/**
3332
* Provider for synchronous prompt list changed consumer callbacks.
@@ -81,7 +80,7 @@ public List<SyncPromptListChangedSpecification> getPromptListChangedSpecificatio
8180
.stream()
8281
.map(consumerObject -> Stream.of(doGetClassMethods(consumerObject))
8382
.filter(method -> method.isAnnotationPresent(McpPromptListChanged.class))
84-
.filter(method -> !Mono.class.isAssignableFrom(method.getReturnType()))
83+
.filter(McpProviderUtils.filterReactiveReturnTypeMethod())
8584
.sorted((m1, m2) -> m1.getName().compareTo(m2.getName()))
8685
.map(mcpPromptListChangedConsumerMethod -> {
8786
var promptListChangedAnnotation = mcpPromptListChangedConsumerMethod

mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/resource/AsyncMcpResourceListChangedProvider.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@
2121
import java.util.function.Function;
2222
import java.util.stream.Stream;
2323

24-
import org.springaicommunity.mcp.annotation.McpResourceListChanged;
25-
import org.springaicommunity.mcp.method.changed.resource.AsyncResourceListChangedSpecification;
26-
import org.springaicommunity.mcp.method.changed.resource.AsyncMcpResourceListChangedMethodCallback;
27-
2824
import io.modelcontextprotocol.spec.McpSchema;
2925
import io.modelcontextprotocol.util.Assert;
26+
import org.springaicommunity.mcp.annotation.McpResourceListChanged;
27+
import org.springaicommunity.mcp.method.changed.resource.AsyncMcpResourceListChangedMethodCallback;
28+
import org.springaicommunity.mcp.method.changed.resource.AsyncResourceListChangedSpecification;
29+
import org.springaicommunity.mcp.provider.McpProviderUtils;
3030
import reactor.core.publisher.Mono;
3131

3232
/**
@@ -82,8 +82,7 @@ public List<AsyncResourceListChangedSpecification> getResourceListChangedSpecifi
8282
.stream()
8383
.map(consumerObject -> Stream.of(doGetClassMethods(consumerObject))
8484
.filter(method -> method.isAnnotationPresent(McpResourceListChanged.class))
85-
.filter(method -> method.getReturnType() == void.class
86-
|| Mono.class.isAssignableFrom(method.getReturnType()))
85+
.filter(McpProviderUtils.filterNonReactiveReturnTypeMethod())
8786
.sorted((m1, m2) -> m1.getName().compareTo(m2.getName()))
8887
.map(mcpResourceListChangedConsumerMethod -> {
8988
var resourceListChangedAnnotation = mcpResourceListChangedConsumerMethod

mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/resource/SyncMcpResourceListChangedProvider.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,12 @@
2121
import java.util.function.Consumer;
2222
import java.util.stream.Stream;
2323

24-
import org.springaicommunity.mcp.annotation.McpResourceListChanged;
25-
import org.springaicommunity.mcp.method.changed.resource.SyncResourceListChangedSpecification;
26-
import org.springaicommunity.mcp.method.changed.resource.SyncMcpResourceListChangedMethodCallback;
27-
2824
import io.modelcontextprotocol.spec.McpSchema;
2925
import io.modelcontextprotocol.util.Assert;
30-
import reactor.core.publisher.Mono;
26+
import org.springaicommunity.mcp.annotation.McpResourceListChanged;
27+
import org.springaicommunity.mcp.method.changed.resource.SyncMcpResourceListChangedMethodCallback;
28+
import org.springaicommunity.mcp.method.changed.resource.SyncResourceListChangedSpecification;
29+
import org.springaicommunity.mcp.provider.McpProviderUtils;
3130

3231
/**
3332
* Provider for synchronous resource list changed consumer callbacks.
@@ -81,7 +80,7 @@ public List<SyncResourceListChangedSpecification> getResourceListChangedSpecific
8180
.stream()
8281
.map(consumerObject -> Stream.of(doGetClassMethods(consumerObject))
8382
.filter(method -> method.isAnnotationPresent(McpResourceListChanged.class))
84-
.filter(method -> !Mono.class.isAssignableFrom(method.getReturnType()))
83+
.filter(McpProviderUtils.filterReactiveReturnTypeMethod())
8584
.sorted((m1, m2) -> m1.getName().compareTo(m2.getName()))
8685
.map(mcpResourceListChangedConsumerMethod -> {
8786
var resourceListChangedAnnotation = mcpResourceListChangedConsumerMethod

mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/tool/AsyncMcpToolListChangedProvider.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@
2121
import java.util.function.Function;
2222
import java.util.stream.Stream;
2323

24-
import org.springaicommunity.mcp.annotation.McpToolListChanged;
25-
import org.springaicommunity.mcp.method.changed.tool.AsyncToolListChangedSpecification;
26-
import org.springaicommunity.mcp.method.changed.tool.AsyncMcpToolListChangedMethodCallback;
27-
2824
import io.modelcontextprotocol.spec.McpSchema;
2925
import io.modelcontextprotocol.util.Assert;
26+
import org.springaicommunity.mcp.annotation.McpToolListChanged;
27+
import org.springaicommunity.mcp.method.changed.tool.AsyncMcpToolListChangedMethodCallback;
28+
import org.springaicommunity.mcp.method.changed.tool.AsyncToolListChangedSpecification;
29+
import org.springaicommunity.mcp.provider.McpProviderUtils;
3030
import reactor.core.publisher.Mono;
3131

3232
/**
@@ -81,8 +81,7 @@ public List<AsyncToolListChangedSpecification> getToolListChangedSpecifications(
8181
List<AsyncToolListChangedSpecification> toolListChangedConsumers = this.toolListChangedConsumerObjects.stream()
8282
.map(consumerObject -> Stream.of(doGetClassMethods(consumerObject))
8383
.filter(method -> method.isAnnotationPresent(McpToolListChanged.class))
84-
.filter(method -> method.getReturnType() == void.class
85-
|| Mono.class.isAssignableFrom(method.getReturnType()))
84+
.filter(McpProviderUtils.filterNonReactiveReturnTypeMethod())
8685
.sorted((m1, m2) -> m1.getName().compareTo(m2.getName()))
8786
.map(mcpToolListChangedConsumerMethod -> {
8887
var toolListChangedAnnotation = mcpToolListChangedConsumerMethod

mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/tool/SyncMcpToolListChangedProvider.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,12 @@
2121
import java.util.function.Consumer;
2222
import java.util.stream.Stream;
2323

24-
import org.springaicommunity.mcp.annotation.McpToolListChanged;
25-
import org.springaicommunity.mcp.method.changed.tool.SyncToolListChangedSpecification;
26-
import org.springaicommunity.mcp.method.changed.tool.SyncMcpToolListChangedMethodCallback;
27-
2824
import io.modelcontextprotocol.spec.McpSchema;
2925
import io.modelcontextprotocol.util.Assert;
30-
import reactor.core.publisher.Mono;
26+
import org.springaicommunity.mcp.annotation.McpToolListChanged;
27+
import org.springaicommunity.mcp.method.changed.tool.SyncMcpToolListChangedMethodCallback;
28+
import org.springaicommunity.mcp.method.changed.tool.SyncToolListChangedSpecification;
29+
import org.springaicommunity.mcp.provider.McpProviderUtils;
3130

3231
/**
3332
* Provider for synchronous tool list changed consumer callbacks.
@@ -80,7 +79,7 @@ public List<SyncToolListChangedSpecification> getToolListChangedSpecifications()
8079
List<SyncToolListChangedSpecification> toolListChangedConsumers = this.toolListChangedConsumerObjects.stream()
8180
.map(consumerObject -> Stream.of(doGetClassMethods(consumerObject))
8281
.filter(method -> method.isAnnotationPresent(McpToolListChanged.class))
83-
.filter(method -> !Mono.class.isAssignableFrom(method.getReturnType()))
82+
.filter(McpProviderUtils.filterReactiveReturnTypeMethod())
8483
.sorted((m1, m2) -> m1.getName().compareTo(m2.getName()))
8584
.map(mcpToolListChangedConsumerMethod -> {
8685
var toolListChangedAnnotation = mcpToolListChangedConsumerMethod

mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/complete/AsyncMcpCompleteProvider.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,12 @@
2323
import io.modelcontextprotocol.server.McpAsyncServerExchange;
2424
import io.modelcontextprotocol.server.McpServerFeatures.AsyncCompletionSpecification;
2525
import io.modelcontextprotocol.util.Assert;
26-
import org.reactivestreams.Publisher;
2726
import org.slf4j.Logger;
2827
import org.slf4j.LoggerFactory;
2928
import org.springaicommunity.mcp.adapter.CompleteAdapter;
3029
import org.springaicommunity.mcp.annotation.McpComplete;
3130
import org.springaicommunity.mcp.method.complete.AsyncMcpCompleteMethodCallback;
32-
import reactor.core.publisher.Flux;
33-
import reactor.core.publisher.Mono;
31+
import org.springaicommunity.mcp.provider.McpProviderUtils;
3432

3533
/**
3634
* Provider for asynchronous MCP complete methods.
@@ -66,9 +64,7 @@ public List<AsyncCompletionSpecification> getCompleteSpecifications() {
6664
List<AsyncCompletionSpecification> asyncCompleteSpecification = this.completeObjects.stream()
6765
.map(completeObject -> Stream.of(doGetClassMethods(completeObject))
6866
.filter(method -> method.isAnnotationPresent(McpComplete.class))
69-
.filter(method -> Mono.class.isAssignableFrom(method.getReturnType())
70-
|| Flux.class.isAssignableFrom(method.getReturnType())
71-
|| Publisher.class.isAssignableFrom(method.getReturnType()))
67+
.filter(McpProviderUtils.filterNonReactiveReturnTypeMethod())
7268
.sorted((m1, m2) -> m1.getName().compareTo(m2.getName()))
7369
.map(mcpCompleteMethod -> {
7470
var completeAnnotation = mcpCompleteMethod.getAnnotation(McpComplete.class);

mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/complete/AsyncStatelessMcpCompleteProvider.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,12 @@
2626
import io.modelcontextprotocol.spec.McpSchema.CompleteRequest;
2727
import io.modelcontextprotocol.spec.McpSchema.CompleteResult;
2828
import io.modelcontextprotocol.util.Assert;
29-
import org.reactivestreams.Publisher;
3029
import org.slf4j.Logger;
3130
import org.slf4j.LoggerFactory;
3231
import org.springaicommunity.mcp.adapter.CompleteAdapter;
3332
import org.springaicommunity.mcp.annotation.McpComplete;
3433
import org.springaicommunity.mcp.method.complete.AsyncStatelessMcpCompleteMethodCallback;
35-
import reactor.core.publisher.Flux;
34+
import org.springaicommunity.mcp.provider.McpProviderUtils;
3635
import reactor.core.publisher.Mono;
3736

3837
/**
@@ -69,9 +68,7 @@ public List<AsyncCompletionSpecification> getCompleteSpecifications() {
6968
List<AsyncCompletionSpecification> completeSpecs = this.completeObjects.stream()
7069
.map(completeObject -> Stream.of(doGetClassMethods(completeObject))
7170
.filter(method -> method.isAnnotationPresent(McpComplete.class))
72-
.filter(method -> Mono.class.isAssignableFrom(method.getReturnType())
73-
|| Flux.class.isAssignableFrom(method.getReturnType())
74-
|| Publisher.class.isAssignableFrom(method.getReturnType()))
71+
.filter(McpProviderUtils.filterNonReactiveReturnTypeMethod())
7572
.sorted((m1, m2) -> m1.getName().compareTo(m2.getName()))
7673
.map(mcpCompleteMethod -> {
7774
var completeAnnotation = mcpCompleteMethod.getAnnotation(McpComplete.class);

mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/complete/SyncMcpCompleteProvider.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
import org.springaicommunity.mcp.adapter.CompleteAdapter;
2626
import org.springaicommunity.mcp.annotation.McpComplete;
2727
import org.springaicommunity.mcp.method.complete.SyncMcpCompleteMethodCallback;
28-
import reactor.core.publisher.Mono;
28+
import org.springaicommunity.mcp.provider.McpProviderUtils;
2929

3030
/**
3131
*/
@@ -43,7 +43,7 @@ public List<SyncCompletionSpecification> getCompleteSpecifications() {
4343
List<SyncCompletionSpecification> syncCompleteSpecification = this.completeObjects.stream()
4444
.map(completeObject -> Stream.of(doGetClassMethods(completeObject))
4545
.filter(method -> method.isAnnotationPresent(McpComplete.class))
46-
.filter(method -> !Mono.class.isAssignableFrom(method.getReturnType()))
46+
.filter(McpProviderUtils.filterReactiveReturnTypeMethod())
4747
.sorted((m1, m2) -> m1.getName().compareTo(m2.getName()))
4848
.map(mcpCompleteMethod -> {
4949
var completeAnnotation = mcpCompleteMethod.getAnnotation(McpComplete.class);

0 commit comments

Comments
 (0)