@@ -12,50 +12,42 @@ import (
1212type SearchResult struct {
1313 Tool mcp.Tool `json:"tool"`
1414 Score float64 `json:"score"`
15- MatchedIn []string `json:"matchedIn"` // Fields where matches were found ( name, description, properties)
15+ MatchedIn []string `json:"matchedIn"` // Signals that contributed to scoring (e.g. name:token , description, parameter:token).
1616}
1717
1818const (
1919 DefaultMaxSearchResults = 3
2020
21- // Scoring weights for different token match types
21+ // Scoring weights used by scoreTool.
2222 substringMatchScore = 5
2323 exactTokensMatchScore = 2.5
2424 descriptionMatchScore = 2
2525 prefixMatchScore = 1.5
2626 parameterMatchScore = 1
2727)
2828
29- // SearchOptions configures the search behavior
29+ // SearchOptions configures search behavior.
3030type SearchOptions struct {
3131 MaxResults int `json:"maxResults"` // Maximum number of results to return (default: 3)
3232}
3333
3434// Search returns the most relevant tools for a free-text query.
3535//
36- // The search scans all tools in the local cache, assigns each tool a relevance
37- // score based on how well its name, description, and parameters match the query,
38- // and returns the highest-scoring results.
39- //
40- // Key behavior:
41- // - Matching strongly favors tool names, with softer boosts for descriptions,
42- // parameters, and fuzzy similarity.
43- // - Results are sorted by relevance, low-quality matches are filtered out,
44- // and the list is limited to MaxResults (default: 3).
45- // - Empty or whitespace-only queries return (nil, nil).
46- // - Errors listing servers abort the search; per-server read errors are skipped.
47- //
48- // Each SearchResult includes the tool, its score, and a short explanation of
49- // where the match came from (MatchedIn) for debugging and tuning.
36+ // Prefer using SearchTools and passing an explicit tool list. This function is
37+ // kept for API compatibility and currently searches an empty tool set.
5038func Search (query string , options ... SearchOptions ) ([]SearchResult , error ) {
5139 return SearchTools (nil , query , options ... )
5240}
5341
5442// SearchTools is like Search, but searches across the provided tool list.
5543//
56- // This is useful for callers that already have an inventory of tools (e.g., from
57- // github.NewInventory(...).Build()) and want to run discovery/scoring without
58- // relying on any external caches.
44+ // Matching uses a weighted combination of:
45+ // - tool name matches (strongest)
46+ // - description matches
47+ // - input parameter name matches (JSON schema property names)
48+ // - fuzzy similarity as a tie-breaker
49+ //
50+ // Empty or whitespace-only queries return (nil, nil).
5951func SearchTools (tools []mcp.Tool , query string , options ... SearchOptions ) ([]SearchResult , error ) {
6052 maxResults := getMaxResults (options )
6153
@@ -98,13 +90,14 @@ func SearchTools(tools []mcp.Tool, query string, options ...SearchOptions) ([]Se
9890 return results , nil
9991}
10092
101- // scoreTool assigns a relevance score to the tool for the given query.
102- // It layers score boosts (name/description/parameter hits, token coverage, prefix and exact-token matches)
103- // and subtracts for extra name tokens, then smooths ordering with fuzzy similarity. MatchedIn lists
104- // each signal that contributed to the score for debugging.
105- // Examples:
106- // - scoreTool(tool("issue_write"), "issue write", ["issue" "write"], "issuewrite") → score≈15, matchedIn=[name:exact-tokens name:token]
107- // - scoreTool(tool("generic"), "issue write", ["issue" "write"], "issuewrite") → score≈0.5, matchedIn=[]
93+ // scoreTool assigns a relevance score to a tool for the given query.
94+ //
95+ // It combines several signals (substrings, token coverage, and similarity) from:
96+ // - tool name
97+ // - tool description
98+ // - input parameter names (schema property names)
99+ //
100+ // MatchedIn records which signals contributed to the score for debugging/tuning.
108101func scoreTool (
109102 tool mcp.Tool ,
110103 queryLower string ,
@@ -236,7 +229,7 @@ func lowerInputPropertyNames(inputSchema any) []string {
236229 return nil
237230 }
238231
239- // Server-side tools (defined via go-sdk helpers) typically use *jsonschema.Schema.
232+ // From the server, this is commonly a *jsonschema.Schema.
240233 if schema , ok := inputSchema .(* jsonschema.Schema ); ok {
241234 if len (schema .Properties ) == 0 {
242235 return nil
@@ -248,7 +241,7 @@ func lowerInputPropertyNames(inputSchema any) []string {
248241 return out
249242 }
250243
251- // Client-side tools arrive as map[string]any.
244+ // From the client (or when unmarshaled), schemas arrive as map[string]any.
252245 if schema , ok := inputSchema .(map [string ]any ); ok {
253246 propsAny , ok := schema ["properties" ]
254247 if ! ok {
0 commit comments