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,71 @@ 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 (toolSpecifications.isEmpty()) {
return Mono.empty();
}

List<McpServerFeatures.AsyncToolSpecification> wrappedToolSpecifications;
try {
wrappedToolSpecifications = sanitizeToolSpecifications(toolSpecifications);
}
catch (IllegalArgumentException e) {
return Mono.error(e);
}
if (this.serverCapabilities.tools() == null) {
return Mono.error(new IllegalStateException("Server must be configured with tool capabilities"));
}
Set<String> toolNames = new HashSet<>(
wrappedToolSpecifications.stream().map(tool -> tool.tool().name()).toList());

return Mono.defer(() -> {
this.tools.removeIf(toolSpecification -> toolNames.contains(toolSpecification.tool().name()));
this.tools.addAll(wrappedToolSpecifications);

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

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

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

for (int i = copiedToolSpecifications.size() - 1; i >= 0; i--) {
var toolSpecification = copiedToolSpecifications.get(i);
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.putIfAbsent(wrappedToolSpecification.tool().name(), wrappedToolSpecification);
}

List<McpServerFeatures.AsyncToolSpecification> sanitizedToolSpecifications = new ArrayList<>(
toolSpecificationsByName.values());
Collections.reverse(sanitizedToolSpecifications);
return sanitizedToolSpecifications;
}

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

Expand Down Expand Up @@ -517,6 +585,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 (toolNames.isEmpty()) {
return Mono.empty();
}

Set<String> toolNamesToRemove = new HashSet<>();
for (String toolName : new ArrayList<>(toolNames)) {
if (toolName == null) {
return Mono.error(new IllegalArgumentException("Tool name must not be null"));
}
toolNamesToRemove.add(toolName);
}
if (this.serverCapabilities.tools() == null) {
return Mono.error(new IllegalStateException("Server must be configured with tool capabilities"));
}

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 @@ -30,10 +30,14 @@

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;
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 +358,69 @@ 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 (toolSpecifications.isEmpty()) {
return Mono.empty();
}

List<McpStatelessServerFeatures.AsyncToolSpecification> wrappedToolSpecifications;
try {
wrappedToolSpecifications = sanitizeToolSpecifications(toolSpecifications);
}
catch (IllegalArgumentException e) {
return Mono.error(e);
}
if (this.serverCapabilities.tools() == null) {
return Mono.error(new IllegalStateException("Server must be configured with tool capabilities"));
}
Set<String> toolNames = new HashSet<>(
wrappedToolSpecifications.stream().map(tool -> tool.tool().name()).toList());

return Mono.defer(() -> {
this.tools.removeIf(toolSpecification -> toolNames.contains(toolSpecification.tool().name()));
this.tools.addAll(wrappedToolSpecifications);

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

return Mono.empty();
});
}

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

for (int i = copiedToolSpecifications.size() - 1; i >= 0; i--) {
var toolSpecification = copiedToolSpecifications.get(i);
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.putIfAbsent(wrappedToolSpecification.tool().name(), wrappedToolSpecification);
}

List<McpStatelessServerFeatures.AsyncToolSpecification> sanitizedToolSpecifications = new ArrayList<>(
toolSpecificationsByName.values());
Collections.reverse(sanitizedToolSpecifications);
return sanitizedToolSpecifications;
}

/**
* List all registered tools.
* @return A Flux stream of all registered tools
Expand Down Expand Up @@ -388,6 +455,42 @@ 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 (toolNames.isEmpty()) {
return Mono.empty();
}

Set<String> toolNamesToRemove = new HashSet<>();
for (String toolName : new ArrayList<>(toolNames)) {
if (toolName == null) {
return Mono.error(new IllegalArgumentException("Tool name must not be null"));
}
toolNamesToRemove.add(toolName);
}
if (this.serverCapabilities.tools() == null) {
return Mono.error(new IllegalStateException("Server must be configured with tool capabilities"));
}

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
Loading