package tools import ( "context" "fmt" "strings" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" "forge.lclr.dev/AI/xdebug-mcp/internal/cache" "forge.lclr.dev/AI/xdebug-mcp/internal/cachegrind" ) // AnalyzeTool returns the MCP tool definition for analyze_profile. func AnalyzeTool() mcp.Tool { return mcp.NewTool("analyze_profile", mcp.WithDescription("Analyze an Xdebug cachegrind profiling file. Returns global stats and top N functions sorted by inclusive time cost."), mcp.WithString("file_path", mcp.Required(), mcp.Description("Absolute or relative path to the cachegrind file (.gz or plain text)"), ), mcp.WithNumber("top_n", mcp.Description("Number of top functions to return (default: 20)"), ), ) } // AnalyzeHandler returns the MCP handler for analyze_profile. func AnalyzeHandler(c *cache.Cache) server.ToolHandlerFunc { return func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { filePath := req.GetString("file_path", "") if filePath == "" { return mcp.NewToolResultError("file_path is required"), nil } topN := req.GetInt("top_n", 20) if topN <= 0 { topN = 20 } p, err := loadProfile(filePath, c) if err != nil { return mcp.NewToolResultError(err.Error()), nil } return mcp.NewToolResultText(Analyze(p, topN)), nil } } // Analyze formats the top-N analysis of p. Exported for testing. func Analyze(p *cachegrind.Profile, topN int) string { sorted := sortedByTime(p.Functions) if topN > len(sorted) { topN = len(sorted) } var sb strings.Builder fmt.Fprintf(&sb, "Command: %s\n", p.Cmd) fmt.Fprintf(&sb, "Events: %s\n", strings.Join(p.Events, ", ")) fmt.Fprintf(&sb, "Functions: %d total\n\n", len(p.Functions)) peak := int64(0) if len(sorted) > 0 && len(sorted[0].Costs) > 0 { peak = sorted[0].Costs[0] } if len(p.Events) > 0 { fmt.Fprintf(&sb, "Top %d functions by %s:\n", topN, p.Events[0]) } for i, fn := range sorted[:topN] { pct := "" if peak > 0 && len(fn.Costs) > 0 { pct = fmt.Sprintf(" (%.1f%% of peak)", float64(fn.Costs[0])/float64(peak)*100) } fmt.Fprintf(&sb, " %3d. %-60s %s%s\n", i+1, fn.Name, formatCosts(fn.Costs, p.Events), pct) fmt.Fprintf(&sb, " %s\n", fn.File) } return sb.String() }