Documentation
¶
Overview ¶
Package discovery provides a unified facade for tool discovery operations.
It combines the functionality of the index, search, semantic, and tooldoc packages into a single, easy-to-use API. This package is the recommended entry point for most tool discovery use cases.
Basic Usage ¶
Create a Discovery instance with default options:
disc, err := discovery.New(discovery.Options{})
if err != nil {
log.Fatal(err)
}
// Register a tool with documentation
err = disc.RegisterTool(tool, backend, &tooldoc.DocEntry{
Summary: "Creates GitHub issues",
Notes: "Requires GITHUB_TOKEN",
Examples: []tooldoc.ToolExample{{Title: "Create bug", Args: map[string]any{"title": "Bug"}}},
})
// Search for tools
results, err := disc.Search(ctx, "create issue", 10)
// Get progressive documentation
doc, err := disc.DescribeTool("github:create-issue", tooldoc.DetailFull)
Hybrid Search ¶
Enable hybrid search by providing an embedder:
disc, err := discovery.New(discovery.Options{
Embedder: myEmbedder, // implements semantic.Embedder
HybridAlpha: 0.7, // 70% BM25, 30% semantic
})
Components ¶
The Discovery facade integrates:
- index.Index: Tool registration and lookup
- index.Searcher: BM25-based text search
- semantic.Strategy: Embedding-based semantic search (optional)
- tooldoc.Store: Progressive documentation
Thread Safety ¶
All Discovery methods are safe for concurrent use.
Index ¶
- Variables
- type BM25OnlySearcher
- func (s *BM25OnlySearcher) Deterministic() bool
- func (s *BM25OnlySearcher) GetScoreType() ScoreType
- func (s *BM25OnlySearcher) Search(query string, limit int, docs []index.SearchDoc) ([]index.Summary, error)
- func (s *BM25OnlySearcher) SearchWithScores(ctx context.Context, query string, limit int, docs []index.SearchDoc) (Results, error)
- type CompositeSearcher
- type Discovery
- func (d *Discovery) DescribeProvider(id string) (adapter.CanonicalProvider, error)
- func (d *Discovery) DescribeTool(id string, level tooldoc.DetailLevel) (tooldoc.ToolDoc, error)
- func (d *Discovery) DocStore() *tooldoc.InMemoryStore
- func (d *Discovery) GetAllBackends(id string) ([]model.ToolBackend, error)
- func (d *Discovery) GetTool(id string) (model.Tool, model.ToolBackend, error)
- func (d *Discovery) Index() index.Index
- func (d *Discovery) ListExamples(id string, maxExamples int) ([]tooldoc.ToolExample, error)
- func (d *Discovery) ListNamespaces() ([]string, error)
- func (d *Discovery) ListProviders() ([]adapter.CanonicalProvider, error)
- func (d *Discovery) OnChange(listener index.ChangeListener) func()
- func (d *Discovery) ProviderStore() provider.Store
- func (d *Discovery) RegisterDoc(toolID string, doc tooldoc.DocEntry) error
- func (d *Discovery) RegisterExamples(toolID string, examples []tooldoc.ToolExample) error
- func (d *Discovery) RegisterProvider(id string, p adapter.CanonicalProvider) (string, error)
- func (d *Discovery) RegisterTool(tool model.Tool, backend model.ToolBackend, doc *tooldoc.DocEntry) error
- func (d *Discovery) RegisterTools(regs []index.ToolRegistration) error
- func (d *Discovery) Search(ctx context.Context, query string, limit int) (Results, error)
- func (d *Discovery) SearchPage(ctx context.Context, query string, limit int, cursor string) (Results, string, error)
- type HybridOptions
- type HybridSearcher
- func (h *HybridSearcher) Deterministic() bool
- func (h *HybridSearcher) GetScoreType() ScoreType
- func (h *HybridSearcher) Search(query string, limit int, docs []index.SearchDoc) ([]index.Summary, error)
- func (h *HybridSearcher) SearchWithScores(ctx context.Context, query string, limit int, docs []index.SearchDoc) (Results, error)
- type Options
- type Result
- type Results
- type ScoreType
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var (
ErrNotFound = errors.New("tool not found")
)
Error values for discovery operations.
Functions ¶
This section is empty.
Types ¶
type BM25OnlySearcher ¶
type BM25OnlySearcher struct {
// contains filtered or unexported fields
}
BM25OnlySearcher wraps the index's lexical search with score tracking.
func NewBM25OnlySearcher ¶
func NewBM25OnlySearcher(scorer semantic.BM25Scorer) *BM25OnlySearcher
NewBM25OnlySearcher creates a searcher using only BM25 scoring.
func (*BM25OnlySearcher) Deterministic ¶
func (s *BM25OnlySearcher) Deterministic() bool
Deterministic reports whether this searcher provides deterministic ordering.
func (*BM25OnlySearcher) GetScoreType ¶
func (s *BM25OnlySearcher) GetScoreType() ScoreType
GetScoreType returns ScoreBM25.
type CompositeSearcher ¶
type CompositeSearcher interface {
index.Searcher
// SearchWithScores returns results with detailed score information.
SearchWithScores(ctx context.Context, query string, limit int, docs []index.SearchDoc) (Results, error)
// ScoreType returns the type of scoring used by this searcher.
GetScoreType() ScoreType
}
CompositeSearcher combines multiple search strategies into a unified searcher. It implements index.Searcher for compatibility with InMemoryIndex.
type Discovery ¶
type Discovery struct {
// contains filtered or unexported fields
}
Discovery is the unified facade for tool discovery operations. It combines index, search, and documentation functionality.
func New ¶
New creates a new Discovery instance with the given options.
Example ¶
package main
import (
"fmt"
"github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/jonwraymond/tooldiscovery/discovery"
"github.com/jonwraymond/toolfoundation/model"
)
func main() {
// Create a Discovery instance with default options (BM25 search)
disc, err := discovery.New(discovery.Options{})
if err != nil {
fmt.Println("Error:", err)
return
}
// Register a tool
tool := model.Tool{
Tool: mcp.Tool{
Name: "create_issue",
Description: "Create a new GitHub issue",
InputSchema: map[string]any{"type": "object"},
},
Namespace: "github",
}
_ = disc.RegisterTool(tool, model.NewMCPBackend("github-server"), nil)
fmt.Println("Discovery initialized")
}
Output: Discovery initialized
func (*Discovery) DescribeProvider ¶ added in v0.3.0
func (d *Discovery) DescribeProvider(id string) (adapter.CanonicalProvider, error)
DescribeProvider returns provider metadata by ID.
func (*Discovery) DescribeTool ¶
DescribeTool returns documentation at the specified detail level.
Example ¶
package main
import (
"fmt"
"github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/jonwraymond/tooldiscovery/discovery"
"github.com/jonwraymond/tooldiscovery/tooldoc"
"github.com/jonwraymond/toolfoundation/model"
)
func main() {
disc, _ := discovery.New(discovery.Options{})
tool := model.Tool{
Tool: mcp.Tool{
Name: "run_query",
Description: "Execute a database query",
InputSchema: map[string]any{
"type": "object",
"properties": map[string]any{
"sql": map[string]any{"type": "string"},
},
"required": []string{"sql"},
},
},
Namespace: "db",
}
_ = disc.RegisterTool(tool, model.NewMCPBackend("db-server"), &tooldoc.DocEntry{
Summary: "Run SQL queries against the database",
Notes: "Use parameterized queries to prevent SQL injection.",
})
// Get summary (minimal)
summary, _ := disc.DescribeTool("db:run_query", tooldoc.DetailSummary)
fmt.Println("Summary:", summary.Summary)
// Get full documentation
full, _ := disc.DescribeTool("db:run_query", tooldoc.DetailFull)
fmt.Println("Notes:", full.Notes)
}
Output: Summary: Run SQL queries against the database Notes: Use parameterized queries to prevent SQL injection.
func (*Discovery) DocStore ¶
func (d *Discovery) DocStore() *tooldoc.InMemoryStore
DocStore returns the underlying documentation store.
func (*Discovery) GetAllBackends ¶
func (d *Discovery) GetAllBackends(id string) ([]model.ToolBackend, error)
GetAllBackends returns all backends for a tool.
func (*Discovery) ListExamples ¶
ListExamples returns examples for a tool.
func (*Discovery) ListNamespaces ¶
ListNamespaces returns all registered namespaces.
func (*Discovery) ListProviders ¶ added in v0.3.0
func (d *Discovery) ListProviders() ([]adapter.CanonicalProvider, error)
ListProviders returns all registered providers.
func (*Discovery) OnChange ¶
func (d *Discovery) OnChange(listener index.ChangeListener) func()
OnChange registers a listener for index changes. Returns an unsubscribe function.
func (*Discovery) ProviderStore ¶ added in v0.3.0
ProviderStore returns the underlying provider store.
func (*Discovery) RegisterDoc ¶
RegisterDoc registers or updates documentation for a tool.
func (*Discovery) RegisterExamples ¶
func (d *Discovery) RegisterExamples(toolID string, examples []tooldoc.ToolExample) error
RegisterExamples adds examples to a tool's documentation.
func (*Discovery) RegisterProvider ¶ added in v0.3.0
RegisterProvider registers a provider and returns the resolved ID.
func (*Discovery) RegisterTool ¶
func (d *Discovery) RegisterTool(tool model.Tool, backend model.ToolBackend, doc *tooldoc.DocEntry) error
RegisterTool registers a tool with its backend and optional documentation. If doc is nil, the tool is registered without additional documentation.
Example ¶
package main
import (
"fmt"
"github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/jonwraymond/tooldiscovery/discovery"
"github.com/jonwraymond/tooldiscovery/tooldoc"
"github.com/jonwraymond/toolfoundation/model"
)
func main() {
disc, _ := discovery.New(discovery.Options{})
tool := model.Tool{
Tool: mcp.Tool{
Name: "search_code",
Description: "Search for code across repositories",
InputSchema: map[string]any{"type": "object"},
},
Namespace: "github",
Tags: []string{"search", "code"},
}
backend := model.NewMCPBackend("github-mcp")
// Register with documentation
doc := &tooldoc.DocEntry{
Summary: "Search code across GitHub repositories",
Notes: "Requires GITHUB_TOKEN. Rate limited to 30 req/min.",
Examples: []tooldoc.ToolExample{
{Title: "Search for function", Args: map[string]any{"query": "func main"}},
},
}
err := disc.RegisterTool(tool, backend, doc)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Tool registered with documentation")
}
Output: Tool registered with documentation
func (*Discovery) RegisterTools ¶
func (d *Discovery) RegisterTools(regs []index.ToolRegistration) error
RegisterTools registers multiple tools with their backends.
func (*Discovery) Search ¶
Search performs a search using the configured strategy. Returns results ordered by relevance score.
Example ¶
package main
import (
"context"
"fmt"
"github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/jonwraymond/tooldiscovery/discovery"
"github.com/jonwraymond/toolfoundation/model"
)
func main() {
disc, _ := discovery.New(discovery.Options{})
// Register some tools
tools := []model.Tool{
{
Tool: mcp.Tool{Name: "git_status", Description: "Show working tree status", InputSchema: map[string]any{"type": "object"}},
Namespace: "git",
Tags: []string{"vcs"},
},
{
Tool: mcp.Tool{Name: "git_commit", Description: "Record changes to repository", InputSchema: map[string]any{"type": "object"}},
Namespace: "git",
Tags: []string{"vcs"},
},
{
Tool: mcp.Tool{Name: "docker_ps", Description: "List containers", InputSchema: map[string]any{"type": "object"}},
Namespace: "docker",
Tags: []string{"containers"},
},
}
for _, t := range tools {
_ = disc.RegisterTool(t, model.NewMCPBackend("server"), nil)
}
// Search for git tools
ctx := context.Background()
results, _ := disc.Search(ctx, "git", 10)
fmt.Println("Found:", len(results), "git-related tools")
for _, r := range results {
fmt.Printf(" - %s (%s)\n", r.Summary.Name, r.Summary.Namespace)
}
}
Output: Found: 2 git-related tools - git_commit (git) - git_status (git)
type HybridOptions ¶
type HybridOptions struct {
// BM25Scorer is an optional custom BM25 scorer. If nil, uses default.
BM25Scorer semantic.BM25Scorer
// Embedder generates embeddings for semantic search. Required.
Embedder semantic.Embedder
// Alpha is the BM25 weight (0.0 to 1.0). Semantic weight is 1-Alpha.
// Default: 0.5 (equal weighting)
Alpha float64
}
HybridOptions configures a HybridSearcher.
type HybridSearcher ¶
type HybridSearcher struct {
// contains filtered or unexported fields
}
HybridSearcher combines BM25 and semantic search with configurable weighting.
func NewHybridSearcher ¶
func NewHybridSearcher(opts HybridOptions) (*HybridSearcher, error)
NewHybridSearcher creates a new hybrid searcher combining BM25 and semantic search.
func (*HybridSearcher) Deterministic ¶
func (h *HybridSearcher) Deterministic() bool
Deterministic reports whether this searcher provides deterministic ordering.
func (*HybridSearcher) GetScoreType ¶
func (h *HybridSearcher) GetScoreType() ScoreType
GetScoreType returns ScoreHybrid.
type Options ¶
type Options struct {
// Index is the tool registry. If nil, creates a new InMemoryIndex.
Index index.Index
// Searcher is the search implementation. If nil, uses BM25Searcher.
// This is ignored if Embedder is provided (uses HybridSearcher instead).
Searcher index.Searcher
// DocStore is the documentation store. If nil, creates a new InMemoryStore.
DocStore tooldoc.Store
// ProviderStore is the provider registry. If nil, creates a new InMemoryStore.
ProviderStore provider.Store
// Embedder enables hybrid search when provided.
// If nil, uses BM25-only search.
Embedder semantic.Embedder
// HybridAlpha is the BM25 weight for hybrid search (0.0 to 1.0).
// Semantic weight is 1-HybridAlpha.
// Default: 0.5 (equal weighting). Only used when Embedder is provided.
HybridAlpha float64
// BM25Config configures the BM25 searcher.
// Only used when Searcher is nil and Embedder is nil.
BM25Config search.BM25Config
// MaxExamples is the default maximum number of examples to return.
// Default: 10
MaxExamples int
}
Options configures a Discovery instance.
type Result ¶
type Result struct {
// Summary contains the tool's metadata (ID, name, namespace, description, tags).
Summary index.Summary
// Score is the relevance score for this result.
// The score's interpretation depends on ScoreType.
Score float64
// ScoreType indicates how the Score was computed.
ScoreType ScoreType
}
Result represents a unified search result with score details.
type Results ¶
type Results []Result
Results is a slice of Result with helper methods.
func (Results) FilterByMinScore ¶
FilterByMinScore returns results with score >= minScore.
func (Results) FilterByNamespace ¶
FilterByNamespace returns results matching the given namespace.
Example ¶
package main
import (
"context"
"fmt"
"github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/jonwraymond/tooldiscovery/discovery"
"github.com/jonwraymond/toolfoundation/model"
)
func main() {
disc, _ := discovery.New(discovery.Options{})
tools := []model.Tool{
{Tool: mcp.Tool{Name: "status", Description: "Git status", InputSchema: map[string]any{"type": "object"}}, Namespace: "git"},
{Tool: mcp.Tool{Name: "commit", Description: "Git commit", InputSchema: map[string]any{"type": "object"}}, Namespace: "git"},
{Tool: mcp.Tool{Name: "ps", Description: "Docker ps", InputSchema: map[string]any{"type": "object"}}, Namespace: "docker"},
}
for _, t := range tools {
_ = disc.RegisterTool(t, model.NewMCPBackend("server"), nil)
}
ctx := context.Background()
results, _ := disc.Search(ctx, "", 10) // Get all
// Filter to just git tools
gitTools := results.FilterByNamespace("git")
fmt.Println("Git tools:", len(gitTools))
}
Output: Git tools: 2
func (Results) IDs ¶
IDs returns just the tool IDs from the results.
Example ¶
package main
import (
"context"
"fmt"
"github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/jonwraymond/tooldiscovery/discovery"
"github.com/jonwraymond/toolfoundation/model"
)
func main() {
disc, _ := discovery.New(discovery.Options{})
tools := []model.Tool{
{Tool: mcp.Tool{Name: "create_issue", Description: "Create issue", InputSchema: map[string]any{"type": "object"}}, Namespace: "github"},
{Tool: mcp.Tool{Name: "list_repos", Description: "List repos", InputSchema: map[string]any{"type": "object"}}, Namespace: "github"},
}
for _, t := range tools {
_ = disc.RegisterTool(t, model.NewMCPBackend("server"), nil)
}
ctx := context.Background()
results, _ := disc.Search(ctx, "github", 10)
ids := results.IDs()
fmt.Println("Found tool IDs:", len(ids))
}
Output: Found tool IDs: 2
type ScoreType ¶
type ScoreType string
ScoreType indicates the source of a search result's score.
const ( // ScoreBM25 indicates the score came from BM25 lexical search. ScoreBM25 ScoreType = "bm25" // ScoreEmbedding indicates the score came from embedding-based semantic search. ScoreEmbedding ScoreType = "embedding" // ScoreHybrid indicates the score is a weighted combination of BM25 and embedding. ScoreHybrid ScoreType = "hybrid" )