84 lines
2.4 KiB
Go
84 lines
2.4 KiB
Go
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"
|
|
)
|
|
|
|
// CalleesTool returns the MCP tool definition for get_callees.
|
|
func CalleesTool() mcp.Tool {
|
|
return mcp.NewTool("get_callees",
|
|
mcp.WithDescription("List functions called by a given function in an Xdebug profiling file, sorted by call cost descending."),
|
|
mcp.WithString("file_path",
|
|
mcp.Required(),
|
|
mcp.Description("Absolute or relative path to the cachegrind file"),
|
|
),
|
|
mcp.WithString("function_name",
|
|
mcp.Required(),
|
|
mcp.Description("Exact function name or substring to search for"),
|
|
),
|
|
mcp.WithNumber("top_n",
|
|
mcp.Description("Maximum number of callees to return (default: 10)"),
|
|
),
|
|
)
|
|
}
|
|
|
|
// CalleesHandler returns the MCP handler for get_callees.
|
|
func CalleesHandler(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
|
|
}
|
|
name := req.GetString("function_name", "")
|
|
if name == "" {
|
|
return mcp.NewToolResultError("function_name is required"), nil
|
|
}
|
|
topN := req.GetInt("top_n", 10)
|
|
if topN <= 0 {
|
|
topN = 10
|
|
}
|
|
p, err := loadProfile(filePath, c)
|
|
if err != nil {
|
|
return mcp.NewToolResultError(err.Error()), nil
|
|
}
|
|
return mcp.NewToolResultText(Callees(p, name, topN)), nil
|
|
}
|
|
}
|
|
|
|
// Callees formats the callees of name in p. Exported for testing.
|
|
func Callees(p *cachegrind.Profile, name string, topN int) string {
|
|
fns, errMsg := findFunctions(p, name)
|
|
if errMsg != "" {
|
|
return errMsg
|
|
}
|
|
|
|
var sb strings.Builder
|
|
for _, fn := range fns {
|
|
fmt.Fprintf(&sb, "Callees of %q [%s]\n", fn.Name, fn.File)
|
|
if len(fn.Calls) == 0 {
|
|
fmt.Fprintf(&sb, " no outgoing calls recorded\n\n")
|
|
continue
|
|
}
|
|
|
|
callees := sortedCalls(fn.Calls)
|
|
if topN < len(callees) {
|
|
callees = callees[:topN]
|
|
}
|
|
fmt.Fprintf(&sb, " calls %d function(s):\n\n", len(fn.Calls))
|
|
for i, call := range callees {
|
|
fmt.Fprintf(&sb, " %3d. %-60s calls=%d %s\n",
|
|
i+1, call.Callee.Name, call.Count, formatCosts(call.Costs, p.Events))
|
|
fmt.Fprintf(&sb, " %s\n", call.Callee.File)
|
|
}
|
|
fmt.Fprintln(&sb)
|
|
}
|
|
return sb.String()
|
|
}
|