diff --git a/README.md b/README.md index 80d8c7f..34dee65 100644 --- a/README.md +++ b/README.md @@ -94,10 +94,10 @@ Each operation type has both synchronous and asynchronous implementations, allow ### Annotations #### Client -- **`@McpLogging`** - Annotates methods that handle logging message notifications from MCP servers (requires `clientId` parameter) -- **`@McpSampling`** - Annotates methods that handle sampling requests from MCP servers (requires `clientId` parameter) -- **`@McpElicitation`** - Annotates methods that handle elicitation requests to gather additional information from users (requires `clientId` parameter) -- **`@McpProgress`** - Annotates methods that handle progress notifications for long-running operations (requires `clientId` parameter) +- **`@McpLogging`** - Annotates methods that handle logging message notifications from MCP servers (requires `clients` parameter) +- **`@McpSampling`** - Annotates methods that handle sampling requests from MCP servers (requires `clients` parameter) +- **`@McpElicitation`** - Annotates methods that handle elicitation requests to gather additional information from users (requires `clients` parameter) +- **`@McpProgress`** - Annotates methods that handle progress notifications for long-running operations (requires `clients` 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 @@ -905,10 +905,10 @@ public class LoggingHandler { /** * Handle logging message notifications with a single parameter. - * Note: clientId is now required for all @McpLogging annotations. + * Note: clients are required for all @McpLogging annotations. * @param notification The logging message notification */ - @McpLogging(clientId = "default-client") + @McpLogging(clients = "default-client") public void handleLoggingMessage(LoggingMessageNotification notification) { System.out.println("Received logging message: " + notification.level() + " - " + notification.logger() + " - " + notification.data()); @@ -916,12 +916,12 @@ public class LoggingHandler { /** * Handle logging message notifications with individual parameters. - * Note: clientId is now required for all @McpLogging annotations. + * Note: clients are required for all @McpLogging annotations. * @param level The logging level * @param logger The logger name * @param data The log message data */ - @McpLogging(clientId = "default-client") + @McpLogging(clients = "default-client") public void handleLoggingMessageWithParams(LoggingLevel level, String logger, String data) { System.out.println("Received logging message with params: " + level + " - " + logger + " - " + data); } @@ -930,7 +930,7 @@ public class LoggingHandler { * Handle logging message notifications for a specific client. * @param notification The logging message notification */ - @McpLogging(clientId = "client-1") + @McpLogging(clients = "client-1") public void handleClient1LoggingMessage(LoggingMessageNotification notification) { System.out.println("Client-1 logging message: " + notification.level() + " - " + notification.data()); } @@ -939,7 +939,7 @@ public class LoggingHandler { * Handle logging message notifications for another specific client. * @param notification The logging message notification */ - @McpLogging(clientId = "client-2") + @McpLogging(clients = "client-2") public void handleClient2LoggingMessage(LoggingMessageNotification notification) { System.out.println("Client-2 logging message: " + notification.level() + " - " + notification.data()); } @@ -971,11 +971,11 @@ public class SamplingHandler { /** * Handle sampling requests with a synchronous implementation. - * Note: clientId is now required for all @McpSampling annotations. + * Note: clients are required for all @McpSampling annotations. * @param request The create message request * @return The create message result */ - @McpSampling(clientId = "default-client") + @McpSampling(clients = "default-client") public CreateMessageResult handleSamplingRequest(CreateMessageRequest request) { // Process the request and generate a response return CreateMessageResult.builder() @@ -990,7 +990,7 @@ public class SamplingHandler { * @param request The create message request * @return The create message result */ - @McpSampling(clientId = "client-1") + @McpSampling(clients = "client-1") public CreateMessageResult handleClient1SamplingRequest(CreateMessageRequest request) { return CreateMessageResult.builder() .role(Role.ASSISTANT) @@ -1004,11 +1004,11 @@ public class AsyncSamplingHandler { /** * Handle sampling requests with an asynchronous implementation. - * Note: clientId is now required for all @McpSampling annotations. + * Note: clients are required for all @McpSampling annotations. * @param request The create message request * @return A Mono containing the create message result */ - @McpSampling(clientId = "default-client") + @McpSampling(clients = "default-client") public Mono handleAsyncSamplingRequest(CreateMessageRequest request) { return Mono.just(CreateMessageResult.builder() .role(Role.ASSISTANT) @@ -1022,7 +1022,7 @@ public class AsyncSamplingHandler { * @param request The create message request * @return A Mono containing the create message result */ - @McpSampling(clientId = "client-2") + @McpSampling(clients = "client-2") public Mono handleClient2AsyncSamplingRequest(CreateMessageRequest request) { return Mono.just(CreateMessageResult.builder() .role(Role.ASSISTANT) @@ -1079,10 +1079,10 @@ public class ProgressHandler { /** * Handle progress notifications with a single parameter. - * Note: clientId is now required for all @McpProgress annotations. + * Note: clients are required for all @McpProgress annotations. * @param notification The progress notification */ - @McpProgress(clientId = "default-client") + @McpProgress(clients = "default-client") public void handleProgressNotification(ProgressNotification notification) { System.out.println(String.format("Progress: %.2f%% - %s", notification.progress() * 100, @@ -1091,13 +1091,13 @@ public class ProgressHandler { /** * Handle progress notifications with individual parameters. - * Note: clientId is now required for all @McpProgress annotations. + * Note: clients are 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(clientId = "default-client") + @McpProgress(clients = "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", @@ -1112,7 +1112,7 @@ public class ProgressHandler { * Handle progress notifications for a specific client. * @param notification The progress notification */ - @McpProgress(clientId = "client-1") + @McpProgress(clients = "client-1") public void handleClient1Progress(ProgressNotification notification) { System.out.println(String.format("Client-1 Progress: %.2f%% - %s", notification.progress() * 100, @@ -1144,7 +1144,7 @@ public class AsyncProgressHandler { * @param message Optional message * @return A Mono that completes when the notification is handled */ - @McpProgress(clientId = "client-2") + @McpProgress(clients = "client-2") public Mono handleClient2AsyncProgress( String progressToken, double progress, @@ -1215,7 +1215,7 @@ public class ToolListChangedHandler { * Handle tool list change notifications for a specific client. * @param updatedTools The updated list of tools after the change */ - @McpToolListChanged(clientId = "client-1") + @McpToolListChanged(clients = "client-1") public void handleClient1ToolListChanged(List updatedTools) { System.out.println("Client-1 tool list updated with " + updatedTools.size() + " tools"); // Process the updated tool list for client-1 @@ -1226,16 +1226,16 @@ public class ToolListChangedHandler { * Handle tool list change notifications for another specific client. * @param updatedTools The updated list of tools after the change */ - @McpToolListChanged(clientId = "client-2") + @McpToolListChanged(clients = "client-2") public void handleClient2ToolListChanged(List updatedTools) { System.out.println("Client-2 tool list updated with " + updatedTools.size() + " tools"); // Process the updated tool list for client-2 updateClientToolCache("client-2", updatedTools); } - private void updateClientToolCache(String clientId, List tools) { + private void updateClientToolCache(String[] clients, List tools) { // Implementation to update tool cache for specific client - System.out.println("Updated tool cache for " + clientId + " with " + tools.size() + " tools"); + System.out.println("Updated tool cache for " + clients + " with " + tools.size() + " tools"); } } @@ -1260,7 +1260,7 @@ public class AsyncToolListChangedHandler { * @param updatedTools The updated list of tools after the change * @return A Mono that completes when the notification is handled */ - @McpToolListChanged(clientId = "client-2") + @McpToolListChanged(clients = "client-2") public Mono handleClient2AsyncToolListChanged(List updatedTools) { return Mono.fromRunnable(() -> { System.out.println("Client-2 async tool list update: " + updatedTools.size() + " tools"); @@ -1274,9 +1274,9 @@ public class AsyncToolListChangedHandler { System.out.println("Processing tool list update with " + tools.size() + " tools"); } - private void processClientToolListUpdate(String clientId, List tools) { + private void processClientToolListUpdate(String[] clients, List tools) { // Implementation to process tool list update for specific client - System.out.println("Processing tool list update for " + clientId + " with " + tools.size() + " tools"); + System.out.println("Processing tool list update for " + clients + " with " + tools.size() + " tools"); } } @@ -1333,7 +1333,7 @@ public class ResourceListChangedHandler { * Handle resource list change notifications for a specific client. * @param updatedResources The updated list of resources after the change */ - @McpResourceListChanged(clientId = "client-1") + @McpResourceListChanged(clients = "client-1") public void handleClient1ResourceListChanged(List updatedResources) { System.out.println("Client-1 resource list updated with " + updatedResources.size() + " resources"); // Process the updated resource list for client-1 @@ -1344,16 +1344,16 @@ public class ResourceListChangedHandler { * Handle resource list change notifications for another specific client. * @param updatedResources The updated list of resources after the change */ - @McpResourceListChanged(clientId = "client-2") + @McpResourceListChanged(clients = "client-2") public void handleClient2ResourceListChanged(List updatedResources) { System.out.println("Client-2 resource list updated with " + updatedResources.size() + " resources"); // Process the updated resource list for client-2 updateClientResourceCache("client-2", updatedResources); } - private void updateClientResourceCache(String clientId, List resources) { + private void updateClientResourceCache(String[] clients, List resources) { // Implementation to update resource cache for specific client - System.out.println("Updated resource cache for " + clientId + " with " + resources.size() + " resources"); + System.out.println("Updated resource cache for " + clients + " with " + resources.size() + " resources"); } } @@ -1378,7 +1378,7 @@ public class AsyncResourceListChangedHandler { * @param updatedResources The updated list of resources after the change * @return A Mono that completes when the notification is handled */ - @McpResourceListChanged(clientId = "client-2") + @McpResourceListChanged(clients = "client-2") public Mono handleClient2AsyncResourceListChanged(List updatedResources) { return Mono.fromRunnable(() -> { System.out.println("Client-2 async resource list update: " + updatedResources.size() + " resources"); @@ -1392,9 +1392,9 @@ public class AsyncResourceListChangedHandler { System.out.println("Processing resource list update with " + resources.size() + " resources"); } - private void processClientResourceListUpdate(String clientId, List resources) { + private void processClientResourceListUpdate(String[] clients, List resources) { // Implementation to process resource list update for specific client - System.out.println("Processing resource list update for " + clientId + " with " + resources.size() + " resources"); + System.out.println("Processing resource list update for " + clients + " with " + resources.size() + " resources"); } } @@ -1451,7 +1451,7 @@ public class PromptListChangedHandler { * Handle prompt list change notifications for a specific client. * @param updatedPrompts The updated list of prompts after the change */ - @McpPromptListChanged(clientId = "client-1") + @McpPromptListChanged(clients = "client-1") public void handleClient1PromptListChanged(List updatedPrompts) { System.out.println("Client-1 prompt list updated with " + updatedPrompts.size() + " prompts"); // Process the updated prompt list for client-1 @@ -1462,16 +1462,16 @@ public class PromptListChangedHandler { * Handle prompt list change notifications for another specific client. * @param updatedPrompts The updated list of prompts after the change */ - @McpPromptListChanged(clientId = "client-2") + @McpPromptListChanged(clients = "client-2") public void handleClient2PromptListChanged(List 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 prompts) { + private void updateClientPromptCache(String[] clients, List prompts) { // Implementation to update prompt cache for specific client - System.out.println("Updated prompt cache for " + clientId + " with " + prompts.size() + " prompts"); + System.out.println("Updated prompt cache for " + clients + " with " + prompts.size() + " prompts"); } } @@ -1496,7 +1496,7 @@ public class AsyncPromptListChangedHandler { * @param updatedPrompts The updated list of prompts after the change * @return A Mono that completes when the notification is handled */ - @McpPromptListChanged(clientId = "client-2") + @McpPromptListChanged(clients = "client-2") public Mono handleClient2AsyncPromptListChanged(List updatedPrompts) { return Mono.fromRunnable(() -> { System.out.println("Client-2 async prompt list update: " + updatedPrompts.size() + " prompts"); @@ -1510,9 +1510,9 @@ public class AsyncPromptListChangedHandler { System.out.println("Processing prompt list update with " + prompts.size() + " prompts"); } - private void processClientPromptListUpdate(String clientId, List prompts) { + private void processClientPromptListUpdate(String[] clients, List prompts) { // Implementation to process prompt list update for specific client - System.out.println("Processing prompt list update for " + clientId + " with " + prompts.size() + " prompts"); + System.out.println("Processing prompt list update for " + clients + " with " + prompts.size() + " prompts"); } } @@ -1555,11 +1555,11 @@ public class ElicitationHandler { /** * Handle elicitation requests with a synchronous implementation. - * Note: clientId is required for all @McpElicitation annotations. + * Note: clients are required for all @McpElicitation annotations. * @param request The elicitation request * @return The elicitation result */ - @McpElicitation(clientId = "default-client") + @McpElicitation(clients = "default-client") public ElicitResult handleElicitationRequest(ElicitRequest request) { // Example implementation that accepts the request and returns user data // In a real implementation, this would present a form to the user @@ -1593,11 +1593,11 @@ public class ElicitationHandler { /** * Handle elicitation requests that should be declined. - * Note: clientId is now required for all @McpElicitation annotations. + * Note: clients are required for all @McpElicitation annotations. * @param request The elicitation request * @return The elicitation result with decline action */ - @McpElicitation(clientId = "default-client") + @McpElicitation(clients = "default-client") public ElicitResult handleDeclineElicitationRequest(ElicitRequest request) { // Example of declining an elicitation request return new ElicitResult(ElicitResult.Action.DECLINE, null); @@ -1608,7 +1608,7 @@ public class ElicitationHandler { * @param request The elicitation request * @return The elicitation result */ - @McpElicitation(clientId = "client-1") + @McpElicitation(clients = "client-1") public ElicitResult handleClient1ElicitationRequest(ElicitRequest request) { Map userData = new HashMap<>(); userData.put("client", "client-1"); @@ -1621,11 +1621,11 @@ public class AsyncElicitationHandler { /** * Handle elicitation requests with an asynchronous implementation. - * Note: clientId is required for all @McpElicitation annotations. + * Note: clients are required for all @McpElicitation annotations. * @param request The elicitation request * @return A Mono containing the elicitation result */ - @McpElicitation(clientId = "default-client") + @McpElicitation(clients = "default-client") public Mono handleAsyncElicitationRequest(ElicitRequest request) { return Mono.fromCallable(() -> { // Simulate async processing of the elicitation request @@ -1643,11 +1643,11 @@ public class AsyncElicitationHandler { /** * Handle elicitation requests that might be cancelled. - * Note: clientId is required for all @McpElicitation annotations. + * Note: clients are required for all @McpElicitation annotations. * @param request The elicitation request * @return A Mono containing the elicitation result with cancel action */ - @McpElicitation(clientId = "default-client") + @McpElicitation(clients = "default-client") public Mono handleCancelElicitationRequest(ElicitRequest request) { return Mono.just(new ElicitResult(ElicitResult.Action.CANCEL, null)); } @@ -1657,7 +1657,7 @@ public class AsyncElicitationHandler { * @param request The elicitation request * @return A Mono containing the elicitation result */ - @McpElicitation(clientId = "client-2") + @McpElicitation(clients = "client-2") public Mono handleClient2AsyncElicitationRequest(ElicitRequest request) { return Mono.fromCallable(() -> { Map userData = new HashMap<>(); diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/annotation/McpElicitation.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/annotation/McpElicitation.java index e7b0afb..66992d0 100644 --- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/annotation/McpElicitation.java +++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/annotation/McpElicitation.java @@ -24,7 +24,7 @@ * *

* Example usage:

{@code
- * @McpElicitation(clientId = "my-client-id")
+ * @McpElicitation(clients = "my-client-id")
  * public ElicitResult handleElicitationRequest(ElicitRequest request) {
  *     return ElicitResult.builder()
  *         .message("Generated response")
@@ -33,7 +33,7 @@
  *         .build();
  * }
  *
- * @McpElicitation(clientId = "my-client-id")
+ * @McpElicitation(clients = "my-client-id")
  * public Mono handleAsyncElicitationRequest(ElicitRequest request) {
  *     return Mono.just(ElicitResult.builder()
  *         .message("Generated response")
@@ -53,9 +53,9 @@
 public @interface McpElicitation {
 
 	/**
-	 * Used as connection or client identifier to select the MCP client, the elicitation
+	 * Used as connection or client identifier to select the MCP clients, the elicitation
 	 * method is associated with.
 	 */
-	String clientId();
+	String[] clients();
 
 }
diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/annotation/McpLogging.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/annotation/McpLogging.java
index 3d07fb9..d7ec815 100644
--- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/annotation/McpLogging.java
+++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/annotation/McpLogging.java
@@ -52,9 +52,9 @@
 public @interface McpLogging {
 
 	/**
-	 * Used as connection or client identifier to select the MCP clients, the logging
-	 * consumer is associated with. At least one client ID must be specified.
+	 * Used as connection or clients identifier to select the MCP clients, the logging
+	 * consumer is associated with. At least one client identifier must be specified.
 	 */
-	String clientId();
+	String[] clients();
 
 }
diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/annotation/McpProgress.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/annotation/McpProgress.java
index ecb71e7..977b881 100644
--- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/annotation/McpProgress.java
+++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/annotation/McpProgress.java
@@ -37,8 +37,8 @@
 
 	/**
 	 * Used as connection or client identifier to select the MCP client, the progress
-	 * consumer is associated with.
+	 * consumer is associated with. At least one client identifier must be specified.
 	 */
-	String clientId();
+	String[] clients();
 
 }
diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/annotation/McpPromptListChanged.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/annotation/McpPromptListChanged.java
index e537370..adbe308 100644
--- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/annotation/McpPromptListChanged.java
+++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/annotation/McpPromptListChanged.java
@@ -28,14 +28,14 @@
  *
  * 

* Example usage:

{@code
- * @McpPromptListChanged
+ * @McpPromptListChanged(clients = "test-client")
  * public void onPromptListChanged(List 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
  * }
  *
- * @McpPromptListChanged
+ * @McpPromptListChanged(clients = "test-client")
  * public Mono onPromptListChangedAsync(List updatedPrompts) {
  *     // Handle prompt list change notification asynchronously
  *     return processUpdatedPrompts(updatedPrompts);
@@ -54,11 +54,10 @@
 
 	/**
 	 * 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.
+	 * change listener is associated with. At least one client identifier must be
+	 * specified.
 	 * @return the client identifier, or empty string to listen to all clients
 	 */
-	String clientId() default "";
+	String[] clients();
 
 }
diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/annotation/McpResourceListChanged.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/annotation/McpResourceListChanged.java
index 4e55092..d55a3e4 100644
--- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/annotation/McpResourceListChanged.java
+++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/annotation/McpResourceListChanged.java
@@ -28,14 +28,14 @@
  *
  * 

* Example usage:

{@code
- * @McpResourceListChanged
+ * @McpResourceListChanged(clients = "test-client")
  * public void onResourceListChanged(List updatedResources) {
  *     // Handle resource list change notification with the updated resources
  *     logger.info("Resource list updated, now contains {} resources", updatedResources.size());
  *     // Process the updated resource list
  * }
  *
- * @McpResourceListChanged
+ * @McpResourceListChanged(clients = "test-client")
  * public Mono onResourceListChangedAsync(List updatedResources) {
  *     // Handle resource list change notification asynchronously
  *     return processUpdatedResources(updatedResources);
@@ -53,12 +53,10 @@
 public @interface McpResourceListChanged {
 
 	/**
-	 * Used as connection or client identifier to select the MCP client that the resource
-	 * 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 resource list change notifications.
+	 * Used as connection or client identifier to select the MCP clients that the resource
+	 * change listener is associated with.
 	 * @return the client identifier, or empty string to listen to all clients
 	 */
-	String clientId() default "";
+	String[] clients();
 
 }
diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/annotation/McpSampling.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/annotation/McpSampling.java
index 0110495..b6ae39f 100644
--- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/annotation/McpSampling.java
+++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/annotation/McpSampling.java
@@ -28,7 +28,7 @@
  *
  * 

* Example usage:

{@code
- * @McpSampling(clientId = "test-client")
+ * @McpSampling(clients = "test-client")
  * public CreateMessageResult handleSamplingRequest(CreateMessageRequest request) {
  *     // Process the request and return a result
  *     return CreateMessageResult.builder()
@@ -36,7 +36,7 @@
  *         .build();
  * }
  *
- * @McpSampling(clientId = "test-client")
+ * @McpSampling(clients = "test-client")
  * public Mono handleAsyncSamplingRequest(CreateMessageRequest request) {
  *     // Process the request asynchronously and return a result
  *     return Mono.just(CreateMessageResult.builder()
@@ -58,6 +58,6 @@
 	 * Used as connection or client identifier to select the MCP client, the sampling
 	 * method is associated with.
 	 */
-	String clientId();
+	String[] clients();
 
 }
diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/annotation/McpToolListChanged.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/annotation/McpToolListChanged.java
index 336150a..7091db3 100644
--- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/annotation/McpToolListChanged.java
+++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/annotation/McpToolListChanged.java
@@ -28,14 +28,14 @@
  *
  * 

* Example usage:

{@code
- * @McpToolListChanged
+ * @McpToolListChanged(clients = "test-client")
  * public void onToolListChanged(List updatedTools) {
  *     // Handle tool list change notification with the updated tools
  *     logger.info("Tool list updated, now contains {} tools", updatedTools.size());
  *     // Process the updated tool list
  * }
  *
- * @McpToolListChanged
+ * @McpToolListChanged(clients = "test-client")
  * public Mono onToolListChangedAsync(List updatedTools) {
  *     // Handle tool list change notification asynchronously
  *     return processUpdatedTools(updatedTools);
@@ -53,12 +53,10 @@
 public @interface McpToolListChanged {
 
 	/**
-	 * Used as connection or client identifier to select the MCP client that the tool
-	 * 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 tool list change notifications.
-	 * @return the client identifier, or empty string to listen to all clients
+	 * Used as connection or client identifier to select the MCP clients that the tool
+	 * change listener is associated with.
+	 * @return the client identifiers, or empty array to listen to all clients
 	 */
-	String clientId() default "";
+	String[] clients();
 
 }
diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/changed/prompt/AsyncPromptListChangedSpecification.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/changed/prompt/AsyncPromptListChangedSpecification.java
index 69d8b4d..0162c95 100644
--- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/changed/prompt/AsyncPromptListChangedSpecification.java
+++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/changed/prompt/AsyncPromptListChangedSpecification.java
@@ -5,11 +5,21 @@
 package org.springaicommunity.mcp.method.changed.prompt;
 
 import java.util.List;
+import java.util.Objects;
 import java.util.function.Function;
 
 import io.modelcontextprotocol.spec.McpSchema;
 import reactor.core.publisher.Mono;
 
-public record AsyncPromptListChangedSpecification(String clientId,
+public record AsyncPromptListChangedSpecification(String[] clients,
 		Function, Mono> promptListChangeHandler) {
+
+	public AsyncPromptListChangedSpecification {
+		Objects.requireNonNull(clients, "clients must not be null");
+		if (clients.length == 0) {
+			throw new IllegalArgumentException("At least one client Id must be specified");
+		}
+		Objects.requireNonNull(promptListChangeHandler, "promptListChangeHandler must not be null");
+	}
+
 }
diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/changed/prompt/SyncPromptListChangedSpecification.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/changed/prompt/SyncPromptListChangedSpecification.java
index c7b78fb..8b22f4e 100644
--- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/changed/prompt/SyncPromptListChangedSpecification.java
+++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/changed/prompt/SyncPromptListChangedSpecification.java
@@ -5,10 +5,20 @@
 package org.springaicommunity.mcp.method.changed.prompt;
 
 import java.util.List;
+import java.util.Objects;
 import java.util.function.Consumer;
 
 import io.modelcontextprotocol.spec.McpSchema;
 
-public record SyncPromptListChangedSpecification(String clientId,
+public record SyncPromptListChangedSpecification(String[] clients,
 		Consumer> promptListChangeHandler) {
+
+	public SyncPromptListChangedSpecification {
+		Objects.requireNonNull(clients, "clients must not be null");
+		if (clients.length == 0) {
+			throw new IllegalArgumentException("At least one client Id must be specified");
+		}
+		Objects.requireNonNull(promptListChangeHandler, "promptListChangeHandler must not be null");
+	}
+
 }
diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/changed/resource/AsyncResourceListChangedSpecification.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/changed/resource/AsyncResourceListChangedSpecification.java
index 66cdf79..f9b8219 100644
--- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/changed/resource/AsyncResourceListChangedSpecification.java
+++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/changed/resource/AsyncResourceListChangedSpecification.java
@@ -4,12 +4,23 @@
 
 package org.springaicommunity.mcp.method.changed.resource;
 
+import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 import java.util.function.Function;
 
 import io.modelcontextprotocol.spec.McpSchema;
 import reactor.core.publisher.Mono;
 
-public record AsyncResourceListChangedSpecification(String clientId,
+public record AsyncResourceListChangedSpecification(String[] clients,
 		Function, Mono> resourceListChangeHandler) {
+
+	public AsyncResourceListChangedSpecification {
+		Objects.requireNonNull(clients, "clients must not be null");
+		if (clients.length == 0 || Arrays.stream(clients).map(String::trim).anyMatch(String::isEmpty)) {
+			throw new IllegalArgumentException("clients must not be empty");
+		}
+		Objects.requireNonNull(resourceListChangeHandler, "resourceListChangeHandler must not be null");
+	}
+
 }
diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/changed/resource/SyncResourceListChangedSpecification.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/changed/resource/SyncResourceListChangedSpecification.java
index d81a0c6..c6bfe23 100644
--- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/changed/resource/SyncResourceListChangedSpecification.java
+++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/changed/resource/SyncResourceListChangedSpecification.java
@@ -4,11 +4,22 @@
 
 package org.springaicommunity.mcp.method.changed.resource;
 
+import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 import java.util.function.Consumer;
 
 import io.modelcontextprotocol.spec.McpSchema;
 
-public record SyncResourceListChangedSpecification(String clientId,
+public record SyncResourceListChangedSpecification(String[] clients,
 		Consumer> resourceListChangeHandler) {
+
+	public SyncResourceListChangedSpecification {
+		Objects.requireNonNull(clients, "clients must not be null");
+		if (clients.length == 0 || Arrays.stream(clients).map(String::trim).anyMatch(String::isEmpty)) {
+			throw new IllegalArgumentException("clients must not be empty");
+		}
+		Objects.requireNonNull(resourceListChangeHandler, "resourceListChangeHandler must not be null");
+	}
+
 }
diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/changed/tool/AsyncToolListChangedSpecification.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/changed/tool/AsyncToolListChangedSpecification.java
index 98b9559..91adf7f 100644
--- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/changed/tool/AsyncToolListChangedSpecification.java
+++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/changed/tool/AsyncToolListChangedSpecification.java
@@ -4,12 +4,23 @@
 
 package org.springaicommunity.mcp.method.changed.tool;
 
+import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 import java.util.function.Function;
 
 import io.modelcontextprotocol.spec.McpSchema;
 import reactor.core.publisher.Mono;
 
-public record AsyncToolListChangedSpecification(String clientId,
+public record AsyncToolListChangedSpecification(String[] clients,
 		Function, Mono> toolListChangeHandler) {
+
+	public AsyncToolListChangedSpecification {
+		Objects.requireNonNull(clients, "clients must not be null");
+		if (clients.length == 0 || Arrays.stream(clients).map(String::trim).anyMatch(String::isEmpty)) {
+			throw new IllegalArgumentException("clients must not be empty");
+		}
+		Objects.requireNonNull(toolListChangeHandler, "toolListChangeHandler must not be null");
+	}
+
 }
\ No newline at end of file
diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/changed/tool/SyncToolListChangedSpecification.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/changed/tool/SyncToolListChangedSpecification.java
index ed074ca..45cf680 100644
--- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/changed/tool/SyncToolListChangedSpecification.java
+++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/changed/tool/SyncToolListChangedSpecification.java
@@ -4,10 +4,21 @@
 
 package org.springaicommunity.mcp.method.changed.tool;
 
+import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 import java.util.function.Consumer;
 
 import io.modelcontextprotocol.spec.McpSchema;
 
-public record SyncToolListChangedSpecification(String clientId, Consumer> toolListChangeHandler) {
+public record SyncToolListChangedSpecification(String[] clients, Consumer> toolListChangeHandler) {
+
+	public SyncToolListChangedSpecification {
+		Objects.requireNonNull(clients, "clients must not be null");
+		if (clients.length == 0 || Arrays.stream(clients).map(String::trim).anyMatch(String::isEmpty)) {
+			throw new IllegalArgumentException("clients must not be empty");
+		}
+		Objects.requireNonNull(toolListChangeHandler, "toolListChangeHandler must not be null");
+	}
+
 }
\ No newline at end of file
diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/elicitation/AsyncElicitationSpecification.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/elicitation/AsyncElicitationSpecification.java
index da1105e..116b483 100644
--- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/elicitation/AsyncElicitationSpecification.java
+++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/elicitation/AsyncElicitationSpecification.java
@@ -4,6 +4,7 @@
 
 package org.springaicommunity.mcp.method.elicitation;
 
+import java.util.Arrays;
 import java.util.Objects;
 import java.util.function.Function;
 
@@ -11,13 +12,13 @@
 import io.modelcontextprotocol.spec.McpSchema.ElicitResult;
 import reactor.core.publisher.Mono;
 
-public record AsyncElicitationSpecification(String clientId,
+public record AsyncElicitationSpecification(String[] clients,
 		Function> elicitationHandler) {
 
 	public AsyncElicitationSpecification {
-		Objects.requireNonNull(clientId, "clientId must not be null");
-		if (clientId.trim().isEmpty()) {
-			throw new IllegalArgumentException("clientId must not be empty");
+		Objects.requireNonNull(clients, "clients must not be null");
+		if (clients.length == 0 || Arrays.stream(clients).map(String::trim).anyMatch(String::isEmpty)) {
+			throw new IllegalArgumentException("clients must not be empty");
 		}
 		Objects.requireNonNull(elicitationHandler, "elicitationHandler must not be null");
 	}
diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/elicitation/SyncElicitationSpecification.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/elicitation/SyncElicitationSpecification.java
index 53eda1c..d15ab01 100644
--- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/elicitation/SyncElicitationSpecification.java
+++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/elicitation/SyncElicitationSpecification.java
@@ -4,17 +4,18 @@
 
 package org.springaicommunity.mcp.method.elicitation;
 
+import java.util.Arrays;
 import java.util.Objects;
 import java.util.function.Function;
 
 import io.modelcontextprotocol.spec.McpSchema.ElicitRequest;
 import io.modelcontextprotocol.spec.McpSchema.ElicitResult;
 
-public record SyncElicitationSpecification(String clientId, Function elicitationHandler) {
+public record SyncElicitationSpecification(String[] clients, Function elicitationHandler) {
 	public SyncElicitationSpecification {
-		Objects.requireNonNull(clientId, "clientId must not be null");
-		if (clientId.trim().isEmpty()) {
-			throw new IllegalArgumentException("clientId must not be empty");
+		Objects.requireNonNull(clients, "clients must not be null");
+		if (clients.length == 0 || Arrays.stream(clients).map(String::trim).anyMatch(String::isEmpty)) {
+			throw new IllegalArgumentException("clients must not be empty");
 		}
 		Objects.requireNonNull(elicitationHandler, "elicitationHandler must not be null");
 	}
diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/logging/AsyncLoggingSpecification.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/logging/AsyncLoggingSpecification.java
index d19412a..24e95ce 100644
--- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/logging/AsyncLoggingSpecification.java
+++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/logging/AsyncLoggingSpecification.java
@@ -4,19 +4,20 @@
 
 package org.springaicommunity.mcp.method.logging;
 
+import java.util.Arrays;
 import java.util.Objects;
 import java.util.function.Function;
 
 import io.modelcontextprotocol.spec.McpSchema.LoggingMessageNotification;
 import reactor.core.publisher.Mono;
 
-public record AsyncLoggingSpecification(String clientId,
+public record AsyncLoggingSpecification(String[] clients,
 		Function> loggingHandler) {
 
 	public AsyncLoggingSpecification {
-		Objects.requireNonNull(clientId, "clientId must not be null");
-		if (clientId.trim().isEmpty()) {
-			throw new IllegalArgumentException("clientId must not be empty");
+		Objects.requireNonNull(clients, "clients must not be null");
+		if (clients.length == 0 || Arrays.stream(clients).anyMatch(String::isEmpty)) {
+			throw new IllegalArgumentException("clients must not be empty");
 		}
 		Objects.requireNonNull(loggingHandler, "loggingHandler must not be null");
 	}
diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/logging/SyncLoggingSpecification.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/logging/SyncLoggingSpecification.java
index 96b1b3c..13b4461 100644
--- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/logging/SyncLoggingSpecification.java
+++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/logging/SyncLoggingSpecification.java
@@ -4,17 +4,18 @@
 
 package org.springaicommunity.mcp.method.logging;
 
+import java.util.Arrays;
 import java.util.Objects;
 import java.util.function.Consumer;
 
 import io.modelcontextprotocol.spec.McpSchema.LoggingMessageNotification;
 
-public record SyncLoggingSpecification(String clientId, Consumer loggingHandler) {
+public record SyncLoggingSpecification(String[] clients, Consumer loggingHandler) {
 
 	public SyncLoggingSpecification {
-		Objects.requireNonNull(clientId, "clientId must not be null");
-		if (clientId.trim().isEmpty()) {
-			throw new IllegalArgumentException("clientId must not be empty");
+		Objects.requireNonNull(clients, "clients must not be null");
+		if (clients.length == 0 || Arrays.stream(clients).anyMatch(String::isEmpty)) {
+			throw new IllegalArgumentException("clients must not be empty");
 		}
 		Objects.requireNonNull(loggingHandler, "loggingHandler must not be null");
 	}
diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/progress/AsyncProgressSpecification.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/progress/AsyncProgressSpecification.java
index 74b9f67..076f9d3 100644
--- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/progress/AsyncProgressSpecification.java
+++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/progress/AsyncProgressSpecification.java
@@ -4,6 +4,7 @@
 
 package org.springaicommunity.mcp.method.progress;
 
+import java.util.Arrays;
 import java.util.Objects;
 import java.util.function.Function;
 
@@ -17,11 +18,11 @@
  * @param progressHandler The function that handles progress notifications asynchronously
  * @author Christian Tzolov
  */
-public record AsyncProgressSpecification(String clientId, Function> progressHandler) {
+public record AsyncProgressSpecification(String[] clients, Function> 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(clients, "clients must not be null");
+		if (clients.length == 0 || Arrays.stream(clients).map(String::trim).anyMatch(String::isEmpty)) {
+			throw new IllegalArgumentException("At least one client Id must be specified");
 		}
 		Objects.requireNonNull(progressHandler, "progressHandler must not be null");
 	}
diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/progress/SyncProgressSpecification.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/progress/SyncProgressSpecification.java
index e72f708..7e62fc4 100644
--- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/progress/SyncProgressSpecification.java
+++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/progress/SyncProgressSpecification.java
@@ -4,6 +4,7 @@
 
 package org.springaicommunity.mcp.method.progress;
 
+import java.util.Arrays;
 import java.util.Objects;
 import java.util.function.Consumer;
 
@@ -16,12 +17,12 @@
  * @param progressHandler The consumer that handles progress notifications
  * @author Christian Tzolov
  */
-public record SyncProgressSpecification(String clientId, Consumer progressHandler) {
+public record SyncProgressSpecification(String[] clients, Consumer 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(clients, "clients must not be null");
+		if (clients.length == 0 || Arrays.stream(clients).map(String::trim).anyMatch(String::isEmpty)) {
+			throw new IllegalArgumentException("At least one client Id must be specified");
 		}
 		Objects.requireNonNull(progressHandler, "progressHandler must not be null");
 	}
diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/sampling/AsyncSamplingSpecification.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/sampling/AsyncSamplingSpecification.java
index b6aec54..79747a4 100644
--- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/sampling/AsyncSamplingSpecification.java
+++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/sampling/AsyncSamplingSpecification.java
@@ -1,5 +1,6 @@
 package org.springaicommunity.mcp.method.sampling;
 
+import java.util.Arrays;
 import java.util.Objects;
 import java.util.function.Function;
 
@@ -7,13 +8,13 @@
 import io.modelcontextprotocol.spec.McpSchema.CreateMessageResult;
 import reactor.core.publisher.Mono;
 
-public record AsyncSamplingSpecification(String clientId,
+public record AsyncSamplingSpecification(String[] clients,
 		Function> samplingHandler) {
 
 	public AsyncSamplingSpecification {
-		Objects.requireNonNull(clientId, "clientId must not be null");
-		if (clientId.trim().isEmpty()) {
-			throw new IllegalArgumentException("clientId must not be empty");
+		Objects.requireNonNull(clients, "clients must not be null");
+		if (clients.length == 0 || Arrays.stream(clients).map(String::trim).anyMatch(String::isEmpty)) {
+			throw new IllegalArgumentException("clients must not be empty");
 		}
 		Objects.requireNonNull(samplingHandler, "samplingHandler must not be null");
 	}
diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/sampling/SyncSamplingSpecification.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/sampling/SyncSamplingSpecification.java
index 11d3e5c..1546796 100644
--- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/sampling/SyncSamplingSpecification.java
+++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/method/sampling/SyncSamplingSpecification.java
@@ -1,18 +1,19 @@
 package org.springaicommunity.mcp.method.sampling;
 
+import java.util.Arrays;
 import java.util.Objects;
 import java.util.function.Function;
 
 import io.modelcontextprotocol.spec.McpSchema.CreateMessageRequest;
 import io.modelcontextprotocol.spec.McpSchema.CreateMessageResult;
 
-public record SyncSamplingSpecification(String clientId,
+public record SyncSamplingSpecification(String[] clients,
 		Function samplingHandler) {
 
 	public SyncSamplingSpecification {
-		Objects.requireNonNull(clientId, "clientId must not be null");
-		if (clientId.trim().isEmpty()) {
-			throw new IllegalArgumentException("clientId must not be empty");
+		Objects.requireNonNull(clients, "clients must not be null");
+		if (clients.length == 0 || Arrays.stream(clients).map(String::trim).anyMatch(String::isEmpty)) {
+			throw new IllegalArgumentException("clients must not be empty");
 		}
 		Objects.requireNonNull(samplingHandler, "samplingHandler must not be null");
 	}
diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/prompt/AsyncMcpPromptListChangedProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/prompt/AsyncMcpPromptListChangedProvider.java
index 135effb..8eb6e25 100644
--- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/prompt/AsyncMcpPromptListChangedProvider.java
+++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/prompt/AsyncMcpPromptListChangedProvider.java
@@ -94,7 +94,7 @@ public List getPromptListChangedSpecificati
 						.bean(consumerObject)
 						.build();
 
-					return new AsyncPromptListChangedSpecification(promptListChangedAnnotation.clientId(),
+					return new AsyncPromptListChangedSpecification(promptListChangedAnnotation.clients(),
 							methodCallback);
 				})
 				.toList())
diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/prompt/SyncMcpPromptListChangedProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/prompt/SyncMcpPromptListChangedProvider.java
index 2728899..26e3799 100644
--- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/prompt/SyncMcpPromptListChangedProvider.java
+++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/prompt/SyncMcpPromptListChangedProvider.java
@@ -92,7 +92,7 @@ public List getPromptListChangedSpecificatio
 						.promptListChanged(promptListChangedAnnotation)
 						.build();
 
-					return new SyncPromptListChangedSpecification(promptListChangedAnnotation.clientId(),
+					return new SyncPromptListChangedSpecification(promptListChangedAnnotation.clients(),
 							methodCallback);
 				})
 				.toList())
diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/resource/AsyncMcpResourceListChangedProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/resource/AsyncMcpResourceListChangedProvider.java
index 239ce90..77f947f 100644
--- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/resource/AsyncMcpResourceListChangedProvider.java
+++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/resource/AsyncMcpResourceListChangedProvider.java
@@ -94,7 +94,7 @@ public List getResourceListChangedSpecifi
 						.bean(consumerObject)
 						.build();
 
-					return new AsyncResourceListChangedSpecification(resourceListChangedAnnotation.clientId(),
+					return new AsyncResourceListChangedSpecification(resourceListChangedAnnotation.clients(),
 							methodCallback);
 				})
 				.toList())
diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/resource/SyncMcpResourceListChangedProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/resource/SyncMcpResourceListChangedProvider.java
index fcc188f..981f23a 100644
--- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/resource/SyncMcpResourceListChangedProvider.java
+++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/resource/SyncMcpResourceListChangedProvider.java
@@ -93,7 +93,7 @@ public List getResourceListChangedSpecific
 						.resourceListChanged(resourceListChangedAnnotation)
 						.build();
 
-					return new SyncResourceListChangedSpecification(resourceListChangedAnnotation.clientId(),
+					return new SyncResourceListChangedSpecification(resourceListChangedAnnotation.clients(),
 							methodCallback);
 				})
 				.toList())
diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/tool/AsyncMcpToolListChangedProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/tool/AsyncMcpToolListChangedProvider.java
index 2f7467b..91138ff 100644
--- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/tool/AsyncMcpToolListChangedProvider.java
+++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/tool/AsyncMcpToolListChangedProvider.java
@@ -93,7 +93,7 @@ public List getToolListChangedSpecifications(
 						.bean(consumerObject)
 						.build();
 
-					return new AsyncToolListChangedSpecification(toolListChangedAnnotation.clientId(), methodCallback);
+					return new AsyncToolListChangedSpecification(toolListChangedAnnotation.clients(), methodCallback);
 				})
 				.toList())
 			.flatMap(List::stream)
diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/tool/SyncMcpToolListChangedProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/tool/SyncMcpToolListChangedProvider.java
index 07db6a9..f33d70b 100644
--- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/tool/SyncMcpToolListChangedProvider.java
+++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/changed/tool/SyncMcpToolListChangedProvider.java
@@ -91,7 +91,7 @@ public List getToolListChangedSpecifications()
 						.toolListChanged(toolListChangedAnnotation)
 						.build();
 
-					return new SyncToolListChangedSpecification(toolListChangedAnnotation.clientId(), methodCallback);
+					return new SyncToolListChangedSpecification(toolListChangedAnnotation.clients(), methodCallback);
 				})
 				.toList())
 			.flatMap(List::stream)
diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/elicitation/AsyncMcpElicitationProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/elicitation/AsyncMcpElicitationProvider.java
index 17b122b..ee83abf 100644
--- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/elicitation/AsyncMcpElicitationProvider.java
+++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/elicitation/AsyncMcpElicitationProvider.java
@@ -101,7 +101,7 @@ public List getElicitationSpecifications() {
 						.elicitation(elicitationAnnotation)
 						.build();
 
-					return new AsyncElicitationSpecification(elicitationAnnotation.clientId(), methodCallback);
+					return new AsyncElicitationSpecification(elicitationAnnotation.clients(), methodCallback);
 				})
 				.toList())
 			.flatMap(List::stream)
diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/elicitation/SyncMcpElicitationProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/elicitation/SyncMcpElicitationProvider.java
index 887f5bb..a271b10 100644
--- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/elicitation/SyncMcpElicitationProvider.java
+++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/elicitation/SyncMcpElicitationProvider.java
@@ -100,7 +100,7 @@ public List getElicitationSpecifications() {
 						.elicitation(elicitationAnnotation)
 						.build();
 
-					return new SyncElicitationSpecification(elicitationAnnotation.clientId(), methodCallback);
+					return new SyncElicitationSpecification(elicitationAnnotation.clients(), methodCallback);
 				})
 				.toList())
 			.flatMap(List::stream)
diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/logging/AsyncMcpLoggingProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/logging/AsyncMcpLoggingProvider.java
index eceb1b5..03c45ee 100644
--- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/logging/AsyncMcpLoggingProvider.java
+++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/logging/AsyncMcpLoggingProvider.java
@@ -90,7 +90,7 @@ public List getLoggingSpecifications() {
 						.loggingConsumer(loggingConsumerAnnotation)
 						.build();
 
-					return new AsyncLoggingSpecification(loggingConsumerAnnotation.clientId(), methodCallback);
+					return new AsyncLoggingSpecification(loggingConsumerAnnotation.clients(), methodCallback);
 				})
 				.toList())
 			.flatMap(List::stream)
diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/logging/SyncMcpLogginProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/logging/SyncMcpLogginProvider.java
index f05b055..2279fdd 100644
--- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/logging/SyncMcpLogginProvider.java
+++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/logging/SyncMcpLogginProvider.java
@@ -90,7 +90,7 @@ public List getLoggingSpecifications() {
 						.loggingConsumer(loggingConsumerAnnotation)
 						.build();
 
-					return new SyncLoggingSpecification(loggingConsumerAnnotation.clientId(), methodCallback);
+					return new SyncLoggingSpecification(loggingConsumerAnnotation.clients(), methodCallback);
 				})
 				.toList())
 			.flatMap(List::stream)
diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/progress/AsyncMcpProgressProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/progress/AsyncMcpProgressProvider.java
index ffd6def..8d7e626 100644
--- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/progress/AsyncMcpProgressProvider.java
+++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/progress/AsyncMcpProgressProvider.java
@@ -106,7 +106,7 @@ public List getProgressSpecifications() {
 						.progress(progressAnnotation)
 						.build();
 
-					return new AsyncProgressSpecification(progressAnnotation.clientId(), methodCallback);
+					return new AsyncProgressSpecification(progressAnnotation.clients(), methodCallback);
 				})
 				.toList())
 			.flatMap(List::stream)
diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/progress/SyncMcpProgressProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/progress/SyncMcpProgressProvider.java
index f3f09a8..ef929d7 100644
--- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/progress/SyncMcpProgressProvider.java
+++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/progress/SyncMcpProgressProvider.java
@@ -91,7 +91,7 @@ public List getProgressSpecifications() {
 						.progress(progressAnnotation)
 						.build();
 
-					return new SyncProgressSpecification(progressAnnotation.clientId(), methodCallback);
+					return new SyncProgressSpecification(progressAnnotation.clients(), methodCallback);
 				})
 				.toList())
 			.flatMap(List::stream)
diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/sampling/AsyncMcpSamplingProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/sampling/AsyncMcpSamplingProvider.java
index d837b31..2db8080 100644
--- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/sampling/AsyncMcpSamplingProvider.java
+++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/sampling/AsyncMcpSamplingProvider.java
@@ -101,7 +101,7 @@ public List getSamplingSpecifictions() {
 						.sampling(samplingAnnotation)
 						.build();
 
-					return new AsyncSamplingSpecification(samplingAnnotation.clientId(), methodCallback);
+					return new AsyncSamplingSpecification(samplingAnnotation.clients(), methodCallback);
 				})
 				.toList())
 			.flatMap(List::stream)
diff --git a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/sampling/SyncMcpSamplingProvider.java b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/sampling/SyncMcpSamplingProvider.java
index 07bf132..7d35d06 100644
--- a/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/sampling/SyncMcpSamplingProvider.java
+++ b/mcp-annotations/src/main/java/org/springaicommunity/mcp/provider/sampling/SyncMcpSamplingProvider.java
@@ -101,7 +101,7 @@ public List getSamplingSpecifications() {
 						.sampling(samplingAnnotation)
 						.build();
 
-					return new SyncSamplingSpecification(samplingAnnotation.clientId(), methodCallback);
+					return new SyncSamplingSpecification(samplingAnnotation.clients(), methodCallback);
 				})
 				.toList())
 			.flatMap(List::stream)
diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/changed/prompt/AsyncMcpPromptListChangedMethodCallbackTests.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/changed/prompt/AsyncMcpPromptListChangedMethodCallbackTests.java
index ceb6c2e..15e815e 100644
--- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/changed/prompt/AsyncMcpPromptListChangedMethodCallbackTests.java
+++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/changed/prompt/AsyncMcpPromptListChangedMethodCallbackTests.java
@@ -36,14 +36,14 @@ static class ValidMethods {
 
 		private List lastUpdatedPrompts;
 
-		@McpPromptListChanged
+		@McpPromptListChanged(clients = "my-client-id")
 		public Mono handlePromptListChanged(List updatedPrompts) {
 			return Mono.fromRunnable(() -> {
 				this.lastUpdatedPrompts = updatedPrompts;
 			});
 		}
 
-		@McpPromptListChanged
+		@McpPromptListChanged(clients = "my-client-id")
 		public void handlePromptListChangedVoid(List updatedPrompts) {
 			this.lastUpdatedPrompts = updatedPrompts;
 		}
@@ -55,27 +55,27 @@ public void handlePromptListChangedVoid(List updatedPrompts) {
 	 */
 	static class InvalidMethods {
 
-		@McpPromptListChanged
+		@McpPromptListChanged(clients = "my-client-id")
 		public String invalidReturnType(List updatedPrompts) {
 			return "Invalid";
 		}
 
-		@McpPromptListChanged
+		@McpPromptListChanged(clients = "my-client-id")
 		public Mono invalidMonoReturnType(List updatedPrompts) {
 			return Mono.just("Invalid");
 		}
 
-		@McpPromptListChanged
+		@McpPromptListChanged(clients = "my-client-id")
 		public Mono invalidParameterCount(List updatedPrompts, String extra) {
 			return Mono.empty();
 		}
 
-		@McpPromptListChanged
+		@McpPromptListChanged(clients = "my-client-id")
 		public Mono invalidParameterType(String invalidType) {
 			return Mono.empty();
 		}
 
-		@McpPromptListChanged
+		@McpPromptListChanged(clients = "my-client-id")
 		public Mono noParameters() {
 			return Mono.empty();
 		}
@@ -229,7 +229,7 @@ void testMethodInvocationException() throws Exception {
 		// Test class that throws an exception in the method
 		class ThrowingMethod {
 
-			@McpPromptListChanged
+			@McpPromptListChanged(clients = "my-client-id")
 			public Mono handlePromptListChanged(List updatedPrompts) {
 				return Mono.fromRunnable(() -> {
 					throw new RuntimeException("Test exception");
@@ -254,7 +254,7 @@ void testMethodInvocationExceptionVoid() throws Exception {
 		// Test class that throws an exception in a void method
 		class ThrowingVoidMethod {
 
-			@McpPromptListChanged
+			@McpPromptListChanged(clients = "my-client-id")
 			public void handlePromptListChanged(List updatedPrompts) {
 				throw new RuntimeException("Test exception");
 			}
diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/changed/prompt/SyncMcpPromptListChangedMethodCallbackTests.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/changed/prompt/SyncMcpPromptListChangedMethodCallbackTests.java
index a1d0fc5..7b3e83b 100644
--- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/changed/prompt/SyncMcpPromptListChangedMethodCallbackTests.java
+++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/changed/prompt/SyncMcpPromptListChangedMethodCallbackTests.java
@@ -34,7 +34,7 @@ static class ValidMethods {
 
 		private List lastUpdatedPrompts;
 
-		@McpPromptListChanged
+		@McpPromptListChanged(clients = "my-client-id")
 		public void handlePromptListChanged(List updatedPrompts) {
 			this.lastUpdatedPrompts = updatedPrompts;
 		}
@@ -46,22 +46,22 @@ public void handlePromptListChanged(List updatedPrompts) {
 	 */
 	static class InvalidMethods {
 
-		@McpPromptListChanged
+		@McpPromptListChanged(clients = "my-client-id")
 		public String invalidReturnType(List updatedPrompts) {
 			return "Invalid";
 		}
 
-		@McpPromptListChanged
+		@McpPromptListChanged(clients = "my-client-id")
 		public void invalidParameterCount(List updatedPrompts, String extra) {
 			// Invalid parameter count
 		}
 
-		@McpPromptListChanged
+		@McpPromptListChanged(clients = "my-client-id")
 		public void invalidParameterType(String invalidType) {
 			// Invalid parameter type
 		}
 
-		@McpPromptListChanged
+		@McpPromptListChanged(clients = "my-client-id")
 		public void noParameters() {
 			// No parameters
 		}
@@ -180,7 +180,7 @@ void testMethodInvocationException() throws Exception {
 		// Test class that throws an exception in the method
 		class ThrowingMethod {
 
-			@McpPromptListChanged
+			@McpPromptListChanged(clients = "my-client-id")
 			public void handlePromptListChanged(List updatedPrompts) {
 				throw new RuntimeException("Test exception");
 			}
diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/changed/resource/AsyncMcpResourceListChangedMethodCallbackTests.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/changed/resource/AsyncMcpResourceListChangedMethodCallbackTests.java
index 03eb67a..0461523 100644
--- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/changed/resource/AsyncMcpResourceListChangedMethodCallbackTests.java
+++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/changed/resource/AsyncMcpResourceListChangedMethodCallbackTests.java
@@ -46,14 +46,14 @@ static class ValidMethods {
 
 		private List lastUpdatedResources;
 
-		@McpResourceListChanged
+		@McpResourceListChanged(clients = "client1")
 		public Mono handleResourceListChanged(List updatedResources) {
 			return Mono.fromRunnable(() -> {
 				this.lastUpdatedResources = updatedResources;
 			});
 		}
 
-		@McpResourceListChanged
+		@McpResourceListChanged(clients = "client1")
 		public void handleResourceListChangedVoid(List updatedResources) {
 			this.lastUpdatedResources = updatedResources;
 		}
@@ -65,27 +65,27 @@ public void handleResourceListChangedVoid(List updatedResour
 	 */
 	static class InvalidMethods {
 
-		@McpResourceListChanged
+		@McpResourceListChanged(clients = "client1")
 		public String invalidReturnType(List updatedResources) {
 			return "Invalid";
 		}
 
-		@McpResourceListChanged
+		@McpResourceListChanged(clients = "client1")
 		public Mono invalidMonoReturnType(List updatedResources) {
 			return Mono.just("Invalid");
 		}
 
-		@McpResourceListChanged
+		@McpResourceListChanged(clients = "client1")
 		public Mono invalidParameterCount(List updatedResources, String extra) {
 			return Mono.empty();
 		}
 
-		@McpResourceListChanged
+		@McpResourceListChanged(clients = "client1")
 		public Mono invalidParameterType(String invalidType) {
 			return Mono.empty();
 		}
 
-		@McpResourceListChanged
+		@McpResourceListChanged(clients = "client1")
 		public Mono noParameters() {
 			return Mono.empty();
 		}
@@ -239,7 +239,7 @@ void testMethodInvocationException() throws Exception {
 		// Test class that throws an exception in the method
 		class ThrowingMethod {
 
-			@McpResourceListChanged
+			@McpResourceListChanged(clients = "client1")
 			public Mono handleResourceListChanged(List updatedResources) {
 				return Mono.fromRunnable(() -> {
 					throw new RuntimeException("Test exception");
@@ -264,7 +264,7 @@ void testMethodInvocationExceptionVoid() throws Exception {
 		// Test class that throws an exception in a void method
 		class ThrowingVoidMethod {
 
-			@McpResourceListChanged
+			@McpResourceListChanged(clients = "client1")
 			public void handleResourceListChanged(List updatedResources) {
 				throw new RuntimeException("Test exception");
 			}
diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/changed/resource/SyncMcpResourceListChangedMethodCallbackTests.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/changed/resource/SyncMcpResourceListChangedMethodCallbackTests.java
index 64d42ac..250b8fc 100644
--- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/changed/resource/SyncMcpResourceListChangedMethodCallbackTests.java
+++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/changed/resource/SyncMcpResourceListChangedMethodCallbackTests.java
@@ -44,7 +44,7 @@ static class ValidMethods {
 
 		private List lastUpdatedResources;
 
-		@McpResourceListChanged
+		@McpResourceListChanged(clients = "client1")
 		public void handleResourceListChanged(List updatedResources) {
 			this.lastUpdatedResources = updatedResources;
 		}
@@ -56,22 +56,22 @@ public void handleResourceListChanged(List updatedResources)
 	 */
 	static class InvalidMethods {
 
-		@McpResourceListChanged
+		@McpResourceListChanged(clients = "client1")
 		public String invalidReturnType(List updatedResources) {
 			return "Invalid";
 		}
 
-		@McpResourceListChanged
+		@McpResourceListChanged(clients = "client1")
 		public void invalidParameterCount(List updatedResources, String extra) {
 			// Invalid parameter count
 		}
 
-		@McpResourceListChanged
+		@McpResourceListChanged(clients = "client1")
 		public void invalidParameterType(String invalidType) {
 			// Invalid parameter type
 		}
 
-		@McpResourceListChanged
+		@McpResourceListChanged(clients = "client1")
 		public void noParameters() {
 			// No parameters
 		}
@@ -190,7 +190,7 @@ void testMethodInvocationException() throws Exception {
 		// Test class that throws an exception in the method
 		class ThrowingMethod {
 
-			@McpResourceListChanged
+			@McpResourceListChanged(clients = "client1")
 			public void handleResourceListChanged(List updatedResources) {
 				throw new RuntimeException("Test exception");
 			}
diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/changed/tool/AsyncMcpToolListChangedMethodCallbackTests.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/changed/tool/AsyncMcpToolListChangedMethodCallbackTests.java
index 7db164b..c9691e8 100644
--- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/changed/tool/AsyncMcpToolListChangedMethodCallbackTests.java
+++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/changed/tool/AsyncMcpToolListChangedMethodCallbackTests.java
@@ -36,14 +36,14 @@ static class ValidMethods {
 
 		private List lastUpdatedTools;
 
-		@McpToolListChanged
+		@McpToolListChanged(clients = { "client1", "client2" })
 		public Mono handleToolListChanged(List updatedTools) {
 			return Mono.fromRunnable(() -> {
 				this.lastUpdatedTools = updatedTools;
 			});
 		}
 
-		@McpToolListChanged
+		@McpToolListChanged(clients = { "client1", "client2" })
 		public void handleToolListChangedVoid(List updatedTools) {
 			this.lastUpdatedTools = updatedTools;
 		}
@@ -55,27 +55,27 @@ public void handleToolListChangedVoid(List updatedTools) {
 	 */
 	static class InvalidMethods {
 
-		@McpToolListChanged
+		@McpToolListChanged(clients = "client1")
 		public String invalidReturnType(List updatedTools) {
 			return "Invalid";
 		}
 
-		@McpToolListChanged
+		@McpToolListChanged(clients = "client1")
 		public Mono invalidMonoReturnType(List updatedTools) {
 			return Mono.just("Invalid");
 		}
 
-		@McpToolListChanged
+		@McpToolListChanged(clients = "client1")
 		public Mono invalidParameterCount(List updatedTools, String extra) {
 			return Mono.empty();
 		}
 
-		@McpToolListChanged
+		@McpToolListChanged(clients = "client1")
 		public Mono invalidParameterType(String invalidType) {
 			return Mono.empty();
 		}
 
-		@McpToolListChanged
+		@McpToolListChanged(clients = "client1")
 		public Mono noParameters() {
 			return Mono.empty();
 		}
@@ -229,7 +229,7 @@ void testMethodInvocationException() throws Exception {
 		// Test class that throws an exception in the method
 		class ThrowingMethod {
 
-			@McpToolListChanged
+			@McpToolListChanged(clients = "client1")
 			public Mono handleToolListChanged(List updatedTools) {
 				return Mono.fromRunnable(() -> {
 					throw new RuntimeException("Test exception");
@@ -254,7 +254,7 @@ void testMethodInvocationExceptionVoid() throws Exception {
 		// Test class that throws an exception in a void method
 		class ThrowingVoidMethod {
 
-			@McpToolListChanged
+			@McpToolListChanged(clients = "client1")
 			public void handleToolListChanged(List updatedTools) {
 				throw new RuntimeException("Test exception");
 			}
diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/changed/tool/SyncMcpToolListChangedMethodCallbackTests.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/changed/tool/SyncMcpToolListChangedMethodCallbackTests.java
index 9851a9b..a657c67 100644
--- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/changed/tool/SyncMcpToolListChangedMethodCallbackTests.java
+++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/changed/tool/SyncMcpToolListChangedMethodCallbackTests.java
@@ -34,7 +34,7 @@ static class ValidMethods {
 
 		private List lastUpdatedTools;
 
-		@McpToolListChanged
+		@McpToolListChanged(clients = "client1")
 		public void handleToolListChanged(List updatedTools) {
 			this.lastUpdatedTools = updatedTools;
 		}
@@ -46,22 +46,22 @@ public void handleToolListChanged(List updatedTools) {
 	 */
 	static class InvalidMethods {
 
-		@McpToolListChanged
+		@McpToolListChanged(clients = "client1")
 		public String invalidReturnType(List updatedTools) {
 			return "Invalid";
 		}
 
-		@McpToolListChanged
+		@McpToolListChanged(clients = "client1")
 		public void invalidParameterCount(List updatedTools, String extra) {
 			// Invalid parameter count
 		}
 
-		@McpToolListChanged
+		@McpToolListChanged(clients = "client1")
 		public void invalidParameterType(String invalidType) {
 			// Invalid parameter type
 		}
 
-		@McpToolListChanged
+		@McpToolListChanged(clients = "client1")
 		public void noParameters() {
 			// No parameters
 		}
@@ -180,7 +180,7 @@ void testMethodInvocationException() throws Exception {
 		// Test class that throws an exception in the method
 		class ThrowingMethod {
 
-			@McpToolListChanged
+			@McpToolListChanged(clients = "client1")
 			public void handleToolListChanged(List updatedTools) {
 				throw new RuntimeException("Test exception");
 			}
diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/elicitation/AsyncMcpElicitationMethodCallbackExample.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/elicitation/AsyncMcpElicitationMethodCallbackExample.java
index 03b2dc7..f75c5f9 100644
--- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/elicitation/AsyncMcpElicitationMethodCallbackExample.java
+++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/elicitation/AsyncMcpElicitationMethodCallbackExample.java
@@ -19,28 +19,28 @@
  */
 public class AsyncMcpElicitationMethodCallbackExample {
 
-	@McpElicitation(clientId = "my-client-id")
+	@McpElicitation(clients = "my-client-id")
 	public Mono handleElicitationRequest(ElicitRequest request) {
 		// Example implementation that accepts the request and returns some content
 		return Mono.just(new ElicitResult(ElicitResult.Action.ACCEPT, Map.of("userInput", "Example async user input",
 				"confirmed", true, "timestamp", System.currentTimeMillis())));
 	}
 
-	@McpElicitation(clientId = "my-client-id")
+	@McpElicitation(clients = "my-client-id")
 	public Mono handleDeclineElicitationRequest(ElicitRequest request) {
 		// Example implementation that declines the request after a delay
 		return Mono.delay(java.time.Duration.ofMillis(100))
 			.then(Mono.just(new ElicitResult(ElicitResult.Action.DECLINE, null)));
 	}
 
-	@McpElicitation(clientId = "my-client-id")
+	@McpElicitation(clients = "my-client-id")
 	public ElicitResult handleSyncElicitationRequest(ElicitRequest request) {
 		// Example implementation that returns synchronously but will be wrapped in Mono
 		return new ElicitResult(ElicitResult.Action.ACCEPT, Map.of("syncResponse",
 				"This was returned synchronously but wrapped in Mono", "requestMessage", request.message()));
 	}
 
-	@McpElicitation(clientId = "my-client-id")
+	@McpElicitation(clients = "my-client-id")
 	public Mono handleCancelElicitationRequest(ElicitRequest request) {
 		// Example implementation that cancels the request
 		return Mono.just(new ElicitResult(ElicitResult.Action.CANCEL, null));
@@ -48,27 +48,27 @@ public Mono handleCancelElicitationRequest(ElicitRequest request)
 
 	// Test methods for invalid scenarios
 
-	@McpElicitation(clientId = "my-client-id")
+	@McpElicitation(clients = "my-client-id")
 	public String invalidReturnType(ElicitRequest request) {
 		return "Invalid return type";
 	}
 
-	@McpElicitation(clientId = "my-client-id")
+	@McpElicitation(clients = "my-client-id")
 	public Mono invalidMonoReturnType(ElicitRequest request) {
 		return Mono.just("Invalid Mono return type");
 	}
 
-	@McpElicitation(clientId = "my-client-id")
+	@McpElicitation(clients = "my-client-id")
 	public Mono invalidParameterType(String request) {
 		return Mono.just(new ElicitResult(ElicitResult.Action.ACCEPT, Map.of("test", "value")));
 	}
 
-	@McpElicitation(clientId = "my-client-id")
+	@McpElicitation(clients = "my-client-id")
 	public Mono noParameters() {
 		return Mono.just(new ElicitResult(ElicitResult.Action.ACCEPT, Map.of("test", "value")));
 	}
 
-	@McpElicitation(clientId = "my-client-id")
+	@McpElicitation(clients = "my-client-id")
 	public Mono tooManyParameters(ElicitRequest request, String extra) {
 		return Mono.just(new ElicitResult(ElicitResult.Action.ACCEPT, Map.of("test", "value")));
 	}
diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/elicitation/ElicitationSpecificationTests.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/elicitation/ElicitationSpecificationTests.java
index 4bee7f9..a871e9c 100644
--- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/elicitation/ElicitationSpecificationTests.java
+++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/elicitation/ElicitationSpecificationTests.java
@@ -26,10 +26,10 @@ public class ElicitationSpecificationTests {
 	@Test
 	void testSyncElicitationSpecificationValidClientId() {
 		// Valid clientId should work
-		SyncElicitationSpecification spec = new SyncElicitationSpecification("valid-client-id",
+		SyncElicitationSpecification spec = new SyncElicitationSpecification(new String[] { "valid-client-id" },
 				request -> new ElicitResult(ElicitResult.Action.ACCEPT, Map.of("test", "value")));
 
-		assertThat(spec.clientId()).isEqualTo("valid-client-id");
+		assertThat(spec.clients()).containsExactly("valid-client-id");
 		assertThat(spec.elicitationHandler()).isNotNull();
 	}
 
@@ -38,28 +38,28 @@ void testSyncElicitationSpecificationNullClientId() {
 		assertThatThrownBy(() -> new SyncElicitationSpecification(null,
 				request -> new ElicitResult(ElicitResult.Action.ACCEPT, Map.of("test", "value"))))
 			.isInstanceOf(NullPointerException.class)
-			.hasMessage("clientId must not be null");
+			.hasMessage("clients must not be null");
 	}
 
 	@Test
 	void testSyncElicitationSpecificationEmptyClientId() {
-		assertThatThrownBy(() -> new SyncElicitationSpecification("",
+		assertThatThrownBy(() -> new SyncElicitationSpecification(new String[] { "" },
 				request -> new ElicitResult(ElicitResult.Action.ACCEPT, Map.of("test", "value"))))
 			.isInstanceOf(IllegalArgumentException.class)
-			.hasMessage("clientId must not be empty");
+			.hasMessage("clients must not be empty");
 	}
 
 	@Test
 	void testSyncElicitationSpecificationBlankClientId() {
-		assertThatThrownBy(() -> new SyncElicitationSpecification("   ",
+		assertThatThrownBy(() -> new SyncElicitationSpecification(new String[] { "   " },
 				request -> new ElicitResult(ElicitResult.Action.ACCEPT, Map.of("test", "value"))))
 			.isInstanceOf(IllegalArgumentException.class)
-			.hasMessage("clientId must not be empty");
+			.hasMessage("clients must not be empty");
 	}
 
 	@Test
 	void testSyncElicitationSpecificationNullHandler() {
-		assertThatThrownBy(() -> new SyncElicitationSpecification("valid-client-id", null))
+		assertThatThrownBy(() -> new SyncElicitationSpecification(new String[] { "valid-client-id" }, null))
 			.isInstanceOf(NullPointerException.class)
 			.hasMessage("elicitationHandler must not be null");
 	}
@@ -67,10 +67,10 @@ void testSyncElicitationSpecificationNullHandler() {
 	@Test
 	void testAsyncElicitationSpecificationValidClientId() {
 		// Valid clientId should work
-		AsyncElicitationSpecification spec = new AsyncElicitationSpecification("valid-client-id",
+		AsyncElicitationSpecification spec = new AsyncElicitationSpecification(new String[] { "valid-client-id" },
 				request -> Mono.just(new ElicitResult(ElicitResult.Action.ACCEPT, Map.of("test", "value"))));
 
-		assertThat(spec.clientId()).isEqualTo("valid-client-id");
+		assertThat(spec.clients()).containsExactly("valid-client-id");
 		assertThat(spec.elicitationHandler()).isNotNull();
 	}
 
@@ -79,35 +79,35 @@ void testAsyncElicitationSpecificationNullClientId() {
 		assertThatThrownBy(() -> new AsyncElicitationSpecification(null,
 				request -> Mono.just(new ElicitResult(ElicitResult.Action.ACCEPT, Map.of("test", "value")))))
 			.isInstanceOf(NullPointerException.class)
-			.hasMessage("clientId must not be null");
+			.hasMessage("clients must not be null");
 	}
 
 	@Test
 	void testAsyncElicitationSpecificationEmptyClientId() {
-		assertThatThrownBy(() -> new AsyncElicitationSpecification("",
+		assertThatThrownBy(() -> new AsyncElicitationSpecification(new String[] { "" },
 				request -> Mono.just(new ElicitResult(ElicitResult.Action.ACCEPT, Map.of("test", "value")))))
 			.isInstanceOf(IllegalArgumentException.class)
-			.hasMessage("clientId must not be empty");
+			.hasMessage("clients must not be empty");
 	}
 
 	@Test
 	void testAsyncElicitationSpecificationBlankClientId() {
-		assertThatThrownBy(() -> new AsyncElicitationSpecification("   ",
+		assertThatThrownBy(() -> new AsyncElicitationSpecification(new String[] { "   " },
 				request -> Mono.just(new ElicitResult(ElicitResult.Action.ACCEPT, Map.of("test", "value")))))
 			.isInstanceOf(IllegalArgumentException.class)
-			.hasMessage("clientId must not be empty");
+			.hasMessage("clients must not be empty");
 	}
 
 	@Test
 	void testAsyncElicitationSpecificationNullHandler() {
-		assertThatThrownBy(() -> new AsyncElicitationSpecification("valid-client-id", null))
+		assertThatThrownBy(() -> new AsyncElicitationSpecification(new String[] { "valid-client-id" }, null))
 			.isInstanceOf(NullPointerException.class)
 			.hasMessage("elicitationHandler must not be null");
 	}
 
 	@Test
 	void testSyncElicitationSpecificationFunctionality() {
-		SyncElicitationSpecification spec = new SyncElicitationSpecification("test-client",
+		SyncElicitationSpecification spec = new SyncElicitationSpecification(new String[] { "test-client" },
 				request -> new ElicitResult(ElicitResult.Action.ACCEPT,
 						Map.of("message", request.message(), "clientId", "test-client")));
 
@@ -122,7 +122,7 @@ void testSyncElicitationSpecificationFunctionality() {
 
 	@Test
 	void testAsyncElicitationSpecificationFunctionality() {
-		AsyncElicitationSpecification spec = new AsyncElicitationSpecification("test-client",
+		AsyncElicitationSpecification spec = new AsyncElicitationSpecification(new String[] { "test-client" },
 				request -> Mono.just(new ElicitResult(ElicitResult.Action.ACCEPT,
 						Map.of("message", request.message(), "clientId", "test-client"))));
 
diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/elicitation/SyncMcpElicitationMethodCallbackExample.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/elicitation/SyncMcpElicitationMethodCallbackExample.java
index 6bcb0da..31b1329 100644
--- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/elicitation/SyncMcpElicitationMethodCallbackExample.java
+++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/elicitation/SyncMcpElicitationMethodCallbackExample.java
@@ -18,20 +18,20 @@
  */
 public class SyncMcpElicitationMethodCallbackExample {
 
-	@McpElicitation(clientId = "my-client-id")
+	@McpElicitation(clients = "my-client-id")
 	public ElicitResult handleElicitationRequest(ElicitRequest request) {
 		// Example implementation that accepts the request and returns some content
 		return new ElicitResult(ElicitResult.Action.ACCEPT,
 				Map.of("userInput", "Example user input", "confirmed", true));
 	}
 
-	@McpElicitation(clientId = "my-client-id")
+	@McpElicitation(clients = "my-client-id")
 	public ElicitResult handleDeclineElicitationRequest(ElicitRequest request) {
 		// Example implementation that declines the request
 		return new ElicitResult(ElicitResult.Action.DECLINE, null);
 	}
 
-	@McpElicitation(clientId = "my-client-id")
+	@McpElicitation(clients = "my-client-id")
 	public ElicitResult handleCancelElicitationRequest(ElicitRequest request) {
 		// Example implementation that cancels the request
 		return new ElicitResult(ElicitResult.Action.CANCEL, null);
@@ -39,22 +39,22 @@ public ElicitResult handleCancelElicitationRequest(ElicitRequest request) {
 
 	// Test methods for invalid scenarios
 
-	@McpElicitation(clientId = "my-client-id")
+	@McpElicitation(clients = "my-client-id")
 	public String invalidReturnType(ElicitRequest request) {
 		return "Invalid return type";
 	}
 
-	@McpElicitation(clientId = "my-client-id")
+	@McpElicitation(clients = "my-client-id")
 	public ElicitResult invalidParameterType(String request) {
 		return new ElicitResult(ElicitResult.Action.ACCEPT, Map.of("test", "value"));
 	}
 
-	@McpElicitation(clientId = "my-client-id")
+	@McpElicitation(clients = "my-client-id")
 	public ElicitResult noParameters() {
 		return new ElicitResult(ElicitResult.Action.ACCEPT, Map.of("test", "value"));
 	}
 
-	@McpElicitation(clientId = "my-client-id")
+	@McpElicitation(clients = "my-client-id")
 	public ElicitResult tooManyParameters(ElicitRequest request, String extra) {
 		return new ElicitResult(ElicitResult.Action.ACCEPT, Map.of("test", "value"));
 	}
diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/logging/AsyncMcpLoggingMethodCallbackExample.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/logging/AsyncMcpLoggingMethodCallbackExample.java
index 9fc613e..2ee7860 100644
--- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/logging/AsyncMcpLoggingMethodCallbackExample.java
+++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/logging/AsyncMcpLoggingMethodCallbackExample.java
@@ -29,7 +29,7 @@ public class AsyncMcpLoggingMethodCallbackExample {
 	 * @param notification The logging message notification
 	 * @return A Mono that completes when the processing is done
 	 */
-	@McpLogging(clientId = "test-client")
+	@McpLogging(clients = "test-client")
 	public Mono handleLoggingMessage(LoggingMessageNotification notification) {
 		return Mono.fromRunnable(() -> {
 			System.out.println("Received logging message: " + notification.level() + " - " + notification.logger()
@@ -45,7 +45,7 @@ public Mono handleLoggingMessage(LoggingMessageNotification notification)
 	 * @param data The log message data
 	 * @return A Mono that completes when the processing is done
 	 */
-	@McpLogging(clientId = "test-client")
+	@McpLogging(clients = "test-client")
 	public Mono handleLoggingMessageWithParams(LoggingLevel level, String logger, String data) {
 		return Mono.fromRunnable(() -> {
 			System.out.println("Received logging message with params: " + level + " - " + logger + " - " + data);
@@ -56,7 +56,7 @@ public Mono handleLoggingMessageWithParams(LoggingLevel level, String logg
 	 * Example method that accepts a LoggingMessageNotification with void return type.
 	 * @param notification The logging message notification
 	 */
-	@McpLogging(clientId = "test-client")
+	@McpLogging(clients = "test-client")
 	public void handleLoggingMessageVoid(LoggingMessageNotification notification) {
 		System.out.println("Received logging message (void): " + notification.level() + " - " + notification.logger()
 				+ " - " + notification.data());
diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/logging/AsyncMcpLoggingMethodCallbackTests.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/logging/AsyncMcpLoggingMethodCallbackTests.java
index 03e6e4a..8fa8f0f 100644
--- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/logging/AsyncMcpLoggingMethodCallbackTests.java
+++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/logging/AsyncMcpLoggingMethodCallbackTests.java
@@ -41,14 +41,14 @@ static class ValidMethods {
 
 		private String lastData;
 
-		@McpLogging(clientId = "test-client")
+		@McpLogging(clients = "test-client")
 		public Mono handleLoggingMessage(LoggingMessageNotification notification) {
 			return Mono.fromRunnable(() -> {
 				this.lastNotification = notification;
 			});
 		}
 
-		@McpLogging(clientId = "test-client")
+		@McpLogging(clients = "test-client")
 		public Mono handleLoggingMessageWithParams(LoggingLevel level, String logger, String data) {
 			return Mono.fromRunnable(() -> {
 				this.lastLevel = level;
@@ -57,7 +57,7 @@ public Mono handleLoggingMessageWithParams(LoggingLevel level, String logg
 			});
 		}
 
-		@McpLogging(clientId = "test-client")
+		@McpLogging(clients = "test-client")
 		public void handleLoggingMessageVoid(LoggingMessageNotification notification) {
 			this.lastNotification = notification;
 		}
@@ -69,27 +69,27 @@ public void handleLoggingMessageVoid(LoggingMessageNotification notification) {
 	 */
 	static class InvalidMethods {
 
-		@McpLogging(clientId = "test-client")
+		@McpLogging(clients = "test-client")
 		public String invalidReturnType(LoggingMessageNotification notification) {
 			return "Invalid";
 		}
 
-		@McpLogging(clientId = "test-client")
+		@McpLogging(clients = "test-client")
 		public Mono invalidMonoReturnType(LoggingMessageNotification notification) {
 			return Mono.just("Invalid");
 		}
 
-		@McpLogging(clientId = "test-client")
+		@McpLogging(clients = "test-client")
 		public Mono invalidParameterCount(LoggingMessageNotification notification, String extra) {
 			return Mono.empty();
 		}
 
-		@McpLogging(clientId = "test-client")
+		@McpLogging(clients = "test-client")
 		public Mono invalidParameterType(String invalidType) {
 			return Mono.empty();
 		}
 
-		@McpLogging(clientId = "test-client")
+		@McpLogging(clients = "test-client")
 		public Mono invalidParameterTypes(String level, int logger, boolean data) {
 			return Mono.empty();
 		}
diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/logging/SyncMcpLoggingMethodCallbackExample.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/logging/SyncMcpLoggingMethodCallbackExample.java
index c5d3c3f..a5e0bc5 100644
--- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/logging/SyncMcpLoggingMethodCallbackExample.java
+++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/logging/SyncMcpLoggingMethodCallbackExample.java
@@ -27,7 +27,7 @@ public class SyncMcpLoggingMethodCallbackExample {
 	 * Example method that accepts a LoggingMessageNotification.
 	 * @param notification The logging message notification
 	 */
-	@McpLogging(clientId = "test-client")
+	@McpLogging(clients = "test-client")
 	public void handleLoggingMessage(LoggingMessageNotification notification) {
 		System.out.println("Received logging message: " + notification.level() + " - " + notification.logger() + " - "
 				+ notification.data());
@@ -39,7 +39,7 @@ public void handleLoggingMessage(LoggingMessageNotification notification) {
 	 * @param logger The logger name
 	 * @param data The log message data
 	 */
-	@McpLogging(clientId = "test-client")
+	@McpLogging(clients = "test-client")
 	public void handleLoggingMessageWithParams(LoggingLevel level, String logger, String data) {
 		System.out.println("Received logging message with params: " + level + " - " + logger + " - " + data);
 	}
diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/logging/SyncMcpLoggingMethodCallbackTests.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/logging/SyncMcpLoggingMethodCallbackTests.java
index 6a3a67e..2afb336 100644
--- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/logging/SyncMcpLoggingMethodCallbackTests.java
+++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/logging/SyncMcpLoggingMethodCallbackTests.java
@@ -39,12 +39,12 @@ static class ValidMethods {
 
 		private String lastData;
 
-		@McpLogging(clientId = "test-client")
+		@McpLogging(clients = "test-client")
 		public void handleLoggingMessage(LoggingMessageNotification notification) {
 			this.lastNotification = notification;
 		}
 
-		@McpLogging(clientId = "test-client")
+		@McpLogging(clients = "test-client")
 		public void handleLoggingMessageWithParams(LoggingLevel level, String logger, String data) {
 			this.lastLevel = level;
 			this.lastLogger = logger;
@@ -58,22 +58,22 @@ public void handleLoggingMessageWithParams(LoggingLevel level, String logger, St
 	 */
 	static class InvalidMethods {
 
-		@McpLogging(clientId = "test-client")
+		@McpLogging(clients = "test-client")
 		public String invalidReturnType(LoggingMessageNotification notification) {
 			return "Invalid";
 		}
 
-		@McpLogging(clientId = "test-client")
+		@McpLogging(clients = "test-client")
 		public void invalidParameterCount(LoggingMessageNotification notification, String extra) {
 			// Invalid parameter count
 		}
 
-		@McpLogging(clientId = "test-client")
+		@McpLogging(clients = "test-client")
 		public void invalidParameterType(String invalidType) {
 			// Invalid parameter type
 		}
 
-		@McpLogging(clientId = "test-client")
+		@McpLogging(clients = "test-client")
 		public void invalidParameterTypes(String level, int logger, boolean data) {
 			// Invalid parameter types
 		}
diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/progress/AsyncMcpProgressMethodCallbackExample.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/progress/AsyncMcpProgressMethodCallbackExample.java
index b1aa4f6..5017c56 100644
--- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/progress/AsyncMcpProgressMethodCallbackExample.java
+++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/progress/AsyncMcpProgressMethodCallbackExample.java
@@ -33,7 +33,7 @@ public static class AsyncProgressService {
 		 * @param notification the progress notification
 		 * @return Mono completing when processing is done
 		 */
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public Mono handleProgressNotificationAsync(ProgressNotification notification) {
 			return Mono.fromRunnable(() -> {
 				int count = notificationCount.incrementAndGet();
@@ -51,7 +51,7 @@ public Mono handleProgressNotificationAsync(ProgressNotification notificat
 		 * @param progressToken the progress token identifier
 		 * @param total the total value as string
 		 */
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "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);
@@ -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(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public Mono 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,
@@ -78,7 +78,7 @@ public Mono handleProgressWithParamsAsync(Double progress, String progress
 		 * @param progressToken the progress token identifier
 		 * @param total the total value as string
 		 */
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "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);
 		}
diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/progress/AsyncMcpProgressMethodCallbackTests.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/progress/AsyncMcpProgressMethodCallbackTests.java
index a2c5416..e32f7d0 100644
--- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/progress/AsyncMcpProgressMethodCallbackTests.java
+++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/progress/AsyncMcpProgressMethodCallbackTests.java
@@ -45,25 +45,25 @@ static class ValidMethods {
 
 		private String lastTotal;
 
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public void handleProgressVoid(ProgressNotification notification) {
 			this.lastNotification = notification;
 		}
 
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public Mono handleProgressMono(ProgressNotification notification) {
 			this.lastNotification = notification;
 			return Mono.empty();
 		}
 
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public void handleProgressWithParams(Double progress, String progressToken, String total) {
 			this.lastProgress = progress;
 			this.lastProgressToken = progressToken;
 			this.lastTotal = total;
 		}
 
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public Mono handleProgressWithParamsMono(Double progress, String progressToken, String total) {
 			this.lastProgress = progress;
 			this.lastProgressToken = progressToken;
@@ -71,7 +71,7 @@ public Mono handleProgressWithParamsMono(Double progress, String progressT
 			return Mono.empty();
 		}
 
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public void handleProgressWithPrimitiveDouble(double progress, String progressToken, String total) {
 			this.lastProgress = progress;
 			this.lastProgressToken = progressToken;
@@ -85,42 +85,42 @@ public void handleProgressWithPrimitiveDouble(double progress, String progressTo
 	 */
 	static class InvalidMethods {
 
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public String invalidReturnType(ProgressNotification notification) {
 			return "Invalid";
 		}
 
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public Mono invalidMonoReturnType(ProgressNotification notification) {
 			return Mono.just("Invalid");
 		}
 
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public void invalidParameterCount(ProgressNotification notification, String extra) {
 			// Invalid parameter count
 		}
 
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public void invalidParameterType(String invalidType) {
 			// Invalid parameter type
 		}
 
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public void invalidParameterTypes(String progress, int progressToken, boolean total) {
 			// Invalid parameter types
 		}
 
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public void invalidFirstParameterType(String progress, String progressToken, String total) {
 			// Invalid first parameter type
 		}
 
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public void invalidSecondParameterType(Double progress, int progressToken, String total) {
 			// Invalid second parameter type
 		}
 
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public void invalidThirdParameterType(Double progress, String progressToken, int total) {
 			// Invalid third parameter type
 		}
diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/progress/SyncMcpProgressMethodCallbackExample.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/progress/SyncMcpProgressMethodCallbackExample.java
index 791a3a9..7ab4c14 100644
--- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/progress/SyncMcpProgressMethodCallbackExample.java
+++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/progress/SyncMcpProgressMethodCallbackExample.java
@@ -28,7 +28,7 @@ public static class ProgressService {
 		 * Handle progress notification with the full notification object.
 		 * @param notification the progress notification
 		 */
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public void handleProgressNotification(ProgressNotification notification) {
 			notificationCount++;
 			System.out.printf("Progress Update #%d: Token=%s, Progress=%.2f%%, Total=%.0f, Message=%s%n",
@@ -42,7 +42,7 @@ public void handleProgressNotification(ProgressNotification notification) {
 		 * @param progressToken the progress token identifier
 		 * @param total the total value as string
 		 */
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "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);
 		}
@@ -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(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public void handleProgressPrimitive(double progress, String progressToken, String total) {
 			System.out.printf("Processing: %.1f%% complete (Token: %s)%n", progress * 100, progressToken);
 		}
diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/progress/SyncMcpProgressMethodCallbackTests.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/progress/SyncMcpProgressMethodCallbackTests.java
index 4f31369..9860a66 100644
--- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/progress/SyncMcpProgressMethodCallbackTests.java
+++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/progress/SyncMcpProgressMethodCallbackTests.java
@@ -43,19 +43,19 @@ static class ValidMethods {
 
 		private String lastTotal;
 
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public void handleProgressNotification(ProgressNotification notification) {
 			this.lastNotification = notification;
 		}
 
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public void handleProgressWithParams(Double progress, String progressToken, String total) {
 			this.lastProgress = progress;
 			this.lastProgressToken = progressToken;
 			this.lastTotal = total;
 		}
 
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public void handleProgressWithPrimitiveDouble(double progress, String progressToken, String total) {
 			this.lastProgress = progress;
 			this.lastProgressToken = progressToken;
@@ -69,37 +69,37 @@ public void handleProgressWithPrimitiveDouble(double progress, String progressTo
 	 */
 	static class InvalidMethods {
 
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public String invalidReturnType(ProgressNotification notification) {
 			return "Invalid";
 		}
 
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public void invalidParameterCount(ProgressNotification notification, String extra) {
 			// Invalid parameter count
 		}
 
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public void invalidParameterType(String invalidType) {
 			// Invalid parameter type
 		}
 
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public void invalidParameterTypes(String progress, int progressToken, boolean total) {
 			// Invalid parameter types
 		}
 
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public void invalidFirstParameterType(String progress, String progressToken, String total) {
 			// Invalid first parameter type
 		}
 
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public void invalidSecondParameterType(Double progress, int progressToken, String total) {
 			// Invalid second parameter type
 		}
 
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public void invalidThirdParameterType(Double progress, String progressToken, int total) {
 			// Invalid third parameter type
 		}
diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/sampling/AsyncMcpSamplingMethodCallbackExample.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/sampling/AsyncMcpSamplingMethodCallbackExample.java
index 52903b3..de62ef9 100644
--- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/sampling/AsyncMcpSamplingMethodCallbackExample.java
+++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/sampling/AsyncMcpSamplingMethodCallbackExample.java
@@ -25,7 +25,7 @@ public class AsyncMcpSamplingMethodCallbackExample {
 	 * @param request The sampling request
 	 * @return The sampling result as a Mono
 	 */
-	@McpSampling(clientId = "test-client")
+	@McpSampling(clients = "test-client")
 	public Mono handleAsyncSamplingRequest(CreateMessageRequest request) {
 		// Process the request asynchronously and return a result
 		return Mono.just(CreateMessageResult.builder()
@@ -40,7 +40,7 @@ public Mono handleAsyncSamplingRequest(CreateMessageRequest
 	 * @param request The sampling request
 	 * @return The sampling result directly
 	 */
-	@McpSampling(clientId = "test-client")
+	@McpSampling(clients = "test-client")
 	public CreateMessageResult handleDirectSamplingRequest(CreateMessageRequest request) {
 		// Process the request and return a direct result
 		return CreateMessageResult.builder()
@@ -55,7 +55,7 @@ public CreateMessageResult handleDirectSamplingRequest(CreateMessageRequest requ
 	 * @param request The sampling request
 	 * @return A Mono with an invalid type
 	 */
-	@McpSampling(clientId = "test-client")
+	@McpSampling(clients = "test-client")
 	public Mono invalidMonoReturnType(CreateMessageRequest request) {
 		return Mono.just("This method has an invalid return type");
 	}
@@ -65,7 +65,7 @@ public Mono invalidMonoReturnType(CreateMessageRequest request) {
 	 * @param invalidParam An invalid parameter type
 	 * @return The sampling result as a Mono
 	 */
-	@McpSampling(clientId = "test-client")
+	@McpSampling(clients = "test-client")
 	public Mono invalidParameterType(String invalidParam) {
 		return Mono.just(CreateMessageResult.builder()
 			.role(Role.ASSISTANT)
@@ -78,7 +78,7 @@ public Mono invalidParameterType(String invalidParam) {
 	 * Example method with no parameters.
 	 * @return The sampling result as a Mono
 	 */
-	@McpSampling(clientId = "test-client")
+	@McpSampling(clients = "test-client")
 	public Mono noParameters() {
 		return Mono.just(CreateMessageResult.builder()
 			.role(Role.ASSISTANT)
@@ -93,7 +93,7 @@ public Mono noParameters() {
 	 * @param extraParam An extra parameter
 	 * @return The sampling result as a Mono
 	 */
-	@McpSampling(clientId = "test-client")
+	@McpSampling(clients = "test-client")
 	public Mono tooManyParameters(CreateMessageRequest request, String extraParam) {
 		return Mono.just(CreateMessageResult.builder()
 			.role(Role.ASSISTANT)
diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/sampling/SyncMcpSamplingMethodCallbackExample.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/sampling/SyncMcpSamplingMethodCallbackExample.java
index 847dfd1..311d618 100644
--- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/sampling/SyncMcpSamplingMethodCallbackExample.java
+++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/method/sampling/SyncMcpSamplingMethodCallbackExample.java
@@ -24,7 +24,7 @@ public class SyncMcpSamplingMethodCallbackExample {
 	 * @param request The sampling request
 	 * @return The sampling result
 	 */
-	@McpSampling(clientId = "test-client")
+	@McpSampling(clients = "test-client")
 	public CreateMessageResult handleSamplingRequest(CreateMessageRequest request) {
 		// Process the request and return a result
 		return CreateMessageResult.builder()
@@ -39,7 +39,7 @@ public CreateMessageResult handleSamplingRequest(CreateMessageRequest request) {
 	 * @param request The sampling request
 	 * @return A string (invalid return type)
 	 */
-	@McpSampling(clientId = "test-client")
+	@McpSampling(clients = "test-client")
 	public String invalidReturnType(CreateMessageRequest request) {
 		return "This method has an invalid return type";
 	}
@@ -49,7 +49,7 @@ public String invalidReturnType(CreateMessageRequest request) {
 	 * @param invalidParam An invalid parameter type
 	 * @return The sampling result
 	 */
-	@McpSampling(clientId = "test-client")
+	@McpSampling(clients = "test-client")
 	public CreateMessageResult invalidParameterType(String invalidParam) {
 		return CreateMessageResult.builder()
 			.role(Role.ASSISTANT)
@@ -62,7 +62,7 @@ public CreateMessageResult invalidParameterType(String invalidParam) {
 	 * Example method with no parameters.
 	 * @return The sampling result
 	 */
-	@McpSampling(clientId = "test-client")
+	@McpSampling(clients = "test-client")
 	public CreateMessageResult noParameters() {
 		return CreateMessageResult.builder()
 			.role(Role.ASSISTANT)
@@ -77,7 +77,7 @@ public CreateMessageResult noParameters() {
 	 * @param extraParam An extra parameter
 	 * @return The sampling result
 	 */
-	@McpSampling(clientId = "test-client")
+	@McpSampling(clients = "test-client")
 	public CreateMessageResult tooManyParameters(CreateMessageRequest request, String extraParam) {
 		return CreateMessageResult.builder()
 			.role(Role.ASSISTANT)
diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/prompt/AsyncMcpPromptListChangedProviderTests.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/prompt/AsyncMcpPromptListChangedProviderTests.java
index 1d96316..dbe8390 100644
--- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/prompt/AsyncMcpPromptListChangedProviderTests.java
+++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/prompt/AsyncMcpPromptListChangedProviderTests.java
@@ -8,6 +8,7 @@
 
 import java.util.List;
 import java.util.function.Function;
+import java.util.stream.Stream;
 
 import org.junit.jupiter.api.Test;
 import org.springaicommunity.mcp.annotation.McpPromptListChanged;
@@ -35,21 +36,21 @@ static class PromptListChangedHandler {
 
 		private List lastUpdatedPrompts;
 
-		@McpPromptListChanged
+		@McpPromptListChanged(clients = "my-client-id")
 		public Mono handlePromptListChanged(List updatedPrompts) {
 			return Mono.fromRunnable(() -> {
 				this.lastUpdatedPrompts = updatedPrompts;
 			});
 		}
 
-		@McpPromptListChanged(clientId = "test-client")
+		@McpPromptListChanged(clients = "test-client")
 		public Mono handlePromptListChangedWithClientId(List updatedPrompts) {
 			return Mono.fromRunnable(() -> {
 				this.lastUpdatedPrompts = updatedPrompts;
 			});
 		}
 
-		@McpPromptListChanged
+		@McpPromptListChanged(clients = "my-client-id")
 		public void handlePromptListChangedVoid(List updatedPrompts) {
 			this.lastUpdatedPrompts = updatedPrompts;
 		}
@@ -108,9 +109,9 @@ void testClientIdSpecifications() {
 		assertThat(specifications).hasSize(3);
 
 		// Check client IDs
-		List clientIds = specifications.stream().map(AsyncPromptListChangedSpecification::clientId).toList();
+		List clientIds = specifications.stream().map(spec -> spec.clients()).flatMap(Stream::of).toList();
 
-		assertThat(clientIds).containsExactlyInAnyOrder("", "test-client", "");
+		assertThat(clientIds).containsExactlyInAnyOrder("my-client-id", "test-client", "my-client-id");
 	}
 
 	@Test
@@ -176,12 +177,12 @@ void testNonAnnotatedMethodsIgnored() {
 	 */
 	static class InvalidReturnTypeHandler {
 
-		@McpPromptListChanged
+		@McpPromptListChanged(clients = "my-client-id")
 		public String invalidReturnType(List updatedPrompts) {
 			return "Invalid";
 		}
 
-		@McpPromptListChanged
+		@McpPromptListChanged(clients = "my-client-id")
 		public int anotherInvalidReturnType(List updatedPrompts) {
 			return 42;
 		}
@@ -206,19 +207,19 @@ static class MixedHandler {
 
 		private List lastUpdatedPrompts;
 
-		@McpPromptListChanged
+		@McpPromptListChanged(clients = "my-client-id")
 		public Mono validMethod(List updatedPrompts) {
 			return Mono.fromRunnable(() -> {
 				this.lastUpdatedPrompts = updatedPrompts;
 			});
 		}
 
-		@McpPromptListChanged
+		@McpPromptListChanged(clients = "my-client-id")
 		public void validVoidMethod(List updatedPrompts) {
 			this.lastUpdatedPrompts = updatedPrompts;
 		}
 
-		@McpPromptListChanged
+		@McpPromptListChanged(clients = "my-client-id")
 		public String invalidMethod(List updatedPrompts) {
 			return "Invalid";
 		}
diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/prompt/SyncMcpPromptListChangedProviderTests.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/prompt/SyncMcpPromptListChangedProviderTests.java
index 901567f..52dbfa2 100644
--- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/prompt/SyncMcpPromptListChangedProviderTests.java
+++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/prompt/SyncMcpPromptListChangedProviderTests.java
@@ -8,6 +8,7 @@
 
 import java.util.List;
 import java.util.function.Consumer;
+import java.util.stream.Stream;
 
 import org.junit.jupiter.api.Test;
 import org.springaicommunity.mcp.annotation.McpPromptListChanged;
@@ -33,12 +34,12 @@ static class PromptListChangedHandler {
 
 		private List lastUpdatedPrompts;
 
-		@McpPromptListChanged
+		@McpPromptListChanged(clients = "my-client-id")
 		public void handlePromptListChanged(List updatedPrompts) {
 			this.lastUpdatedPrompts = updatedPrompts;
 		}
 
-		@McpPromptListChanged(clientId = "test-client")
+		@McpPromptListChanged(clients = "test-client")
 		public void handlePromptListChangedWithClientId(List updatedPrompts) {
 			this.lastUpdatedPrompts = updatedPrompts;
 		}
@@ -91,9 +92,9 @@ void testClientIdSpecifications() {
 		assertThat(specifications).hasSize(2);
 
 		// Check client IDs
-		List clientIds = specifications.stream().map(SyncPromptListChangedSpecification::clientId).toList();
+		List clientIds = specifications.stream().map(spec -> spec.clients()).flatMap(Stream::of).toList();
 
-		assertThat(clientIds).containsExactlyInAnyOrder("", "test-client");
+		assertThat(clientIds).containsExactlyInAnyOrder("test-client", "my-client-id");
 	}
 
 	@Test
diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/resource/AsyncMcpResourceListChangedProviderTests.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/resource/AsyncMcpResourceListChangedProviderTests.java
index cfec18b..7512674 100644
--- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/resource/AsyncMcpResourceListChangedProviderTests.java
+++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/resource/AsyncMcpResourceListChangedProviderTests.java
@@ -8,6 +8,7 @@
 
 import java.util.List;
 import java.util.function.Function;
+import java.util.stream.Stream;
 
 import org.junit.jupiter.api.Test;
 import org.springaicommunity.mcp.annotation.McpResourceListChanged;
@@ -45,21 +46,21 @@ static class ResourceListChangedHandler {
 
 		private List lastUpdatedResources;
 
-		@McpResourceListChanged
+		@McpResourceListChanged(clients = "client1")
 		public Mono handleResourceListChanged(List updatedResources) {
 			return Mono.fromRunnable(() -> {
 				this.lastUpdatedResources = updatedResources;
 			});
 		}
 
-		@McpResourceListChanged(clientId = "test-client")
+		@McpResourceListChanged(clients = "test-client")
 		public Mono handleResourceListChangedWithClientId(List updatedResources) {
 			return Mono.fromRunnable(() -> {
 				this.lastUpdatedResources = updatedResources;
 			});
 		}
 
-		@McpResourceListChanged
+		@McpResourceListChanged(clients = "client1")
 		public void handleResourceListChangedVoid(List updatedResources) {
 			this.lastUpdatedResources = updatedResources;
 		}
@@ -118,9 +119,9 @@ void testClientIdSpecifications() {
 		assertThat(specifications).hasSize(3);
 
 		// Check client IDs
-		List clientIds = specifications.stream().map(AsyncResourceListChangedSpecification::clientId).toList();
+		List clientIds = specifications.stream().map(spec -> spec.clients()).flatMap(Stream::of).toList();
 
-		assertThat(clientIds).containsExactlyInAnyOrder("", "test-client", "");
+		assertThat(clientIds).containsExactlyInAnyOrder("client1", "test-client", "client1");
 	}
 
 	@Test
@@ -187,12 +188,12 @@ void testNonAnnotatedMethodsIgnored() {
 	 */
 	static class InvalidReturnTypeHandler {
 
-		@McpResourceListChanged
+		@McpResourceListChanged(clients = "client1")
 		public String invalidReturnType(List updatedResources) {
 			return "Invalid";
 		}
 
-		@McpResourceListChanged
+		@McpResourceListChanged(clients = "client1")
 		public int anotherInvalidReturnType(List updatedResources) {
 			return 42;
 		}
@@ -217,19 +218,19 @@ static class MixedHandler {
 
 		private List lastUpdatedResources;
 
-		@McpResourceListChanged
+		@McpResourceListChanged(clients = "client1")
 		public Mono validMethod(List updatedResources) {
 			return Mono.fromRunnable(() -> {
 				this.lastUpdatedResources = updatedResources;
 			});
 		}
 
-		@McpResourceListChanged
+		@McpResourceListChanged(clients = "client1")
 		public void validVoidMethod(List updatedResources) {
 			this.lastUpdatedResources = updatedResources;
 		}
 
-		@McpResourceListChanged
+		@McpResourceListChanged(clients = "client1")
 		public String invalidMethod(List updatedResources) {
 			return "Invalid";
 		}
diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/resource/SyncMcpResourceListChangedProviderTests.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/resource/SyncMcpResourceListChangedProviderTests.java
index 5e744c8..bf249e1 100644
--- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/resource/SyncMcpResourceListChangedProviderTests.java
+++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/resource/SyncMcpResourceListChangedProviderTests.java
@@ -8,6 +8,7 @@
 
 import java.util.List;
 import java.util.function.Consumer;
+import java.util.stream.Stream;
 
 import org.junit.jupiter.api.Test;
 import org.springaicommunity.mcp.annotation.McpResourceListChanged;
@@ -43,12 +44,12 @@ static class ResourceListChangedHandler {
 
 		private List lastUpdatedResources;
 
-		@McpResourceListChanged
+		@McpResourceListChanged(clients = "client1")
 		public void handleResourceListChanged(List updatedResources) {
 			this.lastUpdatedResources = updatedResources;
 		}
 
-		@McpResourceListChanged(clientId = "test-client")
+		@McpResourceListChanged(clients = "test-client")
 		public void handleResourceListChangedWithClientId(List updatedResources) {
 			this.lastUpdatedResources = updatedResources;
 		}
@@ -101,9 +102,9 @@ void testClientIdSpecifications() {
 		assertThat(specifications).hasSize(2);
 
 		// Check client IDs
-		List clientIds = specifications.stream().map(SyncResourceListChangedSpecification::clientId).toList();
+		List clientIds = specifications.stream().map(spec -> spec.clients()).flatMap(Stream::of).toList();
 
-		assertThat(clientIds).containsExactlyInAnyOrder("", "test-client");
+		assertThat(clientIds).containsExactlyInAnyOrder("client1", "test-client");
 	}
 
 	@Test
diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/tool/AsyncMcpToolListChangedProviderTests.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/tool/AsyncMcpToolListChangedProviderTests.java
index e359f3e..939ef62 100644
--- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/tool/AsyncMcpToolListChangedProviderTests.java
+++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/tool/AsyncMcpToolListChangedProviderTests.java
@@ -8,6 +8,7 @@
 
 import java.util.List;
 import java.util.function.Function;
+import java.util.stream.Stream;
 
 import org.junit.jupiter.api.Test;
 import org.springaicommunity.mcp.annotation.McpToolListChanged;
@@ -35,21 +36,21 @@ static class ToolListChangedHandler {
 
 		private List lastUpdatedTools;
 
-		@McpToolListChanged
+		@McpToolListChanged(clients = "client1")
 		public Mono handleToolListChanged(List updatedTools) {
 			return Mono.fromRunnable(() -> {
 				this.lastUpdatedTools = updatedTools;
 			});
 		}
 
-		@McpToolListChanged(clientId = "test-client")
+		@McpToolListChanged(clients = "test-client")
 		public Mono handleToolListChangedWithClientId(List updatedTools) {
 			return Mono.fromRunnable(() -> {
 				this.lastUpdatedTools = updatedTools;
 			});
 		}
 
-		@McpToolListChanged
+		@McpToolListChanged(clients = "client1")
 		public void handleToolListChangedVoid(List updatedTools) {
 			this.lastUpdatedTools = updatedTools;
 		}
@@ -108,9 +109,9 @@ void testClientIdSpecifications() {
 		assertThat(specifications).hasSize(3);
 
 		// Check client IDs
-		List clientIds = specifications.stream().map(AsyncToolListChangedSpecification::clientId).toList();
+		List clientIds = specifications.stream().map(spec -> spec.clients()).flatMap(Stream::of).toList();
 
-		assertThat(clientIds).containsExactlyInAnyOrder("", "test-client", "");
+		assertThat(clientIds).containsExactlyInAnyOrder("client1", "test-client", "client1");
 	}
 
 	@Test
@@ -176,12 +177,12 @@ void testNonAnnotatedMethodsIgnored() {
 	 */
 	static class InvalidReturnTypeHandler {
 
-		@McpToolListChanged
+		@McpToolListChanged(clients = "client1")
 		public String invalidReturnType(List updatedTools) {
 			return "Invalid";
 		}
 
-		@McpToolListChanged
+		@McpToolListChanged(clients = "client1")
 		public int anotherInvalidReturnType(List updatedTools) {
 			return 42;
 		}
@@ -206,19 +207,19 @@ static class MixedHandler {
 
 		private List lastUpdatedTools;
 
-		@McpToolListChanged
+		@McpToolListChanged(clients = "client1")
 		public Mono validMethod(List updatedTools) {
 			return Mono.fromRunnable(() -> {
 				this.lastUpdatedTools = updatedTools;
 			});
 		}
 
-		@McpToolListChanged
+		@McpToolListChanged(clients = "client1")
 		public void validVoidMethod(List updatedTools) {
 			this.lastUpdatedTools = updatedTools;
 		}
 
-		@McpToolListChanged
+		@McpToolListChanged(clients = "client1")
 		public String invalidMethod(List updatedTools) {
 			return "Invalid";
 		}
diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/tool/SyncMcpToolListChangedProviderTests.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/tool/SyncMcpToolListChangedProviderTests.java
index 67cddb1..f840122 100644
--- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/tool/SyncMcpToolListChangedProviderTests.java
+++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/changed/tool/SyncMcpToolListChangedProviderTests.java
@@ -8,6 +8,7 @@
 
 import java.util.List;
 import java.util.function.Consumer;
+import java.util.stream.Stream;
 
 import org.junit.jupiter.api.Test;
 import org.springaicommunity.mcp.annotation.McpToolListChanged;
@@ -33,12 +34,12 @@ static class ToolListChangedHandler {
 
 		private List lastUpdatedTools;
 
-		@McpToolListChanged
+		@McpToolListChanged(clients = "client1")
 		public void handleToolListChanged(List updatedTools) {
 			this.lastUpdatedTools = updatedTools;
 		}
 
-		@McpToolListChanged(clientId = "test-client")
+		@McpToolListChanged(clients = "test-client")
 		public void handleToolListChangedWithClientId(List updatedTools) {
 			this.lastUpdatedTools = updatedTools;
 		}
@@ -91,9 +92,9 @@ void testClientIdSpecifications() {
 		assertThat(specifications).hasSize(2);
 
 		// Check client IDs
-		List clientIds = specifications.stream().map(SyncToolListChangedSpecification::clientId).toList();
+		List clientIds = specifications.stream().map(spec -> spec.clients()).flatMap(Stream::of).toList();
 
-		assertThat(clientIds).containsExactlyInAnyOrder("", "test-client");
+		assertThat(clientIds).containsExactlyInAnyOrder("test-client", "client1");
 	}
 
 	@Test
diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/elicitation/AsyncMcpElicitationProviderTests.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/elicitation/AsyncMcpElicitationProviderTests.java
index 3269635..6df1383 100644
--- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/elicitation/AsyncMcpElicitationProviderTests.java
+++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/elicitation/AsyncMcpElicitationProviderTests.java
@@ -69,7 +69,7 @@ public void testGetElicitationHandlerWithSyncMethod() {
 
 	public static class TestElicitationHandler {
 
-		@McpElicitation(clientId = "my-client-id")
+		@McpElicitation(clients = "my-client-id")
 		public Mono handleElicitation(ElicitRequest request) {
 			return Mono.just(new ElicitResult(ElicitResult.Action.ACCEPT,
 					Map.of("name", "Async Test User", "message", request.message())));
@@ -79,7 +79,7 @@ public Mono handleElicitation(ElicitRequest request) {
 
 	public static class SyncElicitationHandler {
 
-		@McpElicitation(clientId = "my-client-id")
+		@McpElicitation(clients = "my-client-id")
 		public ElicitResult handleElicitation(ElicitRequest request) {
 			return new ElicitResult(ElicitResult.Action.ACCEPT,
 					Map.of("name", "Sync Test User", "message", request.message()));
@@ -89,12 +89,12 @@ public ElicitResult handleElicitation(ElicitRequest request) {
 
 	public static class MultipleElicitationHandler {
 
-		@McpElicitation(clientId = "my-client-id")
+		@McpElicitation(clients = "my-client-id")
 		public Mono handleElicitation1(ElicitRequest request) {
 			return Mono.just(new ElicitResult(ElicitResult.Action.ACCEPT, Map.of("handler", "1")));
 		}
 
-		@McpElicitation(clientId = "my-client-id")
+		@McpElicitation(clients = "my-client-id")
 		public Mono handleElicitation2(ElicitRequest request) {
 			return Mono.just(new ElicitResult(ElicitResult.Action.ACCEPT, Map.of("handler", "2")));
 		}
diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/elicitation/SyncMcpElicitationProviderTests.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/elicitation/SyncMcpElicitationProviderTests.java
index d9bec15..9a37182 100644
--- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/elicitation/SyncMcpElicitationProviderTests.java
+++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/elicitation/SyncMcpElicitationProviderTests.java
@@ -46,7 +46,7 @@ public void testGetElicitationHandler() {
 
 	public static class TestElicitationHandler {
 
-		@McpElicitation(clientId = "my-client-id")
+		@McpElicitation(clients = "my-client-id")
 		public ElicitResult handleElicitation(ElicitRequest request) {
 			return new ElicitResult(ElicitResult.Action.ACCEPT,
 					Map.of("name", "Test User", "message", request.message()));
@@ -56,12 +56,12 @@ public ElicitResult handleElicitation(ElicitRequest request) {
 
 	public static class MultipleElicitationHandler {
 
-		@McpElicitation(clientId = "my-client-id")
+		@McpElicitation(clients = "my-client-id")
 		public ElicitResult handleElicitation1(ElicitRequest request) {
 			return new ElicitResult(ElicitResult.Action.ACCEPT, Map.of("handler", "1"));
 		}
 
-		@McpElicitation(clientId = "my-client-id")
+		@McpElicitation(clients = "my-client-id")
 		public ElicitResult handleElicitation2(ElicitRequest request) {
 			return new ElicitResult(ElicitResult.Action.ACCEPT, Map.of("handler", "2"));
 		}
diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/logging/AsyncMcpLoggingProviderTests.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/logging/AsyncMcpLoggingProviderTests.java
index 688c0bc..7bf2e1a 100644
--- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/logging/AsyncMcpLoggingProviderTests.java
+++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/logging/AsyncMcpLoggingProviderTests.java
@@ -38,14 +38,14 @@ static class TestAsyncLoggingProvider {
 
 		private String lastData;
 
-		@McpLogging(clientId = "test-client")
+		@McpLogging(clients = "test-client")
 		public Mono handleLoggingMessage(LoggingMessageNotification notification) {
 			return Mono.fromRunnable(() -> {
 				this.lastNotification = notification;
 			});
 		}
 
-		@McpLogging(clientId = "test-client")
+		@McpLogging(clients = "test-client")
 		public Mono handleLoggingMessageWithParams(LoggingLevel level, String logger, String data) {
 			return Mono.fromRunnable(() -> {
 				this.lastLevel = level;
@@ -54,7 +54,7 @@ public Mono handleLoggingMessageWithParams(LoggingLevel level, String logg
 			});
 		}
 
-		@McpLogging(clientId = "test-client")
+		@McpLogging(clients = "test-client")
 		public void handleLoggingMessageVoid(LoggingMessageNotification notification) {
 			this.lastNotification = notification;
 		}
diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/logging/SyncMcpLoggingProviderTests.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/logging/SyncMcpLoggingProviderTests.java
index cf49400..7062193 100644
--- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/logging/SyncMcpLoggingProviderTests.java
+++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/logging/SyncMcpLoggingProviderTests.java
@@ -36,12 +36,12 @@ static class LoggingHandler {
 
 		private String lastData;
 
-		@McpLogging(clientId = "test-client")
+		@McpLogging(clients = "test-client")
 		public void handleLoggingMessage(LoggingMessageNotification notification) {
 			this.lastNotification = notification;
 		}
 
-		@McpLogging(clientId = "test-client")
+		@McpLogging(clients = "test-client")
 		public void handleLoggingMessageWithParams(LoggingLevel level, String logger, String data) {
 			this.lastLevel = level;
 			this.lastLogger = logger;
diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/progress/AsyncMcpProgressProviderTests.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/progress/AsyncMcpProgressProviderTests.java
index 2287266..46f8d79 100644
--- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/progress/AsyncMcpProgressProviderTests.java
+++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/progress/AsyncMcpProgressProviderTests.java
@@ -37,25 +37,25 @@ static class AsyncProgressHandler {
 
 		private String lastTotal;
 
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public void handleProgressVoid(ProgressNotification notification) {
 			this.lastNotification = notification;
 		}
 
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public Mono handleProgressMono(ProgressNotification notification) {
 			this.lastNotification = notification;
 			return Mono.empty();
 		}
 
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public void handleProgressWithParams(Double progress, String progressToken, String total) {
 			this.lastProgress = progress;
 			this.lastProgressToken = progressToken;
 			this.lastTotal = total;
 		}
 
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public Mono handleProgressWithParamsMono(Double progress, String progressToken, String total) {
 			this.lastProgress = progress;
 			this.lastProgressToken = progressToken;
@@ -63,7 +63,7 @@ public Mono handleProgressWithParamsMono(Double progress, String progressT
 			return Mono.empty();
 		}
 
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public void handleProgressWithPrimitiveDouble(double progress, String progressToken, String total) {
 			this.lastProgress = progress;
 			this.lastProgressToken = progressToken;
@@ -77,13 +77,13 @@ public Mono notAnnotatedMethod(ProgressNotification notification) {
 		}
 
 		// This method has invalid return type and should be ignored
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public String invalidReturnType(ProgressNotification notification) {
 			return "Invalid";
 		}
 
 		// This method has invalid Mono return type and should be ignored
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public Mono invalidMonoReturnType(ProgressNotification notification) {
 			return Mono.just("Invalid");
 		}
@@ -171,8 +171,8 @@ void testClientIdExtraction() {
 
 		List specifications = provider.getProgressSpecifications();
 
-		// All specifications should have non-empty clientId
-		assertThat(specifications).allMatch(spec -> !spec.clientId().isEmpty());
+		// All specifications should have non-empty client Ids
+		assertThat(specifications).allMatch(spec -> spec.clients().length > 0);
 	}
 
 	@Test
@@ -180,7 +180,7 @@ void testErrorHandling() {
 		// Test class with method that throws an exception
 		class ErrorHandler {
 
-			@McpProgress(clientId = "my-client-id")
+			@McpProgress(clients = "my-client-id")
 			public Mono handleProgressWithError(ProgressNotification notification) {
 				return Mono.error(new RuntimeException("Test error"));
 			}
diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/progress/SyncMcpProgressProviderTests.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/progress/SyncMcpProgressProviderTests.java
index 958f6b9..e664fd3 100644
--- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/progress/SyncMcpProgressProviderTests.java
+++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/progress/SyncMcpProgressProviderTests.java
@@ -35,19 +35,19 @@ static class ProgressHandler {
 
 		private String lastTotal;
 
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public void handleProgressNotification(ProgressNotification notification) {
 			this.lastNotification = notification;
 		}
 
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public void handleProgressWithParams(Double progress, String progressToken, String total) {
 			this.lastProgress = progress;
 			this.lastProgressToken = progressToken;
 			this.lastTotal = total;
 		}
 
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public void handleProgressWithPrimitiveDouble(double progress, String progressToken, String total) {
 			this.lastProgress = progress;
 			this.lastProgressToken = progressToken;
@@ -60,7 +60,7 @@ public void notAnnotatedMethod(ProgressNotification notification) {
 		}
 
 		// This method has invalid return type and should be ignored
-		@McpProgress(clientId = "my-client-id")
+		@McpProgress(clients = "my-client-id")
 		public String invalidReturnType(ProgressNotification notification) {
 			return "Invalid";
 		}
@@ -145,8 +145,8 @@ void testClientIdExtraction() {
 
 		List specifications = provider.getProgressSpecifications();
 
-		// All specifications should have non-empty clientId
-		assertThat(specifications).allMatch(spec -> !spec.clientId().isEmpty());
+		// All specifications should have at least one non-empty client Id
+		assertThat(specifications).allMatch(spec -> spec.clients().length > 0);
 	}
 
 }
diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/sampling/AsyncMcpSamplingProviderTests.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/sampling/AsyncMcpSamplingProviderTests.java
index be65c7a..1f928f1 100644
--- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/sampling/AsyncMcpSamplingProviderTests.java
+++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/sampling/AsyncMcpSamplingProviderTests.java
@@ -35,7 +35,7 @@ void testGetSamplingHandler() {
 		// Create a class with only one valid sampling method
 		class SingleValidMethod {
 
-			@McpSampling(clientId = "test-client")
+			@McpSampling(clients = "test-client")
 			public Mono handleAsyncSamplingRequest(CreateMessageRequest request) {
 				return Mono.just(CreateMessageResult.builder()
 					.role(io.modelcontextprotocol.spec.McpSchema.Role.ASSISTANT)
@@ -77,7 +77,7 @@ void testDirectResultMethod() {
 		// Create a class with only the direct result method
 		class DirectResultOnly {
 
-			@McpSampling(clientId = "test-client")
+			@McpSampling(clients = "test-client")
 			public CreateMessageResult handleDirectSamplingRequest(CreateMessageRequest request) {
 				return CreateMessageResult.builder()
 					.role(io.modelcontextprotocol.spec.McpSchema.Role.ASSISTANT)
diff --git a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/sampling/SyncMcpSamplingProviderTests.java b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/sampling/SyncMcpSamplingProviderTests.java
index bd15dc5..a34b135 100644
--- a/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/sampling/SyncMcpSamplingProviderTests.java
+++ b/mcp-annotations/src/test/java/org/springaicommunity/mcp/provider/sampling/SyncMcpSamplingProviderTests.java
@@ -32,7 +32,7 @@ void testGetSamplingHandler() {
 		// Create a class with only one valid sampling method
 		class SingleValidMethod {
 
-			@McpSampling(clientId = "test-client")
+			@McpSampling(clients = "test-client")
 			public CreateMessageResult handleSamplingRequest(CreateMessageRequest request) {
 				return CreateMessageResult.builder()
 					.role(io.modelcontextprotocol.spec.McpSchema.Role.ASSISTANT)