{"record":{"id":"8057badf1ff5b73b","repo":"spring-projects/spring-ai","slug":"multiple-tools-with-the-same-name-s","errorCode":null,"errorMessage":"Multiple tools with the same name (%s)","messagePattern":"Multiple tools with the same name \\((.+?)\\)","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"mcp/common/src/main/java/org/springframework/ai/mcp/AsyncMcpToolCallbackProvider.java","lineNumber":203,"sourceCode":"\t}\n\n\tprivate static McpConnectionInfo connectionInfo(McpAsyncClient mcpClient) {\n\t\treturn McpConnectionInfo.builder()\n\t\t\t.clientCapabilities(mcpClient.getClientCapabilities())\n\t\t\t.clientInfo(mcpClient.getClientInfo())\n\t\t\t.initializeResult(mcpClient.getCurrentInitializationResult())\n\t\t\t.build();\n\t}\n\n\t/**\n\t * Validates tool name uniqueness.\n\t * @param toolCallbacks callbacks to validate\n\t * @throws IllegalStateException if duplicate names found\n\t */\n\tprivate void validateToolCallbacks(List<ToolCallback> toolCallbacks) {\n\t\tList<String> duplicateToolNames = ToolUtils.getDuplicateToolNames(toolCallbacks);\n\t\tif (!duplicateToolNames.isEmpty()) {\n\t\t\tthrow new IllegalStateException(\n\t\t\t\t\t\"Multiple tools with the same name (%s)\".formatted(String.join(\", \", duplicateToolNames)));\n\t\t}\n\t}\n\n\t/**\n\t * Creates a reactive stream of tool callbacks from multiple MCP clients.\n\t * <p>\n\t * Provides fully reactive tool discovery suitable for non-blocking applications.\n\t * Combines tools from all clients into a single stream with name conflict validation.\n\t * @param mcpClients MCP clients for tool discovery\n\t * @return Flux of tool callbacks from all clients\n\t */\n\tpublic static Flux<ToolCallback> asyncToolCallbacks(List<McpAsyncClient> mcpClients) {\n\t\tif (CollectionUtils.isEmpty(mcpClients)) {\n\t\t\treturn Flux.empty();\n\t\t}\n\n\t\treturn Flux.fromArray(new AsyncMcpToolCallbackProvider(mcpClients).getToolCallbacks());","sourceCodeStart":185,"sourceCodeEnd":221,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/mcp/common/src/main/java/org/springframework/ai/mcp/AsyncMcpToolCallbackProvider.java#L185-L221","documentation":"AsyncMcpToolCallbackProvider.getToolCallbacks() collects ToolCallbacks from multiple MCP clients and rejects the whole set if two or more callbacks resolve to the same tool name, since the framework cannot disambiguate which callback to invoke. Duplicate names arise when multiple MCP servers expose identically named tools or when connectionNamePrefix is absent or insufficient to namespace them.","triggerScenarios":"Calling getToolCallbacks() on an AsyncMcpToolCallbackProvider built from two or more McpAsyncClient instances where ToolUtils.prefixedToolName(...) yields identical names for tools from different clients (no connectionNamePrefix set, or the same prefix used twice).","commonSituations":"Configuring multiple MCP servers that both expose a tool named e.g. 'search' without a per-connection connectionNamePrefix; two server entries in configuration sharing the same name/prefix; upgrading spring-ai where prefixing behavior changed so previously-distinct names now collide.","solutions":["Set a unique connection name prefix per MCP client so tool names are namespaced as <prefix>_<toolName>","Rename the duplicate tool on one of the MCP servers","Inspect the comma-separated list in the message to identify the colliding tool names and which servers expose them","If only one server is needed, remove the redundant McpAsyncClient from the provider"],"exampleFix":"// before — two clients both expose tool \"search\", no prefixes\nnew AsyncMcpToolCallbackProvider(List.of(asyncClientA, asyncClientB));\n// after — prefixes namespace names: serverA_search, serverB_search\nnew AsyncMcpToolCallbackProvider(\n    Map.of(\"serverA\", asyncClientA, \"serverB\", asyncClientB));","handlingStrategy":"validation","validationCode":"List<String> dups = ToolUtils.getDuplicateToolNames(provider.getToolCallbacks());\nif (!dups.isEmpty()) throw new ConfigurationException(\"Duplicate MCP tool names: \" + dups); // run at startup, before publishing callbacks","typeGuard":"Set<String> seen = new HashSet<>();\nList<ToolCallback> unique = callbacks.stream()\n    .filter(c -> seen.add(c.getToolDefinition().name()))\n    .toList();\nif (unique.size() != callbacks.size()) throw new IllegalStateException(\"Duplicate MCP tool names detected\");","tryCatchPattern":null,"preventionTips":["Always set a distinct connectionNamePrefix for every MCP client connection","Startup-check getToolCallbacks() for duplicates before registering them with ChatClient","Avoid two server connections exposing the same toolset","Pin server versions so tool names don't drift"],"tags":["mcp","duplicate-tool-names","configuration"],"backgroundTag":"duplicate-tool-name","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}