Files
gocp/main.go

248 lines
7.1 KiB
Go
Raw Normal View History

2025-06-27 20:03:53 -07:00
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"time"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
type RunResult struct {
Stdout string `json:"stdout"`
Stderr string `json:"stderr"`
ExitCode int `json:"exit_code"`
Error string `json:"error,omitempty"`
}
func main() {
mcpServer := server.NewMCPServer(
"gocp",
2025-06-27 20:03:53 -07:00
"1.0.0",
server.WithToolCapabilities(false),
2025-06-27 20:03:53 -07:00
)
// Define the build_and_run_go tool
buildAndRunTool := mcp.NewTool("build_and_run_go",
2025-06-27 20:03:53 -07:00
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)"),
),
2025-06-27 20:03:53 -07:00
)
mcpServer.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)
2025-06-27 20:03:53 -07:00
// Start the server
if err := server.ServeStdio(mcpServer); err != nil {
2025-06-27 20:03:53 -07:00
fmt.Fprintf(os.Stderr, "Server error: %v\n", err)
os.Exit(1)
}
}
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
2025-06-27 20:03:53 -07:00
}
timeout := request.GetFloat("timeout", 30.0)
2025-06-27 20:03:53 -07:00
stdout, stderr, exitCode, runErr := buildAndRunGo(code, time.Duration(timeout)*time.Second)
2025-06-27 20:03:53 -07:00
result := RunResult{
Stdout: stdout,
Stderr: stderr,
ExitCode: exitCode,
}
if runErr != nil {
result.Error = runErr.Error()
2025-06-27 20:03:53 -07:00
}
jsonData, err := json.Marshal(result)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to marshal result: %v", err)), nil
2025-06-27 20:03:53 -07:00
}
return mcp.NewToolResultText(string(jsonData)), nil
2025-06-27 20:03:53 -07:00
}
func buildAndRunGo(code string, timeout time.Duration) (stdout, stderr string, exitCode int, err error) {
tmpDir, err := os.MkdirTemp("", "gocp-*")
if err != nil {
return "", "", -1, fmt.Errorf("failed to create temp dir: %w", err)
}
defer os.RemoveAll(tmpDir)
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)
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
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)
}
runCmd := exec.CommandContext(ctx, "go", "run", tmpFile)
runCmd.Dir = tmpDir
var stdoutBuf, stderrBuf bytes.Buffer
runCmd.Stdout = &stdoutBuf
runCmd.Stderr = &stderrBuf
err = runCmd.Run()
exitCode = 0
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
exitCode = exitErr.ExitCode()
err = nil
2025-06-27 20:03:53 -07:00
} else if ctx.Err() == context.DeadlineExceeded {
return stdoutBuf.String(), stderrBuf.String(), -1, fmt.Errorf("execution timeout exceeded")
} else {
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
2025-06-27 20:03:53 -07:00
}