11// Package optimizer provides vMCP integration for semantic tool discovery.
22//
33// This package implements the RFC-0022 optimizer integration, exposing:
4- // - optim.find_tool : Semantic/keyword-based tool discovery
5- // - optim.call_tool : Dynamic tool invocation across backends
4+ // - optim_find_tool : Semantic/keyword-based tool discovery
5+ // - optim_call_tool : Dynamic tool invocation across backends
66//
77// Architecture:
88// - Embeddings are generated during session initialization (OnRegisterSession hook)
@@ -110,7 +110,7 @@ func NewIntegration(
110110// This hook:
111111// 1. Extracts backend tools from discovered capabilities
112112// 2. Generates embeddings for all tools (parallel per-backend)
113- // 3. Registers optim.find_tool and optim.call_tool as session tools
113+ // 3. Registers optim_find_tool and optim_call_tool as session tools
114114func (o * OptimizerIntegration ) OnRegisterSession (
115115 _ context.Context ,
116116 session server.ClientSession ,
@@ -140,7 +140,76 @@ func (o *OptimizerIntegration) OnRegisterSession(
140140 return nil
141141}
142142
143+ // RegisterGlobalTools registers optimizer tools globally (available to all sessions).
144+ // This should be called during server initialization, before any sessions are created.
145+ // Registering tools globally ensures they are immediately available when clients connect,
146+ // avoiding timing issues where list_tools is called before per-session registration completes.
147+ func (o * OptimizerIntegration ) RegisterGlobalTools () error {
148+ if o == nil {
149+ return nil // Optimizer not enabled
150+ }
151+
152+ // Define optimizer tools with handlers
153+ findToolHandler := o .createFindToolHandler ()
154+ callToolHandler := o .CreateCallToolHandler ()
155+
156+ // Register optim_find_tool globally
157+ o .mcpServer .AddTool (mcp.Tool {
158+ Name : "optim_find_tool" ,
159+ Description : "Semantic search across all backend tools using natural language description and optional keywords" ,
160+ InputSchema : mcp.ToolInputSchema {
161+ Type : "object" ,
162+ Properties : map [string ]any {
163+ "tool_description" : map [string ]any {
164+ "type" : "string" ,
165+ "description" : "Natural language description of the tool you're looking for" ,
166+ },
167+ "tool_keywords" : map [string ]any {
168+ "type" : "string" ,
169+ "description" : "Optional space-separated keywords for keyword-based search" ,
170+ },
171+ "limit" : map [string ]any {
172+ "type" : "integer" ,
173+ "description" : "Maximum number of tools to return (default: 10)" ,
174+ "default" : 10 ,
175+ },
176+ },
177+ Required : []string {"tool_description" },
178+ },
179+ }, findToolHandler )
180+
181+ // Register optim_call_tool globally
182+ o .mcpServer .AddTool (mcp.Tool {
183+ Name : "optim_call_tool" ,
184+ Description : "Dynamically invoke any tool on any backend using the backend_id from find_tool" ,
185+ InputSchema : mcp.ToolInputSchema {
186+ Type : "object" ,
187+ Properties : map [string ]any {
188+ "backend_id" : map [string ]any {
189+ "type" : "string" ,
190+ "description" : "Backend ID from find_tool results" ,
191+ },
192+ "tool_name" : map [string ]any {
193+ "type" : "string" ,
194+ "description" : "Tool name to invoke" ,
195+ },
196+ "parameters" : map [string ]any {
197+ "type" : "object" ,
198+ "description" : "Parameters to pass to the tool" ,
199+ },
200+ },
201+ Required : []string {"backend_id" , "tool_name" , "parameters" },
202+ },
203+ }, callToolHandler )
204+
205+ logger .Info ("Optimizer tools registered globally (optim_find_tool, optim_call_tool)" )
206+ return nil
207+ }
208+
143209// RegisterTools adds optimizer tools to the session.
210+ // Even though tools are registered globally via RegisterGlobalTools(),
211+ // with WithToolCapabilities(false), we also need to register them per-session
212+ // to ensure they appear in list_tools responses.
144213// This should be called after OnRegisterSession completes.
145214func (o * OptimizerIntegration ) RegisterTools (_ context.Context , session server.ClientSession ) error {
146215 if o == nil {
@@ -149,11 +218,11 @@ func (o *OptimizerIntegration) RegisterTools(_ context.Context, session server.C
149218
150219 sessionID := session .SessionID ()
151220
152- // Define optimizer tools with handlers
221+ // Define optimizer tools with handlers (same as global registration)
153222 optimizerTools := []server.ServerTool {
154223 {
155224 Tool : mcp.Tool {
156- Name : "optim.find_tool " ,
225+ Name : "optim_find_tool " ,
157226 Description : "Semantic search across all backend tools using natural language description and optional keywords" ,
158227 InputSchema : mcp.ToolInputSchema {
159228 Type : "object" ,
@@ -179,7 +248,7 @@ func (o *OptimizerIntegration) RegisterTools(_ context.Context, session server.C
179248 },
180249 {
181250 Tool : mcp.Tool {
182- Name : "optim.call_tool " ,
251+ Name : "optim_call_tool " ,
183252 Description : "Dynamically invoke any tool on any backend using the backend_id from find_tool" ,
184253 InputSchema : mcp.ToolInputSchema {
185254 Type : "object" ,
@@ -204,16 +273,71 @@ func (o *OptimizerIntegration) RegisterTools(_ context.Context, session server.C
204273 },
205274 }
206275
207- // Add tools to session
276+ // Add tools to session (required when WithToolCapabilities(false))
208277 if err := o .mcpServer .AddSessionTools (sessionID , optimizerTools ... ); err != nil {
209278 return fmt .Errorf ("failed to add optimizer tools to session: %w" , err )
210279 }
211280
212- logger .Debugw ("Optimizer tools registered" , "session_id" , sessionID )
281+ logger .Debugw ("Optimizer tools registered for session " , "session_id" , sessionID )
213282 return nil
214283}
215284
216- // CreateFindToolHandler creates the handler for optim.find_tool
285+ // GetOptimizerToolDefinitions returns the tool definitions for optimizer tools
286+ // without handlers. This is useful for adding tools to capabilities before session registration.
287+ func (o * OptimizerIntegration ) GetOptimizerToolDefinitions () []mcp.Tool {
288+ if o == nil {
289+ return nil
290+ }
291+ return []mcp.Tool {
292+ {
293+ Name : "optim_find_tool" ,
294+ Description : "Semantic search across all backend tools using natural language description and optional keywords" ,
295+ InputSchema : mcp.ToolInputSchema {
296+ Type : "object" ,
297+ Properties : map [string ]any {
298+ "tool_description" : map [string ]any {
299+ "type" : "string" ,
300+ "description" : "Natural language description of the tool you're looking for" ,
301+ },
302+ "tool_keywords" : map [string ]any {
303+ "type" : "string" ,
304+ "description" : "Optional space-separated keywords for keyword-based search" ,
305+ },
306+ "limit" : map [string ]any {
307+ "type" : "integer" ,
308+ "description" : "Maximum number of tools to return (default: 10)" ,
309+ "default" : 10 ,
310+ },
311+ },
312+ Required : []string {"tool_description" },
313+ },
314+ },
315+ {
316+ Name : "optim_call_tool" ,
317+ Description : "Dynamically invoke any tool on any backend using the backend_id from find_tool" ,
318+ InputSchema : mcp.ToolInputSchema {
319+ Type : "object" ,
320+ Properties : map [string ]any {
321+ "backend_id" : map [string ]any {
322+ "type" : "string" ,
323+ "description" : "Backend ID from find_tool results" ,
324+ },
325+ "tool_name" : map [string ]any {
326+ "type" : "string" ,
327+ "description" : "Tool name to invoke" ,
328+ },
329+ "parameters" : map [string ]any {
330+ "type" : "object" ,
331+ "description" : "Parameters to pass to the tool" ,
332+ },
333+ },
334+ Required : []string {"backend_id" , "tool_name" , "parameters" },
335+ },
336+ },
337+ }
338+ }
339+
340+ // CreateFindToolHandler creates the handler for optim_find_tool
217341// Exported for testing purposes
218342func (o * OptimizerIntegration ) CreateFindToolHandler () func (context.Context , mcp.CallToolRequest ) (* mcp.CallToolResult , error ) {
219343 return o .createFindToolHandler ()
@@ -335,10 +459,10 @@ func convertSearchResultsToResponse(
335459 return responseTools , totalReturnedTokens
336460}
337461
338- // createFindToolHandler creates the handler for optim.find_tool
462+ // createFindToolHandler creates the handler for optim_find_tool
339463func (o * OptimizerIntegration ) createFindToolHandler () func (context.Context , mcp.CallToolRequest ) (* mcp.CallToolResult , error ) {
340464 return func (ctx context.Context , request mcp.CallToolRequest ) (* mcp.CallToolResult , error ) {
341- logger .Debugw ("optim.find_tool called" , "request" , request )
465+ logger .Debugw ("optim_find_tool called" , "request" , request )
342466
343467 // Extract parameters from request arguments
344468 args , ok := request .Params .Arguments .(map [string ]any )
@@ -423,7 +547,7 @@ func (o *OptimizerIntegration) createFindToolHandler() func(context.Context, mcp
423547 return mcp .NewToolResultError (fmt .Sprintf ("failed to marshal response: %v" , err3 )), nil
424548 }
425549
426- logger .Infow ("optim.find_tool completed" ,
550+ logger .Infow ("optim_find_tool completed" ,
427551 "query" , toolDescription ,
428552 "results_count" , len (responseTools ),
429553 "tokens_saved" , tokensSaved ,
@@ -456,7 +580,7 @@ func (*OptimizerIntegration) recordTokenMetrics(
456580
457581 returnedCounter , err := meter .Int64Counter (
458582 "toolhive_vmcp_optimizer_returned_tokens" ,
459- metric .WithDescription ("Total tokens for tools returned by optim.find_tool " ),
583+ metric .WithDescription ("Total tokens for tools returned by optim_find_tool " ),
460584 )
461585 if err != nil {
462586 logger .Debugw ("Failed to create returned_tokens counter" , "error" , err )
@@ -465,7 +589,7 @@ func (*OptimizerIntegration) recordTokenMetrics(
465589
466590 savedCounter , err := meter .Int64Counter (
467591 "toolhive_vmcp_optimizer_tokens_saved" ,
468- metric .WithDescription ("Number of tokens saved by filtering tools with optim.find_tool " ),
592+ metric .WithDescription ("Number of tokens saved by filtering tools with optim_find_tool " ),
469593 )
470594 if err != nil {
471595 logger .Debugw ("Failed to create tokens_saved counter" , "error" , err )
@@ -499,16 +623,16 @@ func (*OptimizerIntegration) recordTokenMetrics(
499623 "savings_percentage" , savingsPercentage )
500624}
501625
502- // CreateCallToolHandler creates the handler for optim.call_tool
626+ // CreateCallToolHandler creates the handler for optim_call_tool
503627// Exported for testing purposes
504628func (o * OptimizerIntegration ) CreateCallToolHandler () func (context.Context , mcp.CallToolRequest ) (* mcp.CallToolResult , error ) {
505629 return o .createCallToolHandler ()
506630}
507631
508- // createCallToolHandler creates the handler for optim.call_tool
632+ // createCallToolHandler creates the handler for optim_call_tool
509633func (o * OptimizerIntegration ) createCallToolHandler () func (context.Context , mcp.CallToolRequest ) (* mcp.CallToolResult , error ) {
510634 return func (ctx context.Context , request mcp.CallToolRequest ) (* mcp.CallToolResult , error ) {
511- logger .Debugw ("optim.call_tool called" , "request" , request )
635+ logger .Debugw ("optim_call_tool called" , "request" , request )
512636
513637 // Extract parameters from request arguments
514638 args , ok := request .Params .Arguments .(map [string ]any )
@@ -587,7 +711,7 @@ func (o *OptimizerIntegration) createCallToolHandler() func(context.Context, mcp
587711 return mcp .NewToolResultError (fmt .Sprintf ("failed to marshal result: %v" , err )), nil
588712 }
589713
590- logger .Infow ("optim.call_tool completed successfully" ,
714+ logger .Infow ("optim_call_tool completed successfully" ,
591715 "backend_id" , backendID ,
592716 "tool_name" , toolName )
593717
0 commit comments