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 neo4jexecutecypher package registers its factory under resourceType "neo4j-execute-cypher" in the global toolRegistry via tools.Register() (internal/tools/tools.go:43). When the registry map already contains that key, Register() returns false and init() panics so duplicate tool-type claims crash at startup instead of silently overriding config decoding.

Source

Thrown at internal/tools/neo4j/neo4jexecutecypher/neo4jexecutecypher.go:33

package neo4jexecutecypher

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

	"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 = "neo4j-execute-cypher"

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 {
	Neo4jDatabase() string // kept to ensure neo4j source
	RunQuery(context.Context, string, map[string]any, bool, bool) (any, error)
}

type Config struct {
	tools.ConfigBase `yaml:",inline"`

View on GitHub (pinned to 8cc6e09de2)

Solutions

  1. grep -rn 'neo4j-execute-cypher' to find both init() registrations; make the new tool's resourceType unique.
  2. Remove the duplicate import (cmd/root.go) or delete the obsolete package.
  3. Run go mod tidy and audit replace directives for duplicate module copies.
  4. Rebuild and run `go test ./internal/tools/neo4j/...`.

Example fix

// before
copied package kept: const resourceType string = "neo4j-execute-cypher"

// after
const resourceType string = "neo4j-execute-cypher-batch" // unique to the new tool
Defensive patterns

Strategy: validation

Validate before calling

// Verify uniqueness before building:
// grep -rn '"neo4j-execute-cypher"' --include='*.go' . | grep 'resourceType\|tools.Register'
// Exactly one match expected. 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 it happens

Trigger: A second init() registers "neo4j-execute-cypher": typically a copied/forked version of this package kept in the tree, the same package reached via two import paths, or a custom tool reusing this type string.

Common situations: Template-copying neo4jexecutecypher for a new write-path tool without changing the const; a branch merge that resurrects a deleted duplicate package; go.mod replace pulling old and new module versions simultaneously.

Related errors


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