Add AST analysis tools and go run/test execution tools

This commit is contained in:
Ian Gulliver
2025-06-27 22:54:45 -07:00
parent 9e94d0693b
commit ee04ab585a
18 changed files with 3796 additions and 5 deletions

363
main.go
View File

@@ -423,6 +423,137 @@ func main() {
)
mcpServer.AddTool(searchReplaceTool, searchReplaceHandler)
// Define the find_method_receivers tool
findMethodReceiversTool := mcp.NewTool("find_method_receivers",
mcp.WithDescription("Track pointer vs value receivers inconsistencies and suggest standardization"),
mcp.WithString("dir",
mcp.Description("Directory to search (default: current directory)"),
),
)
mcpServer.AddTool(findMethodReceiversTool, findMethodReceiversHandler)
// Define the analyze_goroutines tool
analyzeGoroutinesTool := mcp.NewTool("analyze_goroutines",
mcp.WithDescription("Find goroutine leaks, missing waitgroups, and unsafe concurrent access patterns"),
mcp.WithString("dir",
mcp.Description("Directory to search (default: current directory)"),
),
)
mcpServer.AddTool(analyzeGoroutinesTool, analyzeGoroutinesHandler)
// Define the find_panic_recover tool
findPanicRecoverTool := mcp.NewTool("find_panic_recover",
mcp.WithDescription("Locate panic/recover patterns and suggest error handling improvements"),
mcp.WithString("dir",
mcp.Description("Directory to search (default: current directory)"),
),
)
mcpServer.AddTool(findPanicRecoverTool, findPanicRecoverHandler)
// Define the analyze_channels tool
analyzeChannelsTool := mcp.NewTool("analyze_channels",
mcp.WithDescription("Detect channel deadlocks, unbuffered channel issues, and goroutine communication patterns"),
mcp.WithString("dir",
mcp.Description("Directory to search (default: current directory)"),
),
)
mcpServer.AddTool(analyzeChannelsTool, analyzeChannelsHandler)
// Define the find_type_assertions tool
findTypeAssertionsTool := mcp.NewTool("find_type_assertions",
mcp.WithDescription("Find unsafe type assertions without ok checks"),
mcp.WithString("dir",
mcp.Description("Directory to search (default: current directory)"),
),
)
mcpServer.AddTool(findTypeAssertionsTool, findTypeAssertionsHandler)
// Define the analyze_memory_allocations tool
analyzeMemoryAllocationsTool := mcp.NewTool("analyze_memory_allocations",
mcp.WithDescription("Identify excessive allocations, escaping variables, and suggest optimizations"),
mcp.WithString("dir",
mcp.Description("Directory to search (default: current directory)"),
),
)
mcpServer.AddTool(analyzeMemoryAllocationsTool, analyzeMemoryAllocationsHandler)
// Define the find_reflection_usage tool
findReflectionUsageTool := mcp.NewTool("find_reflection_usage",
mcp.WithDescription("Track reflect package usage for performance and type safety analysis"),
mcp.WithString("dir",
mcp.Description("Directory to search (default: current directory)"),
),
)
mcpServer.AddTool(findReflectionUsageTool, findReflectionUsageHandler)
// Define the find_init_functions tool
findInitFunctionsTool := mcp.NewTool("find_init_functions",
mcp.WithDescription("Track init() functions and their initialization order dependencies"),
mcp.WithString("dir",
mcp.Description("Directory to search (default: current directory)"),
),
)
mcpServer.AddTool(findInitFunctionsTool, findInitFunctionsHandler)
// Define the analyze_defer_patterns tool
analyzeDeferPatternsTool := mcp.NewTool("analyze_defer_patterns",
mcp.WithDescription("Find incorrect defer usage and resource leak risks"),
mcp.WithString("dir",
mcp.Description("Directory to search (default: current directory)"),
),
)
mcpServer.AddTool(analyzeDeferPatternsTool, analyzeDeferPatternsHandler)
// Define the find_empty_blocks tool
findEmptyBlocksTool := mcp.NewTool("find_empty_blocks",
mcp.WithDescription("Locate empty if/else/for blocks and suggest removal"),
mcp.WithString("dir",
mcp.Description("Directory to search (default: current directory)"),
),
)
mcpServer.AddTool(findEmptyBlocksTool, findEmptyBlocksHandler)
// Define the analyze_naming_conventions tool
analyzeNamingConventionsTool := mcp.NewTool("analyze_naming_conventions",
mcp.WithDescription("Check Go naming conventions (camelCase, exported names, etc.)"),
mcp.WithString("dir",
mcp.Description("Directory to search (default: current directory)"),
),
)
mcpServer.AddTool(analyzeNamingConventionsTool, analyzeNamingConventionsHandler)
// Define the go_run tool
goRunTool := mcp.NewTool("go_run",
mcp.WithDescription("Execute go run command with specified path and optional flags"),
mcp.WithString("path",
mcp.Required(),
mcp.Description("Path to Go file or package to run"),
),
mcp.WithString("flags",
mcp.Description("Optional flags for go run (space-separated)"),
),
mcp.WithNumber("timeout",
mcp.Description("Timeout in seconds (default: 30)"),
),
)
mcpServer.AddTool(goRunTool, goRunHandler)
// Define the go_test tool
goTestTool := mcp.NewTool("go_test",
mcp.WithDescription("Execute go test command with specified path and optional flags"),
mcp.WithString("path",
mcp.Required(),
mcp.Description("Path to Go package or directory to test"),
),
mcp.WithString("flags",
mcp.Description("Optional flags for go test (space-separated, e.g., '-v -cover -race')"),
),
mcp.WithNumber("timeout",
mcp.Description("Timeout in seconds (default: 60)"),
),
)
mcpServer.AddTool(goTestTool, goTestHandler)
// Start the server
if err := server.ServeStdio(mcpServer); err != nil {
fmt.Fprintf(os.Stderr, "Server error: %v\n", err)
@@ -1066,5 +1197,237 @@ func searchReplaceHandler(ctx context.Context, request mcp.CallToolRequest) (*mc
return mcp.NewToolResultError(fmt.Sprintf("failed to marshal result: %v", err)), nil
}
return mcp.NewToolResultText(string(jsonData)), nil
}
func findMethodReceiversHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
dir := request.GetString("dir", "./")
analysis, err := findMethodReceivers(dir)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to analyze method receivers: %v", err)), nil
}
jsonData, err := json.Marshal(analysis)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to marshal analysis: %v", err)), nil
}
return mcp.NewToolResultText(string(jsonData)), nil
}
func analyzeGoroutinesHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
dir := request.GetString("dir", "./")
analysis, err := analyzeGoroutines(dir)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to analyze goroutines: %v", err)), nil
}
jsonData, err := json.Marshal(analysis)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to marshal analysis: %v", err)), nil
}
return mcp.NewToolResultText(string(jsonData)), nil
}
func findPanicRecoverHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
dir := request.GetString("dir", "./")
analysis, err := findPanicRecover(dir)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to find panic/recover: %v", err)), nil
}
jsonData, err := json.Marshal(analysis)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to marshal analysis: %v", err)), nil
}
return mcp.NewToolResultText(string(jsonData)), nil
}
func analyzeChannelsHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
dir := request.GetString("dir", "./")
analysis, err := analyzeChannels(dir)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to analyze channels: %v", err)), nil
}
jsonData, err := json.Marshal(analysis)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to marshal analysis: %v", err)), nil
}
return mcp.NewToolResultText(string(jsonData)), nil
}
func findTypeAssertionsHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
dir := request.GetString("dir", "./")
analysis, err := findTypeAssertions(dir)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to find type assertions: %v", err)), nil
}
jsonData, err := json.Marshal(analysis)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to marshal analysis: %v", err)), nil
}
return mcp.NewToolResultText(string(jsonData)), nil
}
func analyzeMemoryAllocationsHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
dir := request.GetString("dir", "./")
analysis, err := analyzeMemoryAllocations(dir)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to analyze memory allocations: %v", err)), nil
}
jsonData, err := json.Marshal(analysis)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to marshal analysis: %v", err)), nil
}
return mcp.NewToolResultText(string(jsonData)), nil
}
func findReflectionUsageHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
dir := request.GetString("dir", "./")
analysis, err := findReflectionUsage(dir)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to find reflection usage: %v", err)), nil
}
jsonData, err := json.Marshal(analysis)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to marshal analysis: %v", err)), nil
}
return mcp.NewToolResultText(string(jsonData)), nil
}
func findInitFunctionsHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
dir := request.GetString("dir", "./")
analysis, err := findInitFunctions(dir)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to find init functions: %v", err)), nil
}
jsonData, err := json.Marshal(analysis)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to marshal analysis: %v", err)), nil
}
return mcp.NewToolResultText(string(jsonData)), nil
}
func analyzeDeferPatternsHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
dir := request.GetString("dir", "./")
analysis, err := analyzeDeferPatterns(dir)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to analyze defer patterns: %v", err)), nil
}
jsonData, err := json.Marshal(analysis)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to marshal analysis: %v", err)), nil
}
return mcp.NewToolResultText(string(jsonData)), nil
}
func findEmptyBlocksHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
dir := request.GetString("dir", "./")
analysis, err := findEmptyBlocks(dir)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to find empty blocks: %v", err)), nil
}
jsonData, err := json.Marshal(analysis)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to marshal analysis: %v", err)), nil
}
return mcp.NewToolResultText(string(jsonData)), nil
}
func analyzeNamingConventionsHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
dir := request.GetString("dir", "./")
analysis, err := analyzeNamingConventions(dir)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to analyze naming conventions: %v", err)), nil
}
jsonData, err := json.Marshal(analysis)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to marshal analysis: %v", err)), nil
}
return mcp.NewToolResultText(string(jsonData)), nil
}
func goRunHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
path, err := request.RequireString("path")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
flagsStr := request.GetString("flags", "")
timeout := request.GetFloat("timeout", 30.0)
// Parse flags
var flags []string
if flagsStr != "" {
flags = strings.Fields(flagsStr)
}
result, err := goRun(path, flags, time.Duration(timeout)*time.Second)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to run go run: %v", err)), nil
}
jsonData, err := json.Marshal(result)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to marshal result: %v", err)), nil
}
return mcp.NewToolResultText(string(jsonData)), nil
}
func goTestHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
path, err := request.RequireString("path")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
flagsStr := request.GetString("flags", "")
timeout := request.GetFloat("timeout", 60.0)
// Parse flags
var flags []string
if flagsStr != "" {
flags = strings.Fields(flagsStr)
}
result, err := goTest(path, flags, time.Duration(timeout)*time.Second)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to run go test: %v", err)), nil
}
jsonData, err := json.Marshal(result)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to marshal result: %v", err)), nil
}
return mcp.NewToolResultText(string(jsonData)), nil
}