@@ -3,6 +3,7 @@ package index
33import (
44 "fmt"
55 "path/filepath"
6+ "strings"
67
78 "github.com/blevesearch/bleve/v2"
89 "github.com/blevesearch/bleve/v2/analysis/analyzer/keyword"
@@ -20,12 +21,14 @@ type BleveIndex struct {
2021
2122// ToolDocument represents a tool document in the index
2223type ToolDocument struct {
23- ToolName string `json:"tool_name"`
24- ServerName string `json:"server_name"`
25- Description string `json:"description"`
26- ParamsJSON string `json:"params_json"`
27- Hash string `json:"hash"`
28- Tags string `json:"tags"`
24+ ToolName string `json:"tool_name"` // Just the tool name (without server prefix)
25+ FullToolName string `json:"full_tool_name"` // Complete server:tool format
26+ ServerName string `json:"server_name"`
27+ Description string `json:"description"`
28+ ParamsJSON string `json:"params_json"`
29+ Hash string `json:"hash"`
30+ Tags string `json:"tags"`
31+ SearchableText string `json:"searchable_text"` // Combined searchable content
2932}
3033
3134// NewBleveIndex creates a new Bleve index
@@ -59,12 +62,19 @@ func createBleveIndex(indexPath string) (bleve.Index, error) {
5962 // Create document mapping for tools
6063 toolMapping := bleve .NewDocumentMapping ()
6164
62- // Tool name field (keyword analyzer - exact match)
63- toolNameField := bleve .NewTextFieldMapping ()
64- toolNameField .Analyzer = keyword .Name
65- toolNameField .Store = true
66- toolNameField .Index = true
67- toolMapping .AddFieldMappingsAt ("tool_name" , toolNameField )
65+ // Tool name field (both keyword and standard analyzers for different search types)
66+ toolNameFieldKeyword := bleve .NewTextFieldMapping ()
67+ toolNameFieldKeyword .Analyzer = keyword .Name
68+ toolNameFieldKeyword .Store = true
69+ toolNameFieldKeyword .Index = true
70+ toolMapping .AddFieldMappingsAt ("tool_name" , toolNameFieldKeyword )
71+
72+ // Full tool name field (keyword analyzer for exact matches)
73+ fullToolNameField := bleve .NewTextFieldMapping ()
74+ fullToolNameField .Analyzer = keyword .Name
75+ fullToolNameField .Store = true
76+ fullToolNameField .Index = true
77+ toolMapping .AddFieldMappingsAt ("full_tool_name" , fullToolNameField )
6878
6979 // Server name field (keyword analyzer)
7080 serverNameField := bleve .NewTextFieldMapping ()
@@ -101,6 +111,13 @@ func createBleveIndex(indexPath string) (bleve.Index, error) {
101111 tagsField .Index = true
102112 toolMapping .AddFieldMappingsAt ("tags" , tagsField )
103113
114+ // Searchable text field (standard analyzer) - combines all searchable content
115+ searchableTextField := bleve .NewTextFieldMapping ()
116+ searchableTextField .Analyzer = standard .Name
117+ searchableTextField .Store = false // Don't store, just index for search
118+ searchableTextField .Index = true
119+ toolMapping .AddFieldMappingsAt ("searchable_text" , searchableTextField )
120+
104121 // Add document mapping to index
105122 indexMapping .AddDocumentMapping ("tool" , toolMapping )
106123 indexMapping .DefaultMapping = toolMapping
@@ -116,19 +133,34 @@ func (b *BleveIndex) Close() error {
116133
117134// IndexTool indexes a tool document
118135func (b * BleveIndex ) IndexTool (toolMeta * config.ToolMetadata ) error {
136+ // Extract just the tool name (remove server prefix)
137+ toolName := toolMeta .Name
138+ if parts := strings .SplitN (toolMeta .Name , ":" , 2 ); len (parts ) == 2 {
139+ toolName = parts [1 ]
140+ }
141+
142+ // Create combined searchable text for better full-text search
143+ searchableText := fmt .Sprintf ("%s %s %s %s" ,
144+ toolName ,
145+ toolMeta .Name ,
146+ toolMeta .Description ,
147+ toolMeta .ParamsJSON )
148+
119149 doc := & ToolDocument {
120- ToolName : toolMeta .Name ,
121- ServerName : toolMeta .ServerName ,
122- Description : toolMeta .Description ,
123- ParamsJSON : toolMeta .ParamsJSON ,
124- Hash : toolMeta .Hash ,
125- Tags : "" , // Can be extended later
150+ ToolName : toolName ,
151+ FullToolName : toolMeta .Name ,
152+ ServerName : toolMeta .ServerName ,
153+ Description : toolMeta .Description ,
154+ ParamsJSON : toolMeta .ParamsJSON ,
155+ Hash : toolMeta .Hash ,
156+ Tags : "" , // Can be extended later
157+ SearchableText : searchableText ,
126158 }
127159
128160 // Use server:tool format as document ID for uniqueness
129- docID := fmt .Sprintf ("%s:%s" , toolMeta .ServerName , toolMeta . Name )
161+ docID := fmt .Sprintf ("%s:%s" , toolMeta .ServerName , toolName )
130162
131- b .logger .Debug ("Indexing tool" , zap .String ("doc_id" , docID ))
163+ b .logger .Debug ("Indexing tool" , zap .String ("doc_id" , docID ), zap . String ( "tool_name" , toolName ) )
132164 return b .index .Index (docID , doc )
133165}
134166
@@ -168,22 +200,59 @@ func (b *BleveIndex) DeleteServerTools(serverName string) error {
168200 return nil
169201}
170202
171- // SearchTools searches for tools using BM25 scoring
172- func (b * BleveIndex ) SearchTools (query string , limit int ) ([]* config.SearchResult , error ) {
173- if query == "" {
203+ // SearchTools searches for tools using multiple query strategies for better results
204+ func (b * BleveIndex ) SearchTools (queryStr string , limit int ) ([]* config.SearchResult , error ) {
205+ if queryStr == "" {
174206 return nil , fmt .Errorf ("search query cannot be empty" )
175207 }
176208
177- // Create a match query for full-text search
178- matchQuery := bleve .NewMatchQuery (query )
209+ // Create a boolean query to combine multiple search strategies
210+ boolQuery := bleve .NewBooleanQuery ()
211+
212+ // 1. Exact match on tool name (highest priority)
213+ exactToolNameQuery := bleve .NewTermQuery (queryStr )
214+ exactToolNameQuery .SetField ("tool_name" )
215+ exactToolNameQuery .SetBoost (5.0 )
216+ boolQuery .AddShould (exactToolNameQuery )
217+
218+ // 2. Exact match on full tool name
219+ exactFullToolNameQuery := bleve .NewTermQuery (queryStr )
220+ exactFullToolNameQuery .SetField ("full_tool_name" )
221+ exactFullToolNameQuery .SetBoost (4.0 )
222+ boolQuery .AddShould (exactFullToolNameQuery )
223+
224+ // 3. Prefix match on tool name for partial matches
225+ prefixToolNameQuery := bleve .NewPrefixQuery (queryStr )
226+ prefixToolNameQuery .SetField ("tool_name" )
227+ prefixToolNameQuery .SetBoost (3.0 )
228+ boolQuery .AddShould (prefixToolNameQuery )
229+
230+ // 4. Wildcard search for underscore-separated terms
231+ if strings .Contains (queryStr , "_" ) {
232+ wildcardQuery := bleve .NewWildcardQuery ("*" + queryStr + "*" )
233+ wildcardQuery .SetField ("tool_name" )
234+ wildcardQuery .SetBoost (2.5 )
235+ boolQuery .AddShould (wildcardQuery )
236+ }
237+
238+ // 5. Full-text search across all fields
239+ matchQuery := bleve .NewMatchQuery (queryStr )
240+ matchQuery .SetBoost (1.0 )
241+ boolQuery .AddShould (matchQuery )
242+
243+ // 6. Search in combined searchable text
244+ searchableTextQuery := bleve .NewMatchQuery (queryStr )
245+ searchableTextQuery .SetField ("searchable_text" )
246+ searchableTextQuery .SetBoost (1.5 )
247+ boolQuery .AddShould (searchableTextQuery )
179248
180249 // Create search request
181- searchReq := bleve .NewSearchRequest (matchQuery )
250+ searchReq := bleve .NewSearchRequest (boolQuery )
182251 searchReq .Size = limit
183- searchReq .Fields = []string {"tool_name" , "server_name" , "description" , "params_json" , "hash" }
252+ searchReq .Fields = []string {"tool_name" , "full_tool_name" , " server_name" , "description" , "params_json" , "hash" }
184253 searchReq .Highlight = bleve .NewHighlight ()
185254
186- b .logger .Debug ("Searching tools" , zap .String ("query" , query ), zap .Int ("limit" , limit ))
255+ b .logger .Debug ("Searching tools with enhanced query " , zap .String ("query" , queryStr ), zap .Int ("limit" , limit ))
187256
188257 searchResult , err := b .index .Search (searchReq )
189258 if err != nil {
@@ -194,7 +263,7 @@ func (b *BleveIndex) SearchTools(query string, limit int) ([]*config.SearchResul
194263 var results []* config.SearchResult
195264 for _ , hit := range searchResult .Hits {
196265 toolMeta := & config.ToolMetadata {
197- Name : getStringField (hit .Fields , "tool_name " ),
266+ Name : getStringField (hit .Fields , "full_tool_name " ),
198267 ServerName : getStringField (hit .Fields , "server_name" ),
199268 Description : getStringField (hit .Fields , "description" ),
200269 ParamsJSON : getStringField (hit .Fields , "params_json" ),
@@ -207,7 +276,7 @@ func (b *BleveIndex) SearchTools(query string, limit int) ([]*config.SearchResul
207276 })
208277 }
209278
210- b .logger .Debug ("Found tools matching query" , zap .Int ("count" , len (results )), zap .String ("query" , query ))
279+ b .logger .Debug ("Found tools matching query" , zap .Int ("count" , len (results )), zap .String ("query" , queryStr ))
211280 return results , nil
212281}
213282
@@ -223,16 +292,31 @@ func (b *BleveIndex) BatchIndex(tools []*config.ToolMetadata) error {
223292 batch := b .index .NewBatch ()
224293
225294 for _ , toolMeta := range tools {
295+ // Extract just the tool name (remove server prefix)
296+ toolName := toolMeta .Name
297+ if parts := strings .SplitN (toolMeta .Name , ":" , 2 ); len (parts ) == 2 {
298+ toolName = parts [1 ]
299+ }
300+
301+ // Create combined searchable text
302+ searchableText := fmt .Sprintf ("%s %s %s %s" ,
303+ toolName ,
304+ toolMeta .Name ,
305+ toolMeta .Description ,
306+ toolMeta .ParamsJSON )
307+
226308 doc := & ToolDocument {
227- ToolName : toolMeta .Name ,
228- ServerName : toolMeta .ServerName ,
229- Description : toolMeta .Description ,
230- ParamsJSON : toolMeta .ParamsJSON ,
231- Hash : toolMeta .Hash ,
232- Tags : "" ,
309+ ToolName : toolName ,
310+ FullToolName : toolMeta .Name ,
311+ ServerName : toolMeta .ServerName ,
312+ Description : toolMeta .Description ,
313+ ParamsJSON : toolMeta .ParamsJSON ,
314+ Hash : toolMeta .Hash ,
315+ Tags : "" ,
316+ SearchableText : searchableText ,
233317 }
234318
235- docID := fmt .Sprintf ("%s:%s" , toolMeta .ServerName , toolMeta . Name )
319+ docID := fmt .Sprintf ("%s:%s" , toolMeta .ServerName , toolName )
236320 batch .Index (docID , doc )
237321 }
238322
0 commit comments