Skip to content

Commit af2831c

Browse files
Dumbrisclaude
andcommitted
feat: add Anthropic tool_search_tool_bm25_20251119 for lazy loading
Add support for Anthropic's Advanced Tool Use standard by implementing the tool_search_tool_bm25_20251119 tool alongside existing retrieve_tools. This tool name is fine-tuned into Claude models for on-demand tool discovery. Returns tool_reference content blocks per Anthropic's custom implementation format, enabling lazy loading of tools. Key changes: - Add tool_search_tool_bm25_20251119 tool registration - Implement handleToolSearchBM25 handler with BM25 search - Return tool_reference format: [{"type":"tool_reference","tool_name":"..."}] - Fixed 5-result limit per Anthropic standard - Full activity logging (Spec 024) References: - https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool - https://www.anthropic.com/engineering/advanced-tool-use Closes: anthropics/claude-code#7336 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 50e5bb8 commit af2831c

1 file changed

Lines changed: 77 additions & 0 deletions

File tree

internal/server/mcp.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,21 @@ func (p *MCPProxyServer) registerTools(_ bool) {
300300
)
301301
p.server.AddTool(retrieveToolsTool, p.handleRetrieveTools)
302302

303+
// tool_search_tool_bm25_20251119 - Anthropic-compatible tool search (lazy loading)
304+
// Claude models are fine-tuned to recognize this specific tool name for on-demand tool discovery.
305+
// Returns tool_reference content blocks per Anthropic's custom implementation format.
306+
// See: https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool
307+
toolSearchBM25Tool := mcp.NewTool("tool_search_tool_bm25_20251119",
308+
mcp.WithDescription("Search for tools by keyword or description. Use this to find relevant tools before calling them."),
309+
mcp.WithTitleAnnotation("Tool Search (BM25)"),
310+
mcp.WithReadOnlyHintAnnotation(true),
311+
mcp.WithString("query",
312+
mcp.Required(),
313+
mcp.Description("The search query (e.g. 'weather', 'git commit', 'database query')."),
314+
),
315+
)
316+
p.server.AddTool(toolSearchBM25Tool, p.handleToolSearchBM25)
317+
303318
// Intent-based tool variants (Spec 018)
304319
// These replace the legacy call_tool with three operation-specific variants
305320
// that enable granular IDE permission control and require explicit intent declaration.
@@ -900,6 +915,68 @@ func (p *MCPProxyServer) handleRetrieveTools(ctx context.Context, request mcp.Ca
900915
return mcp.NewToolResultText(string(jsonResult)), nil
901916
}
902917

918+
// handleToolSearchBM25 implements the Anthropic-standard tool search tool
919+
// This tool name (tool_search_tool_bm25_20251119) is fine-tuned into Claude models
920+
// for lazy loading tool discovery. Returns tool_reference content blocks per
921+
// Anthropic's custom implementation format.
922+
// See: https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool
923+
func (p *MCPProxyServer) handleToolSearchBM25(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
924+
startTime := time.Now()
925+
926+
// Extract session info for activity logging (Spec 024)
927+
var sessionID string
928+
if sess := mcpserver.ClientSessionFromContext(ctx); sess != nil {
929+
sessionID = sess.SessionID()
930+
}
931+
requestID := fmt.Sprintf("%d-tool_search_bm25", time.Now().UnixNano())
932+
933+
query, err := request.RequireString("query")
934+
if err != nil {
935+
p.emitActivityInternalToolCall("tool_search_tool_bm25_20251119", "", "", "",
936+
sessionID, requestID, "error", err.Error(),
937+
time.Since(startTime).Milliseconds(), nil, nil, nil)
938+
return mcp.NewToolResultError(fmt.Sprintf("Missing required parameter 'query': %v", err)), nil
939+
}
940+
941+
// Build arguments map for activity logging
942+
args := map[string]interface{}{"query": query}
943+
944+
// Search using existing BM25 index (limit to 5 per Anthropic standard)
945+
results, err := p.index.Search(query, 5)
946+
if err != nil {
947+
p.logger.Error("Tool search failed", zap.String("query", query), zap.Error(err))
948+
p.emitActivityInternalToolCall("tool_search_tool_bm25_20251119", "", "", "",
949+
sessionID, requestID, "error", err.Error(),
950+
time.Since(startTime).Milliseconds(), args, nil, nil)
951+
return mcp.NewToolResultError(fmt.Sprintf("Search failed: %v", err)), nil
952+
}
953+
954+
// Build tool_reference content blocks (Anthropic custom implementation format)
955+
content := make([]map[string]interface{}, 0, len(results))
956+
for _, result := range results {
957+
content = append(content, map[string]interface{}{
958+
"type": "tool_reference",
959+
"tool_name": result.Tool.Name,
960+
})
961+
}
962+
963+
// Return as JSON array (Claude will parse this)
964+
jsonResult, err := json.Marshal(content)
965+
if err != nil {
966+
p.emitActivityInternalToolCall("tool_search_tool_bm25_20251119", "", "", "",
967+
sessionID, requestID, "error", err.Error(),
968+
time.Since(startTime).Milliseconds(), args, nil, nil)
969+
return mcp.NewToolResultError(fmt.Sprintf("Failed to serialize results: %v", err)), nil
970+
}
971+
972+
// Emit success event
973+
p.emitActivityInternalToolCall("tool_search_tool_bm25_20251119", "", "", "",
974+
sessionID, requestID, "success", "",
975+
time.Since(startTime).Milliseconds(), args, content, nil)
976+
977+
return mcp.NewToolResultText(string(jsonResult)), nil
978+
}
979+
903980
// handleCallToolRead implements the call_tool_read functionality (Spec 018)
904981
func (p *MCPProxyServer) handleCallToolRead(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
905982
return p.handleCallToolVariant(ctx, request, contracts.ToolVariantRead)

0 commit comments

Comments
 (0)