{"record":{"id":"db9769bccc50676d","repo":"alibaba/open-code-review","slug":"tool-names-in-s-must-not-be-empty","errorCode":null,"errorMessage":"tool names in %s must not be empty","messagePattern":"tool names in (.+?) must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/opencodereview/config_cmd.go","lineNumber":915,"sourceCode":"\t\t\treturn fmt.Errorf(\"MCP server URL %q must include a host\", value)\n\t\t}\n\t\tentry.URL = value\n\tcase \"headers\":\n\t\tparsed, err := parseMCPHeaders(value)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"invalid headers for %s: %w\", key, err)\n\t\t}\n\t\tentry.Headers = parsed\n\tcase \"tools\":\n\t\tvar tools []string\n\t\tif err := json.Unmarshal([]byte(value), &tools); err != nil {\n\t\t\treturn fmt.Errorf(\"invalid JSON array for %s: %w\", key, err)\n\t\t}\n\t\tseen := make(map[string]struct{}, len(tools))\n\t\tfiltered := make([]string, 0, len(tools))\n\t\tfor _, t := range tools {\n\t\t\tif t == \"\" {\n\t\t\t\treturn fmt.Errorf(\"tool names in %s must not be empty\", key)\n\t\t\t}\n\t\t\tif _, dup := seen[t]; dup {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tseen[t] = struct{}{}\n\t\t\tfiltered = append(filtered, t)\n\t\t}\n\t\tentry.Tools = filtered\n\tcase \"setup\":\n\t\tentry.Setup = value\n\tdefault:\n\t\treturn fmt.Errorf(\"unknown MCP server field %q: supported fields are type, command, args, env, url, headers, tools, setup\", field)\n\t}\n\n\tcfg.MCPServers[name] = entry\n\treturn nil\n}\n","sourceCodeStart":897,"sourceCodeEnd":933,"githubUrl":"https://github.com/alibaba/open-code-review/blob/5cf97d0d15cbd41b602513c4be3bfec3cee5bf7f/cmd/opencodereview/config_cmd.go#L897-L933","documentation":"After successfully parsing the tools JSON array, setMCPServerValue rejects empty strings inside the array with \"tool names in <server> must not be empty\". Empty tool names would never match a server-exposed tool, so they are treated as config mistakes.","triggerScenarios":"A tools array containing \"\" — e.g. '[\"read_file\",\"\"]' — usually from a trailing comma in a hand-written list, or from splitting an empty string into an element.","commonSituations":"Trailing comma left inside the JSON array; programmatic construction joining values with commas producing \"a,,b\"; placeholder left unfilled.","solutions":["Remove the empty entry: `ocr config set mcp-servers.myserver.tools '[\"read_file\"]'`","Check for double or trailing commas in the array","Verify each tool name against the server's advertised tool list"],"exampleFix":"// before\nocr config set mcp-servers.myserver.tools '[\"read_file\",]'\n// after\nocr config set mcp-servers.myserver.tools '[\"read_file\"]'","handlingStrategy":"validation","validationCode":"func hasNoEmptyNames(s string) bool {\n    var a []string\n    if json.Unmarshal([]byte(s), &a) != nil { return false }\n    for _, t := range a { if t == \"\" { return false } }\n    return len(a) > 0\n}","typeGuard":"func allNonEmpty(xs []string) bool { for _, x := range xs { if x == \"\" { return false } }; return true }","tryCatchPattern":"if err := setMCPServerValue(cfg, name, \"tools\", raw); err != nil {\n    if strings.Contains(err.Error(), \"must not be empty\") { /* drop empty entries and retry */ }\n    return err\n}","preventionTips":["Filter empty strings before serializing the tools list","Join tool names into JSON via encoding/json, not strings.Join with commas","Validate the list against the server's advertised tools"],"tags":["cli","config","validation","mcp"],"backgroundTag":"empty-field-validation","analyzedSha":"5cf97d0d15cbd41b602513c4be3bfec3cee5bf7f","analyzedAt":"2026-09-02T02:08:09.116Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}