{"record":{"id":"a4d8b97de1717798","repo":"spring-projects/spring-ai","slug":"multiple-tools-with-the-same-name-s-a4d8b9","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/SyncMcpToolCallbackProvider.java","lineNumber":185,"sourceCode":"\t}\n\n\tprivate static McpConnectionInfo connectionInfo(McpSyncClient 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 callbacks for duplicate names.\n\t * @param toolCallbacks callbacks to validate\n\t * @throws IllegalStateException if duplicate names exist\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 tool callbacks from multiple MCP clients.\n\t * <p>\n\t * Discovers and consolidates tools from all provided clients into a single list,\n\t * ensuring no naming conflicts.\n\t * @param mcpClients MCP clients to discover tools from\n\t * @return consolidated list of tool callbacks\n\t */\n\tpublic static List<ToolCallback> syncToolCallbacks(List<McpSyncClient> mcpClients) {\n\n\t\tif (CollectionUtils.isEmpty(mcpClients)) {\n\t\t\treturn List.of();\n\t\t}\n\t\treturn List.of((new SyncMcpToolCallbackProvider(mcpClients).getToolCallbacks()));","sourceCodeStart":167,"sourceCodeEnd":203,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/mcp/common/src/main/java/org/springframework/ai/mcp/SyncMcpToolCallbackProvider.java#L167-L203","documentation":"SyncMcpToolCallbackProvider.getToolCallbacks() validates that the aggregated list of ToolCallbacks across all configured MCP clients has no duplicate tool names and throws IllegalStateException otherwise. Because tool invocation is by name, duplicates would be ambiguous, so the provider fails fast during discovery.","triggerScenarios":"Calling getToolCallbacks() on a SyncMcpToolCallbackProvider aggregating multiple McpSyncClients where tools from different clients resolve to identical names — typically because no connectionNamePrefix is set or the same prefix is reused.","commonSituations":"spring.ai.mcp.client connections for two servers that both expose e.g. 'fetch_docs' without connection-name-prefix configured; duplicating a server entry in the connections map; upgrading to a spring-ai version where default naming no longer includes a unique prefix.","solutions":["Configure a unique connectionNamePrefix per MCP client so tool names become <prefix>_<toolName>","Rename or deduplicate the colliding tool on one of the MCP servers","Use the duplicated names listed in the message to locate the offending clients/servers","Remove the redundant MCP client connection if both expose the same toolset"],"exampleFix":"// before (application.yml) — no prefixes, both servers expose \"search\"\nspring:\n  ai:\n    mcp:\n      client:\n        connections:\n          server1:\n            url: http://host1/sse\n          server2:\n            url: http://host2/sse\n// after — prefixes ensure unique tool names (server1_search, server2_search)\nspring:\n  ai:\n    mcp:\n      client:\n        toolcallback:\n          connection-name-prefix: mcp\n        connections:\n          server1:\n            url: http://host1/sse\n          server2:\n            url: http://host2/sse","handlingStrategy":"validation","validationCode":"SyncMcpToolCallbackProvider p = new SyncMcpToolCallbackProvider(clients);\nList<ToolCallback> cbs = p.getToolCallbacks(); // wraps validation\nList<String> dups = ToolUtils.getDuplicateToolNames(cbs);\nif (!dups.isEmpty()) throw new IllegalStateException(\"Duplicate MCP tool names at startup: \" + dups);","typeGuard":"long distinct = callbacks.stream().map(c -> c.getToolDefinition().name()).distinct().count();\nboolean hasDuplicates = distinct != callbacks.size();","tryCatchPattern":null,"preventionTips":["Assign a unique connection-name-prefix per MCP server connection in configuration","Call getToolCallbacks() early at startup so duplicates surface before serving traffic","Don't register the same MCP server twice in the connections map","Watch for spring-ai upgrades that change default tool naming and re-run duplicate checks"],"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"}