Initial Go MCP server implementation
This commit is contained in:
32
CONTEXT.md
Normal file
32
CONTEXT.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Context - gocp Project
|
||||||
|
|
||||||
|
## CRITICAL INSTRUCTIONS - MUST FOLLOW
|
||||||
|
|
||||||
|
1. **Simple commits**: One-line commit messages ONLY. NEVER add "Generated by Claude" footers, emojis, or any multi-line messages. Just describe what changed in one line.
|
||||||
|
|
||||||
|
2. **Minimal comments**: Only add comments when absolutely critical for disambiguation
|
||||||
|
|
||||||
|
3. **Never use `go build`**: Always use `go run` instead of `go build` for testing Go programs
|
||||||
|
|
||||||
|
4. **Never change directories**: Never change directories with `cd` - always use absolute paths instead
|
||||||
|
|
||||||
|
5. **Error handling**: Always propagate errors with proper messages, never silently handle errors
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
gocp is a Go MCP (Model Context Protocol) server that provides tools for building and executing Go code. It uses the go-mcp library to implement the MCP protocol.
|
||||||
|
|
||||||
|
## Key Files
|
||||||
|
- `main.go`: MCP server implementation with build_and_run_go tool
|
||||||
|
- `go.mod`: Module definition with go-mcp dependency
|
||||||
|
|
||||||
|
## Tool Details
|
||||||
|
- **build_and_run_go**: Executes Go code using `go run`
|
||||||
|
- Parameters:
|
||||||
|
- `code` (required): Go source code to execute
|
||||||
|
- `timeout` (optional): Timeout in seconds (default: 30)
|
||||||
|
- Returns JSON with:
|
||||||
|
- `stdout`: Standard output
|
||||||
|
- `stderr`: Standard error
|
||||||
|
- `exit_code`: Process exit code
|
||||||
|
- `error`: Error message if any
|
||||||
|
- Creates temporary directories with `gocp-*` prefix
|
||||||
5
go.mod
Normal file
5
go.mod
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module github.com/flamingcow/gocp
|
||||||
|
|
||||||
|
go 1.21
|
||||||
|
|
||||||
|
require github.com/mark3labs/mcp-go v0.1.0
|
||||||
140
main.go
Normal file
140
main.go
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
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() {
|
||||||
|
// Create MCP server
|
||||||
|
s := server.NewMCPServer(
|
||||||
|
"go-executor",
|
||||||
|
"1.0.0",
|
||||||
|
server.WithToolCapabilities(true),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Define the build_and_run_go tool
|
||||||
|
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)")),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Add tool handler
|
||||||
|
s.AddTool(buildAndRunTool, buildAndRunHandler)
|
||||||
|
|
||||||
|
// Start the server
|
||||||
|
if err := s.Serve(); 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")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract timeout parameter (optional)
|
||||||
|
timeout := 30.0
|
||||||
|
if t, ok := args["timeout"].(float64); ok {
|
||||||
|
timeout = t
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build and run the code
|
||||||
|
stdout, stderr, exitCode, err := 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to JSON
|
||||||
|
jsonData, err := json.Marshal(result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to marshal result: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mcp.NewCallToolResult(
|
||||||
|
mcp.NewTextContent(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
|
||||||
|
} 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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user