{"record":{"id":"c5b2e181631207f3","repo":"alibaba/spring-ai-alibaba","slug":"extracted-tool-name-is-empty","errorCode":null,"errorMessage":"Extracted tool name is empty","messagePattern":"Extracted tool name is empty","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"spring-boot-starters/spring-ai-alibaba-starter-config-nacos/src/main/java/com/alibaba/cloud/ai/agent/nacos/tools/NacosMcpGatewayToolCallback.java","lineNumber":508,"sourceCode":"\t\t\t\t// 获取工具名称 - 从工具定义名称中提取实际的工具名称\n\t\t\t\tString toolDefinitionName = this.toolDefinition.name();\n\t\t\t\tif (toolDefinitionName == null || toolDefinitionName.isEmpty()) {\n\t\t\t\t\tthrow new RuntimeException(\"Tool definition name is not available\");\n\t\t\t\t}\n\n\t\t\t\t// 工具定义名称格式为: serverName_tools_toolName\n\t\t\t\t// 需要提取最后的 toolName 部分\n\t\t\t\tString toolName;\n\t\t\t\tif (toolDefinitionName.contains(\"_tools_\")) {\n\t\t\t\t\ttoolName = toolDefinitionName.substring(toolDefinitionName.lastIndexOf(\"_tools_\") + 7);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\t// 如果没有 _tools_ 分隔符，使用整个名称\n\t\t\t\t\ttoolName = toolDefinitionName;\n\t\t\t\t}\n\n\t\t\t\tif (toolName.isEmpty()) {\n\t\t\t\t\tthrow new RuntimeException(\"Extracted tool name is empty\");\n\t\t\t\t}\n\n\t\t\t\t// 构建传输层\n\t\t\t\tStringBuilder sseEndpoint = new StringBuilder(\"/sse\");\n\t\t\t\tif (exportPath != null && !exportPath.isEmpty()) {\n\t\t\t\t\tsseEndpoint = new StringBuilder(exportPath);\n\t\t\t\t\tif (mcpServerVO.getQueryParams() != null) {\n\n\n\t\t\t\t\t\tif (!sseEndpoint.toString().contains(\"?\")) {\n\t\t\t\t\t\t\tsseEndpoint.append(\"?\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\tIterator<Map.Entry<String, String>> iterator = mcpServerVO.getQueryParams().entrySet()\n\t\t\t\t\t\t\t\t.iterator();\n\t\t\t\t\t\twhile (iterator.hasNext()) {\n\t\t\t\t\t\t\tMap.Entry<String, String> next = iterator.next();\n\t\t\t\t\t\t\tsseEndpoint.append(next.getKey()).append(\"=\").append(next.getValue())\n\t\t\t\t\t\t\t\t\t.append(iterator.hasNext() ? \"&\" : \"\");","sourceCodeStart":490,"sourceCodeEnd":526,"githubUrl":"https://github.com/alibaba/spring-ai-alibaba/blob/f82da0b50f35744c13968191be2b1cd2452ef550/spring-boot-starters/spring-ai-alibaba-starter-config-nacos/src/main/java/com/alibaba/cloud/ai/agent/nacos/tools/NacosMcpGatewayToolCallback.java#L490-L526","documentation":"After splitting toolDefinition.name() on the '_tools_' separator, the code takes the trailing segment as the MCP tool name. If that segment is empty (name ended with the separator, e.g. 'server_tools_'), no remote tool can be addressed and a RuntimeException is thrown.","triggerScenarios":"call() -> handleMcpStreamProtocol with a definition name like 'myServer_tools_' or '_tools_' so the extracted suffix after '_tools_' is the empty string.","commonSituations":"Tool names generated by concatenating 'serverName_tools_' + toolName where toolName was empty or only whitespace; truncated names from a bad sync/parsing of Nacos tool metadata; manual renames that left a dangling separator.","solutions":["Fix the tool name in Nacos / the source that generates 'serverName_tools_toolName' so the toolName segment is non-empty.","Trim the input and validate before concatenation: reject empty tool names when building the definition.","Add an upstream check (StringUtils.isBlank) on the toolName segment before creating the callback."],"exampleFix":"// before\nString fullName = serverName + \"_tools_\" + toolName; // toolName may be \"\"\n// after\nif (toolName == null || toolName.isBlank()) { throw new IllegalArgumentException(\"toolName required\"); }\nString fullName = serverName + \"_tools_\" + toolName;","handlingStrategy":"validation","validationCode":"int idx = toolDefinition.name().indexOf(\"_tools_\");\nif (idx < 0 || idx + \"_tools_\".length() >= toolDefinition.name().length()) {\n    throw new IllegalArgumentException(\"Tool name must end with a non-empty toolName after '_tools_'\");\n}","typeGuard":null,"tryCatchPattern":"try { return callback.call(args); }\ncatch (RuntimeException e) {\n    if (\"Extracted tool name is empty\".equals(e.getMessage())) { log.error(\"bad tool name format: {}\", toolDefinition.name()); }\n    throw e;\n}","preventionTips":["Validate toolName is non-blank before concatenating 'serverName_tools_' + toolName","Trim whitespace from names pulled from Nacos metadata","Add a unit test for the name-extraction logic with edge cases"],"tags":["nacos","mcp","name-parsing","empty-string"],"backgroundTag":"invalid-identifier-format","analyzedSha":"f82da0b50f35744c13968191be2b1cd2452ef550","analyzedAt":"2026-09-09T15:32:42.421Z","contentChangedAt":"2026-09-09T15:32:42.421Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}