{"record":{"id":"7d1d1d3e53be9790","repo":"alibaba/arthas","slug":"tool-with-name-already-exists","errorCode":null,"errorMessage":"Tool with name '{}' already exists","messagePattern":"Tool with name '(.+?)' already exists","errorType":"validation","errorClass":"McpError","httpStatus":null,"severity":"error","filePath":"arthas-mcp-server/src/main/java/com/taobao/arthas/mcp/server/protocol/server/McpStatelessNettyServer.java","lineNumber":200,"sourceCode":"\t\t\tfuture.completeExceptionally(new McpError(\"Tool must not be null\"));\n\t\t\treturn future;\n\t\t}\n\t\tif (toolSpecification.getCall() == null) {\n\t\t\tCompletableFuture<Void> future = new CompletableFuture<>();\n\t\t\tfuture.completeExceptionally(new McpError(\"Tool call handler must not be null\"));\n\t\t\treturn future;\n\t\t}\n\t\tif (this.serverCapabilities.getTools() == null) {\n\t\t\tCompletableFuture<Void> future = new CompletableFuture<>();\n\t\t\tfuture.completeExceptionally(new McpError(\"Server must be configured with tool capabilities\"));\n\t\t\treturn future;\n\t\t}\n\n\t\treturn CompletableFuture\n\t\t\t\t.runAsync(() -> {\n\t\t\t\t\tif (this.tools.stream().anyMatch(th ->\n\t\t\t\t\t\t\tth.getTool().getName().equals(toolSpecification.getTool().getName()))) {\n\t\t\t\t\t\tthrow new CompletionException(\n\t\t\t\t\t\t\t\tnew McpError(\"Tool with name '\" + toolSpecification.getTool().getName() + \"' already exists\"));\n\t\t\t\t\t}\n\t\t\t\t\tthis.tools.add(toolSpecification);\n\t\t\t\t\tlogger.debug(\"Added tool handler: {}\", toolSpecification.getTool().getName());\n\t\t\t\t})\n\t\t\t\t.exceptionally(ex -> {\n\t\t\t\t\tThrowable cause = ex instanceof CompletionException ? ex.getCause() : ex;\n\t\t\t\t\tlogger.error(\"Error while adding tool\", cause);\n\t\t\t\t\tthrow new CompletionException(cause);\n\t\t\t\t});\n\t}\n\n\tpublic CompletableFuture<Void> removeTool(String toolName) {\n\t\tif (toolName == null) {\n\t\t\tCompletableFuture<Void> future = new CompletableFuture<>();\n\t\t\tfuture.completeExceptionally(new McpError(\"Tool name must not be null\"));\n\t\t\treturn future;\n\t\t}","sourceCodeStart":182,"sourceCodeEnd":218,"githubUrl":"https://github.com/alibaba/arthas/blob/21cf2e9ba52b305290be7223b980ff504bb9cb5b/arthas-mcp-server/src/main/java/com/taobao/arthas/mcp/server/protocol/server/McpStatelessNettyServer.java#L182-L218","documentation":"Thrown by McpStatelessNettyServer.addTool() when dynamically adding a ToolSpecification whose name already exists in the stateless server's tools list. The add runs async and fails the returned CompletableFuture with a McpError wrapped in CompletionException. Unlike the stateful builder, this is a runtime dynamic-registration check on a stateless server.","triggerScenarios":"Calling addTool(spec) at runtime when a tool with the same getTool().getName() is already present; re-adding after a prior add succeeded.","commonSituations":"Live tool registration without checking current tools; plugin hot-load colliding with an existing tool; duplicate add during retries.","solutions":["Before addTool, inspect the current tool list and skip if the name exists, or removeTool first.","Generate unique tool names for dynamically added tools.","Catch the CompletionException/McpError and treat duplicate-add as upsert by removeTool then addTool.","Coordinate tool registration across concurrent callers (addTool is not synchronized across names)."],"exampleFix":"// before\nstatelessServer.addTool(spec).join(); // name exists\n\n// after\nString name = spec.getTool().getName();\nstatelessServer.removeTool(name).exceptionally(ex -> null).join();\nstatelessServer.addTool(spec).join();","handlingStrategy":"validation","validationCode":"// Upsert: remove existing then add\nString name = spec.getTool().getName();\nstatelessServer.removeTool(name).exceptionally(ex -> null).join();\nstatelessServer.addTool(spec).join();","typeGuard":null,"tryCatchPattern":"try {\n    statelessServer.addTool(spec).join();\n} catch (CompletionException e) {\n    Throwable c = e.getCause();\n    if (c instanceof McpError me && me.getMessage().contains(\"already exists\")) {\n        statelessServer.removeTool(spec.getTool().getName())\n            .exceptionally(x -> null).join();\n        statelessServer.addTool(spec).join();\n    } else throw e;\n}","preventionTips":["Implement upsert: removeTool (ignore absent) then addTool.","Generate unique names for dynamic tools.","Serialize dynamic registrations per name to avoid races."],"tags":["mcp","tool","dynamic-registration","duplicate","stateless-server","java"],"backgroundTag":null,"analyzedSha":"21cf2e9ba52b305290be7223b980ff504bb9cb5b","analyzedAt":"2026-08-14T00:57:07.243Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}