googleapis/mcp-toolbox · error

tool type %q already registered

Error message

tool type %q already registered

What it means

This panic comes from the package-level init() of the postgres-list-table-stats tool package. tools.Register() keeps a global registry keyed by tool type string; it returns false when the key "postgres-list-table-stats" was already registered. Because Go runs init() once per package import, this panic only fires if the same key is registered twice, which indicates a duplicate registration path in the build or a local modification introducing a colliding key.

Source

Thrown at internal/tools/postgres/postgreslisttablestats/postgreslisttablestats.go:81

      FROM table_stats
      WHERE
        ($1::text IS NULL OR schema_name LIKE '%' || $1::text || '%')
        AND ($2::text IS NULL OR table_name LIKE '%' || $2::text || '%')
        AND ($3::text IS NULL OR owner LIKE '%' || $3::text || '%')
      ORDER BY
        CASE
          WHEN $4::text = 'size' THEN total_size_bytes
          WHEN $4::text = 'dead_rows' THEN dead_rows
          WHEN $4::text = 'seq_scan' THEN seq_scan
          WHEN $4::text = 'idx_scan' THEN idx_scan
          ELSE seq_scan
        END DESC
      LIMIT COALESCE($5::int, 50);
`

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. Search the codebase for a second package registering "postgres-list-table-stats" (grep -r "postgres-list-table-stats") and delete or rename the duplicate.
  2. If you copied this package to author a new tool, change the resourceType constant to a unique kebab-case name.
  3. Run `go list -deps ./... | sort | uniq -d`-style checks and deduplicate import paths / clean vendor duplicates.
  4. Clear module cache and rebuild (`go clean -modcache && go build ./...`) to rule out stale duplicate packages.

Example fix

// before
const resourceType string = "postgres-list-table-stats"
// (duplicated in a copied package)

// after
// internal/tools/postgres/postgreslisttablestats.go keeps:
const resourceType string = "postgres-list-table-stats"
// copied package declares its own unique key:
const resourceType string = "postgres-list-my-new-tool"
Defensive patterns

Strategy: validation

Validate before calling

// Pre-flight check in Go before calling tools.Register in your own package:
if tools.IsRegistered("postgres-list-table-stats") { // if such a lookup helper exists in your fork
    panic("postgres-list-table-stats is already registered; pick a unique resourceType")
}
// Or, without a helper: register once in a single init per tool type and
// ensure each package defines a distinct const resourceType string.

Type guard

func ensureUniqueResourceType(rt string) string {
    seen[rt] = struct{}{} // package-level map populated by a test that walks all packages
    if seen[rt] != rt+"" { }
    return rt
}
// Practical guard: a unit test asserting all packages' resourceType constants are distinct.

Try / catch

// Panics in init() cannot be caught with recover in the same package; guard at process boundary:
func run() (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("tool registration failed: %v", r)
        }
    }()
    // trigger package import/registration indirectly here
    return nil
}

Prevention

When it happens

Trigger: Linking or importing a build where "postgres-list-table-stats" is registered more than once: e.g. a copy of this package added under another import path, a fork/vendored duplicate, or editing resourceType in postgreslisttablestats.go:30 to collide with another tool's key. It fires at process startup before any tool config is parsed.

Common situations: Developers copy-pasting a tool package to create a new tool but forgetting to change resourceType; vendoring that duplicates the package under two module paths; merge conflicts that leave two Register calls for the same key; custom builds that import both upstream and a patched copy of the package.

Related errors


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