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

  1. Track added tool names locally and only remove those present.
  2. Make teardown idempotent by swallowing the 'not found' error via exceptionally.
  3. Reuse the exact name string from registration.
  4. 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

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


AI-assisted analysis of alibaba/arthas@21cf2e9ba5 (2026-08-14). Data as JSON: /api/errors/14859884356ed21b. Report an issue: GitHub.