googleapis/mcp-toolbox · critical
tool type %q already registered
Error message
tool type %q already registered
What it means
This is a startup panic from postgreslistdatabasestats' init(): tools.Register (internal/tools/tools.go:43) returned false because a factory for the same resourceType string ("postgres-list-database-stats") already exists in the shared toolRegistry map. Duplicate registrations would make a config's `type` field ambiguous, so the library panics during package initialization, before main() executes.
Source
Thrown at internal/tools/postgres/postgreslistdatabasestats/postgreslistdatabasestats.go:100
-- Exclude template databases if not requested
AND ( $2::boolean IS TRUE OR d.datistemplate IS FALSE )
)
SELECT *
FROM database_stats
WHERE
($1::text IS NULL OR database_name LIKE '%' || $1::text || '%')
AND ($3::text IS NULL OR database_owner LIKE '%' || $3::text || '%')
AND ($4::text IS NULL OR default_tablespace LIKE '%' || $4::text || '%')
ORDER BY
CASE WHEN $5::text = 'size' THEN database_size_bytes END DESC,
CASE WHEN $5::text = 'commit' THEN xact_commit END DESC,
database_name
LIMIT COALESCE($6::int, 10);
`
func init() {
if !tools.Register(resourceType, newConfig) {
panic(fmt.Sprintf("tool type %q already registered", resourceType))
}
}
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
actual := Config{ConfigBase: tools.ConfigBase{Name: name}}
if err := decoder.DecodeContext(ctx, &actual); err != nil {
return nil, err
}
return actual, nil
}
type compatibleSource interface {
PostgresPool() *pgxpool.Pool
RunSQL(context.Context, string, []any) (any, error)
}
type Config struct {
tools.ConfigBase `yaml:",inline"`View on GitHub (pinned to 8cc6e09de2)
Solutions
- rg '"postgres-list-database-stats"' internal/ cmd/ to find the second registration and change one `resourceType` constant.
- For an intentional variant, choose a distinct kebab-case type (e.g. "postgres-list-database-stats-detailed") and matching package name.
- Remove the unintended duplicate package/import.
- Verify with `go build ./...` and unit tests that init completes.
Example fix
// before (tweaked copy) const resourceType string = "postgres-list-database-stats" // after const resourceType string = "postgres-list-database-stats-detailed"
Defensive patterns
Strategy: validation
Validate before calling
// Assert the type string is declared exactly once before building:
// rg -F '"postgres-list-database-stats"' internal/ cmd/ # expect 1 hit
// Link-all smoke test:
func TestInitNoDuplicateRegistration(t *testing.T) {
// importing all internal/tools packages must not panic
} Type guard
func hasSingleRegistration(root, resourceType string) bool {
return countLiteralMatches(root, "\""+resourceType+"\"") == 1
} Prevention
- Copy-paste rule: first edit after duplicating a tool is renaming `resourceType`.
- Keep the constant in sync with the package/directory name to spot drift in review.
- Add a CI smoke test that triggers all tool init()s.
- Run a literal search for the type string before importing new/forked packages.
- Remove superseded duplicates rather than leaving both importable.
When it happens
Trigger: Building a binary that imports two packages whose init() both call tools.Register with "postgres-list-database-stats" — typically a copied postgreslistdatabasestats.go retaining the original constant, or two files declaring that same constant.
Common situations: Duplicating the stats tool to tweak the SQL (different LIMIT, extra filters) while renaming only the package; a merge that preserved duplicate directories; vendoring a fork and importing both it and the original.
Related errors
- tool type %q already registered
- tool type %q already registered
- tool type %q already registered
- tool type %q already registered
- tool type %q already registered
AI-assisted analysis of googleapis/mcp-toolbox@8cc6e09de2 (2026-09-05).
Data as JSON: /api/errors/883daf822ba1be07.
Report an issue: GitHub.