Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 (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 @@ -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,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