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 postgresexecutesql's init(): tools.Register (internal/tools/tools.go:43) returned false because the type string "postgres-execute-sql" was already used to register a ToolConfigFactory in the global toolRegistry. The registry forbids overwriting so that a YAML tool config's `type` unambiguously resolves to one factory; on collision the process panics during init, before main().

Source

Thrown at internal/tools/postgres/postgresexecutesql/postgresexecutesql.go:34

import (
	"context"
	"fmt"
	"net/http"

	yaml "github.com/goccy/go-yaml"
	"github.com/googleapis/mcp-toolbox/internal/sources"
	"github.com/googleapis/mcp-toolbox/internal/tools"
	"github.com/googleapis/mcp-toolbox/internal/util"
	"github.com/googleapis/mcp-toolbox/internal/util/parameters"
	"github.com/jackc/pgx/v5/pgxpool"
)

const resourceType string = "postgres-execute-sql"

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-execute-sql"' internal/ cmd/ to locate the second registration and make one `resourceType` unique.
  2. For an intentional variant tool, rename the constant and package to a new kebab-case type such as "postgres-execute-sql-readonly".
  3. Remove the unintended duplicate package/import.
  4. Confirm with `go build ./...` and unit tests that the server starts.

Example fix

// before (variant copy)
const resourceType string = "postgres-execute-sql"
// after
const resourceType string = "postgres-execute-sql-readonly"
Defensive patterns

Strategy: validation

Validate before calling

// Before building a custom toolbox binary, verify the type string appears exactly once:
//   rg -F '"postgres-execute-sql"' internal/ cmd/   # expect 1 hit in postgresexecutesql.go
// And keep a CI test that links all tool packages:
func TestAllToolsRegister(t *testing.T) {
	// importing internal/tools/... packages must not panic
}

Type guard

func registrationCollides(resourceType string) bool {
	probe := resourceType + "__collision_probe__"
	ok := tools.Register(probe, nil)
	_ = ok
	// search source for the real type string; >1 hit means a future collision
	return countSourceOccurrences(resourceType) > 1
}

Prevention

When it happens

Trigger: Building a binary that imports two packages registering "postgres-execute-sql" — typically a duplicated postgresexecutesql.go whose `resourceType` constant was unchanged, or the same constant declared twice within reachable code.

Common situations: Copy-pasting the execute-sql tool to make a restricted variant (e.g. read-only) and renaming only the package/directory; merge conflicts resolved with both copies present; accidental import of a forked vendored copy alongside the original.

Related errors


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