Skip to content

Commit 13cb037

Browse files
tzolovscottslewis
authored andcommitted
feat!: make clientId required for @McpProgress annotation (spring-ai-community#36)
BREAKING CHANGE: The @McpProgress annotation now requires a clientId parameter. - Remove default empty string from McpProgress.clientId() - Add validation in AsyncProgressSpecification and SyncProgressSpecification constructors - Update all examples and tests to include explicit clientId values - Update README documentation to reflect the required parameter This ensures proper client identification for progress notifications and prevents ambiguous progress handling across multiple MCP clients.
1 parent e87493f commit 13cb037

File tree

10 files changed

+75
-54
lines changed

10 files changed

+75
-54
lines changed

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ The Spring integration module provides seamless integration with Spring AI and S
114114
- **`@McpLogging`** - Annotates methods that handle logging message notifications from MCP servers (requires `clientId` parameter)
115115
- **`@McpSampling`** - Annotates methods that handle sampling requests from MCP servers
116116
- **`@McpElicitation`** - Annotates methods that handle elicitation requests to gather additional information from users
117-
- **`@McpProgress`** - Annotates methods that handle progress notifications for long-running operations
117+
- **`@McpProgress`** - Annotates methods that handle progress notifications for long-running operations (requires `clientId` parameter)
118118
- **`@McpToolListChanged`** - Annotates methods that handle tool list change notifications from MCP servers
119119
- **`@McpResourceListChanged`** - Annotates methods that handle resource list change notifications from MCP servers
120120
- **`@McpPromptListChanged`** - Annotates methods that handle prompt list change notifications from MCP servers
@@ -1103,9 +1103,10 @@ public class ProgressHandler {
11031103

11041104
/**
11051105
* Handle progress notifications with a single parameter.
1106+
* Note: clientId is now required for all @McpProgress annotations.
11061107
* @param notification The progress notification
11071108
*/
1108-
@McpProgress
1109+
@McpProgress(clientId = "default-client")
11091110
public void handleProgressNotification(ProgressNotification notification) {
11101111
System.out.println(String.format("Progress: %.2f%% - %s",
11111112
notification.progress() * 100,
@@ -1114,12 +1115,13 @@ public class ProgressHandler {
11141115

11151116
/**
11161117
* Handle progress notifications with individual parameters.
1118+
* Note: clientId is now required for all @McpProgress annotations.
11171119
* @param progressToken The progress token identifying the operation
11181120
* @param progress The current progress (0.0 to 1.0)
11191121
* @param total Optional total value for the operation
11201122
* @param message Optional progress message
11211123
*/
1122-
@McpProgress
1124+
@McpProgress(clientId = "default-client")
11231125
public void handleProgressWithParams(String progressToken, double progress, Double total, String message) {
11241126
if (total != null) {
11251127
System.out.println(String.format("Progress [%s]: %.0f/%.0f - %s",

mcp-annotations/src/main/java/org/springaicommunity/mcp/annotation/McpProgress.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@
2121
*
2222
* <p>
2323
* Example usage: <pre>{@code
24-
* &#64;McpProgress
24+
* &#64;McpProgress(clientId = "my-client-id")
2525
* public void handleProgressMessage(ProgressNotification notification) {
26-
* // Handle the notification *
26+
* // Handle the progress notification
2727
* }</pre>
2828
*
2929
* @author Christian Tzolov
@@ -36,9 +36,9 @@
3636
public @interface McpProgress {
3737

3838
/**
39-
* Used as connection or client identifier to select the MCP client, the logging
40-
* consumer is associated with. If not specified, is applied to all clients.
39+
* Used as connection or client identifier to select the MCP client, the progress
40+
* consumer is associated with.
4141
*/
42-
String clientId() default "";
42+
String clientId();
4343

4444
}

mcp-annotations/src/main/java/org/springaicommunity/mcp/method/progress/AsyncProgressSpecification.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
package org.springaicommunity.mcp.method.progress;
66

7+
import java.util.Objects;
78
import java.util.function.Function;
89

910
import io.modelcontextprotocol.spec.McpSchema.ProgressNotification;
@@ -17,4 +18,12 @@
1718
* @author Christian Tzolov
1819
*/
1920
public record AsyncProgressSpecification(String clientId, Function<ProgressNotification, Mono<Void>> progressHandler) {
21+
public AsyncProgressSpecification {
22+
Objects.requireNonNull(clientId, "clientId must not be null");
23+
if (clientId.trim().isEmpty()) {
24+
throw new IllegalArgumentException("clientId must not be empty");
25+
}
26+
Objects.requireNonNull(progressHandler, "progressHandler must not be null");
27+
}
28+
2029
}

mcp-annotations/src/main/java/org/springaicommunity/mcp/method/progress/SyncProgressSpecification.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
package org.springaicommunity.mcp.method.progress;
66

7+
import java.util.Objects;
78
import java.util.function.Consumer;
89

910
import io.modelcontextprotocol.spec.McpSchema.ProgressNotification;
@@ -16,4 +17,13 @@
1617
* @author Christian Tzolov
1718
*/
1819
public record SyncProgressSpecification(String clientId, Consumer<ProgressNotification> progressHandler) {
20+
21+
public SyncProgressSpecification {
22+
Objects.requireNonNull(clientId, "clientId must not be null");
23+
if (clientId.trim().isEmpty()) {
24+
throw new IllegalArgumentException("clientId must not be empty");
25+
}
26+
Objects.requireNonNull(progressHandler, "progressHandler must not be null");
27+
}
28+
1929
}

mcp-annotations/src/test/java/org/springaicommunity/mcp/method/progress/AsyncMcpProgressMethodCallbackExample.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public static class AsyncProgressService {
3333
* @param notification the progress notification
3434
* @return Mono completing when processing is done
3535
*/
36-
@McpProgress
36+
@McpProgress(clientId = "my-client-id")
3737
public Mono<Void> handleProgressNotificationAsync(ProgressNotification notification) {
3838
return Mono.fromRunnable(() -> {
3939
int count = notificationCount.incrementAndGet();
@@ -51,7 +51,7 @@ public Mono<Void> handleProgressNotificationAsync(ProgressNotification notificat
5151
* @param progressToken the progress token identifier
5252
* @param total the total value as string
5353
*/
54-
@McpProgress
54+
@McpProgress(clientId = "my-client-id")
5555
public void handleProgressWithParams(Double progress, String progressToken, String total) {
5656
System.out.printf("[Sync in Async] Progress: %.2f%% for token %s (Total: %s)%n", progress * 100,
5757
progressToken, total);
@@ -64,7 +64,7 @@ public void handleProgressWithParams(Double progress, String progressToken, Stri
6464
* @param total the total value as string
6565
* @return Mono completing when processing is done
6666
*/
67-
@McpProgress
67+
@McpProgress(clientId = "my-client-id")
6868
public Mono<Void> handleProgressWithParamsAsync(Double progress, String progressToken, String total) {
6969
return Mono.fromRunnable(() -> {
7070
System.out.printf("[Async Params] Progress: %.2f%% for token %s (Total: %s)%n", progress * 100,
@@ -78,7 +78,7 @@ public Mono<Void> handleProgressWithParamsAsync(Double progress, String progress
7878
* @param progressToken the progress token identifier
7979
* @param total the total value as string
8080
*/
81-
@McpProgress
81+
@McpProgress(clientId = "my-client-id")
8282
public void handleProgressPrimitive(double progress, String progressToken, String total) {
8383
System.out.printf("[Primitive] Processing: %.1f%% complete (Token: %s)%n", progress * 100, progressToken);
8484
}

mcp-annotations/src/test/java/org/springaicommunity/mcp/method/progress/AsyncMcpProgressMethodCallbackTests.java

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,33 +45,33 @@ static class ValidMethods {
4545

4646
private String lastTotal;
4747

48-
@McpProgress
48+
@McpProgress(clientId = "my-client-id")
4949
public void handleProgressVoid(ProgressNotification notification) {
5050
this.lastNotification = notification;
5151
}
5252

53-
@McpProgress
53+
@McpProgress(clientId = "my-client-id")
5454
public Mono<Void> handleProgressMono(ProgressNotification notification) {
5555
this.lastNotification = notification;
5656
return Mono.empty();
5757
}
5858

59-
@McpProgress
59+
@McpProgress(clientId = "my-client-id")
6060
public void handleProgressWithParams(Double progress, String progressToken, String total) {
6161
this.lastProgress = progress;
6262
this.lastProgressToken = progressToken;
6363
this.lastTotal = total;
6464
}
6565

66-
@McpProgress
66+
@McpProgress(clientId = "my-client-id")
6767
public Mono<Void> handleProgressWithParamsMono(Double progress, String progressToken, String total) {
6868
this.lastProgress = progress;
6969
this.lastProgressToken = progressToken;
7070
this.lastTotal = total;
7171
return Mono.empty();
7272
}
7373

74-
@McpProgress
74+
@McpProgress(clientId = "my-client-id")
7575
public void handleProgressWithPrimitiveDouble(double progress, String progressToken, String total) {
7676
this.lastProgress = progress;
7777
this.lastProgressToken = progressToken;
@@ -85,42 +85,42 @@ public void handleProgressWithPrimitiveDouble(double progress, String progressTo
8585
*/
8686
static class InvalidMethods {
8787

88-
@McpProgress
88+
@McpProgress(clientId = "my-client-id")
8989
public String invalidReturnType(ProgressNotification notification) {
9090
return "Invalid";
9191
}
9292

93-
@McpProgress
93+
@McpProgress(clientId = "my-client-id")
9494
public Mono<String> invalidMonoReturnType(ProgressNotification notification) {
9595
return Mono.just("Invalid");
9696
}
9797

98-
@McpProgress
98+
@McpProgress(clientId = "my-client-id")
9999
public void invalidParameterCount(ProgressNotification notification, String extra) {
100100
// Invalid parameter count
101101
}
102102

103-
@McpProgress
103+
@McpProgress(clientId = "my-client-id")
104104
public void invalidParameterType(String invalidType) {
105105
// Invalid parameter type
106106
}
107107

108-
@McpProgress
108+
@McpProgress(clientId = "my-client-id")
109109
public void invalidParameterTypes(String progress, int progressToken, boolean total) {
110110
// Invalid parameter types
111111
}
112112

113-
@McpProgress
113+
@McpProgress(clientId = "my-client-id")
114114
public void invalidFirstParameterType(String progress, String progressToken, String total) {
115115
// Invalid first parameter type
116116
}
117117

118-
@McpProgress
118+
@McpProgress(clientId = "my-client-id")
119119
public void invalidSecondParameterType(Double progress, int progressToken, String total) {
120120
// Invalid second parameter type
121121
}
122122

123-
@McpProgress
123+
@McpProgress(clientId = "my-client-id")
124124
public void invalidThirdParameterType(Double progress, String progressToken, int total) {
125125
// Invalid third parameter type
126126
}

mcp-annotations/src/test/java/org/springaicommunity/mcp/method/progress/SyncMcpProgressMethodCallbackExample.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public static class ProgressService {
2828
* Handle progress notification with the full notification object.
2929
* @param notification the progress notification
3030
*/
31-
@McpProgress
31+
@McpProgress(clientId = "my-client-id")
3232
public void handleProgressNotification(ProgressNotification notification) {
3333
notificationCount++;
3434
System.out.printf("Progress Update #%d: Token=%s, Progress=%.2f%%, Total=%.0f, Message=%s%n",
@@ -42,7 +42,7 @@ public void handleProgressNotification(ProgressNotification notification) {
4242
* @param progressToken the progress token identifier
4343
* @param total the total value as string
4444
*/
45-
@McpProgress
45+
@McpProgress(clientId = "my-client-id")
4646
public void handleProgressWithParams(Double progress, String progressToken, String total) {
4747
System.out.printf("Progress: %.2f%% for token %s (Total: %s)%n", progress * 100, progressToken, total);
4848
}
@@ -53,7 +53,7 @@ public void handleProgressWithParams(Double progress, String progressToken, Stri
5353
* @param progressToken the progress token identifier
5454
* @param total the total value as string
5555
*/
56-
@McpProgress
56+
@McpProgress(clientId = "my-client-id")
5757
public void handleProgressPrimitive(double progress, String progressToken, String total) {
5858
System.out.printf("Processing: %.1f%% complete (Token: %s)%n", progress * 100, progressToken);
5959
}

mcp-annotations/src/test/java/org/springaicommunity/mcp/method/progress/SyncMcpProgressMethodCallbackTests.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,19 +43,19 @@ static class ValidMethods {
4343

4444
private String lastTotal;
4545

46-
@McpProgress
46+
@McpProgress(clientId = "my-client-id")
4747
public void handleProgressNotification(ProgressNotification notification) {
4848
this.lastNotification = notification;
4949
}
5050

51-
@McpProgress
51+
@McpProgress(clientId = "my-client-id")
5252
public void handleProgressWithParams(Double progress, String progressToken, String total) {
5353
this.lastProgress = progress;
5454
this.lastProgressToken = progressToken;
5555
this.lastTotal = total;
5656
}
5757

58-
@McpProgress
58+
@McpProgress(clientId = "my-client-id")
5959
public void handleProgressWithPrimitiveDouble(double progress, String progressToken, String total) {
6060
this.lastProgress = progress;
6161
this.lastProgressToken = progressToken;
@@ -69,37 +69,37 @@ public void handleProgressWithPrimitiveDouble(double progress, String progressTo
6969
*/
7070
static class InvalidMethods {
7171

72-
@McpProgress
72+
@McpProgress(clientId = "my-client-id")
7373
public String invalidReturnType(ProgressNotification notification) {
7474
return "Invalid";
7575
}
7676

77-
@McpProgress
77+
@McpProgress(clientId = "my-client-id")
7878
public void invalidParameterCount(ProgressNotification notification, String extra) {
7979
// Invalid parameter count
8080
}
8181

82-
@McpProgress
82+
@McpProgress(clientId = "my-client-id")
8383
public void invalidParameterType(String invalidType) {
8484
// Invalid parameter type
8585
}
8686

87-
@McpProgress
87+
@McpProgress(clientId = "my-client-id")
8888
public void invalidParameterTypes(String progress, int progressToken, boolean total) {
8989
// Invalid parameter types
9090
}
9191

92-
@McpProgress
92+
@McpProgress(clientId = "my-client-id")
9393
public void invalidFirstParameterType(String progress, String progressToken, String total) {
9494
// Invalid first parameter type
9595
}
9696

97-
@McpProgress
97+
@McpProgress(clientId = "my-client-id")
9898
public void invalidSecondParameterType(Double progress, int progressToken, String total) {
9999
// Invalid second parameter type
100100
}
101101

102-
@McpProgress
102+
@McpProgress(clientId = "my-client-id")
103103
public void invalidThirdParameterType(Double progress, String progressToken, int total) {
104104
// Invalid third parameter type
105105
}

mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/progress/AsyncMcpProgressProviderTests.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,33 +37,33 @@ static class AsyncProgressHandler {
3737

3838
private String lastTotal;
3939

40-
@McpProgress
40+
@McpProgress(clientId = "my-client-id")
4141
public void handleProgressVoid(ProgressNotification notification) {
4242
this.lastNotification = notification;
4343
}
4444

45-
@McpProgress
45+
@McpProgress(clientId = "my-client-id")
4646
public Mono<Void> handleProgressMono(ProgressNotification notification) {
4747
this.lastNotification = notification;
4848
return Mono.empty();
4949
}
5050

51-
@McpProgress
51+
@McpProgress(clientId = "my-client-id")
5252
public void handleProgressWithParams(Double progress, String progressToken, String total) {
5353
this.lastProgress = progress;
5454
this.lastProgressToken = progressToken;
5555
this.lastTotal = total;
5656
}
5757

58-
@McpProgress
58+
@McpProgress(clientId = "my-client-id")
5959
public Mono<Void> handleProgressWithParamsMono(Double progress, String progressToken, String total) {
6060
this.lastProgress = progress;
6161
this.lastProgressToken = progressToken;
6262
this.lastTotal = total;
6363
return Mono.empty();
6464
}
6565

66-
@McpProgress
66+
@McpProgress(clientId = "my-client-id")
6767
public void handleProgressWithPrimitiveDouble(double progress, String progressToken, String total) {
6868
this.lastProgress = progress;
6969
this.lastProgressToken = progressToken;
@@ -77,13 +77,13 @@ public Mono<Void> notAnnotatedMethod(ProgressNotification notification) {
7777
}
7878

7979
// This method has invalid return type and should be ignored
80-
@McpProgress
80+
@McpProgress(clientId = "my-client-id")
8181
public String invalidReturnType(ProgressNotification notification) {
8282
return "Invalid";
8383
}
8484

8585
// This method has invalid Mono return type and should be ignored
86-
@McpProgress
86+
@McpProgress(clientId = "my-client-id")
8787
public Mono<String> invalidMonoReturnType(ProgressNotification notification) {
8888
return Mono.just("Invalid");
8989
}
@@ -171,16 +171,16 @@ void testClientIdExtraction() {
171171

172172
List<AsyncProgressSpecification> specifications = provider.getProgressSpecifications();
173173

174-
// All specifications should have empty clientId (default value from annotation)
175-
assertThat(specifications).allMatch(spec -> spec.clientId().equals(""));
174+
// All specifications should have non-empty clientId
175+
assertThat(specifications).allMatch(spec -> !spec.clientId().isEmpty());
176176
}
177177

178178
@Test
179179
void testErrorHandling() {
180180
// Test class with method that throws an exception
181181
class ErrorHandler {
182182

183-
@McpProgress
183+
@McpProgress(clientId = "my-client-id")
184184
public Mono<Void> handleProgressWithError(ProgressNotification notification) {
185185
return Mono.error(new RuntimeException("Test error"));
186186
}

0 commit comments

Comments
 (0)