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)
Using Discovery Facade (Recommended)¶
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
}
}
Step 4: Migrate Search¶
// 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]
},
})