alibaba/arthas · error · McpError
Tool with name '{}' not found
Error message
Tool with name '{}' not found What it means
Thrown by McpStatelessNettyServer.removeTool() when dynamically removing a tool name not present in the stateless server's tools list. The async removal uses removeIf; if no element matched, the future fails with this McpError wrapped in CompletionException.
Source
Thrown at arthas-mcp-server/src/main/java/com/taobao/arthas/mcp/server/protocol/server/McpStatelessNettyServer.java:230
public CompletableFuture<Void> removeTool(String toolName) {
if (toolName == null) {
CompletableFuture<Void> future = new CompletableFuture<>();
future.completeExceptionally(new McpError("Tool name must not be null"));
return future;
}
if (this.serverCapabilities.getTools() == null) {
CompletableFuture<Void> future = new CompletableFuture<>();
future.completeExceptionally(new McpError("Server must be configured with tool capabilities"));
return future;
}
return CompletableFuture
.runAsync(() -> {
boolean removed = this.tools.removeIf(
spec -> spec.getTool().getName().equals(toolName));
if (!removed) {
throw new CompletionException(
new McpError("Tool with name '" + toolName + "' not found"));
}
logger.debug("Removed tool handler: {}", toolName);
})
.exceptionally(ex -> {
Throwable cause = (ex instanceof CompletionException) ? ex.getCause() : ex;
logger.error("Error while removing tool '{}'", toolName, cause);
throw new CompletionException(cause);
});
}
private McpStatelessRequestHandler<McpSchema.ListToolsResult> toolsListRequestHandler() {
return (exchange, commandContext, params) -> {
List<McpSchema.Tool> tools = new ArrayList<>();
for (McpStatelessServerFeatures.ToolSpecification toolSpec : this.tools) {
tools.add(toolSpec.getTool());
}
View on GitHub (pinned to 21cf2e9ba5)
Solutions
- Track added tool names locally and only remove those present.
- Make teardown idempotent by swallowing the 'not found' error via exceptionally.
- Reuse the exact name string from registration.
- Verify you target the same McpStatelessNettyServer that owns the tool.
Example fix
// before
statelessServer.removeTool(name).join(); // throws if absent
// after
statelessServer.removeTool(name)
.exceptionally(ex -> null)
.join(); // idempotent cleanup Defensive patterns
Strategy: validation
Validate before calling
Set<String> addedTools = ConcurrentHashMap.newKeySet();
// on add: addedTools.add(name);
if (addedTools.remove(toolName)) {
statelessServer.removeTool(toolName).join();
} Try / catch
statelessServer.removeTool(toolName)
.exceptionally(ex -> {
Throwable c = ex instanceof CompletionException ? ex.getCause() : ex;
if (c instanceof McpError me && me.getMessage().contains("not found")) return null;
throw new CompletionException(c);
})
.join(); Prevention
- Track added tool names locally.
- Make teardown idempotent.
- Reuse the exact registered name.
When it happens
Trigger: Calling removeTool(toolName) for a name never added, already removed, or mistyped on a stateless server instance.
Common situations: Double teardown; cleanup of a tool that failed to register; operating on the wrong stateless server instance; name mismatch between add and remove.
Related errors
- Tool with name '{}' already exists
- Resource with URI '{}' already exists
- Resource with URI '{}' not found
- Prompt with name '{}' already exists
- Prompt with name '{}' not found
AI-assisted analysis of alibaba/arthas@21cf2e9ba5 (2026-08-14).
Data as JSON: /api/errors/14859884356ed21b.
Report an issue: GitHub.