Skip to content

Commit 302c994

Browse files
authored
feat: add deterministic method sorting to all MCP providers (#48)
- Sort annotated methods by name in all provider classes to ensure consistent processing order - Apply sorting to 32 provider classes across tool, resource, prompt, complete, progress, logging, sampling, and elicitation providers - Include both sync/async and stateless variants - Add test infrastructure improvements with CountDownLatch for better async testing - Ensures deterministic behavior across different JVM runs and environments This change improves reliability by eliminating non-deterministic method ordering that could lead to inconsistent behavior in MCP server implementations. Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
1 parent 0a5bc24 commit 302c994

32 files changed

+58
-2
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ public List<AsyncPromptListChangedSpecification> getPromptListChangedSpecificati
8484
.filter(method -> method.isAnnotationPresent(McpPromptListChanged.class))
8585
.filter(method -> method.getReturnType() == void.class
8686
|| Mono.class.isAssignableFrom(method.getReturnType()))
87+
.sorted((m1, m2) -> m1.getName().compareTo(m2.getName()))
8788
.map(mcpPromptListChangedConsumerMethod -> {
8889
var promptListChangedAnnotation = mcpPromptListChangedConsumerMethod
8990
.getAnnotation(McpPromptListChanged.class);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ public List<SyncPromptListChangedSpecification> getPromptListChangedSpecificatio
8282
.map(consumerObject -> Stream.of(doGetClassMethods(consumerObject))
8383
.filter(method -> method.isAnnotationPresent(McpPromptListChanged.class))
8484
.filter(method -> !Mono.class.isAssignableFrom(method.getReturnType()))
85+
.sorted((m1, m2) -> m1.getName().compareTo(m2.getName()))
8586
.map(mcpPromptListChangedConsumerMethod -> {
8687
var promptListChangedAnnotation = mcpPromptListChangedConsumerMethod
8788
.getAnnotation(McpPromptListChanged.class);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ public List<AsyncResourceListChangedSpecification> getResourceListChangedSpecifi
8484
.filter(method -> method.isAnnotationPresent(McpResourceListChanged.class))
8585
.filter(method -> method.getReturnType() == void.class
8686
|| Mono.class.isAssignableFrom(method.getReturnType()))
87+
.sorted((m1, m2) -> m1.getName().compareTo(m2.getName()))
8788
.map(mcpResourceListChangedConsumerMethod -> {
8889
var resourceListChangedAnnotation = mcpResourceListChangedConsumerMethod
8990
.getAnnotation(McpResourceListChanged.class);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ public List<SyncResourceListChangedSpecification> getResourceListChangedSpecific
8282
.map(consumerObject -> Stream.of(doGetClassMethods(consumerObject))
8383
.filter(method -> method.isAnnotationPresent(McpResourceListChanged.class))
8484
.filter(method -> !Mono.class.isAssignableFrom(method.getReturnType()))
85+
.sorted((m1, m2) -> m1.getName().compareTo(m2.getName()))
8586
.map(mcpResourceListChangedConsumerMethod -> {
8687
var resourceListChangedAnnotation = mcpResourceListChangedConsumerMethod
8788
.getAnnotation(McpResourceListChanged.class);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ public List<AsyncToolListChangedSpecification> getToolListChangedSpecifications(
8383
.filter(method -> method.isAnnotationPresent(McpToolListChanged.class))
8484
.filter(method -> method.getReturnType() == void.class
8585
|| Mono.class.isAssignableFrom(method.getReturnType()))
86+
.sorted((m1, m2) -> m1.getName().compareTo(m2.getName()))
8687
.map(mcpToolListChangedConsumerMethod -> {
8788
var toolListChangedAnnotation = mcpToolListChangedConsumerMethod
8889
.getAnnotation(McpToolListChanged.class);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ public List<SyncToolListChangedSpecification> getToolListChangedSpecifications()
8181
.map(consumerObject -> Stream.of(doGetClassMethods(consumerObject))
8282
.filter(method -> method.isAnnotationPresent(McpToolListChanged.class))
8383
.filter(method -> !Mono.class.isAssignableFrom(method.getReturnType()))
84+
.sorted((m1, m2) -> m1.getName().compareTo(m2.getName()))
8485
.map(mcpToolListChangedConsumerMethod -> {
8586
var toolListChangedAnnotation = mcpToolListChangedConsumerMethod
8687
.getAnnotation(McpToolListChanged.class);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ public List<AsyncCompletionSpecification> getCompleteSpecifications() {
6969
.filter(method -> Mono.class.isAssignableFrom(method.getReturnType())
7070
|| Flux.class.isAssignableFrom(method.getReturnType())
7171
|| Publisher.class.isAssignableFrom(method.getReturnType()))
72+
.sorted((m1, m2) -> m1.getName().compareTo(m2.getName()))
7273
.map(mcpCompleteMethod -> {
7374
var completeAnnotation = mcpCompleteMethod.getAnnotation(McpComplete.class);
7475
var completeRef = CompleteAdapter.asCompleteReference(completeAnnotation, mcpCompleteMethod);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ public List<AsyncCompletionSpecification> getCompleteSpecifications() {
7272
.filter(method -> Mono.class.isAssignableFrom(method.getReturnType())
7373
|| Flux.class.isAssignableFrom(method.getReturnType())
7474
|| Publisher.class.isAssignableFrom(method.getReturnType()))
75+
.sorted((m1, m2) -> m1.getName().compareTo(m2.getName()))
7576
.map(mcpCompleteMethod -> {
7677
var completeAnnotation = mcpCompleteMethod.getAnnotation(McpComplete.class);
7778
var completeRef = CompleteAdapter.asCompleteReference(completeAnnotation, mcpCompleteMethod);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public List<SyncCompletionSpecification> getCompleteSpecifications() {
4444
.map(completeObject -> Stream.of(doGetClassMethods(completeObject))
4545
.filter(method -> method.isAnnotationPresent(McpComplete.class))
4646
.filter(method -> !Mono.class.isAssignableFrom(method.getReturnType()))
47+
.sorted((m1, m2) -> m1.getName().compareTo(m2.getName()))
4748
.map(mcpCompleteMethod -> {
4849
var completeAnnotation = mcpCompleteMethod.getAnnotation(McpComplete.class);
4950
var completeRef = CompleteAdapter.asCompleteReference(completeAnnotation, mcpCompleteMethod);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ public List<SyncCompletionSpecification> getCompleteSpecifications() {
6868
.map(completeObject -> Stream.of(doGetClassMethods(completeObject))
6969
.filter(method -> method.isAnnotationPresent(McpComplete.class))
7070
.filter(method -> !Mono.class.isAssignableFrom(method.getReturnType()))
71+
.sorted((m1, m2) -> m1.getName().compareTo(m2.getName()))
7172
.map(mcpCompleteMethod -> {
7273
var completeAnnotation = mcpCompleteMethod.getAnnotation(McpComplete.class);
7374
var completeRef = CompleteAdapter.asCompleteReference(completeAnnotation, mcpCompleteMethod);

0 commit comments

Comments
 (0)