Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ The Spring integration module provides seamless integration with Spring AI and S
- **`@McpLogging`** - Annotates methods that handle logging message notifications from MCP servers (requires `clientId` parameter)
- **`@McpSampling`** - Annotates methods that handle sampling requests from MCP servers
- **`@McpElicitation`** - Annotates methods that handle elicitation requests to gather additional information from users
- **`@McpProgress`** - Annotates methods that handle progress notifications for long-running operations
- **`@McpProgress`** - Annotates methods that handle progress notifications for long-running operations (requires `clientId` parameter)
- **`@McpToolListChanged`** - Annotates methods that handle tool list change notifications from MCP servers
- **`@McpResourceListChanged`** - Annotates methods that handle resource list change notifications from MCP servers
- **`@McpPromptListChanged`** - Annotates methods that handle prompt list change notifications from MCP servers
Expand Down Expand Up @@ -1103,9 +1103,10 @@ public class ProgressHandler {

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

/**
* Handle progress notifications with individual parameters.
* Note: clientId is now required for all @McpProgress annotations.
* @param progressToken The progress token identifying the operation
* @param progress The current progress (0.0 to 1.0)
* @param total Optional total value for the operation
* @param message Optional progress message
*/
@McpProgress
@McpProgress(clientId = "default-client")
public void handleProgressWithParams(String progressToken, double progress, Double total, String message) {
if (total != null) {
System.out.println(String.format("Progress [%s]: %.0f/%.0f - %s",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@
*
* <p>
* Example usage: <pre>{@code
* &#64;McpProgress
* &#64;McpProgress(clientId = "my-client-id")
* public void handleProgressMessage(ProgressNotification notification) {
* // Handle the notification *
* // Handle the progress notification
* }</pre>
*
* @author Christian Tzolov
Expand All @@ -36,9 +36,9 @@
public @interface McpProgress {

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

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

package org.springaicommunity.mcp.method.progress;

import java.util.Objects;
import java.util.function.Function;

import io.modelcontextprotocol.spec.McpSchema.ProgressNotification;
Expand All @@ -17,4 +18,12 @@
* @author Christian Tzolov
*/
public record AsyncProgressSpecification(String clientId, Function<ProgressNotification, Mono<Void>> progressHandler) {
public AsyncProgressSpecification {
Objects.requireNonNull(clientId, "clientId must not be null");
if (clientId.trim().isEmpty()) {
throw new IllegalArgumentException("clientId must not be empty");
}
Objects.requireNonNull(progressHandler, "progressHandler must not be null");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

package org.springaicommunity.mcp.method.progress;

import java.util.Objects;
import java.util.function.Consumer;

import io.modelcontextprotocol.spec.McpSchema.ProgressNotification;
Expand All @@ -16,4 +17,13 @@
* @author Christian Tzolov
*/
public record SyncProgressSpecification(String clientId, Consumer<ProgressNotification> progressHandler) {

public SyncProgressSpecification {
Objects.requireNonNull(clientId, "clientId must not be null");
if (clientId.trim().isEmpty()) {
throw new IllegalArgumentException("clientId must not be empty");
}
Objects.requireNonNull(progressHandler, "progressHandler must not be null");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public static class AsyncProgressService {
* @param notification the progress notification
* @return Mono completing when processing is done
*/
@McpProgress
@McpProgress(clientId = "my-client-id")
public Mono<Void> handleProgressNotificationAsync(ProgressNotification notification) {
return Mono.fromRunnable(() -> {
int count = notificationCount.incrementAndGet();
Expand All @@ -51,7 +51,7 @@ public Mono<Void> handleProgressNotificationAsync(ProgressNotification notificat
* @param progressToken the progress token identifier
* @param total the total value as string
*/
@McpProgress
@McpProgress(clientId = "my-client-id")
public void handleProgressWithParams(Double progress, String progressToken, String total) {
System.out.printf("[Sync in Async] Progress: %.2f%% for token %s (Total: %s)%n", progress * 100,
progressToken, total);
Expand All @@ -64,7 +64,7 @@ public void handleProgressWithParams(Double progress, String progressToken, Stri
* @param total the total value as string
* @return Mono completing when processing is done
*/
@McpProgress
@McpProgress(clientId = "my-client-id")
public Mono<Void> handleProgressWithParamsAsync(Double progress, String progressToken, String total) {
return Mono.fromRunnable(() -> {
System.out.printf("[Async Params] Progress: %.2f%% for token %s (Total: %s)%n", progress * 100,
Expand All @@ -78,7 +78,7 @@ public Mono<Void> handleProgressWithParamsAsync(Double progress, String progress
* @param progressToken the progress token identifier
* @param total the total value as string
*/
@McpProgress
@McpProgress(clientId = "my-client-id")
public void handleProgressPrimitive(double progress, String progressToken, String total) {
System.out.printf("[Primitive] Processing: %.1f%% complete (Token: %s)%n", progress * 100, progressToken);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,33 +45,33 @@ static class ValidMethods {

private String lastTotal;

@McpProgress
@McpProgress(clientId = "my-client-id")
public void handleProgressVoid(ProgressNotification notification) {
this.lastNotification = notification;
}

@McpProgress
@McpProgress(clientId = "my-client-id")
public Mono<Void> handleProgressMono(ProgressNotification notification) {
this.lastNotification = notification;
return Mono.empty();
}

@McpProgress
@McpProgress(clientId = "my-client-id")
public void handleProgressWithParams(Double progress, String progressToken, String total) {
this.lastProgress = progress;
this.lastProgressToken = progressToken;
this.lastTotal = total;
}

@McpProgress
@McpProgress(clientId = "my-client-id")
public Mono<Void> handleProgressWithParamsMono(Double progress, String progressToken, String total) {
this.lastProgress = progress;
this.lastProgressToken = progressToken;
this.lastTotal = total;
return Mono.empty();
}

@McpProgress
@McpProgress(clientId = "my-client-id")
public void handleProgressWithPrimitiveDouble(double progress, String progressToken, String total) {
this.lastProgress = progress;
this.lastProgressToken = progressToken;
Expand All @@ -85,42 +85,42 @@ public void handleProgressWithPrimitiveDouble(double progress, String progressTo
*/
static class InvalidMethods {

@McpProgress
@McpProgress(clientId = "my-client-id")
public String invalidReturnType(ProgressNotification notification) {
return "Invalid";
}

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

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

@McpProgress
@McpProgress(clientId = "my-client-id")
public void invalidParameterType(String invalidType) {
// Invalid parameter type
}

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

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

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

@McpProgress
@McpProgress(clientId = "my-client-id")
public void invalidThirdParameterType(Double progress, String progressToken, int total) {
// Invalid third parameter type
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public static class ProgressService {
* Handle progress notification with the full notification object.
* @param notification the progress notification
*/
@McpProgress
@McpProgress(clientId = "my-client-id")
public void handleProgressNotification(ProgressNotification notification) {
notificationCount++;
System.out.printf("Progress Update #%d: Token=%s, Progress=%.2f%%, Total=%.0f, Message=%s%n",
Expand All @@ -42,7 +42,7 @@ public void handleProgressNotification(ProgressNotification notification) {
* @param progressToken the progress token identifier
* @param total the total value as string
*/
@McpProgress
@McpProgress(clientId = "my-client-id")
public void handleProgressWithParams(Double progress, String progressToken, String total) {
System.out.printf("Progress: %.2f%% for token %s (Total: %s)%n", progress * 100, progressToken, total);
}
Expand All @@ -53,7 +53,7 @@ public void handleProgressWithParams(Double progress, String progressToken, Stri
* @param progressToken the progress token identifier
* @param total the total value as string
*/
@McpProgress
@McpProgress(clientId = "my-client-id")
public void handleProgressPrimitive(double progress, String progressToken, String total) {
System.out.printf("Processing: %.1f%% complete (Token: %s)%n", progress * 100, progressToken);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,19 @@ static class ValidMethods {

private String lastTotal;

@McpProgress
@McpProgress(clientId = "my-client-id")
public void handleProgressNotification(ProgressNotification notification) {
this.lastNotification = notification;
}

@McpProgress
@McpProgress(clientId = "my-client-id")
public void handleProgressWithParams(Double progress, String progressToken, String total) {
this.lastProgress = progress;
this.lastProgressToken = progressToken;
this.lastTotal = total;
}

@McpProgress
@McpProgress(clientId = "my-client-id")
public void handleProgressWithPrimitiveDouble(double progress, String progressToken, String total) {
this.lastProgress = progress;
this.lastProgressToken = progressToken;
Expand All @@ -69,37 +69,37 @@ public void handleProgressWithPrimitiveDouble(double progress, String progressTo
*/
static class InvalidMethods {

@McpProgress
@McpProgress(clientId = "my-client-id")
public String invalidReturnType(ProgressNotification notification) {
return "Invalid";
}

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

@McpProgress
@McpProgress(clientId = "my-client-id")
public void invalidParameterType(String invalidType) {
// Invalid parameter type
}

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

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

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

@McpProgress
@McpProgress(clientId = "my-client-id")
public void invalidThirdParameterType(Double progress, String progressToken, int total) {
// Invalid third parameter type
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,33 +37,33 @@ static class AsyncProgressHandler {

private String lastTotal;

@McpProgress
@McpProgress(clientId = "my-client-id")
public void handleProgressVoid(ProgressNotification notification) {
this.lastNotification = notification;
}

@McpProgress
@McpProgress(clientId = "my-client-id")
public Mono<Void> handleProgressMono(ProgressNotification notification) {
this.lastNotification = notification;
return Mono.empty();
}

@McpProgress
@McpProgress(clientId = "my-client-id")
public void handleProgressWithParams(Double progress, String progressToken, String total) {
this.lastProgress = progress;
this.lastProgressToken = progressToken;
this.lastTotal = total;
}

@McpProgress
@McpProgress(clientId = "my-client-id")
public Mono<Void> handleProgressWithParamsMono(Double progress, String progressToken, String total) {
this.lastProgress = progress;
this.lastProgressToken = progressToken;
this.lastTotal = total;
return Mono.empty();
}

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

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

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

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

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

@Test
void testErrorHandling() {
// Test class with method that throws an exception
class ErrorHandler {

@McpProgress
@McpProgress(clientId = "my-client-id")
public Mono<Void> handleProgressWithError(ProgressNotification notification) {
return Mono.error(new RuntimeException("Test error"));
}
Expand Down
Loading