Skip to content

Commit 3f3a011

Browse files
author
nigel brown
committed
Fix unrecognized dotty names
Signed-off-by: nigel brown <nigel@stacklok.com>
1 parent 0598613 commit 3f3a011

File tree

9 files changed

+245
-68
lines changed

9 files changed

+245
-68
lines changed

pkg/vmcp/config/config.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ type Config struct {
148148
Audit *audit.Config `json:"audit,omitempty" yaml:"audit,omitempty"`
149149

150150
// Optimizer configures the MCP optimizer for context optimization on large toolsets.
151-
// When enabled, vMCP exposes optim.find_tool and optim.call_tool operations to clients
151+
// When enabled, vMCP exposes optim_find_tool and optim_call_tool operations to clients
152152
// instead of all backend tools directly. This reduces token usage by allowing
153153
// LLMs to discover relevant tools on demand rather than receiving all tool definitions.
154154
// +optional
@@ -700,7 +700,7 @@ type OutputProperty struct {
700700
// +gendoc
701701
type OptimizerConfig struct {
702702
// Enabled determines whether the optimizer is active.
703-
// When true, vMCP exposes optim.find_tool and optim.call_tool instead of all backend tools.
703+
// When true, vMCP exposes optim_find_tool and optim_call_tool instead of all backend tools.
704704
// +optional
705705
Enabled bool `json:"enabled" yaml:"enabled"`
706706

pkg/vmcp/optimizer/find_tool_semantic_search_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ func TestFindTool_SemanticSearch(t *testing.T) {
272272

273273
request := mcp.CallToolRequest{
274274
Params: mcp.CallToolParams{
275-
Name: "optim.find_tool",
275+
Name: "optim_find_tool",
276276
Arguments: map[string]any{
277277
"tool_description": tc.query,
278278
"tool_keywords": tc.keywords,
@@ -472,7 +472,7 @@ func TestFindTool_SemanticVsKeyword(t *testing.T) {
472472
// Test semantic search
473473
requestSemantic := mcp.CallToolRequest{
474474
Params: mcp.CallToolParams{
475-
Name: "optim.find_tool",
475+
Name: "optim_find_tool",
476476
Arguments: map[string]any{
477477
"tool_description": query,
478478
"tool_keywords": "",
@@ -489,7 +489,7 @@ func TestFindTool_SemanticVsKeyword(t *testing.T) {
489489
// Test keyword search
490490
requestKeyword := mcp.CallToolRequest{
491491
Params: mcp.CallToolParams{
492-
Name: "optim.find_tool",
492+
Name: "optim_find_tool",
493493
Arguments: map[string]any{
494494
"tool_description": query,
495495
"tool_keywords": "",
@@ -647,7 +647,7 @@ func TestFindTool_SemanticSimilarityScores(t *testing.T) {
647647

648648
request := mcp.CallToolRequest{
649649
Params: mcp.CallToolParams{
650-
Name: "optim.find_tool",
650+
Name: "optim_find_tool",
651651
Arguments: map[string]any{
652652
"tool_description": query,
653653
"tool_keywords": "",

pkg/vmcp/optimizer/find_tool_string_matching_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ func TestFindTool_StringMatching(t *testing.T) {
286286
// Create the tool call request
287287
request := mcp.CallToolRequest{
288288
Params: mcp.CallToolParams{
289-
Name: "optim.find_tool",
289+
Name: "optim_find_tool",
290290
Arguments: map[string]any{
291291
"tool_description": tc.query,
292292
"tool_keywords": tc.keywords,
@@ -506,7 +506,7 @@ func TestFindTool_ExactStringMatch(t *testing.T) {
506506

507507
request := mcp.CallToolRequest{
508508
Params: mcp.CallToolParams{
509-
Name: "optim.find_tool",
509+
Name: "optim_find_tool",
510510
Arguments: map[string]any{
511511
"tool_description": tc.query,
512512
"tool_keywords": tc.keywords,
@@ -651,7 +651,7 @@ func TestFindTool_CaseInsensitive(t *testing.T) {
651651

652652
request := mcp.CallToolRequest{
653653
Params: mcp.CallToolParams{
654-
Name: "optim.find_tool",
654+
Name: "optim_find_tool",
655655
Arguments: map[string]any{
656656
"tool_description": query,
657657
"tool_keywords": strings.ToLower(query),

pkg/vmcp/optimizer/optimizer.go

Lines changed: 142 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
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
114114
func (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.
145214
func (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
218342
func (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
339463
func (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
504628
func (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
509633
func (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

Comments
 (0)