Skip to content

Migration Guide

This guide helps you migrate to tooldiscovery from other tool discovery systems.

Migrating from toolindex

The tooldiscovery/index package is the successor to github.com/jonwraymond/toolindex. The migration is straightforward as the API is largely compatible.

Import Changes

// Before
import "github.com/jonwraymond/toolindex"

// After
import "github.com/jonwraymond/tooldiscovery/index"

Type Mapping

toolindex tooldiscovery/index Notes
Index Index Same interface
InMemoryIndex InMemoryIndex Same implementation
Summary Summary Same fields
SearchDoc SearchDoc Same fields
Searcher Searcher Same interface
ToolRegistration ToolRegistration Same struct

New Features

tooldiscovery adds: - discovery package for unified facade - semantic package for embedding-based search - tooldoc package for progressive documentation - search package with production BM25

Code Changes

Most code works unchanged:

// Before
idx := toolindex.NewInMemoryIndex()
idx.RegisterTool(tool, backend)
results, _ := idx.Search("query", 10)

// After (identical)
idx := index.NewInMemoryIndex()
idx.RegisterTool(tool, backend)
results, _ := idx.Search("query", 10)

For new code, use the discovery package:

// New recommended approach
disc, _ := discovery.New(discovery.Options{})
disc.RegisterTool(tool, backend, &tooldoc.DocEntry{
    Summary: "Tool description",
})
results, _ := disc.Search(ctx, "query", 10)

Migrating from Custom Tool Registries

If you have a custom tool registry, here's how to migrate:

Step 1: Map Your Tool Type

Create a function to convert your tools to model.Tool:

func convertTool(myTool MyTool) model.Tool {
    return model.Tool{
        Tool: mcp.Tool{
            Name:        myTool.Name,
            Description: myTool.Description,
            InputSchema: myTool.Schema,
        },
        Namespace: myTool.Category, // Map to namespace
        Tags:      myTool.Keywords, // Map to tags
    }
}

Step 2: Map Your Backend Type

func convertBackend(myBackend MyBackend) model.ToolBackend {
    switch myBackend.Type {
    case "mcp":
        return model.NewMCPBackend(myBackend.ServerName)
    case "local":
        return model.NewLocalBackend(myBackend.HandlerName)
    case "external":
        return model.NewProviderBackend(myBackend.Provider, myBackend.ID)
    default:
        return model.NewMCPBackend("default")
    }
}

Step 3: Migrate Registration

// Create index
idx := index.NewInMemoryIndex()

// Migrate existing tools
for _, myTool := range myRegistry.GetAllTools() {
    tool := convertTool(myTool)
    backend := convertBackend(myTool.Backend)

    if err := idx.RegisterTool(tool, backend); err != nil {
        log.Printf("Failed to migrate %s: %v", myTool.Name, err)
        continue
    }
}
// Before (custom registry)
results := myRegistry.Search(query)

// After
summaries, _ := idx.Search(query, 100)

Step 5: Add Documentation (Optional)

store := tooldoc.NewInMemoryStore(tooldoc.StoreOptions{Index: idx})

for _, myTool := range myRegistry.GetAllTools() {
    toolID := fmt.Sprintf("%s:%s", myTool.Category, myTool.Name)

    store.RegisterDoc(toolID, tooldoc.DocEntry{
        Summary:  myTool.ShortHelp,
        Notes:    myTool.LongHelp,
        Examples: convertExamples(myTool.Examples),
    })
}

Integrating with Existing MCP Servers

Receiving Tools from MCP Server

// When you receive tools from an MCP server
func onToolsReceived(serverName string, mcpTools []mcp.Tool) {
    // Convert to model.Tool (which embeds mcp.Tool)
    tools := make([]model.Tool, len(mcpTools))
    for i, t := range mcpTools {
        tools[i] = model.Tool{Tool: t}
    }

    // Register all tools from this server
    if err := idx.RegisterToolsFromMCP(serverName, tools); err != nil {
        log.Printf("Failed to register tools from %s: %v", serverName, err)
    }
}

Handling Server Disconnect

// When an MCP server disconnects, remove its tools
func onServerDisconnect(serverName string) {
    // Get all tools and find ones from this server
    // (You'd need to track this mapping separately)
    for _, toolID := range toolsFromServer[serverName] {
        idx.UnregisterBackend(toolID, model.BackendKindMCP, serverName)
    }
}

Using with toolfoundation v0.2.0

tooldiscovery requires toolfoundation v0.2.0+:

go get github.com/jonwraymond/toolfoundation@v0.2.0

Key features from toolfoundation used by tooldiscovery: - model.Tool with embedded mcp.Tool - model.ToolBackend with MCP/Provider/Local variants - model.NormalizeTags for consistent tag handling - Backend factory functions (NewMCPBackend, etc.)

Gradual Migration Strategy

For large codebases, migrate gradually:

Phase 1: Add tooldiscovery Alongside Existing

// Keep existing registry
oldRegistry := myRegistry.New()

// Add tooldiscovery
idx := index.NewInMemoryIndex()

// Sync tools to both
func registerTool(tool MyTool) {
    oldRegistry.Register(tool)
    idx.RegisterTool(convertTool(tool), convertBackend(tool.Backend))
}

Phase 2: Shadow Read

// Search both, compare results
func search(query string) []MyTool {
    oldResults := oldRegistry.Search(query)

    // Shadow: also search new index
    newResults, _ := idx.Search(query, 100)
    logComparison(oldResults, newResults)

    return oldResults // Still use old results
}

Phase 3: Switch Read Path

func search(query string) []MyTool {
    summaries, _ := idx.Search(query, 100)
    return convertToMyTools(summaries)
}

Phase 4: Remove Old Registry

// Only use tooldiscovery
idx := index.NewInMemoryIndex()
// ... registration and search ...

Common Migration Issues

Tool ID Format

tooldiscovery uses namespace:name:version format for tool IDs when version is set (otherwise namespace:name):

// Tool with namespace "git" and name "status"
toolID := "git:status"

// Tool without namespace
toolID := "simple_tool"

Ensure your code handles both formats.

Search Result Differences

tooldiscovery's BM25 search may return different results than simple substring matching. Tune the BM25Config to match your expected behavior:

// For behavior closer to substring matching
searcher := search.NewBM25Searcher(search.BM25Config{
    NameBoost:      5,  // Strongly prefer name matches
    NamespaceBoost: 1,
    TagsBoost:      1,
})

Backend Selection

If you have multiple backends per tool, customize the selector:

idx := index.NewInMemoryIndex(index.IndexOptions{
    BackendSelector: func(backends []model.ToolBackend) model.ToolBackend {
        // Your custom logic
        for _, b := range backends {
            if b.Kind == model.BackendKindLocal {
                return b // Prefer local backends
            }
        }
        return backends[0]
    },
})