Add Go AST analysis tools and refactor to use common walk code
This commit is contained in:
190
main.go
190
main.go
@@ -22,119 +22,227 @@ type RunResult struct {
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Create MCP server
|
||||
s := server.NewMCPServer(
|
||||
"go-executor",
|
||||
mcpServer := server.NewMCPServer(
|
||||
"gocp",
|
||||
"1.0.0",
|
||||
server.WithToolCapabilities(true),
|
||||
server.WithToolCapabilities(false),
|
||||
)
|
||||
|
||||
// Define the build_and_run_go tool
|
||||
buildAndRunTool := mcp.NewTool(
|
||||
"build_and_run_go",
|
||||
buildAndRunTool := mcp.NewTool("build_and_run_go",
|
||||
mcp.WithDescription("Build and execute Go code"),
|
||||
mcp.WithString("code", mcp.Required(), mcp.Description("The Go source code to build and run")),
|
||||
mcp.WithNumber("timeout", mcp.Description("Timeout in seconds (default: 30)")),
|
||||
mcp.WithString("code",
|
||||
mcp.Required(),
|
||||
mcp.Description("The Go source code to build and run"),
|
||||
),
|
||||
mcp.WithNumber("timeout",
|
||||
mcp.Description("Timeout in seconds (default: 30)"),
|
||||
),
|
||||
)
|
||||
mcpServer.AddTool(buildAndRunTool, buildAndRunHandler)
|
||||
|
||||
// Add tool handler
|
||||
s.AddTool(buildAndRunTool, buildAndRunHandler)
|
||||
// Define the find_symbols tool
|
||||
findSymbolsTool := mcp.NewTool("find_symbols",
|
||||
mcp.WithDescription("Find all functions, types, interfaces, constants, and variables by name/pattern"),
|
||||
mcp.WithString("dir",
|
||||
mcp.Description("Directory to search (default: current directory)"),
|
||||
),
|
||||
mcp.WithString("pattern",
|
||||
mcp.Description("Symbol name pattern to search for (case-insensitive substring match)"),
|
||||
),
|
||||
)
|
||||
mcpServer.AddTool(findSymbolsTool, findSymbolsHandler)
|
||||
|
||||
// Define the get_type_info tool
|
||||
getTypeInfoTool := mcp.NewTool("get_type_info",
|
||||
mcp.WithDescription("Get detailed information about a type including fields, methods, and embedded types"),
|
||||
mcp.WithString("dir",
|
||||
mcp.Description("Directory to search (default: current directory)"),
|
||||
),
|
||||
mcp.WithString("type",
|
||||
mcp.Required(),
|
||||
mcp.Description("Type name to get information for"),
|
||||
),
|
||||
)
|
||||
mcpServer.AddTool(getTypeInfoTool, getTypeInfoHandler)
|
||||
|
||||
// Define the find_references tool
|
||||
findReferencesTool := mcp.NewTool("find_references",
|
||||
mcp.WithDescription("Find all references to a symbol (function calls, type usage, etc.)"),
|
||||
mcp.WithString("dir",
|
||||
mcp.Description("Directory to search (default: current directory)"),
|
||||
),
|
||||
mcp.WithString("symbol",
|
||||
mcp.Required(),
|
||||
mcp.Description("Symbol name to find references for"),
|
||||
),
|
||||
)
|
||||
mcpServer.AddTool(findReferencesTool, findReferencesHandler)
|
||||
|
||||
// Define the list_packages tool
|
||||
listPackagesTool := mcp.NewTool("list_packages",
|
||||
mcp.WithDescription("List all Go packages in directory tree"),
|
||||
mcp.WithString("dir",
|
||||
mcp.Description("Directory to search (default: current directory)"),
|
||||
),
|
||||
mcp.WithBoolean("include_tests",
|
||||
mcp.Description("Include test files in package listings (default: false)"),
|
||||
),
|
||||
)
|
||||
mcpServer.AddTool(listPackagesTool, listPackagesHandler)
|
||||
|
||||
// Start the server
|
||||
if err := s.Serve(); err != nil {
|
||||
if err := server.ServeStdio(mcpServer); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Server error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func buildAndRunHandler(ctx context.Context, args map[string]interface{}) (*mcp.CallToolResult, error) {
|
||||
// Extract code parameter
|
||||
code, ok := args["code"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("code parameter is required and must be a string")
|
||||
func buildAndRunHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
code, err := request.RequireString("code")
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(err.Error()), nil
|
||||
}
|
||||
|
||||
// Extract timeout parameter (optional)
|
||||
timeout := 30.0
|
||||
if t, ok := args["timeout"].(float64); ok {
|
||||
timeout = t
|
||||
}
|
||||
timeout := request.GetFloat("timeout", 30.0)
|
||||
|
||||
// Build and run the code
|
||||
stdout, stderr, exitCode, err := buildAndRunGo(code, time.Duration(timeout)*time.Second)
|
||||
stdout, stderr, exitCode, runErr := buildAndRunGo(code, time.Duration(timeout)*time.Second)
|
||||
|
||||
// Create structured result
|
||||
result := RunResult{
|
||||
Stdout: stdout,
|
||||
Stderr: stderr,
|
||||
ExitCode: exitCode,
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
result.Error = err.Error()
|
||||
if runErr != nil {
|
||||
result.Error = runErr.Error()
|
||||
}
|
||||
|
||||
// Convert to JSON
|
||||
jsonData, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal result: %w", err)
|
||||
return mcp.NewToolResultError(fmt.Sprintf("failed to marshal result: %v", err)), nil
|
||||
}
|
||||
|
||||
return mcp.NewCallToolResult(
|
||||
mcp.NewTextContent(string(jsonData)),
|
||||
), nil
|
||||
return mcp.NewToolResultText(string(jsonData)), nil
|
||||
}
|
||||
|
||||
func buildAndRunGo(code string, timeout time.Duration) (stdout, stderr string, exitCode int, err error) {
|
||||
// Create temporary directory
|
||||
tmpDir, err := os.MkdirTemp("", "gocp-*")
|
||||
if err != nil {
|
||||
return "", "", -1, fmt.Errorf("failed to create temp dir: %w", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
// Write code to temporary file
|
||||
tmpFile := filepath.Join(tmpDir, "main.go")
|
||||
if err := os.WriteFile(tmpFile, []byte(code), 0644); err != nil {
|
||||
return "", "", -1, fmt.Errorf("failed to write code: %w", err)
|
||||
}
|
||||
|
||||
// Create context with timeout
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
// Initialize go.mod in temp directory
|
||||
modCmd := exec.CommandContext(ctx, "go", "mod", "init", "temp")
|
||||
modCmd.Dir = tmpDir
|
||||
if err := modCmd.Run(); err != nil {
|
||||
return "", "", -1, fmt.Errorf("failed to initialize go.mod: %w", err)
|
||||
}
|
||||
|
||||
// Run the code directly with go run
|
||||
runCmd := exec.CommandContext(ctx, "go", "run", tmpFile)
|
||||
runCmd.Dir = tmpDir
|
||||
|
||||
// Capture stdout and stderr separately
|
||||
var stdoutBuf, stderrBuf bytes.Buffer
|
||||
runCmd.Stdout = &stdoutBuf
|
||||
runCmd.Stderr = &stderrBuf
|
||||
|
||||
// Run the command
|
||||
err = runCmd.Run()
|
||||
|
||||
// Get exit code
|
||||
exitCode = 0
|
||||
if err != nil {
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
exitCode = exitErr.ExitCode()
|
||||
err = nil // Clear error since we got the exit code
|
||||
err = nil
|
||||
} else if ctx.Err() == context.DeadlineExceeded {
|
||||
return stdoutBuf.String(), stderrBuf.String(), -1, fmt.Errorf("execution timeout exceeded")
|
||||
} else {
|
||||
// Some other error occurred
|
||||
return stdoutBuf.String(), stderrBuf.String(), -1, err
|
||||
}
|
||||
}
|
||||
|
||||
return stdoutBuf.String(), stderrBuf.String(), exitCode, nil
|
||||
}
|
||||
|
||||
func findSymbolsHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
dir := request.GetString("dir", "./")
|
||||
pattern := request.GetString("pattern", "")
|
||||
|
||||
symbols, err := findSymbols(dir, pattern)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("failed to find symbols: %v", err)), nil
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(symbols)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("failed to marshal symbols: %v", err)), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(string(jsonData)), nil
|
||||
}
|
||||
|
||||
func getTypeInfoHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
dir := request.GetString("dir", "./")
|
||||
|
||||
typeName, err := request.RequireString("type")
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(err.Error()), nil
|
||||
}
|
||||
|
||||
info, err := getTypeInfo(dir, typeName)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("failed to get type info: %v", err)), nil
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(info)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("failed to marshal type info: %v", err)), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(string(jsonData)), nil
|
||||
}
|
||||
|
||||
func findReferencesHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
dir := request.GetString("dir", "./")
|
||||
|
||||
symbol, err := request.RequireString("symbol")
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(err.Error()), nil
|
||||
}
|
||||
|
||||
refs, err := findReferences(dir, symbol)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("failed to find references: %v", err)), nil
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(refs)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("failed to marshal references: %v", err)), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(string(jsonData)), nil
|
||||
}
|
||||
|
||||
func listPackagesHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
dir := request.GetString("dir", "./")
|
||||
includeTests := request.GetBool("include_tests", false)
|
||||
|
||||
packages, err := listPackages(dir, includeTests)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("failed to list packages: %v", err)), nil
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(packages)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("failed to marshal packages: %v", err)), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(string(jsonData)), nil
|
||||
}
|
||||
Reference in New Issue
Block a user