googleapis/mcp-toolbox · error
tool %q declared more than once
Error message
tool %q declared more than once
What it means
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.
Source
Thrown at internal/server/config.go:277
return nil, nil, nil, nil, nil, nil, fmt.Errorf("authService %q declared more than once", name)
}
authServiceConfigs[name] = c
case "tool":
c, err := UnmarshalYAMLToolConfig(ctx, name, resource)
if err != nil {
if len(file.Docs) > 1 {
return nil, nil, nil, nil, nil, nil, fmt.Errorf("document %d: error unmarshaling %s %q: %w", docIndex, kind, name, err)
}
return nil, nil, nil, nil, nil, nil, fmt.Errorf("error unmarshaling %s: %w", kind, err)
}
if c == nil {
continue
}
if toolConfigs == nil {
toolConfigs = make(ToolConfigs)
}
if _, exists := toolConfigs[name]; exists {
return nil, nil, nil, nil, nil, nil, fmt.Errorf("tool %q declared more than once", name)
}
toolConfigs[name] = c
case "toolset":
c, err := UnmarshalYAMLToolsetConfig(ctx, name, resource)
if err != nil {
if len(file.Docs) > 1 {
return nil, nil, nil, nil, nil, nil, fmt.Errorf("document %d: error unmarshaling %s %q: %w", docIndex, kind, name, err)
}
return nil, nil, nil, nil, nil, nil, fmt.Errorf("error unmarshaling %s: %w", kind, err)
}
if toolsetGroups == nil {
toolsetGroups = make(map[string]group.GroupConfig)
}
if _, exists := toolsetGroups[name]; exists {
return nil, nil, nil, nil, nil, nil, fmt.Errorf("toolset %q declared more than once", name)
}
toolsetGroups[name] = group.GroupConfig{Name: name, ToolNames: c.ToolNames}
case "embeddingModel":View on GitHub (pinned to 8cc6e09de2)
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.
Example fix
// before
tools:
get-user:
kind: postgres-sql
source: pg
statement: SELECT 1
get-user:
kind: postgres-sql
source: pg
statement: SELECT 2
// after
tools:
get-user:
kind: postgres-sql
source: pg
statement: SELECT 1
get-user-count:
kind: postgres-sql
source: pg
statement: SELECT 2 Defensive patterns
Strategy: validation
Validate before calling
func checkDuplicateToolNames(yamlBytes []byte) error {
var probe struct {
Tools map[string]yaml.Node `yaml:"tools"`
}
if err := yaml.Unmarshal(yamlBytes, &probe); err != nil {
return err
}
if len(probe.Tools) == 0 {
return nil // YAML maps silently collapse duplicates; check raw keys below
}
return nil
}
// YAML map keys dedupe in Go, so scan raw lines:
func rawDuplicateKeys(data string, section string) []string {
var seen, dups []string
inSection := false
for _, line := range strings.Split(data, "\n") {
trimmed := strings.TrimSpace(line)
if strings.HasPrefix(line, section+":") { inSection = true; continue }
if inSection && strings.HasSuffix(trimmed, ":") && !strings.HasPrefix(trimmed, "-") {
name := strings.TrimSuffix(trimmed, ":")
if slices.Contains(seen, name) { dups = append(dups, name) }
seen = append(seen, name)
}
}
return dups
} Type guard
func isDuplicateToolErr(err error) bool {
return err != nil && strings.Contains(err.Error(), "declared more than once")
} Try / catch
cfg, err := server.ParseConfig(ctx, yamlBytes)
if err != nil {
if strings.Contains(err.Error(), "tool \"" ) && strings.Contains(err.Error(), "declared more than once") {
name := extractQuoted(err.Error())
return fmt.Errorf("rename or remove the duplicate tool %q", name)
}
return err
} Prevention
- 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).
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- source %q declared more than once
- authService %q declared more than once
- toolset %q declared more than once
- embeddingModel %q declared more than once
- prompt %q declared more than once
AI-assisted analysis of googleapis/mcp-toolbox@8cc6e09de2 (2026-09-05).
Data as JSON: /api/errors/bafbfc9b6759942c.
Report an issue: GitHub.