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

  1. rg '"postgres-list-database-stats"' internal/ cmd/ to find the second registration and change one `resourceType` constant.
  2. For an intentional variant, choose a distinct kebab-case type (e.g. "postgres-list-database-stats-detailed") and matching package name.
  3. Remove the unintended duplicate package/import.
  4. 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

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


AI-assisted analysis of googleapis/mcp-toolbox@8cc6e09de2 (2026-09-05). Data as JSON: /api/errors/883daf822ba1be07. Report an issue: GitHub.