Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
package io.modelcontextprotocol.server;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand Down Expand Up @@ -365,6 +368,66 @@ public Mono<Void> addTool(McpServerFeatures.AsyncToolSpecification toolSpecifica
});
}

/**
* Add multiple tool call specifications at runtime.
* @param toolSpecifications The tool specifications to add
* @return Mono that completes when clients have been notified of the change
*/
public Mono<Void> addTools(List<McpServerFeatures.AsyncToolSpecification> toolSpecifications) {
if (toolSpecifications == null) {
return Mono.error(new IllegalArgumentException("Tool specifications must not be null"));
}

if (this.serverCapabilities.tools() == null) {
return Mono.error(new IllegalStateException("Server must be configured with tool capabilities"));
}
if (toolSpecifications.isEmpty()) {
return Mono.empty();
}

Map<String, McpServerFeatures.AsyncToolSpecification> wrappedToolSpecificationsByName;
try {
wrappedToolSpecificationsByName = sanitizeToolSpecifications(toolSpecifications);
}
catch (IllegalArgumentException e) {
return Mono.error(e);
}

return Mono.defer(() -> {
this.tools.removeIf(
toolSpecification -> wrappedToolSpecificationsByName.containsKey(toolSpecification.tool().name()));
this.tools.addAll(wrappedToolSpecificationsByName.values());

logger.debug("Added tool handlers: {}", wrappedToolSpecificationsByName.keySet());

if (this.serverCapabilities.tools().listChanged()) {
Comment thread
amaan75 marked this conversation as resolved.
return notifyToolsListChanged();
}
return Mono.empty();
});
}

private Map<String, McpServerFeatures.AsyncToolSpecification> sanitizeToolSpecifications(
List<McpServerFeatures.AsyncToolSpecification> toolSpecifications) {
LinkedHashMap<String, McpServerFeatures.AsyncToolSpecification> toolSpecificationsByName = new LinkedHashMap<>();

for (var toolSpecification : toolSpecifications) {
if (toolSpecification == null) {
throw new IllegalArgumentException("Tool specification must not be null");
}
if (toolSpecification.tool() == null) {
throw new IllegalArgumentException("Tool must not be null");
}
if (toolSpecification.callHandler() == null) {
throw new IllegalArgumentException("Tool call handler must not be null");
}
var wrappedToolSpecification = withStructuredOutputHandling(this.jsonSchemaValidator, toolSpecification);
toolSpecificationsByName.put(wrappedToolSpecification.tool().name(), wrappedToolSpecification);
}

return toolSpecificationsByName;
}

private static class StructuredOutputCallToolHandler
implements BiFunction<McpAsyncServerExchange, McpSchema.CallToolRequest, Mono<McpSchema.CallToolResult>> {

Expand Down Expand Up @@ -517,6 +580,45 @@ public Mono<Void> removeTool(String toolName) {
});
}

/**
* Remove multiple tool handlers at runtime.
* @param toolNames The names of the tool handlers to remove
* @return Mono that completes when clients have been notified of the change
*/
public Mono<Void> removeTools(List<String> toolNames) {
if (toolNames == null) {
return Mono.error(new IllegalArgumentException("Tool names must not be null"));
}
if (this.serverCapabilities.tools() == null) {
return Mono.error(new IllegalStateException("Server must be configured with tool capabilities"));
}
if (toolNames.isEmpty()) {
return Mono.empty();
}

Set<String> toolNamesToRemove = new HashSet<>();
for (String toolName : toolNames) {
if (toolName == null) {
return Mono.error(new IllegalArgumentException("Tool name must not be null"));
}
toolNamesToRemove.add(toolName);
}

return Mono.defer(() -> {
if (this.tools.removeIf(toolSpecification -> toolNamesToRemove.contains(toolSpecification.tool().name()))) {
logger.debug("Removed tool handlers: {}", toolNamesToRemove);
if (this.serverCapabilities.tools().listChanged()) {
Comment thread
amaan75 marked this conversation as resolved.
return notifyToolsListChanged();
}
}
else {
logger.warn("Ignore as no Tools with names '{}' were found", toolNamesToRemove);
}

return Mono.empty();
});
}

/**
* Notifies clients that the list of available tools has changed.
* @return A Mono that completes when all clients have been notified
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.BiFunction;
Expand Down Expand Up @@ -354,6 +357,63 @@ public Mono<Void> addTool(McpStatelessServerFeatures.AsyncToolSpecification tool
});
}

/**
* Add multiple tool specifications at runtime.
* @param toolSpecifications The tool specifications to add
* @return Mono that completes when the tools have been added
*/
public Mono<Void> addTools(List<McpStatelessServerFeatures.AsyncToolSpecification> toolSpecifications) {
if (toolSpecifications == null) {
return Mono.error(new IllegalArgumentException("Tool specifications must not be null"));
}
if (this.serverCapabilities.tools() == null) {
return Mono.error(new IllegalStateException("Server must be configured with tool capabilities"));
}

if (toolSpecifications.isEmpty()) {
return Mono.empty();
}

Map<String, McpStatelessServerFeatures.AsyncToolSpecification> wrappedToolSpecificationsByName;
try {
wrappedToolSpecificationsByName = sanitizeToolSpecifications(toolSpecifications);
}
catch (IllegalArgumentException e) {
return Mono.error(e);
}

return Mono.defer(() -> {
this.tools.removeIf(
toolSpecification -> wrappedToolSpecificationsByName.containsKey(toolSpecification.tool().name()));
this.tools.addAll(wrappedToolSpecificationsByName.values());

logger.debug("Added tool handlers: {}", wrappedToolSpecificationsByName.keySet());

return Mono.empty();
});
}

private Map<String, McpStatelessServerFeatures.AsyncToolSpecification> sanitizeToolSpecifications(
List<McpStatelessServerFeatures.AsyncToolSpecification> toolSpecifications) {
LinkedHashMap<String, McpStatelessServerFeatures.AsyncToolSpecification> toolSpecificationsByName = new LinkedHashMap<>();

for (var toolSpecification : toolSpecifications) {
if (toolSpecification == null) {
throw new IllegalArgumentException("Tool specification must not be null");
}
if (toolSpecification.tool() == null) {
throw new IllegalArgumentException("Tool must not be null");
}
if (toolSpecification.callHandler() == null) {
throw new IllegalArgumentException("Tool call handler must not be null");
}
var wrappedToolSpecification = withStructuredOutputHandling(this.jsonSchemaValidator, toolSpecification);
toolSpecificationsByName.put(wrappedToolSpecification.tool().name(), wrappedToolSpecification);
}

return toolSpecificationsByName;
}

/**
* List all registered tools.
* @return A Flux stream of all registered tools
Expand Down Expand Up @@ -388,6 +448,43 @@ public Mono<Void> removeTool(String toolName) {
});
}

/**
* Remove multiple tool handlers at runtime.
* @param toolNames The names of the tool handlers to remove
* @return Mono that completes when the tools have been removed
*/
public Mono<Void> removeTools(List<String> toolNames) {
if (toolNames == null) {
return Mono.error(new IllegalArgumentException("Tool names must not be null"));
}
if (this.serverCapabilities.tools() == null) {
return Mono.error(new IllegalStateException("Server must be configured with tool capabilities"));
}
if (toolNames.isEmpty()) {
return Mono.empty();
}

Set<String> toolNamesToRemove = new HashSet<>();
for (String toolName : toolNames) {
if (toolName == null) {
return Mono.error(new IllegalArgumentException("Tool name must not be null"));
}
toolNamesToRemove.add(toolName);
}


return Mono.defer(() -> {
if (this.tools.removeIf(toolSpecification -> toolNamesToRemove.contains(toolSpecification.tool().name()))) {
logger.debug("Removed tool handlers: {}", toolNamesToRemove);
}
else {
logger.warn("Ignore as no Tools with names '{}' were found", toolNamesToRemove);
}

return Mono.empty();
});
}

private McpStatelessRequestHandler<McpSchema.ListToolsResult> toolsListRequestHandler() {
return (ctx, params) -> {
List<Tool> tools = this.tools.stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,23 @@ public void addTool(McpStatelessServerFeatures.SyncToolSpecification toolSpecifi
.block();
}

/**
* Add multiple tool specifications at runtime.
* @param toolSpecifications The tool specifications to add
*/
public void addTools(List<McpStatelessServerFeatures.SyncToolSpecification> toolSpecifications) {
if (toolSpecifications == null) {
this.asyncServer.addTools(null).block();
return;
}
this.asyncServer
.addTools(toolSpecifications.stream()
.map(toolSpecification -> McpStatelessServerFeatures.AsyncToolSpecification.fromSync(toolSpecification,
this.immediateExecution))
.toList())
.block();
}

/**
* List all registered tools.
* @return A list of all registered tools
Expand All @@ -90,6 +107,14 @@ public void removeTool(String toolName) {
this.asyncServer.removeTool(toolName).block();
}

/**
* Remove multiple tool handlers at runtime.
* @param toolNames The names of the tool handlers to remove
*/
public void removeTools(List<String> toolNames) {
this.asyncServer.removeTools(toolNames).block();
}

/**
* Add a new resource handler at runtime.
* @param resourceSpecification The resource handler to add
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,23 @@ public void addTool(McpServerFeatures.SyncToolSpecification toolHandler) {
.block();
}

/**
* Add multiple tool handlers.
* @param toolHandlers The tool handlers to add
*/
public void addTools(List<McpServerFeatures.SyncToolSpecification> toolHandlers) {
if (toolHandlers == null) {
this.asyncServer.addTools(null).block();
return;
}
this.asyncServer
.addTools(toolHandlers.stream()
.map(toolHandler -> McpServerFeatures.AsyncToolSpecification.fromSync(toolHandler,
this.immediateExecution))
.toList())
.block();
}

/**
* List all registered tools.
* @return A list of all registered tools
Expand All @@ -105,6 +122,14 @@ public void removeTool(String toolName) {
this.asyncServer.removeTool(toolName).block();
}

/**
* Remove multiple tool handlers.
* @param toolNames The names of the tool handlers to remove
*/
public void removeTools(List<String> toolNames) {
this.asyncServer.removeTools(toolNames).block();
}

/**
* Add a new resource handler.
* @param resourceSpecification The resource specification to add
Expand Down
Loading