feat: implement resource update notifications (#264)
This enables servers to notify clients when specific resources are updated, allowing clients to react to individual resource changes rather than just list changes. - Add ResourcesUpdatedNotification schema with uri field - Add METHOD_NOTIFICATION_RESOURCES_UPDATED constant - Implement notifyResourcesUpdated() methods in McpAsyncServer and McpSyncServer - Add client-side support for resource update consumers and handlers - Client automatically reads updated resource content when notification received - Add tests for both async and sync server implementations Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
This commit is contained in:
parent
be719f53d8
commit
9ebff0c5d9
10 changed files with 146 additions and 7 deletions
|
|
@ -194,6 +194,18 @@ public abstract class AbstractMcpAsyncServerTests {
|
||||||
assertThatCode(() -> mcpAsyncServer.closeGracefully().block(Duration.ofSeconds(10))).doesNotThrowAnyException();
|
assertThatCode(() -> mcpAsyncServer.closeGracefully().block(Duration.ofSeconds(10))).doesNotThrowAnyException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testNotifyResourcesUpdated() {
|
||||||
|
var mcpAsyncServer = McpServer.async(createMcpTransportProvider()).serverInfo("test-server", "1.0.0").build();
|
||||||
|
|
||||||
|
StepVerifier
|
||||||
|
.create(mcpAsyncServer
|
||||||
|
.notifyResourcesUpdated(new McpSchema.ResourcesUpdatedNotification(TEST_RESOURCE_URI)))
|
||||||
|
.verifyComplete();
|
||||||
|
|
||||||
|
assertThatCode(() -> mcpAsyncServer.closeGracefully().block(Duration.ofSeconds(10))).doesNotThrowAnyException();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testAddResource() {
|
void testAddResource() {
|
||||||
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
|
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
|
||||||
|
|
|
||||||
|
|
@ -191,6 +191,17 @@ public abstract class AbstractMcpSyncServerTests {
|
||||||
assertThatCode(() -> mcpSyncServer.closeGracefully()).doesNotThrowAnyException();
|
assertThatCode(() -> mcpSyncServer.closeGracefully()).doesNotThrowAnyException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testNotifyResourcesUpdated() {
|
||||||
|
var mcpSyncServer = McpServer.sync(createMcpTransportProvider()).serverInfo("test-server", "1.0.0").build();
|
||||||
|
|
||||||
|
assertThatCode(() -> mcpSyncServer
|
||||||
|
.notifyResourcesUpdated(new McpSchema.ResourcesUpdatedNotification(TEST_RESOURCE_URI)))
|
||||||
|
.doesNotThrowAnyException();
|
||||||
|
|
||||||
|
assertThatCode(() -> mcpSyncServer.closeGracefully()).doesNotThrowAnyException();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testAddResource() {
|
void testAddResource() {
|
||||||
var mcpSyncServer = McpServer.sync(createMcpTransportProvider())
|
var mcpSyncServer = McpServer.sync(createMcpTransportProvider())
|
||||||
|
|
|
||||||
|
|
@ -236,6 +236,18 @@ public class McpAsyncClient {
|
||||||
notificationHandlers.put(McpSchema.METHOD_NOTIFICATION_RESOURCES_LIST_CHANGED,
|
notificationHandlers.put(McpSchema.METHOD_NOTIFICATION_RESOURCES_LIST_CHANGED,
|
||||||
asyncResourcesChangeNotificationHandler(resourcesChangeConsumersFinal));
|
asyncResourcesChangeNotificationHandler(resourcesChangeConsumersFinal));
|
||||||
|
|
||||||
|
// Resources Update Notification
|
||||||
|
List<Function<List<McpSchema.ResourceContents>, Mono<Void>>> resourcesUpdateConsumersFinal = new ArrayList<>();
|
||||||
|
resourcesUpdateConsumersFinal
|
||||||
|
.add((notification) -> Mono.fromRunnable(() -> logger.debug("Resources updated: {}", notification)));
|
||||||
|
|
||||||
|
if (!Utils.isEmpty(features.resourcesUpdateConsumers())) {
|
||||||
|
resourcesUpdateConsumersFinal.addAll(features.resourcesUpdateConsumers());
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationHandlers.put(McpSchema.METHOD_NOTIFICATION_RESOURCES_UPDATED,
|
||||||
|
asyncResourcesUpdatedNotificationHandler(resourcesUpdateConsumersFinal));
|
||||||
|
|
||||||
// Prompts Change Notification
|
// Prompts Change Notification
|
||||||
List<Function<List<McpSchema.Prompt>, Mono<Void>>> promptsChangeConsumersFinal = new ArrayList<>();
|
List<Function<List<McpSchema.Prompt>, Mono<Void>>> promptsChangeConsumersFinal = new ArrayList<>();
|
||||||
promptsChangeConsumersFinal
|
promptsChangeConsumersFinal
|
||||||
|
|
@ -867,6 +879,24 @@ public class McpAsyncClient {
|
||||||
.then());
|
.then());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private NotificationHandler asyncResourcesUpdatedNotificationHandler(
|
||||||
|
List<Function<List<McpSchema.ResourceContents>, Mono<Void>>> resourcesUpdateConsumers) {
|
||||||
|
return params -> {
|
||||||
|
McpSchema.ResourcesUpdatedNotification resourcesUpdatedNotification = transport.unmarshalFrom(params,
|
||||||
|
new TypeReference<>() {
|
||||||
|
});
|
||||||
|
|
||||||
|
return readResource(new McpSchema.ReadResourceRequest(resourcesUpdatedNotification.uri()))
|
||||||
|
.flatMap(readResourceResult -> Flux.fromIterable(resourcesUpdateConsumers)
|
||||||
|
.flatMap(consumer -> consumer.apply(readResourceResult.contents()))
|
||||||
|
.onErrorResume(error -> {
|
||||||
|
logger.error("Error handling resource update notification", error);
|
||||||
|
return Mono.empty();
|
||||||
|
})
|
||||||
|
.then());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// --------------------------
|
// --------------------------
|
||||||
// Prompts
|
// Prompts
|
||||||
// --------------------------
|
// --------------------------
|
||||||
|
|
|
||||||
|
|
@ -171,6 +171,8 @@ public interface McpClient {
|
||||||
|
|
||||||
private final List<Consumer<List<McpSchema.Resource>>> resourcesChangeConsumers = new ArrayList<>();
|
private final List<Consumer<List<McpSchema.Resource>>> resourcesChangeConsumers = new ArrayList<>();
|
||||||
|
|
||||||
|
private final List<Consumer<List<McpSchema.ResourceContents>>> resourcesUpdateConsumers = new ArrayList<>();
|
||||||
|
|
||||||
private final List<Consumer<List<McpSchema.Prompt>>> promptsChangeConsumers = new ArrayList<>();
|
private final List<Consumer<List<McpSchema.Prompt>>> promptsChangeConsumers = new ArrayList<>();
|
||||||
|
|
||||||
private final List<Consumer<McpSchema.LoggingMessageNotification>> loggingConsumers = new ArrayList<>();
|
private final List<Consumer<McpSchema.LoggingMessageNotification>> loggingConsumers = new ArrayList<>();
|
||||||
|
|
@ -382,8 +384,8 @@ public interface McpClient {
|
||||||
*/
|
*/
|
||||||
public McpSyncClient build() {
|
public McpSyncClient build() {
|
||||||
McpClientFeatures.Sync syncFeatures = new McpClientFeatures.Sync(this.clientInfo, this.capabilities,
|
McpClientFeatures.Sync syncFeatures = new McpClientFeatures.Sync(this.clientInfo, this.capabilities,
|
||||||
this.roots, this.toolsChangeConsumers, this.resourcesChangeConsumers, this.promptsChangeConsumers,
|
this.roots, this.toolsChangeConsumers, this.resourcesChangeConsumers, this.resourcesUpdateConsumers,
|
||||||
this.loggingConsumers, this.samplingHandler, this.elicitationHandler);
|
this.promptsChangeConsumers, this.loggingConsumers, this.samplingHandler, this.elicitationHandler);
|
||||||
|
|
||||||
McpClientFeatures.Async asyncFeatures = McpClientFeatures.Async.fromSync(syncFeatures);
|
McpClientFeatures.Async asyncFeatures = McpClientFeatures.Async.fromSync(syncFeatures);
|
||||||
|
|
||||||
|
|
@ -427,6 +429,8 @@ public interface McpClient {
|
||||||
|
|
||||||
private final List<Function<List<McpSchema.Resource>, Mono<Void>>> resourcesChangeConsumers = new ArrayList<>();
|
private final List<Function<List<McpSchema.Resource>, Mono<Void>>> resourcesChangeConsumers = new ArrayList<>();
|
||||||
|
|
||||||
|
private final List<Function<List<McpSchema.ResourceContents>, Mono<Void>>> resourcesUpdateConsumers = new ArrayList<>();
|
||||||
|
|
||||||
private final List<Function<List<McpSchema.Prompt>, Mono<Void>>> promptsChangeConsumers = new ArrayList<>();
|
private final List<Function<List<McpSchema.Prompt>, Mono<Void>>> promptsChangeConsumers = new ArrayList<>();
|
||||||
|
|
||||||
private final List<Function<McpSchema.LoggingMessageNotification, Mono<Void>>> loggingConsumers = new ArrayList<>();
|
private final List<Function<McpSchema.LoggingMessageNotification, Mono<Void>>> loggingConsumers = new ArrayList<>();
|
||||||
|
|
@ -589,6 +593,23 @@ public interface McpClient {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a consumer to be notified when a specific resource is updated. This allows
|
||||||
|
* the client to react to changes in individual resources, such as updates to
|
||||||
|
* their content or metadata.
|
||||||
|
* @param resourcesUpdateConsumer A consumer function that processes the updated
|
||||||
|
* resource and returns a Mono indicating the completion of the processing. Must
|
||||||
|
* not be null.
|
||||||
|
* @return This builder instance for method chaining.
|
||||||
|
* @throws IllegalArgumentException If the resourcesUpdateConsumer is null.
|
||||||
|
*/
|
||||||
|
public AsyncSpec resourcesUpdateConsumer(
|
||||||
|
Function<List<McpSchema.ResourceContents>, Mono<Void>> resourcesUpdateConsumer) {
|
||||||
|
Assert.notNull(resourcesUpdateConsumer, "Resources update consumer must not be null");
|
||||||
|
this.resourcesUpdateConsumers.add(resourcesUpdateConsumer);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a consumer to be notified when the available prompts change. This allows
|
* Adds a consumer to be notified when the available prompts change. This allows
|
||||||
* the client to react to changes in the server's prompt templates, such as new
|
* the client to react to changes in the server's prompt templates, such as new
|
||||||
|
|
@ -641,8 +662,9 @@ public interface McpClient {
|
||||||
public McpAsyncClient build() {
|
public McpAsyncClient build() {
|
||||||
return new McpAsyncClient(this.transport, this.requestTimeout, this.initializationTimeout,
|
return new McpAsyncClient(this.transport, this.requestTimeout, this.initializationTimeout,
|
||||||
new McpClientFeatures.Async(this.clientInfo, this.capabilities, this.roots,
|
new McpClientFeatures.Async(this.clientInfo, this.capabilities, this.roots,
|
||||||
this.toolsChangeConsumers, this.resourcesChangeConsumers, this.promptsChangeConsumers,
|
this.toolsChangeConsumers, this.resourcesChangeConsumers, this.resourcesUpdateConsumers,
|
||||||
this.loggingConsumers, this.samplingHandler, this.elicitationHandler));
|
this.promptsChangeConsumers, this.loggingConsumers, this.samplingHandler,
|
||||||
|
this.elicitationHandler));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@ class McpClientFeatures {
|
||||||
record Async(McpSchema.Implementation clientInfo, McpSchema.ClientCapabilities clientCapabilities,
|
record Async(McpSchema.Implementation clientInfo, McpSchema.ClientCapabilities clientCapabilities,
|
||||||
Map<String, McpSchema.Root> roots, List<Function<List<McpSchema.Tool>, Mono<Void>>> toolsChangeConsumers,
|
Map<String, McpSchema.Root> roots, List<Function<List<McpSchema.Tool>, Mono<Void>>> toolsChangeConsumers,
|
||||||
List<Function<List<McpSchema.Resource>, Mono<Void>>> resourcesChangeConsumers,
|
List<Function<List<McpSchema.Resource>, Mono<Void>>> resourcesChangeConsumers,
|
||||||
|
List<Function<List<McpSchema.ResourceContents>, Mono<Void>>> resourcesUpdateConsumers,
|
||||||
List<Function<List<McpSchema.Prompt>, Mono<Void>>> promptsChangeConsumers,
|
List<Function<List<McpSchema.Prompt>, Mono<Void>>> promptsChangeConsumers,
|
||||||
List<Function<McpSchema.LoggingMessageNotification, Mono<Void>>> loggingConsumers,
|
List<Function<McpSchema.LoggingMessageNotification, Mono<Void>>> loggingConsumers,
|
||||||
Function<McpSchema.CreateMessageRequest, Mono<McpSchema.CreateMessageResult>> samplingHandler,
|
Function<McpSchema.CreateMessageRequest, Mono<McpSchema.CreateMessageResult>> samplingHandler,
|
||||||
|
|
@ -85,6 +86,7 @@ class McpClientFeatures {
|
||||||
Map<String, McpSchema.Root> roots,
|
Map<String, McpSchema.Root> roots,
|
||||||
List<Function<List<McpSchema.Tool>, Mono<Void>>> toolsChangeConsumers,
|
List<Function<List<McpSchema.Tool>, Mono<Void>>> toolsChangeConsumers,
|
||||||
List<Function<List<McpSchema.Resource>, Mono<Void>>> resourcesChangeConsumers,
|
List<Function<List<McpSchema.Resource>, Mono<Void>>> resourcesChangeConsumers,
|
||||||
|
List<Function<List<McpSchema.ResourceContents>, Mono<Void>>> resourcesUpdateConsumers,
|
||||||
List<Function<List<McpSchema.Prompt>, Mono<Void>>> promptsChangeConsumers,
|
List<Function<List<McpSchema.Prompt>, Mono<Void>>> promptsChangeConsumers,
|
||||||
List<Function<McpSchema.LoggingMessageNotification, Mono<Void>>> loggingConsumers,
|
List<Function<McpSchema.LoggingMessageNotification, Mono<Void>>> loggingConsumers,
|
||||||
Function<McpSchema.CreateMessageRequest, Mono<McpSchema.CreateMessageResult>> samplingHandler,
|
Function<McpSchema.CreateMessageRequest, Mono<McpSchema.CreateMessageResult>> samplingHandler,
|
||||||
|
|
@ -101,6 +103,7 @@ class McpClientFeatures {
|
||||||
|
|
||||||
this.toolsChangeConsumers = toolsChangeConsumers != null ? toolsChangeConsumers : List.of();
|
this.toolsChangeConsumers = toolsChangeConsumers != null ? toolsChangeConsumers : List.of();
|
||||||
this.resourcesChangeConsumers = resourcesChangeConsumers != null ? resourcesChangeConsumers : List.of();
|
this.resourcesChangeConsumers = resourcesChangeConsumers != null ? resourcesChangeConsumers : List.of();
|
||||||
|
this.resourcesUpdateConsumers = resourcesUpdateConsumers != null ? resourcesUpdateConsumers : List.of();
|
||||||
this.promptsChangeConsumers = promptsChangeConsumers != null ? promptsChangeConsumers : List.of();
|
this.promptsChangeConsumers = promptsChangeConsumers != null ? promptsChangeConsumers : List.of();
|
||||||
this.loggingConsumers = loggingConsumers != null ? loggingConsumers : List.of();
|
this.loggingConsumers = loggingConsumers != null ? loggingConsumers : List.of();
|
||||||
this.samplingHandler = samplingHandler;
|
this.samplingHandler = samplingHandler;
|
||||||
|
|
@ -128,8 +131,13 @@ class McpClientFeatures {
|
||||||
.subscribeOn(Schedulers.boundedElastic()));
|
.subscribeOn(Schedulers.boundedElastic()));
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Function<List<McpSchema.Prompt>, Mono<Void>>> promptsChangeConsumers = new ArrayList<>();
|
List<Function<List<McpSchema.ResourceContents>, Mono<Void>>> resourcesUpdateConsumers = new ArrayList<>();
|
||||||
|
for (Consumer<List<McpSchema.ResourceContents>> consumer : syncSpec.resourcesUpdateConsumers()) {
|
||||||
|
resourcesUpdateConsumers.add(r -> Mono.<Void>fromRunnable(() -> consumer.accept(r))
|
||||||
|
.subscribeOn(Schedulers.boundedElastic()));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Function<List<McpSchema.Prompt>, Mono<Void>>> promptsChangeConsumers = new ArrayList<>();
|
||||||
for (Consumer<List<McpSchema.Prompt>> consumer : syncSpec.promptsChangeConsumers()) {
|
for (Consumer<List<McpSchema.Prompt>> consumer : syncSpec.promptsChangeConsumers()) {
|
||||||
promptsChangeConsumers.add(p -> Mono.<Void>fromRunnable(() -> consumer.accept(p))
|
promptsChangeConsumers.add(p -> Mono.<Void>fromRunnable(() -> consumer.accept(p))
|
||||||
.subscribeOn(Schedulers.boundedElastic()));
|
.subscribeOn(Schedulers.boundedElastic()));
|
||||||
|
|
@ -150,8 +158,8 @@ class McpClientFeatures {
|
||||||
.subscribeOn(Schedulers.boundedElastic());
|
.subscribeOn(Schedulers.boundedElastic());
|
||||||
|
|
||||||
return new Async(syncSpec.clientInfo(), syncSpec.clientCapabilities(), syncSpec.roots(),
|
return new Async(syncSpec.clientInfo(), syncSpec.clientCapabilities(), syncSpec.roots(),
|
||||||
toolsChangeConsumers, resourcesChangeConsumers, promptsChangeConsumers, loggingConsumers,
|
toolsChangeConsumers, resourcesChangeConsumers, resourcesUpdateConsumers, promptsChangeConsumers,
|
||||||
samplingHandler, elicitationHandler);
|
loggingConsumers, samplingHandler, elicitationHandler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -172,6 +180,7 @@ class McpClientFeatures {
|
||||||
public record Sync(McpSchema.Implementation clientInfo, McpSchema.ClientCapabilities clientCapabilities,
|
public record Sync(McpSchema.Implementation clientInfo, McpSchema.ClientCapabilities clientCapabilities,
|
||||||
Map<String, McpSchema.Root> roots, List<Consumer<List<McpSchema.Tool>>> toolsChangeConsumers,
|
Map<String, McpSchema.Root> roots, List<Consumer<List<McpSchema.Tool>>> toolsChangeConsumers,
|
||||||
List<Consumer<List<McpSchema.Resource>>> resourcesChangeConsumers,
|
List<Consumer<List<McpSchema.Resource>>> resourcesChangeConsumers,
|
||||||
|
List<Consumer<List<McpSchema.ResourceContents>>> resourcesUpdateConsumers,
|
||||||
List<Consumer<List<McpSchema.Prompt>>> promptsChangeConsumers,
|
List<Consumer<List<McpSchema.Prompt>>> promptsChangeConsumers,
|
||||||
List<Consumer<McpSchema.LoggingMessageNotification>> loggingConsumers,
|
List<Consumer<McpSchema.LoggingMessageNotification>> loggingConsumers,
|
||||||
Function<McpSchema.CreateMessageRequest, McpSchema.CreateMessageResult> samplingHandler,
|
Function<McpSchema.CreateMessageRequest, McpSchema.CreateMessageResult> samplingHandler,
|
||||||
|
|
@ -184,6 +193,7 @@ class McpClientFeatures {
|
||||||
* @param roots the roots.
|
* @param roots the roots.
|
||||||
* @param toolsChangeConsumers the tools change consumers.
|
* @param toolsChangeConsumers the tools change consumers.
|
||||||
* @param resourcesChangeConsumers the resources change consumers.
|
* @param resourcesChangeConsumers the resources change consumers.
|
||||||
|
* @param resourcesUpdateConsumers the resource update consumers.
|
||||||
* @param promptsChangeConsumers the prompts change consumers.
|
* @param promptsChangeConsumers the prompts change consumers.
|
||||||
* @param loggingConsumers the logging consumers.
|
* @param loggingConsumers the logging consumers.
|
||||||
* @param samplingHandler the sampling handler.
|
* @param samplingHandler the sampling handler.
|
||||||
|
|
@ -192,6 +202,7 @@ class McpClientFeatures {
|
||||||
public Sync(McpSchema.Implementation clientInfo, McpSchema.ClientCapabilities clientCapabilities,
|
public Sync(McpSchema.Implementation clientInfo, McpSchema.ClientCapabilities clientCapabilities,
|
||||||
Map<String, McpSchema.Root> roots, List<Consumer<List<McpSchema.Tool>>> toolsChangeConsumers,
|
Map<String, McpSchema.Root> roots, List<Consumer<List<McpSchema.Tool>>> toolsChangeConsumers,
|
||||||
List<Consumer<List<McpSchema.Resource>>> resourcesChangeConsumers,
|
List<Consumer<List<McpSchema.Resource>>> resourcesChangeConsumers,
|
||||||
|
List<Consumer<List<McpSchema.ResourceContents>>> resourcesUpdateConsumers,
|
||||||
List<Consumer<List<McpSchema.Prompt>>> promptsChangeConsumers,
|
List<Consumer<List<McpSchema.Prompt>>> promptsChangeConsumers,
|
||||||
List<Consumer<McpSchema.LoggingMessageNotification>> loggingConsumers,
|
List<Consumer<McpSchema.LoggingMessageNotification>> loggingConsumers,
|
||||||
Function<McpSchema.CreateMessageRequest, McpSchema.CreateMessageResult> samplingHandler,
|
Function<McpSchema.CreateMessageRequest, McpSchema.CreateMessageResult> samplingHandler,
|
||||||
|
|
@ -208,6 +219,7 @@ class McpClientFeatures {
|
||||||
|
|
||||||
this.toolsChangeConsumers = toolsChangeConsumers != null ? toolsChangeConsumers : List.of();
|
this.toolsChangeConsumers = toolsChangeConsumers != null ? toolsChangeConsumers : List.of();
|
||||||
this.resourcesChangeConsumers = resourcesChangeConsumers != null ? resourcesChangeConsumers : List.of();
|
this.resourcesChangeConsumers = resourcesChangeConsumers != null ? resourcesChangeConsumers : List.of();
|
||||||
|
this.resourcesUpdateConsumers = resourcesUpdateConsumers != null ? resourcesUpdateConsumers : List.of();
|
||||||
this.promptsChangeConsumers = promptsChangeConsumers != null ? promptsChangeConsumers : List.of();
|
this.promptsChangeConsumers = promptsChangeConsumers != null ? promptsChangeConsumers : List.of();
|
||||||
this.loggingConsumers = loggingConsumers != null ? loggingConsumers : List.of();
|
this.loggingConsumers = loggingConsumers != null ? loggingConsumers : List.of();
|
||||||
this.samplingHandler = samplingHandler;
|
this.samplingHandler = samplingHandler;
|
||||||
|
|
|
||||||
|
|
@ -430,6 +430,15 @@ public class McpAsyncServer {
|
||||||
return this.mcpTransportProvider.notifyClients(McpSchema.METHOD_NOTIFICATION_RESOURCES_LIST_CHANGED, null);
|
return this.mcpTransportProvider.notifyClients(McpSchema.METHOD_NOTIFICATION_RESOURCES_LIST_CHANGED, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies clients that the resources have updated.
|
||||||
|
* @return A Mono that completes when all clients have been notified
|
||||||
|
*/
|
||||||
|
public Mono<Void> notifyResourcesUpdated(McpSchema.ResourcesUpdatedNotification resourcesUpdatedNotification) {
|
||||||
|
return this.mcpTransportProvider.notifyClients(McpSchema.METHOD_NOTIFICATION_RESOURCES_UPDATED,
|
||||||
|
resourcesUpdatedNotification);
|
||||||
|
}
|
||||||
|
|
||||||
private McpServerSession.RequestHandler<McpSchema.ListResourcesResult> resourcesListRequestHandler() {
|
private McpServerSession.RequestHandler<McpSchema.ListResourcesResult> resourcesListRequestHandler() {
|
||||||
return (exchange, params) -> {
|
return (exchange, params) -> {
|
||||||
var resourceList = this.resources.values()
|
var resourceList = this.resources.values()
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,13 @@ public class McpSyncServer {
|
||||||
this.asyncServer.notifyResourcesListChanged().block();
|
this.asyncServer.notifyResourcesListChanged().block();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify clients that the resources have updated.
|
||||||
|
*/
|
||||||
|
public void notifyResourcesUpdated(McpSchema.ResourcesUpdatedNotification resourcesUpdatedNotification) {
|
||||||
|
this.asyncServer.notifyResourcesUpdated(resourcesUpdatedNotification).block();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify clients that the list of available prompts has changed.
|
* Notify clients that the list of available prompts has changed.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,8 @@ public final class McpSchema {
|
||||||
|
|
||||||
public static final String METHOD_NOTIFICATION_RESOURCES_LIST_CHANGED = "notifications/resources/list_changed";
|
public static final String METHOD_NOTIFICATION_RESOURCES_LIST_CHANGED = "notifications/resources/list_changed";
|
||||||
|
|
||||||
|
public static final String METHOD_NOTIFICATION_RESOURCES_UPDATED = "notifications/resources/updated";
|
||||||
|
|
||||||
public static final String METHOD_RESOURCES_TEMPLATES_LIST = "resources/templates/list";
|
public static final String METHOD_RESOURCES_TEMPLATES_LIST = "resources/templates/list";
|
||||||
|
|
||||||
public static final String METHOD_RESOURCES_SUBSCRIBE = "resources/subscribe";
|
public static final String METHOD_RESOURCES_SUBSCRIBE = "resources/subscribe";
|
||||||
|
|
@ -1314,6 +1316,17 @@ public final class McpSchema {
|
||||||
@JsonProperty("total") Double total) {
|
@JsonProperty("total") Double total) {
|
||||||
}// @formatter:on
|
}// @formatter:on
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Model Context Protocol (MCP) provides a standardized way for servers to send
|
||||||
|
* resources update message to clients.
|
||||||
|
*
|
||||||
|
* @param uri The updated resource uri.
|
||||||
|
*/
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public record ResourcesUpdatedNotification(// @formatter:off
|
||||||
|
@JsonProperty("uri") String uri) {
|
||||||
|
}// @formatter:on
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Model Context Protocol (MCP) provides a standardized way for servers to send
|
* The Model Context Protocol (MCP) provides a standardized way for servers to send
|
||||||
* structured log messages to clients. Clients can control logging verbosity by
|
* structured log messages to clients. Clients can control logging verbosity by
|
||||||
|
|
|
||||||
|
|
@ -193,6 +193,18 @@ public abstract class AbstractMcpAsyncServerTests {
|
||||||
assertThatCode(() -> mcpAsyncServer.closeGracefully().block(Duration.ofSeconds(10))).doesNotThrowAnyException();
|
assertThatCode(() -> mcpAsyncServer.closeGracefully().block(Duration.ofSeconds(10))).doesNotThrowAnyException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testNotifyResourcesUpdated() {
|
||||||
|
var mcpAsyncServer = McpServer.async(createMcpTransportProvider()).serverInfo("test-server", "1.0.0").build();
|
||||||
|
|
||||||
|
StepVerifier
|
||||||
|
.create(mcpAsyncServer
|
||||||
|
.notifyResourcesUpdated(new McpSchema.ResourcesUpdatedNotification(TEST_RESOURCE_URI)))
|
||||||
|
.verifyComplete();
|
||||||
|
|
||||||
|
assertThatCode(() -> mcpAsyncServer.closeGracefully().block(Duration.ofSeconds(10))).doesNotThrowAnyException();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testAddResource() {
|
void testAddResource() {
|
||||||
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
|
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
|
||||||
|
|
|
||||||
|
|
@ -190,6 +190,17 @@ public abstract class AbstractMcpSyncServerTests {
|
||||||
assertThatCode(() -> mcpSyncServer.closeGracefully()).doesNotThrowAnyException();
|
assertThatCode(() -> mcpSyncServer.closeGracefully()).doesNotThrowAnyException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testNotifyResourcesUpdated() {
|
||||||
|
var mcpSyncServer = McpServer.sync(createMcpTransportProvider()).serverInfo("test-server", "1.0.0").build();
|
||||||
|
|
||||||
|
assertThatCode(() -> mcpSyncServer
|
||||||
|
.notifyResourcesUpdated(new McpSchema.ResourcesUpdatedNotification(TEST_RESOURCE_URI)))
|
||||||
|
.doesNotThrowAnyException();
|
||||||
|
|
||||||
|
assertThatCode(() -> mcpSyncServer.closeGracefully()).doesNotThrowAnyException();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testAddResource() {
|
void testAddResource() {
|
||||||
var mcpSyncServer = McpServer.sync(createMcpTransportProvider())
|
var mcpSyncServer = McpServer.sync(createMcpTransportProvider())
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue