diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java index 91f973f18..32e76264e 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java @@ -723,6 +723,10 @@ public Mono removeResourceTemplate(String uriTemplate) { McpServerFeatures.AsyncResourceTemplateSpecification removed = this.resourceTemplates.remove(uriTemplate); if (removed != null) { logger.debug("Removed resource template: {}", uriTemplate); + if (Boolean.TRUE.equals(this.serverCapabilities.resources().listChanged())) { + return notifyResourcesListChanged(); + } + return Mono.empty(); } else { logger.warn("Failed to remove a resource template with URI '{}' (not found)", uriTemplate); diff --git a/mcp-test/src/test/java/io/modelcontextprotocol/server/DerivedCapabilitiesRuntimeChangesTests.java b/mcp-test/src/test/java/io/modelcontextprotocol/server/DerivedCapabilitiesRuntimeChangesTests.java index 17d164ad6..5edde2c02 100644 --- a/mcp-test/src/test/java/io/modelcontextprotocol/server/DerivedCapabilitiesRuntimeChangesTests.java +++ b/mcp-test/src/test/java/io/modelcontextprotocol/server/DerivedCapabilitiesRuntimeChangesTests.java @@ -78,6 +78,7 @@ void addAndRemoveResourceAndTemplateWithDerivedCapabilities() { StepVerifier.create(server.addResource(asyncResource("test://added"))).verifyComplete(); StepVerifier.create(server.removeResource("test://initial")).verifyComplete(); StepVerifier.create(server.addResourceTemplate(asyncTemplate("test://items/{id}"))).verifyComplete(); + StepVerifier.create(server.removeResourceTemplate("test://items/{id}")).verifyComplete(); assertThat(transport.getAllSentMessages()).isEmpty(); } diff --git a/mcp-test/src/test/java/io/modelcontextprotocol/server/ResourceTemplateManagementTests.java b/mcp-test/src/test/java/io/modelcontextprotocol/server/ResourceTemplateManagementTests.java index eaf3d41a3..927398441 100644 --- a/mcp-test/src/test/java/io/modelcontextprotocol/server/ResourceTemplateManagementTests.java +++ b/mcp-test/src/test/java/io/modelcontextprotocol/server/ResourceTemplateManagementTests.java @@ -9,6 +9,8 @@ import io.modelcontextprotocol.MockMcpServerTransport; import io.modelcontextprotocol.MockMcpServerTransportProvider; +import io.modelcontextprotocol.spec.McpSchema; +import io.modelcontextprotocol.spec.McpSchema.JSONRPCNotification; import io.modelcontextprotocol.spec.McpSchema.ReadResourceResult; import io.modelcontextprotocol.spec.McpSchema.ResourceTemplate; import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities; @@ -146,6 +148,38 @@ void testRemoveNonexistentResourceTemplate() { StepVerifier.create(mcpAsyncServer.removeResourceTemplate("nonexistent://template/{id}")).verifyComplete(); } + @Test + void testRemoveResourceTemplateNotifiesListChanged() { + ResourceTemplate template = ResourceTemplate.builder(TEST_TEMPLATE_URI, TEST_TEMPLATE_NAME).build(); + + McpServerFeatures.AsyncResourceTemplateSpecification specification = new McpServerFeatures.AsyncResourceTemplateSpecification( + template, (exchange, req) -> Mono.just(ReadResourceResult.builder(List.of()).build())); + + mcpAsyncServer = McpServer.async(mockTransportProvider) + .serverInfo("test-server", "1.0.0") + .capabilities(ServerCapabilities.builder().resources(false, true).build()) + .resourceTemplates(specification) + .build(); + + StepVerifier.create(mcpAsyncServer.removeResourceTemplate(TEST_TEMPLATE_URI)).verifyComplete(); + + assertThat(mockTransportProvider.getTransport().getAllSentMessages()).singleElement() + .isInstanceOfSatisfying(JSONRPCNotification.class, notification -> assertThat(notification.method()) + .isEqualTo(McpSchema.METHOD_NOTIFICATION_RESOURCES_LIST_CHANGED)); + } + + @Test + void testRemoveNonexistentResourceTemplateDoesNotNotify() { + mcpAsyncServer = McpServer.async(mockTransportProvider) + .serverInfo("test-server", "1.0.0") + .capabilities(ServerCapabilities.builder().resources(false, true).build()) + .build(); + + StepVerifier.create(mcpAsyncServer.removeResourceTemplate("nonexistent://template/{id}")).verifyComplete(); + + assertThat(mockTransportProvider.getTransport().getAllSentMessages()).isEmpty(); + } + @Test void testReplaceExistingResourceTemplate() { ResourceTemplate originalTemplate = ResourceTemplate.builder(TEST_TEMPLATE_URI, TEST_TEMPLATE_NAME)