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
133 changes: 133 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ The Spring integration module provides seamless integration with Spring AI and S
- **`@McpProgress`** - Annotates methods that handle progress notifications for long-running operations
- **`@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

#### Server
- **`@McpComplete`** - Annotates methods that provide completion functionality for prompts or URI templates
Expand Down Expand Up @@ -216,6 +217,8 @@ The project includes provider classes that scan for annotated methods and create
- `AsyncMcpToolListChangedProvider` - Processes `@McpToolListChanged` annotations for asynchronous operations
- `SyncMcpResourceListChangedProvider` - Processes `@McpResourceListChanged` annotations for synchronous operations
- `AsyncMcpResourceListChangedProvider` - Processes `@McpResourceListChanged` annotations for asynchronous operations
- `SyncMcpPromptListChangedProvider` - Processes `@McpPromptListChanged` annotations for synchronous operations
- `AsyncMcpPromptListChangedProvider` - Processes `@McpPromptListChanged` annotations for asynchronous operations

#### Stateless Providers (using McpTransportContext)
- `SyncStatelessMcpCompleteProvider` - Processes `@McpComplete` annotations for synchronous stateless operations
Expand Down Expand Up @@ -1441,6 +1444,124 @@ public class MyMcpClient {
}
```

### Mcp Client Prompt List Changed Example

```java
public class PromptListChangedHandler {

/**
* Handle prompt list change notifications with a single parameter.
* @param updatedPrompts The updated list of prompts after the change
*/
@McpPromptListChanged
public void handlePromptListChanged(List<McpSchema.Prompt> updatedPrompts) {
System.out.println("Prompt list updated, now contains " + updatedPrompts.size() + " prompts:");
for (McpSchema.Prompt prompt : updatedPrompts) {
System.out.println(" - " + prompt.name() + ": " + prompt.description());
}
}

/**
* Handle prompt list change notifications for a specific client.
* @param updatedPrompts The updated list of prompts after the change
*/
@McpPromptListChanged(clientId = "client-1")
public void handleClient1PromptListChanged(List<McpSchema.Prompt> updatedPrompts) {
System.out.println("Client-1 prompt list updated with " + updatedPrompts.size() + " prompts");
// Process the updated prompt list for client-1
updateClientPromptCache("client-1", updatedPrompts);
}

/**
* Handle prompt list change notifications for another specific client.
* @param updatedPrompts The updated list of prompts after the change
*/
@McpPromptListChanged(clientId = "client-2")
public void handleClient2PromptListChanged(List<McpSchema.Prompt> updatedPrompts) {
System.out.println("Client-2 prompt list updated with " + updatedPrompts.size() + " prompts");
// Process the updated prompt list for client-2
updateClientPromptCache("client-2", updatedPrompts);
}

private void updateClientPromptCache(String clientId, List<McpSchema.Prompt> prompts) {
// Implementation to update prompt cache for specific client
System.out.println("Updated prompt cache for " + clientId + " with " + prompts.size() + " prompts");
}
}

public class AsyncPromptListChangedHandler {

/**
* Handle prompt list change notifications asynchronously.
* @param updatedPrompts The updated list of prompts after the change
* @return A Mono that completes when the notification is handled
*/
@McpPromptListChanged
public Mono<Void> handleAsyncPromptListChanged(List<McpSchema.Prompt> updatedPrompts) {
return Mono.fromRunnable(() -> {
System.out.println("Async prompt list update: " + updatedPrompts.size() + " prompts");
// Process the updated prompt list asynchronously
processPromptListUpdate(updatedPrompts);
});
}

/**
* Handle prompt list change notifications for a specific client asynchronously.
* @param updatedPrompts The updated list of prompts after the change
* @return A Mono that completes when the notification is handled
*/
@McpPromptListChanged(clientId = "client-2")
public Mono<Void> handleClient2AsyncPromptListChanged(List<McpSchema.Prompt> updatedPrompts) {
return Mono.fromRunnable(() -> {
System.out.println("Client-2 async prompt list update: " + updatedPrompts.size() + " prompts");
// Process the updated prompt list for client-2 asynchronously
processClientPromptListUpdate("client-2", updatedPrompts);
}).then();
}

private void processPromptListUpdate(List<McpSchema.Prompt> prompts) {
// Implementation to process prompt list update
System.out.println("Processing prompt list update with " + prompts.size() + " prompts");
}

private void processClientPromptListUpdate(String clientId, List<McpSchema.Prompt> prompts) {
// Implementation to process prompt list update for specific client
System.out.println("Processing prompt list update for " + clientId + " with " + prompts.size() + " prompts");
}
}

public class MyMcpClient {

public static McpSyncClient createSyncClientWithPromptListChanged(PromptListChangedHandler promptListChangedHandler) {
List<Consumer<List<McpSchema.Prompt>>> promptListChangedConsumers =
new SyncMcpPromptListChangedProvider(List.of(promptListChangedHandler)).getPromptListChangedConsumers();

McpSyncClient client = McpClient.sync(transport)
.capabilities(ClientCapabilities.builder()
// Enable capabilities...
.build())
.promptListChangedConsumers(promptListChangedConsumers)
.build();

return client;
}

public static McpAsyncClient createAsyncClientWithPromptListChanged(AsyncPromptListChangedHandler asyncPromptListChangedHandler) {
List<Function<List<McpSchema.Prompt>, Mono<Void>>> promptListChangedHandlers =
new AsyncMcpPromptListChangedProvider(List.of(asyncPromptListChangedHandler)).getPromptListChangedHandlers();

McpAsyncClient client = McpClient.async(transport)
.capabilities(ClientCapabilities.builder()
// Enable capabilities...
.build())
.promptListChangedHandlers(promptListChangedHandlers)
.build();

return client;
}
}
```

### Mcp Client Elicitation Example

```java
Expand Down Expand Up @@ -1882,6 +2003,18 @@ public class McpConfig {
return SpringAiMcpAnnotationProvider.createAsyncResourceListChangedSpecifications(asyncResourceListChangedHandlers);
}

@Bean
public List<SyncPromptListChangedSpecification> syncPromptListChangedSpecifications(
List<PromptListChangedHandler> promptListChangedHandlers) {
return SpringAiMcpAnnotationProvider.createSyncPromptListChangedSpecifications(promptListChangedHandlers);
}

@Bean
public List<AsyncPromptListChangedSpecification> asyncPromptListChangedSpecifications(
List<AsyncPromptListChangedHandler> asyncPromptListChangedHandlers) {
return SpringAiMcpAnnotationProvider.createAsyncPromptListChangedSpecifications(asyncPromptListChangedHandlers);
}

// Stateless Spring Integration Examples

@Bean
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright 2025-2025 the original author or authors.
*/

package org.springaicommunity.mcp.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation for methods that handle prompt list change notifications from MCP servers.
* This annotation is applicable only for MCP clients.
*
* <p>
* Methods annotated with this annotation are used to listen for notifications when the
* list of available prompts changes on an MCP server. According to the MCP specification,
* servers that declare the {@code listChanged} capability will send notifications when
* their prompt list is modified.
*
* <p>
* The annotated method must have a void return type for synchronous consumers, or can
* return {@code Mono<Void>} for asynchronous consumers. The method should accept a single
* parameter of type {@code List<McpSchema.Prompt>} that represents the updated list of
* prompts after the change notification.
*
* <p>
* Example usage: <pre>{@code
* &#64;McpPromptListChanged
* public void onPromptListChanged(List<McpSchema.Prompt> updatedPrompts) {
* // Handle prompt list change notification with the updated prompts
* logger.info("Prompt list updated, now contains {} prompts", updatedPrompts.size());
* // Process the updated prompt list
* }
*
* &#64;McpPromptListChanged
* public Mono<Void> onPromptListChangedAsync(List<McpSchema.Prompt> updatedPrompts) {
* // Handle prompt list change notification asynchronously
* return processUpdatedPrompts(updatedPrompts);
* }
* }</pre>
*
* @author Christian Tzolov
* @see <a href=
* "https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#list-changed-notification">MCP
* Prompt List Changed Notification</a>
*/
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface McpPromptListChanged {

/**
* Used as connection or client identifier to select the MCP client that the prompt
* change listener is associated with. If not specified, the listener is applied to
* all clients and will receive notifications from any connected MCP server that
* supports prompt list change notifications.
* @return the client identifier, or empty string to listen to all clients
*/
String clientId() default "";

}
Loading