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 mysqlshowquerystats package registers its factory under resourceType "mysql-show-query-stats" in the global toolRegistry via tools.Register() (internal/tools/tools.go:43). When Register() returns false — the map already contains that type key — the init() panics so that duplicate tool-type claims fail the build/startup immediately instead of nondeterministically overriding a factory.
Source
Thrown at internal/tools/mysql/mysqlshowquerystats/mysqlshowquerystats.go:54
count_star AS 'execution_count',
ROUND(sum_timer_wait / 1000000000, 2) AS 'total_latency_ms',
ROUND(avg_timer_wait / 1000000000, 2) AS 'average_latency_ms',
ROUND(max_timer_wait / 1000000000, 2) AS 'max_latency_ms',
sum_rows_sent AS 'total_rows_sent',
sum_rows_examined AS 'total_rows_examined',
sum_no_index_used AS 'full_table_scan_count',
sum_no_good_index_used AS 'inefficient_index_used_count',
last_seen AS 'last_executed'
FROM performance_schema.events_statements_summary_by_digest
WHERE schema_name NOT IN ('information_schema', 'performance_schema', 'mysql', 'sys')
AND (schema_name = COALESCE(NULLIF(?, ''), NULLIF(DATABASE(), '')) OR COALESCE(NULLIF(?, ''), NULLIF(DATABASE(), '')) IS NULL)
ORDER BY sum_timer_wait 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
PerformanceSchemaEnabled(context.Context) (bool, error)
}
View on GitHub (pinned to 8cc6e09de2)
Solutions
- grep -rn 'mysql-show-query-stats' to identify both registering packages; assign the new tool a unique resourceType.
- Delete or unimport the obsolete duplicate package (check cmd/root.go imports).
- Deduplicate module copies (go mod tidy; remove duplicate replace directives).
- Verify with `go build ./...` and `go test ./internal/tools/mysql/...`.
Example fix
// before const resourceType string = "mysql-show-query-stats" // in both original and copied package // after const resourceType string = "mysql-show-query-stats" // original only; copied tool uses "mysql-statement-digest-stats"
Defensive patterns
Strategy: validation
Validate before calling
// Verify uniqueness before building:
// grep -rn '"mysql-show-query-stats"' --include='*.go' . | grep 'resourceType\|tools.Register'
// Exactly one match expected. Optional soft guard for 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:
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
- When starting from a copied tool file, make changing resourceType the first commit-level edit and verify with grep.
- Add a CI test asserting all registered resourceType strings are unique.
- Prefer creating a new package directory with its own name over editing copies in place.
- Avoid multiple replace/vendor copies of the toolbox module in dependents.
- Run go build ./... locally; the init panic fires immediately on import.
When it happens
Trigger: A second init() in the same binary calls tools.Register("mysql-show-query-stats", ...): a copy of this package kept after a refactor, the same package linked under two import paths (e.g. via a replace/fork), or a hand-written tool reusing this type string in its own registration.
Common situations: Template-copying another mysql performance_schema tool and leaving the const unchanged; merging branches that each import a variant of the tool; a monorepo tool override colliding with the stock toolbox 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/08a8deb179156473.
Report an issue: GitHub.