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-long-running-transactions tool package. tools.Register() stores tool constructors in a global registry keyed by type string; returning false means "postgres-long-running-transactions" was already registered, so init() panics. The failure happens at process startup, before any YAML tool config is decoded.

Source

Thrown at internal/tools/postgres/postgreslongrunningtransactions/postgreslongrunningtransactions.go:62

        wait_event_type,
        wait_event,
        query
    FROM
        pg_stat_activity
    WHERE
        state <> 'idle'
        AND (now() - xact_start) > COALESCE($1::INTERVAL, interval '5 minutes')
        AND xact_start IS NOT NULL
        AND pid <> pg_backend_pid()
    ORDER BY
        xact_age DESC
    LIMIT
        COALESCE($2::int, 20);
`

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 tree for "postgres-long-running-transactions" and remove or rename the second registration.
  2. Change the resourceType in any copied package to a unique value.
  3. Deduplicate go.mod replaces / vendor copies and rebuild with a clean cache.
  4. Confirm via fresh checkout that local edits are not introducing the collision.

Example fix

// before
const resourceType string = "postgres-long-running-transactions" // duplicated

// after
// stock package unchanged; new package:
const resourceType string = "postgres-stuck-transactions"
Defensive patterns

Strategy: validation

Validate before calling

// Validate uniqueness before launching the server:
const key = "postgres-long-running-transactions"
if countRegistrations(key) != 1 { // from your build-time registry scan
    return fmt.Errorf("expected exactly one registration for %q", key)
}

Type guard

func uniqueKey(rt string, all map[string][]string) string {
    if len(all[rt]) > 1 {
        panic("duplicate resourceType: " + strings.Join(all[rt], ", "))
    }
    return rt
}

Try / catch

func main() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Fprintf(os.Stderr, "startup panic, check for duplicate tool registration: %v\n", r)
            os.Exit(1)
        }
    }()
    // server startup that links tool packages
}

Prevention

When it happens

Trigger: Linking a binary where two packages register "postgres-long-running-transactions": a duplicated postgreslongrunningtransactions package under a second import path, or an edited resourceType in another package colliding with it (postgreslongrunningtransactions.go:30).

Common situations: Copying the long-running-transactions tool as a template and forgetting to change resourceType; fork/vendor duplication; merge conflicts leaving two inits; internal forks that register a patched version alongside upstream.

Related errors


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