scoreTool assigns a relevance score to a tool for the given query. It combines several signals (substrings, token coverage, and similarity) from: - tool name - tool description - input parameter names (schema property names) MatchedIn records which signals contributed to the score for debugging/tu
( tool mcp.Tool, queryLower string, queryTokens []string, normalizedQueryCompact string, )
| 99 | // |
| 100 | // MatchedIn records which signals contributed to the score for debugging/tuning. |
| 101 | func scoreTool( |
| 102 | tool mcp.Tool, |
| 103 | queryLower string, |
| 104 | queryTokens []string, |
| 105 | normalizedQueryCompact string, |
| 106 | ) (score float64, matchedIn []string) { |
| 107 | nameLower := strings.ToLower(tool.Name) |
| 108 | descLower := strings.ToLower(tool.Description) |
| 109 | |
| 110 | normalizedNameCompact := strings.ReplaceAll(nameLower, "_", "") |
| 111 | nameTokens := splitTokens(nameLower) |
| 112 | propertyNames := lowerInputPropertyNames(tool.InputSchema) |
| 113 | |
| 114 | matches := newMatchTracker(3) |
| 115 | score = 0.0 |
| 116 | |
| 117 | // Strong boosts for direct substring matches |
| 118 | if strings.Contains(nameLower, queryLower) { |
| 119 | score += substringMatchScore |
| 120 | matches.Add("name:substring") |
| 121 | } |
| 122 | if strings.HasPrefix(nameLower, queryLower) { |
| 123 | score += prefixMatchScore |
| 124 | matches.Add("name:prefix") |
| 125 | } |
| 126 | if normalizedNameCompact == normalizedQueryCompact && len(queryTokens) > 1 { |
| 127 | score += exactTokensMatchScore |
| 128 | matches.Add("name:exact-tokens") |
| 129 | } |
| 130 | if strings.Contains(descLower, queryLower) { |
| 131 | score += descriptionMatchScore |
| 132 | matches.Add("description") |
| 133 | } |
| 134 | |
| 135 | for _, prop := range propertyNames { |
| 136 | if strings.Contains(prop, queryLower) { |
| 137 | score += parameterMatchScore |
| 138 | matches.Add("parameter") |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | matchedTokens := make(map[string]struct{}) |
| 143 | |
| 144 | // Token-level matches for multi-word queries |
| 145 | for _, token := range queryTokens { |
| 146 | if strings.Contains(nameLower, token) { |
| 147 | score++ |
| 148 | matchedTokens[token] = struct{}{} |
| 149 | matches.Add("name:token") |
| 150 | } else if strings.Contains(descLower, token) { |
| 151 | score += 0.6 |
| 152 | matchedTokens[token] = struct{}{} |
| 153 | matches.Add("description:token") |
| 154 | } |
| 155 | |
| 156 | for _, prop := range propertyNames { |
| 157 | if strings.Contains(prop, token) { |
| 158 | // Only credit the first parameter match per token to avoid double-counting |
no test coverage detected