googleapis/mcp-toolbox · critical
tool type %q already registered
Error message
tool type %q already registered
What it means
This is a fail-fast panic raised during package initialization. The mysqllisttablestats package registers its factory under resourceType "mysql-list-table-stats" in the global toolRegistry via tools.Register() (internal/tools/tools.go:43). A false return means the type string was already claimed by another init(), so the package panics rather than allowing a silent overwrite that would corrupt config decoding.
Source
Thrown at internal/tools/mysql/mysqllisttablestats/mysqllisttablestats.go:72
WHERE
t.table_schema NOT IN ('sys', 'information_schema', 'mysql', 'performance_schema')
AND (t.table_schema = COALESCE(NULLIF(?, ''), NULLIF(DATABASE(), '')) OR COALESCE(NULLIF(?, ''), NULLIF(DATABASE(), '')) IS NULL)
AND (COALESCE(?, '') = '' OR t.table_name = ?)
ORDER BY
CASE ?
WHEN 'row_count' THEN row_count
WHEN 'rows_fetched' THEN rows_fetched
WHEN 'rows_inserted' THEN rows_inserted
WHEN 'rows_updated' THEN rows_updated
WHEN 'rows_deleted' THEN rows_deleted
ELSE ts.total_latency
END DESC
LIMIT ?;
`
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 {
MySQLPool() *sql.DB
RunSQL(context.Context, string, []any) (any, error)
MySQLDatabase() string
}
type Config struct {View on GitHub (pinned to 8cc6e09de2)
Solutions
- grep -rn 'mysql-list-table-stats' to find both init() registrations; update the duplicate package's resourceType to a unique kebab-case name.
- Remove the unnecessary import of the duplicate package from cmd/root.go or wherever it is linked.
- Check go.mod / vendor/ for two copies of the module being linked into the binary.
- Rebuild and run `go test ./internal/tools/mysql/mysqllisttablestats/...`.
Example fix
// before const resourceType string = "mysql-list-table-stats" // duplicated in a copied package // after const resourceType string = "mysql-list-table-storage-stats" // unique in the new package
Defensive patterns
Strategy: validation
Validate before calling
// Verify uniqueness before building:
// grep -rn '"mysql-list-table-stats"' --include='*.go' . | grep 'resourceType\|tools.Register'
// Exactly one match expected. Optional soft guard in custom tools:
func init() {
if !tools.Register(resourceType, newConfig) {
fmt.Printf("WARNING: %s already registered; skipping duplicate init\n", resourceType)
return
}
} Type guard
func isRegistered(resourceType string) bool {
_, exists := registrySnapshot[resourceType]
return exists
} Try / catch
// Recover at the process boundary (init panics are unrecoverable in init):
func main() {
defer func() {
if r := recover(); r != nil {
if s, ok := r.(string); ok && strings.Contains(s, "already registered") {
log.Fatalf("duplicate tool registration: %s", s)
}
panic(r)
}
}()
rootCmd.Execute()
} Prevention
- Rename resourceType, package, and directory together whenever you fork a tool package.
- Add a registry-uniqueness unit test to internal/tools tests.
- Review import lists in cmd/root.go after merges to remove resurrected duplicates.
- Run go mod tidy to prevent two module copies from registering the same package.
- grep the type string before committing registration changes.
When it happens
Trigger: Another package's init() also calls tools.Register("mysql-list-table-stats", ...) — usually a duplicated/forked copy of this tool package, or this package imported twice under different module paths — causing the registry map lookup at internal/tools/tools.go:44 to find an existing entry.
Common situations: Cloning this file to create a related stats tool without changing the const on line 30; a git merge reintroducing an import; two replace directives pulling old and new versions of the module; a fork registering an identically named tool.
Related errors
- tool type %q already registered
- tool type %q already registered
- tool type %q already registered
- tool type %q already registered
- tool type %q already registered
AI-assisted analysis of googleapis/mcp-toolbox@8cc6e09de2 (2026-09-05).
Data as JSON: /api/errors/9e35bfbdb907bc29.
Report an issue: GitHub.