SearchTools is like Search, but searches across the provided tool list. Matching uses a weighted combination of: - tool name matches (strongest) - description matches - input parameter name matches (JSON schema property names) - fuzzy similarity as a tie-breaker Empty or whitespace-only queries re
(tools []mcp.Tool, query string, options ...SearchOptions)
| 49 | // |
| 50 | // Empty or whitespace-only queries return (nil, nil). |
| 51 | func SearchTools(tools []mcp.Tool, query string, options ...SearchOptions) ([]SearchResult, error) { |
| 52 | maxResults := getMaxResults(options) |
| 53 | |
| 54 | query = strings.TrimSpace(query) |
| 55 | if query == "" { |
| 56 | return nil, nil |
| 57 | } |
| 58 | |
| 59 | queryLower := strings.ToLower(query) |
| 60 | queryTokens := strings.Fields(queryLower) |
| 61 | normalizedQueryCompact := strings.ReplaceAll(strings.ReplaceAll(queryLower, " ", ""), "_", "") |
| 62 | |
| 63 | results := make([]SearchResult, 0, len(tools)) |
| 64 | for _, tool := range tools { |
| 65 | score, matchedIn := scoreTool(tool, queryLower, queryTokens, normalizedQueryCompact) |
| 66 | results = append(results, SearchResult{ |
| 67 | Tool: tool, |
| 68 | Score: score, |
| 69 | MatchedIn: matchedIn, |
| 70 | }) |
| 71 | } |
| 72 | |
| 73 | sort.Slice(results, func(i, j int) bool { return results[i].Score > results[j].Score }) |
| 74 | |
| 75 | // Filter out low-relevance results |
| 76 | const minScore = 1.0 |
| 77 | filtered := results[:0] |
| 78 | for _, r := range results { |
| 79 | if r.Score > minScore { |
| 80 | filtered = append(filtered, r) |
| 81 | } |
| 82 | } |
| 83 | results = filtered |
| 84 | |
| 85 | // Limit results |
| 86 | if len(results) > maxResults { |
| 87 | results = results[:maxResults] |
| 88 | } |
| 89 | |
| 90 | return results, nil |
| 91 | } |
| 92 | |
| 93 | // scoreTool assigns a relevance score to a tool for the given query. |
| 94 | // |