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 mysqlsql package registers its config factory under resourceType "mysql-sql" in the global toolRegistry via tools.Register() (internal/tools/tools.go:43). Register() returns false when the type key already exists, and init() panics because two packages claiming the same YAML `kind` would make tool config decoding ambiguous.
Source
Thrown at internal/tools/mysql/mysqlsql/mysqlsql.go:34
import (
"context"
"database/sql"
"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"
)
const resourceType string = "mysql-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 {
MySQLPool() *sql.DB
RunSQL(context.Context, string, []any) (any, error)
}
type Config struct {
tools.ConfigBase `yaml:",inline"`View on GitHub (pinned to 8cc6e09de2)
Solutions
- grep -rn '"mysql-sql"' to find every tools.Register call with this type; keep exactly one.
- If a fork was intended, rename the resourceType (e.g. "mysql-sql-custom") and the package directory.
- Remove duplicate imports or extra replace directives causing the package to be linked twice.
- Run `go build ./...` and `go test ./internal/tools/mysql/mysqlsql/...` to confirm.
Example fix
// before (forked copy) const resourceType string = "mysql-sql" // after const resourceType string = "mysql-sql-v2"
Defensive patterns
Strategy: validation
Validate before calling
// Verify uniqueness before building:
// grep -rn '"mysql-sql"' --include='*.go' . | grep 'resourceType\|tools.Register'
// Exactly one match expected. Soft guard for forked 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
- If forking a built-in tool, always rename resourceType to a non-conflicting string (users' YAML `kind` must change too).
- Add a registry-uniqueness test that fails CI on duplicate type strings.
- Keep exactly one copy of the toolbox module in go.mod (no overlapping replace directives).
- After rebases, run go build ./... to catch duplicated packages early.
- Document the tool-type naming convention (kebab-case, product-prefixed) so new tools pick unique names.
When it happens
Trigger: Another init() also registers "mysql-sql" — e.g. a copied fork of mysqlsql.go left in the tree, this package imported twice via different module paths, or a custom tool reusing the built-in mysql-sql type string.
Common situations: Forking mysqlsql to tweak behavior without renaming the package/type; a vendored copy of the toolbox alongside the direct dependency; rebasing a feature branch that duplicates the package directory.
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/539c80bd0b199fd3.
Report an issue: GitHub.