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 postgreslistavailableextensions' init(): tools.Register (internal/tools/tools.go:43) returned false because the global toolRegistry already has a factory registered for the same resourceType string ("postgres-list-available-extensions"). The registry is populated once per tool package via init(); a repeated key means two factories claim one YAML `type`, so the process panics immediately at init time.

Source

Thrown at internal/tools/postgres/postgreslistavailableextensions/postgreslistavailableextensions.go:44

	"github.com/googleapis/mcp-toolbox/internal/util/parameters"
	"github.com/jackc/pgx/v5/pgxpool"
)

const resourceType string = "postgres-list-available-extensions"

const listAvailableExtensionsQuery = `
    SELECT
        name,
        default_version,
        comment as description
    FROM
        pg_available_extensions
    ORDER BY name;
`

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 for the collision: rg '"postgres-list-available-extensions"' internal/ cmd/; change one `resourceType` constant to a unique string.
  2. If the duplicate is a real new tool, rename the constant and package to its own kebab-case type.
  3. Delete the leftover duplicate package and import.
  4. Confirm with `go build ./...` and postgres tool tests.

Example fix

// before (duplicate package)
const resourceType string = "postgres-list-available-extensions"
// after
const resourceType string = "postgres-list-available-extensions-v2"
Defensive patterns

Strategy: validation

Validate before calling

// Pre-build uniqueness check:
//   rg -F '"postgres-list-available-extensions"' internal/ cmd/  # expect 1 hit
// CI smoke test that links all tool packages and thus runs every init():
func TestRegistrationsUnique(t *testing.T) {
	// must not panic with "tool type %q already registered"
}

Type guard

func registrationIsUnique(root, resourceType string) bool {
	return countLiteralOccurrences(root, "\""+resourceType+"\"") == 1
}

Prevention

When it happens

Trigger: Linking code where two init() functions register "postgres-list-available-extensions" — normally a duplicated postgreslistavailableextensions.go whose `resourceType` constant was never changed, or the same constant declared in two packages both imported.

Common situations: Copy-pasting the file to create a variant extensions tool and renaming only the package/directory; resolving a merge by keeping both copies; importing both upstream and a locally forked version into a custom binary.

Related errors


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