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 postgresdatabaseoverview's init(): tools.Register (internal/tools/tools.go:43) returned false because a factory for the resourceType string ("postgres-database-overview") is already in the global toolRegistry map. The registry maps YAML config `type` strings to factory functions; a collision is treated as a programmer error and the binary panics during package initialization, before any server code runs.

Source

Thrown at internal/tools/postgres/postgresdatabaseoverview/postgresdatabaseoverview.go:48

const resourceType string = "postgres-database-overview"

const databaseOverviewStatement = `
    SELECT
    current_setting('server_version') AS pg_version,
    pg_is_in_recovery() AS is_replica,
    (now() - pg_postmaster_start_time())::TEXT AS uptime,
    current_setting('max_connections')::int AS max_connections,
    (SELECT count(*) FROM pg_stat_activity) AS current_connections,
    (SELECT count(*) FROM pg_stat_activity WHERE state = 'active') AS active_connections,
    round(
        (100.0 * (SELECT count(*) FROM pg_stat_activity) / current_setting('max_connections')::int),
        2
    ) AS pct_connections_used;
`

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-database-overview"' across internal/ and cmd/ to find the colliding registration; change one `resourceType` constant.
  2. If the copy is a legitimate new tool, give it its own kebab-case type (e.g. "postgres-database-overview-detailed") and matching package name.
  3. Delete the redundant copied package and import if it is leftover.
  4. Verify with `go build ./...` and the postgres tool unit tests.

Example fix

// before (duplicate copy)
const resourceType string = "postgres-database-overview"
// after
const resourceType string = "postgres-database-overview-detailed"
Defensive patterns

Strategy: validation

Validate before calling

// Pre-build uniqueness check (script or test):
//   rg -F '"postgres-database-overview"' internal/ cmd/  # expect exactly 1 match
// Go-side smoke test:
func TestToolboxInit(t *testing.T) {
	// importing all tool packages + calling any code path that triggers init()
	// must not panic with "already registered"
	_ = tools.ErrUnknownToolType
}

Type guard

func typeIsTaken(resourceType string) bool {
	return !tools.Register(resourceType, func(ctx context.Context, name string, d *yaml.Decoder) (tools.ToolConfig, error) {
		return nil, fmt.Errorf("probe")
	})
}

Prevention

When it happens

Trigger: Linking a binary that imports two packages whose init() both register the same postgres tool type string — almost always a copied postgresdatabaseoverview.go whose `resourceType` constant kept the original value, or two files declaring the same constant in one package.

Common situations: Cloning the file to add a tweaked overview tool and renaming only the package; a bad merge leaving duplicate directories; vendoring/forking the package and importing both versions into cmd/toolbox.

Related errors


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