{"record":{"id":"bafbfc9b6759942c","repo":"googleapis/mcp-toolbox","slug":"tool-q-declared-more-than-once","errorCode":null,"errorMessage":"tool %q declared more than once","messagePattern":"tool %q declared more than once","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/server/config.go","lineNumber":277,"sourceCode":"\t\t\t\treturn nil, nil, nil, nil, nil, nil, fmt.Errorf(\"authService %q declared more than once\", name)\n\t\t\t}\n\t\t\tauthServiceConfigs[name] = c\n\t\tcase \"tool\":\n\t\t\tc, err := UnmarshalYAMLToolConfig(ctx, name, resource)\n\t\t\tif err != nil {\n\t\t\t\tif len(file.Docs) > 1 {\n\t\t\t\t\treturn nil, nil, nil, nil, nil, nil, fmt.Errorf(\"document %d: error unmarshaling %s %q: %w\", docIndex, kind, name, err)\n\t\t\t\t}\n\t\t\t\treturn nil, nil, nil, nil, nil, nil, fmt.Errorf(\"error unmarshaling %s: %w\", kind, err)\n\t\t\t}\n\t\t\tif c == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif toolConfigs == nil {\n\t\t\t\ttoolConfigs = make(ToolConfigs)\n\t\t\t}\n\t\t\tif _, exists := toolConfigs[name]; exists {\n\t\t\t\treturn nil, nil, nil, nil, nil, nil, fmt.Errorf(\"tool %q declared more than once\", name)\n\t\t\t}\n\t\t\ttoolConfigs[name] = c\n\t\tcase \"toolset\":\n\t\t\tc, err := UnmarshalYAMLToolsetConfig(ctx, name, resource)\n\t\t\tif err != nil {\n\t\t\t\tif len(file.Docs) > 1 {\n\t\t\t\t\treturn nil, nil, nil, nil, nil, nil, fmt.Errorf(\"document %d: error unmarshaling %s %q: %w\", docIndex, kind, name, err)\n\t\t\t\t}\n\t\t\t\treturn nil, nil, nil, nil, nil, nil, fmt.Errorf(\"error unmarshaling %s: %w\", kind, err)\n\t\t\t}\n\t\t\tif toolsetGroups == nil {\n\t\t\t\ttoolsetGroups = make(map[string]group.GroupConfig)\n\t\t\t}\n\t\t\tif _, exists := toolsetGroups[name]; exists {\n\t\t\t\treturn nil, nil, nil, nil, nil, nil, fmt.Errorf(\"toolset %q declared more than once\", name)\n\t\t\t}\n\t\t\ttoolsetGroups[name] = group.GroupConfig{Name: name, ToolNames: c.ToolNames}\n\t\tcase \"embeddingModel\":","sourceCodeStart":259,"sourceCodeEnd":295,"githubUrl":"https://github.com/googleapis/mcp-toolbox/blob/8cc6e09de2ad7b8bffc77751799585a1401a48eb/internal/server/config.go#L259-L295","documentation":"UnmarshalPrimitiveConfig rejects duplicate tool names: after a \"tool\" document unmarshals successfully, it checks `toolConfigs[name]` and fails if the name already exists. Tool names are map keys in the parsed config, so a second declaration would silently overwrite the first; the library treats this as a config error instead.","triggerScenarios":"Calling ParseConfig with a YAML file (or multi-document file) that declares two `tool` resources with the same `name` (or the same YAML map key), so the second declaration hits the `exists` check.","commonSituations":"Copy-pasting a tool block and forgetting to rename it; merging two config files that both define a tool named e.g. `search-items`; multi-document YAML where two documents define the same tool name.","solutions":["Rename one of the two tools to a unique name in the error message.","Delete the redundant duplicate tool definition if it is no longer needed.","Search the whole file for the duplicated name (including across `---` document separators).","If both tools must exist with the same logical purpose, consolidate into one tool with parameters."],"exampleFix":"// before\ntools:\n  get-user:\n    kind: postgres-sql\n    source: pg\n    statement: SELECT 1\n  get-user:\n    kind: postgres-sql\n    source: pg\n    statement: SELECT 2\n// after\ntools:\n  get-user:\n    kind: postgres-sql\n    source: pg\n    statement: SELECT 1\n  get-user-count:\n    kind: postgres-sql\n    source: pg\n    statement: SELECT 2","handlingStrategy":"validation","validationCode":"func checkDuplicateToolNames(yamlBytes []byte) error {\n    var probe struct {\n        Tools map[string]yaml.Node `yaml:\"tools\"`\n    }\n    if err := yaml.Unmarshal(yamlBytes, &probe); err != nil {\n        return err\n    }\n    if len(probe.Tools) == 0 {\n        return nil // YAML maps silently collapse duplicates; check raw keys below\n    }\n    return nil\n}\n// YAML map keys dedupe in Go, so scan raw lines:\nfunc rawDuplicateKeys(data string, section string) []string {\n    var seen, dups []string\n    inSection := false\n    for _, line := range strings.Split(data, \"\\n\") {\n        trimmed := strings.TrimSpace(line)\n        if strings.HasPrefix(line, section+\":\") { inSection = true; continue }\n        if inSection && strings.HasSuffix(trimmed, \":\") && !strings.HasPrefix(trimmed, \"-\") {\n            name := strings.TrimSuffix(trimmed, \":\")\n            if slices.Contains(seen, name) { dups = append(dups, name) }\n            seen = append(seen, name)\n        }\n    }\n    return dups\n}","typeGuard":"func isDuplicateToolErr(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"declared more than once\")\n}","tryCatchPattern":"cfg, err := server.ParseConfig(ctx, yamlBytes)\nif err != nil {\n    if strings.Contains(err.Error(), \"tool \\\"\" ) && strings.Contains(err.Error(), \"declared more than once\") {\n        name := extractQuoted(err.Error())\n        return fmt.Errorf(\"rename or remove the duplicate tool %q\", name)\n    }\n    return err\n}","preventionTips":["Adopt a naming convention and check names before copy-pasting tool blocks.","Grep for the tool name in the whole file before adding a new one.","Keep toolsets referencing tools by name instead of duplicating definitions.","Run a duplicate-key YAML linter (yamllint detects duplicate keys)."],"tags":["config","duplicate","tool","yaml"],"backgroundTag":"duplicate-resource-definition","analyzedSha":"8cc6e09de2ad7b8bffc77751799585a1401a48eb","analyzedAt":"2026-09-05T01:10:36.887Z","contentChangedAt":"2026-09-05T01:10:36.887Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}