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 postgresgetcolumncardinality's init(): tools.Register (internal/tools/tools.go:43) returned false because the tool registry map already contains a factory for the package's `resourceType` string ("postgres-get-column-cardinality"). Duplicate type strings make config decoding ambiguous, so the code panics at package-init time rather than allowing a silent overwrite.

Source

Thrown at internal/tools/postgres/postgresgetcolumncardinality/postgresgetcolumncardinality.go:53

          ROUND(
              CASE
                  WHEN s.n_distinct < 0 THEN ABS(s.n_distinct) * c.reltuples
                  ELSE s.n_distinct
              END
          ) AS estimated_cardinality
      FROM pg_stats s
      JOIN pg_class c ON s.tablename = c.relname
      JOIN pg_namespace n ON c.relnamespace = n.oid
      WHERE s.schemaname = $1 -- Parameter for schema
        AND s.tablename = $2 -- Parameter for table
        AND n.nspname = $1  -- (same parameter as schema)
        AND s.attname = COALESCE($3, s.attname)
      ORDER BY estimated_cardinality DESC;
`

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. Find the collision with rg '"postgres-get-column-cardinality"' internal/ cmd/ and change one `resourceType` constant.
  2. If the copy is a deliberate new tool, assign a distinct kebab-case type string and matching package name.
  3. Delete the redundant copy and its import if unintended.
  4. Run `go build ./...` and the postgres tool tests to verify.

Example fix

// before (duplicated tool)
const resourceType string = "postgres-get-column-cardinality"
// after
const resourceType string = "postgres-get-column-cardinality-estimated"
Defensive patterns

Strategy: validation

Validate before calling

// Assert uniqueness before building:
//   rg -F '"postgres-get-column-cardinality"' internal/ cmd/  # exactly 1 expected
// CI smoke test that forces init() of every tool package:
func TestInitNoPanics(t *testing.T) {
	// must complete without "tool type already registered" panic
}

Type guard

func isUniqueRegistration(sourceRoot, resourceType string) bool {
	return countLiteralMatches(sourceRoot, "\""+resourceType+"\"") == 1
}

Prevention

When it happens

Trigger: Compiling a binary that pulls in two init() calls registering the same postgres tool type — usually a copied postgresgetcolumncardinality.go that kept the original `resourceType` constant, or two declarations of the same constant in the linked package set.

Common situations: Duplicating the file to create a customized cardinality query and renaming only the directory/package; a merge keeping old and new copies; importing both the upstream package and an internal fork.

Related errors


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