#
tokens: 45046/50000 20/20 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── cmd
│   └── mcp-go-debugger
│       └── main.go
├── docs
│   ├── Implementation-Guide.md
│   ├── Phase1-Tasks.md
│   ├── PRD.md
│   └── Response-Improvements.md
├── go.mod
├── go.sum
├── LICENSE
├── Makefile
├── pkg
│   ├── debugger
│   │   ├── breakpoints.go
│   │   ├── client.go
│   │   ├── execution.go
│   │   ├── helpers.go
│   │   ├── output.go
│   │   ├── program.go
│   │   └── variables.go
│   ├── logger
│   │   └── logger.go
│   ├── mcp
│   │   ├── server_test.go
│   │   └── server.go
│   └── types
│       └── debug_types.go
├── README.md
└── testdata
    └── calculator
        ├── calculator_test.go
        └── calculator.go
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
1 | bin/
2 | *.log
3 | testdata/sample/sample
4 | .idea
5 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # MCP Go Debugger
  2 | 
  3 | A debugger interface for Go programs that integrates with MCP (Model Context Protocol).
  4 | 
  5 | ## Features
  6 | 
  7 | - Launch and debug Go applications
  8 | - Attach to existing Go processes
  9 | - Set breakpoints
 10 | - Step through code (step into, step over, step out)
 11 | - Eval variables
 12 | - View stack traces
 13 | - List all variables in current scope
 14 | - Get current execution position
 15 | - Debug individual test functions with `debug_test`
 16 | - Native integration with Delve debugger API types
 17 | - Capture and display program output during debugging
 18 | - Support for custom test flags when debugging tests
 19 | - Detailed variable inspection with configurable depth
 20 | 
 21 | ## Installation
 22 | 
 23 | ### Prerequisites
 24 | 
 25 | - Go 1.20 or higher
 26 | 
 27 | ### Using Go
 28 | 
 29 | The easiest way to install the MCP Go Debugger is with Go:
 30 | 
 31 | ```bash
 32 | go install github.com/sunfmin/mcp-go-debugger/cmd/mcp-go-debugger@latest
 33 | ```
 34 | 
 35 | This will download, compile, and install the binary to your `$GOPATH/bin` directory.
 36 | 
 37 | ### From Source
 38 | 
 39 | Alternatively, you can build from source:
 40 | 
 41 | ```bash
 42 | git clone https://github.com/sunfmin/mcp-go-debugger.git
 43 | cd mcp-go-debugger
 44 | make install
 45 | ```
 46 | 
 47 | ## Configuration
 48 | 
 49 | ### Cursor
 50 | 
 51 | Add the following to your Cursor configuration (`~/.cursor/mcp.json`):
 52 | 
 53 | ```json
 54 | {
 55 |   "mcpServers": {
 56 |     "go-debugger": {
 57 |       "command": "mcp-go-debugger",
 58 |       "args": []
 59 |     }
 60 |   }
 61 | }
 62 | ```
 63 | 
 64 | ### Claude Desktop
 65 | 
 66 | Add the MCP to Claude Desktop:
 67 | 
 68 | ```bash
 69 | claude mcp add go-debugger mcp-go-debugger
 70 | ```
 71 | 
 72 | Verify the connection:
 73 | ```
 74 | /mcp
 75 | ```
 76 | 
 77 | ## Usage
 78 | 
 79 | This debugger is designed to be integrated with MCP-compatible clients. The tools provided include:
 80 | 
 81 | - `ping` - Test connection to the debugger
 82 | - `status` - Check debugger status and server uptime
 83 | - `launch` - Launch a Go program with debugging
 84 | - `attach` - Attach to a running Go process
 85 | - `debug` - Debug a Go source file directly
 86 | - `debug_test` - Debug a specific Go test function
 87 | - `set_breakpoint` - Set a breakpoint at a specific file and line
 88 | - `list_breakpoints` - List all current breakpoints
 89 | - `remove_breakpoint` - Remove a breakpoint
 90 | - `continue` - Continue execution until next breakpoint or program end
 91 | - `step` - Step into the next function call
 92 | - `step_over` - Step over the next function call
 93 | - `step_out` - Step out of the current function
 94 | - `eval_variable` - Eval a variable's value with configurable depth
 95 | - `list_scope_variables` - List all variables in current scope (local, args, package)
 96 | - `get_execution_position` - Get current execution position (file, line, function)
 97 | - `get_debugger_output` - Retrieve captured stdout and stderr from the debugged program
 98 | - `close` - Close the current debugging session
 99 | 
100 | ### Basic Usage Examples
101 | 
102 | #### Debugging a Go Program
103 | 
104 | Ask the AI assistant to debug your Go program:
105 | ```
106 | > Please debug my Go application main.go
107 | ```
108 | 
109 | The AI assistant will use the MCP to:
110 | - Launch the program with debugging enabled
111 | - Set breakpoints as needed
112 | - Eval variables and program state
113 | - Help diagnose issues
114 | 
115 | #### Attaching to a Running Process
116 | 
117 | If your Go application is already running, you can attach the debugger:
118 | 
119 | ```
120 | > Attach to my Go application running with PID 12345
121 | ```
122 | 
123 | ### Finding a Bug Example
124 | 
125 | ```
126 | > I'm getting a panic in my processOrders function when handling large orders. 
127 | > Can you help me debug it?
128 | ```
129 | 
130 | The AI will help you:
131 | 1. Set breakpoints in the relevant function
132 | 2. Eval variables as execution proceeds
133 | 3. Identify the root cause of the issue
134 | 
135 | #### Debugging a Single Test
136 | 
137 | If you want to debug a specific test function instead of an entire application:
138 | 
139 | ```
140 | > Please debug the TestCalculateTotal function in my calculator_test.go file
141 | ```
142 | 
143 | The AI assistant will use the `debug_test` tool to:
144 | - Launch only the specific test with debugging enabled
145 | - Set breakpoints at key points in the test
146 | - Help you inspect variables as the test executes
147 | - Step through the test execution to identify issues
148 | - Eval assertion failures or unexpected behavior
149 | 
150 | You can also specify test flags:
151 | 
152 | ```
153 | > Debug the TestUserAuthentication test in auth_test.go with a timeout of 30 seconds
154 | ```
155 | 
156 | This is especially useful for:
157 | - Tests that are failing inconsistently
158 | - Complex tests with multiple assertions
159 | - Tests involving concurrency or timing issues
160 | - Understanding how a test interacts with your code
161 | 
162 | #### Inspecting Complex Data Structures
163 | 
164 | When working with nested structures or complex types:
165 | 
166 | ```
167 | > Can you eval the user.Profile.Preferences object at line 45? I need to see all nested fields in detail.
168 | ```
169 | 
170 | The AI will:
171 | - Set a breakpoint at the specified location
172 | - Use the `eval_variable` tool with appropriate depth parameters
173 | - Format the structure for easier understanding
174 | - Help navigate through nested fields
175 | 
176 | #### Debugging with Command Line Arguments
177 | 
178 | To debug a program that requires command-line arguments:
179 | 
180 | ```
181 | > Debug my data_processor.go with the arguments "--input=data.json --verbose --max-records=1000"
182 | ```
183 | 
184 | The debugger will:
185 | - Launch the program with the specified arguments
186 | - Allow you to set breakpoints and eval how the arguments affect execution
187 | - Help track how argument values flow through your program
188 | 
189 | #### Working with Goroutines
190 | 
191 | For debugging concurrent Go programs:
192 | 
193 | ```
194 | > I think I have a race condition in my worker pool implementation. Can you help me debug it?
195 | ```
196 | 
197 | The AI can:
198 | - Set strategic breakpoints around goroutine creation and synchronization points
199 | - Help eval channel states and mutex locks
200 | - Track goroutine execution to identify race conditions
201 | - Suggest solutions for concurrency issues
202 | 
203 | #### Stepping Through Function Calls
204 | 
205 | For understanding complex control flow:
206 | 
207 | ```
208 | > Walk me through what happens when the processPayment function is called with an invalid credit card
209 | ```
210 | 
211 | The AI assistant will:
212 | - Set breakpoints at the entry point
213 | - Use step-in/step-over commands strategically
214 | - Show you the execution path
215 | - Eval variables at key decision points
216 | - Explain the program flow
217 | 
218 | 
219 | ## License
220 | 
221 | [MIT License](LICENSE) 
```

--------------------------------------------------------------------------------
/testdata/calculator/calculator.go:
--------------------------------------------------------------------------------

```go
 1 | // Package calculator provides basic arithmetic operations.
 2 | package calculator
 3 | 
 4 | // Add returns the sum of two integers.
 5 | func Add(a, b int) int {
 6 | 	return a + b
 7 | }
 8 | 
 9 | // Subtract returns the difference between two integers.
10 | func Subtract(a, b int) int {
11 | 	return a - b
12 | }
13 | 
14 | // Multiply returns the product of two integers.
15 | func Multiply(a, b int) int {
16 | 	return a * b
17 | }
18 | 
19 | // Divide returns the quotient of two integers.
20 | // If b is 0, it returns 0 and doesn't panic.
21 | func Divide(a, b int) int {
22 | 	if b == 0 {
23 | 		return 0 // Avoid division by zero
24 | 	}
25 | 	return a / b
26 | } 
```

--------------------------------------------------------------------------------
/cmd/mcp-go-debugger/main.go:
--------------------------------------------------------------------------------

```go
 1 | package main
 2 | 
 3 | import (
 4 | 	"os"
 5 | 
 6 | 	"github.com/mark3labs/mcp-go/server"
 7 | 	"github.com/sunfmin/mcp-go-debugger/pkg/logger"
 8 | 	"github.com/sunfmin/mcp-go-debugger/pkg/mcp"
 9 | )
10 | 
11 | // Version is set during build
12 | var Version = "dev"
13 | 
14 | func main() {
15 | 	// Configure additional file logging if needed
16 | 	logFile, err := os.OpenFile("mcp-go-debugger.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
17 | 	if err == nil {
18 | 		defer logFile.Close()
19 | 		// We're using the logger package which already sets up logging
20 | 		// This file is just for additional logging if needed
21 | 	} else {
22 | 		logger.Warn("Failed to set up log file", "error", err)
23 | 	}
24 | 
25 | 	logger.Info("Starting MCP Go Debugger", "version", Version)
26 | 
27 | 	// Create MCP debug server
28 | 	debugServer := mcp.NewMCPDebugServer(Version)
29 | 
30 | 	// Start the stdio server
31 | 	logger.Info("Starting MCP server...")
32 | 	if err := server.ServeStdio(debugServer.Server()); err != nil {
33 | 		logger.Error("Server error", "error", err)
34 | 		os.Exit(1)
35 | 	}
36 | } 
```

--------------------------------------------------------------------------------
/testdata/calculator/calculator_test.go:
--------------------------------------------------------------------------------

```go
 1 | package calculator
 2 | 
 3 | import (
 4 | 	"testing"
 5 | )
 6 | 
 7 | // TestAdd tests the Add function.
 8 | func TestAdd(t *testing.T) {
 9 | 	// A simple test case that can be debugged
10 | 	result := Add(2, 3)
11 | 	if result != 5 {
12 | 		t.Errorf("Add(2, 3) = %d; want 5", result)
13 | 	}
14 | 	
15 | 	// Additional tests
16 | 	cases := []struct {
17 | 		a, b, want int
18 | 	}{
19 | 		{0, 0, 0},
20 | 		{-1, 1, 0},
21 | 		{10, 5, 15},
22 | 	}
23 | 	
24 | 	for _, tc := range cases {
25 | 		got := Add(tc.a, tc.b)
26 | 		if got != tc.want {
27 | 			t.Errorf("Add(%d, %d) = %d; want %d", tc.a, tc.b, got, tc.want)
28 | 		}
29 | 	}
30 | }
31 | 
32 | // TestSubtract tests the Subtract function.
33 | func TestSubtract(t *testing.T) {
34 | 	result := Subtract(5, 3)
35 | 	if result != 2 {
36 | 		t.Errorf("Subtract(5, 3) = %d; want 2", result)
37 | 	}
38 | 	
39 | 	// This assertion will fail deliberately
40 | 	// Useful for testing debug capabilities
41 | 	if Subtract(5, 5) != 1 { // Deliberately wrong (should be 0)
42 | 		// This comment is here to help debug - the expected value should be 0, not 1
43 | 		t.Logf("This is a deliberate failure to test debugging")
44 | 	}
45 | }
46 | 
47 | // TestMultiply tests the Multiply function.
48 | func TestMultiply(t *testing.T) {
49 | 	cases := []struct {
50 | 		a, b, want int
51 | 	}{
52 | 		{0, 5, 0},
53 | 		{1, 1, 1},
54 | 		{2, 3, 6},
55 | 		{-2, 3, -6},
56 | 	}
57 | 	
58 | 	for _, tc := range cases {
59 | 		got := Multiply(tc.a, tc.b)
60 | 		if got != tc.want {
61 | 			t.Errorf("Multiply(%d, %d) = %d; want %d", tc.a, tc.b, got, tc.want)
62 | 		}
63 | 	}
64 | }
65 | 
66 | // TestDivide tests the Divide function.
67 | func TestDivide(t *testing.T) {
68 | 	// Test normal division
69 | 	result := Divide(10, 2)
70 | 	if result != 5 {
71 | 		t.Errorf("Divide(10, 2) = %d; want 5", result)
72 | 	}
73 | 	
74 | 	// Test division by zero
75 | 	result = Divide(10, 0)
76 | 	if result != 0 {
77 | 		t.Errorf("Divide(10, 0) = %d; want 0", result)
78 | 	}
79 | 	
80 | 	// Logs for debugging
81 | 	t.Logf("Division by zero handled correctly, returned %d", result)
82 | } 
```

--------------------------------------------------------------------------------
/pkg/logger/logger.go:
--------------------------------------------------------------------------------

```go
 1 | // Package logger provides a logging utility based on log/slog
 2 | //
 3 | // DEBUG logging can be enabled by setting the MCP_DEBUG environment variable:
 4 | //   export MCP_DEBUG=1
 5 | //
 6 | // By default, debug logging is disabled to reduce noise in normal operation.
 7 | package logger
 8 | 
 9 | import (
10 | 	"fmt"
11 | 	"log/slog"
12 | 	"os"
13 | 	"strings"
14 | )
15 | 
16 | var (
17 | 	// Logger is the global logger instance
18 | 	Logger *slog.Logger
19 | )
20 | 
21 | func init() {
22 | 	// Set up the logger based on environment variable
23 | 	debugEnabled := false
24 | 	debugEnv := os.Getenv("MCP_DEBUG")
25 | 	
26 | 	if debugEnv != "" && strings.ToLower(debugEnv) != "false" && debugEnv != "0" {
27 | 		debugEnabled = true
28 | 	}
29 | 
30 | 	// Create handler with appropriate level
31 | 	logLevel := slog.LevelInfo
32 | 	if debugEnabled {
33 | 		logLevel = slog.LevelDebug
34 | 	}
35 | 
36 | 	// Configure the global logger
37 | 	opts := &slog.HandlerOptions{
38 | 		Level: logLevel,
39 | 	}
40 | 	
41 | 	// You could customize the output format here if needed
42 | 	handler := slog.NewTextHandler(os.Stderr, opts)
43 | 	Logger = slog.New(handler)
44 | 
45 | 	// Replace the default slog logger too
46 | 	slog.SetDefault(Logger)
47 | }
48 | 
49 | // Debug logs a debug message if debug logging is enabled
50 | func Debug(msg string, args ...any) {
51 | 	Logger.Debug(msg, args...)
52 | }
53 | 
54 | // Info logs an info message
55 | func Info(msg string, args ...any) {
56 | 	Logger.Info(msg, args...)
57 | }
58 | 
59 | // Warn logs a warning message
60 | func Warn(msg string, args ...any) {
61 | 	Logger.Warn(msg, args...)
62 | }
63 | 
64 | // Error logs an error message
65 | func Error(msg string, args ...any) {
66 | 	Logger.Error(msg, args...)
67 | }
68 | 
69 | // Printf logs a formatted message at INFO level
70 | // This is for compatibility with the old log.Printf calls
71 | func Printf(format string, args ...any) {
72 | 	message := fmt.Sprintf(format, args...)
73 | 	if strings.HasPrefix(message, "DEBUG:") {
74 | 		Debug(strings.TrimPrefix(message, "DEBUG:"))
75 | 	} else if strings.HasPrefix(message, "Warning:") {
76 | 		Warn(strings.TrimPrefix(message, "Warning:"))
77 | 	} else if strings.HasPrefix(message, "Error:") {
78 | 		Error(strings.TrimPrefix(message, "Error:"))
79 | 	} else {
80 | 		Info(message)
81 | 	}
82 | }
83 | 
84 | // Println logs a message at INFO level
85 | // This is for compatibility with the old log.Println calls
86 | func Println(args ...any) {
87 | 	message := fmt.Sprintln(args...)
88 | 	message = strings.TrimSuffix(message, "\n") // Remove trailing newline
89 | 	
90 | 	if strings.HasPrefix(message, "DEBUG:") {
91 | 		Debug(strings.TrimPrefix(message, "DEBUG:"))
92 | 	} else if strings.HasPrefix(message, "Warning:") {
93 | 		Warn(strings.TrimPrefix(message, "Warning:"))
94 | 	} else if strings.HasPrefix(message, "Error:") {
95 | 		Error(strings.TrimPrefix(message, "Error:"))
96 | 	} else {
97 | 		Info(message)
98 | 	}
99 | } 
```

--------------------------------------------------------------------------------
/pkg/debugger/output.go:
--------------------------------------------------------------------------------

```go
  1 | package debugger
  2 | 
  3 | import (
  4 | 	"fmt"
  5 | 	"strings"
  6 | 	"time"
  7 | 
  8 | 	"github.com/sunfmin/mcp-go-debugger/pkg/types"
  9 | )
 10 | 
 11 | // OutputMessage represents a captured output message
 12 | type OutputMessage struct {
 13 | 	Source    string    `json:"source"` // "stdout" or "stderr"
 14 | 	Content   string    `json:"content"`
 15 | 	Timestamp time.Time `json:"timestamp"`
 16 | }
 17 | 
 18 | // GetDebuggerOutput returns the captured stdout and stderr from the debugged program
 19 | func (c *Client) GetDebuggerOutput() types.DebuggerOutputResponse {
 20 | 	if c.client == nil {
 21 | 		return types.DebuggerOutputResponse{
 22 | 			Status: "error",
 23 | 			Context: types.DebugContext{
 24 | 				Timestamp:    time.Now(),
 25 | 				Operation:    "get_output",
 26 | 				ErrorMessage: "no active debug session",
 27 | 			},
 28 | 		}
 29 | 	}
 30 | 
 31 | 	// Get the captured output regardless of state
 32 | 	c.outputMutex.Lock()
 33 | 	stdout := c.stdout.String()
 34 | 	stderr := c.stderr.String()
 35 | 	c.outputMutex.Unlock()
 36 | 
 37 | 	// Create a summary of the output for LLM
 38 | 	outputSummary := generateOutputSummary(stdout, stderr)
 39 | 
 40 | 	// Try to get state, but don't fail if unable
 41 | 	state, err := c.client.GetState()
 42 | 	if err != nil {
 43 | 		// Process might have exited, but we still want to return the captured output
 44 | 		return types.DebuggerOutputResponse{
 45 | 			Status: "success",
 46 | 			Context: types.DebugContext{
 47 | 				Timestamp:    time.Now(),
 48 | 				Operation:    "get_output",
 49 | 				ErrorMessage: fmt.Sprintf("state unavailable: %v", err),
 50 | 			},
 51 | 			Stdout:        stdout,
 52 | 			Stderr:        stderr,
 53 | 			OutputSummary: outputSummary,
 54 | 		}
 55 | 	}
 56 | 
 57 | 	context := c.createDebugContext(state)
 58 | 	context.Operation = "get_output"
 59 | 
 60 | 	return types.DebuggerOutputResponse{
 61 | 		Status:        "success",
 62 | 		Context:       context,
 63 | 		Stdout:        stdout,
 64 | 		Stderr:        stderr,
 65 | 		OutputSummary: outputSummary,
 66 | 	}
 67 | }
 68 | 
 69 | // generateOutputSummary creates a concise summary of stdout and stderr for LLM use
 70 | func generateOutputSummary(stdout, stderr string) string {
 71 | 	// If no output, return a simple message
 72 | 	if stdout == "" && stderr == "" {
 73 | 		return "No program output captured"
 74 | 	}
 75 | 
 76 | 	var summary strings.Builder
 77 | 
 78 | 	// Add stdout summary
 79 | 	if stdout != "" {
 80 | 		lines := strings.Split(strings.TrimSpace(stdout), "\n")
 81 | 		if len(lines) <= 5 {
 82 | 			summary.WriteString(fmt.Sprintf("Program output (%d lines): %s", len(lines), stdout))
 83 | 		} else {
 84 | 			// Summarize with first 3 and last 2 lines
 85 | 			summary.WriteString(fmt.Sprintf("Program output (%d lines): \n", len(lines)))
 86 | 			for i := 0; i < 3 && i < len(lines); i++ {
 87 | 				summary.WriteString(fmt.Sprintf("  %s\n", lines[i]))
 88 | 			}
 89 | 			summary.WriteString("  ... more lines ...\n")
 90 | 			for i := len(lines) - 2; i < len(lines); i++ {
 91 | 				summary.WriteString(fmt.Sprintf("  %s\n", lines[i]))
 92 | 			}
 93 | 		}
 94 | 	}
 95 | 
 96 | 	// Add stderr if present
 97 | 	if stderr != "" {
 98 | 		if summary.Len() > 0 {
 99 | 			summary.WriteString("\n")
100 | 		}
101 | 		lines := strings.Split(strings.TrimSpace(stderr), "\n")
102 | 		if len(lines) <= 3 {
103 | 			summary.WriteString(fmt.Sprintf("Error output (%d lines): %s", len(lines), stderr))
104 | 		} else {
105 | 			summary.WriteString(fmt.Sprintf("Error output (%d lines): \n", len(lines)))
106 | 			for i := 0; i < 2 && i < len(lines); i++ {
107 | 				summary.WriteString(fmt.Sprintf("  %s\n", lines[i]))
108 | 			}
109 | 			summary.WriteString("  ... more lines ...\n")
110 | 			summary.WriteString(fmt.Sprintf("  %s\n", lines[len(lines)-1]))
111 | 		}
112 | 	}
113 | 
114 | 	return summary.String()
115 | }
116 | 
```

--------------------------------------------------------------------------------
/pkg/debugger/client.go:
--------------------------------------------------------------------------------

```go
  1 | // Package debugger provides an interface to the Delve debugger
  2 | package debugger
  3 | 
  4 | import (
  5 | 	"bufio"
  6 | 	"bytes"
  7 | 	"fmt"
  8 | 	"io"
  9 | 	"net"
 10 | 	"sync"
 11 | 	"time"
 12 | 
 13 | 	"github.com/go-delve/delve/service/api"
 14 | 	"github.com/go-delve/delve/service/rpc2"
 15 | 	"github.com/go-delve/delve/service/rpccommon"
 16 | 	"github.com/sunfmin/mcp-go-debugger/pkg/types"
 17 | )
 18 | 
 19 | // Client encapsulates the Delve debug client functionality
 20 | type Client struct {
 21 | 	client      *rpc2.RPCClient
 22 | 	target      string
 23 | 	pid         int
 24 | 	server      *rpccommon.ServerImpl
 25 | 	tempDir     string
 26 | 	stdout      bytes.Buffer       // Buffer for captured stdout
 27 | 	stderr      bytes.Buffer       // Buffer for captured stderr
 28 | 	outputChan  chan OutputMessage // Channel for captured output
 29 | 	stopOutput  chan struct{}      // Channel to signal stopping output capture
 30 | 	outputMutex sync.Mutex         // Mutex for synchronizing output buffer access
 31 | }
 32 | 
 33 | // NewClient creates a new Delve client wrapper
 34 | func NewClient() *Client {
 35 | 	return &Client{
 36 | 		outputChan: make(chan OutputMessage, 100), // Buffer for output messages
 37 | 		stopOutput: make(chan struct{}),
 38 | 	}
 39 | }
 40 | 
 41 | // GetTarget returns the target program being debugged
 42 | func (c *Client) GetTarget() string {
 43 | 	return c.target
 44 | }
 45 | 
 46 | // GetPid returns the PID of the process being debugged
 47 | func (c *Client) GetPid() int {
 48 | 	return c.pid
 49 | }
 50 | 
 51 | // Helper function to get an available port
 52 | func getFreePort() (int, error) {
 53 | 	addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
 54 | 	if err != nil {
 55 | 		return 0, err
 56 | 	}
 57 | 
 58 | 	l, err := net.ListenTCP("tcp", addr)
 59 | 	if err != nil {
 60 | 		return 0, err
 61 | 	}
 62 | 	defer l.Close()
 63 | 	return l.Addr().(*net.TCPAddr).Port, nil
 64 | }
 65 | 
 66 | // Helper function to wait for server to be available
 67 | func waitForServer(addr string) error {
 68 | 	timeout := time.Now().Add(5 * time.Second)
 69 | 	for time.Now().Before(timeout) {
 70 | 		conn, err := net.DialTimeout("tcp", addr, 100*time.Millisecond)
 71 | 		if err == nil {
 72 | 			conn.Close()
 73 | 			return nil
 74 | 		}
 75 | 		time.Sleep(100 * time.Millisecond)
 76 | 	}
 77 | 	return fmt.Errorf("timeout waiting for server")
 78 | }
 79 | 
 80 | // waitForStop polls the debugger until it reaches a stopped state or times out
 81 | func waitForStop(c *Client, timeout time.Duration) (*api.DebuggerState, error) {
 82 | 	deadline := time.Now().Add(timeout)
 83 | 	for time.Now().Before(deadline) {
 84 | 		state, err := c.client.GetState()
 85 | 		if err != nil {
 86 | 			return nil, fmt.Errorf("failed to get debugger state: %v", err)
 87 | 		}
 88 | 
 89 | 		// Check if the program has stopped
 90 | 		if !state.Running {
 91 | 			return state, nil
 92 | 		}
 93 | 
 94 | 		// Wait a bit before checking again
 95 | 		time.Sleep(100 * time.Millisecond)
 96 | 	}
 97 | 
 98 | 	return nil, fmt.Errorf("timeout waiting for program to stop")
 99 | }
100 | 
101 | // captureOutput reads from a reader and sends the output to the output channel and buffer
102 | func (c *Client) captureOutput(reader io.ReadCloser, source string) {
103 | 	defer reader.Close()
104 | 
105 | 	scanner := bufio.NewScanner(reader)
106 | 	for scanner.Scan() {
107 | 		line := scanner.Text()
108 | 
109 | 		// Write to appropriate buffer
110 | 		if source == "stdout" {
111 | 			c.stdout.WriteString(line + "\n")
112 | 		} else if source == "stderr" {
113 | 			c.stderr.WriteString(line + "\n")
114 | 		}
115 | 
116 | 		// Also send to channel for real-time monitoring
117 | 		select {
118 | 		case <-c.stopOutput:
119 | 			return
120 | 		case c.outputChan <- OutputMessage{
121 | 			Source:    source,
122 | 			Content:   line,
123 | 			Timestamp: time.Now(),
124 | 		}:
125 | 		}
126 | 	}
127 | }
128 | 
129 | // createDebugContext creates a debug context from a state
130 | func (c *Client) createDebugContext(state *api.DebuggerState) types.DebugContext {
131 | 	context := types.DebugContext{
132 | 		Timestamp: time.Now(),
133 | 		Operation: "",
134 | 	}
135 | 
136 | 	if state != nil {
137 | 		context.DelveState = state
138 | 
139 | 		// Add current position
140 | 		if state.CurrentThread != nil {
141 | 			loc := getCurrentLocation(state)
142 | 			context.CurrentLocation = loc
143 | 		}
144 | 
145 | 		// Add stop reason
146 | 		context.StopReason = getStateReason(state)
147 | 
148 | 		// Get local variables if we have a client
149 | 		if c != nil {
150 | 			context.LocalVariables, _ = c.getLocalVariables(state)
151 | 		}
152 | 	}
153 | 
154 | 	return context
155 | }
156 | 
```

--------------------------------------------------------------------------------
/pkg/debugger/helpers.go:
--------------------------------------------------------------------------------

```go
  1 | package debugger
  2 | 
  3 | import (
  4 | 	"fmt"
  5 | 	"github.com/go-delve/delve/service/api"
  6 | 	"strings"
  7 | )
  8 | 
  9 | // getFunctionName extracts a human-readable function name from various Delve types
 10 | func getFunctionName(thread *api.Thread) string {
 11 | 	if thread == nil || thread.Function == nil {
 12 | 		return "unknown"
 13 | 	}
 14 | 	return thread.Function.Name()
 15 | }
 16 | 
 17 | // getPackageName extracts the package name from a function name
 18 | func getPackageName(thread *api.Thread) string {
 19 | 	if thread == nil || thread.Function == nil {
 20 | 		return "unknown"
 21 | 	}
 22 | 	parts := strings.Split(thread.Function.Name(), ".")
 23 | 	if len(parts) > 1 {
 24 | 		return parts[0]
 25 | 	}
 26 | 	return "unknown"
 27 | }
 28 | 
 29 | // getFunctionNameFromBreakpoint extracts a human-readable function name from a breakpoint
 30 | func getFunctionNameFromBreakpoint(bp *api.Breakpoint) string {
 31 | 	if bp == nil || bp.FunctionName == "" {
 32 | 		return "unknown"
 33 | 	}
 34 | 	return bp.FunctionName
 35 | }
 36 | 
 37 | // getPackageNameFromBreakpoint extracts the package name from a breakpoint
 38 | func getPackageNameFromBreakpoint(bp *api.Breakpoint) string {
 39 | 	if bp == nil || bp.FunctionName == "" {
 40 | 		return "unknown"
 41 | 	}
 42 | 	parts := strings.Split(bp.FunctionName, ".")
 43 | 	if len(parts) > 1 {
 44 | 		return parts[0]
 45 | 	}
 46 | 	return "unknown"
 47 | }
 48 | 
 49 | // getThreadStatus returns a human-readable thread status
 50 | func getThreadStatus(thread *api.Thread) string {
 51 | 	if thread == nil {
 52 | 		return "unknown"
 53 | 	}
 54 | 	if thread.Breakpoint != nil {
 55 | 		return "at breakpoint"
 56 | 	}
 57 | 	return "running"
 58 | }
 59 | 
 60 | // getGoroutineStatus returns a human-readable status for a goroutine
 61 | func getGoroutineStatus(g *api.Goroutine) string {
 62 | 	if g == nil {
 63 | 		return "unknown"
 64 | 	}
 65 | 	switch g.Status {
 66 | 	case 0:
 67 | 		return "running"
 68 | 	case 1:
 69 | 		return "sleeping"
 70 | 	case 2:
 71 | 		return "blocked"
 72 | 	case 3:
 73 | 		return "waiting"
 74 | 	case 4:
 75 | 		return "dead"
 76 | 	default:
 77 | 		return fmt.Sprintf("unknown status %d", g.Status)
 78 | 	}
 79 | }
 80 | 
 81 | // getWaitReason returns a human-readable wait reason for a goroutine
 82 | func getWaitReason(g *api.Goroutine) string {
 83 | 	if g == nil || g.WaitReason == 0 {
 84 | 		return ""
 85 | 	}
 86 | 
 87 | 	// Based on runtime/runtime2.go waitReasons
 88 | 	switch g.WaitReason {
 89 | 	case 1:
 90 | 		return "waiting for GC cycle"
 91 | 	case 2:
 92 | 		return "waiting for GC (write barrier)"
 93 | 	case 3:
 94 | 		return "waiting for GC (mark assist)"
 95 | 	case 4:
 96 | 		return "waiting for finalizer"
 97 | 	case 5:
 98 | 		return "waiting for channel operation"
 99 | 	case 6:
100 | 		return "waiting for select operation"
101 | 	case 7:
102 | 		return "waiting for mutex/rwmutex"
103 | 	case 8:
104 | 		return "waiting for concurrent map operation"
105 | 	case 9:
106 | 		return "waiting for garbage collection scan"
107 | 	case 10:
108 | 		return "waiting for channel receive"
109 | 	case 11:
110 | 		return "waiting for channel send"
111 | 	case 12:
112 | 		return "waiting for semaphore"
113 | 	case 13:
114 | 		return "waiting for sleep"
115 | 	case 14:
116 | 		return "waiting for timer"
117 | 	case 15:
118 | 		return "waiting for defer"
119 | 	default:
120 | 		return fmt.Sprintf("unknown wait reason %d", g.WaitReason)
121 | 	}
122 | }
123 | 
124 | // getBreakpointStatus returns a human-readable breakpoint status
125 | func getBreakpointStatus(bp *api.Breakpoint) string {
126 | 	if bp.Disabled {
127 | 		return "disabled"
128 | 	}
129 | 	if bp.TotalHitCount > 0 {
130 | 		return "hit"
131 | 	}
132 | 	return "enabled"
133 | }
134 | 
135 | // getStateReason returns a human-readable reason for the current state
136 | func getStateReason(state *api.DebuggerState) string {
137 | 	if state == nil {
138 | 		return "unknown"
139 | 	}
140 | 
141 | 	if state.Exited {
142 | 		return fmt.Sprintf("process exited with status %d", state.ExitStatus)
143 | 	}
144 | 
145 | 	if state.Running {
146 | 		return "process is running"
147 | 	}
148 | 
149 | 	if state.CurrentThread != nil && state.CurrentThread.Breakpoint != nil {
150 | 		return "hit breakpoint"
151 | 	}
152 | 
153 | 	return "process is stopped"
154 | }
155 | 
156 | // getCurrentLocation gets the current location from a DebuggerState
157 | func getCurrentLocation(state *api.DebuggerState) *string {
158 | 	if state == nil || state.CurrentThread == nil {
159 | 		return nil
160 | 	}
161 | 	if state.CurrentThread.File == "" || state.CurrentThread.Function == nil {
162 | 		return nil
163 | 	}
164 | 
165 | 	r := fmt.Sprintf("At %s:%d in %s", state.CurrentThread.File, state.CurrentThread.Line, getFunctionName(state.CurrentThread))
166 | 	return &r
167 | }
168 | 
169 | func getBreakpointLocation(bp *api.Breakpoint) *string {
170 | 	r := fmt.Sprintf("At %s:%d in %s", bp.File, bp.Line, getFunctionNameFromBreakpoint(bp))
171 | 	return &r
172 | }
173 | 
```

--------------------------------------------------------------------------------
/docs/Implementation-Guide.md:
--------------------------------------------------------------------------------

```markdown
  1 | # MCP Go Debugger Implementation Guide
  2 | 
  3 | This guide outlines best practices for implementing features in the MCP Go Debugger project.
  4 | 
  5 | ## Development Workflow
  6 | 
  7 | When implementing a new feature or modifying existing code, follow these steps:
  8 | 
  9 | 1. **Understand the Task**: Thoroughly review the relevant task description.
 10 | 2. **Explore the Codebase**: Identify relevant files and understand existing patterns.
 11 | 3. **Make Focused Changes**: Implement one coherent feature/fix at a time.
 12 | 4. **Write Tests**: Add tests for the new functionality.
 13 | 5. **Validate**: Run tests to ensure all tests pass.
 14 | 6. **Commit**: Commit changes with descriptive messages.
 15 | 7. **Repeat**: Only after successfully completing a change, move to the next task.
 16 | 
 17 | ## Understanding the Codebase
 18 | 
 19 | Before making changes:
 20 | 
 21 | 1. Explore the project structure
 22 | 2. Identify key files and packages
 23 | 3. Review existing tests for similar functionality
 24 | 
 25 | ## Implementation Strategies
 26 | 
 27 | ### Adding a New Feature
 28 | 
 29 | When adding a new feature:
 30 | 
 31 | 1. **Review Similar Features**: Look at how similar features are implemented.
 32 | 2. **Follow Conventions**: Match existing code style and patterns.
 33 | 3. **Implement Core Logic**: Create the main functionality first.
 34 | 4. **Add Error Handling**: Ensure proper error cases are handled.
 35 | 5. **Update Interfaces**: Update any interfaces or API definitions.
 36 | 
 37 | ### Modifying Existing Code
 38 | 
 39 | When modifying existing code:
 40 | 
 41 | 1. **Locate Target Code**: Find all related code sections that need changes.
 42 | 2. **Understand Dependencies**: Identify code that depends on what you're changing.
 43 | 3. **Make Minimal Changes**: Change only what's necessary for the feature.
 44 | 4. **Preserve Behavior**: Ensure existing functionality remains intact unless deliberately changing it.
 45 | 
 46 | ## Test-Driven Development
 47 | 
 48 | Follow these testing principles:
 49 | 
 50 | 1. **Write Tests First**: When possible, write tests before implementing features.
 51 | 2. **Test Edge Cases**: Include tests for error conditions and edge cases.
 52 | 3. **Ensure Coverage**: Aim for good test coverage of new functionality.
 53 | 4. **Test Commands**: For each MCP command, test both success and failure paths.
 54 | 
 55 | ## Running Tests
 56 | 
 57 | Run tests before committing:
 58 | 
 59 | ```sh
 60 | go test ./...
 61 | ```
 62 | 
 63 | For specific packages:
 64 | 
 65 | ```sh
 66 | go test ./pkg/debugger
 67 | ```
 68 | 
 69 | With verbose output:
 70 | 
 71 | ```sh
 72 | go test -v ./...
 73 | ```
 74 | 
 75 | ## Git Workflow
 76 | 
 77 | For each feature implementation:
 78 | 
 79 | 1. **Start Clean**: Begin with a clean working directory.
 80 | 2. **Feature Branch**: Consider using feature branches for significant changes.
 81 | 3. **Atomic Commits**: Make focused commits that implement one logical change.
 82 | 4. **Descriptive Messages**: Use clear commit messages following this format:
 83 |    ```
 84 |    [Component] Brief description of change
 85 |    
 86 |    More detailed explanation if needed
 87 |    ```
 88 | 5. **Verify Before Commit**: Always run tests before committing.
 89 | 
 90 | ### Sample Commit Workflow
 91 | 
 92 | ```sh
 93 | # Run tests to ensure everything passes before changes
 94 | go test ./...
 95 | 
 96 | # Make your changes
 97 | 
 98 | # Run tests again to verify changes
 99 | go test ./...
100 | 
101 | # Commit changes with descriptive message
102 | git add .
103 | git commit -m "[Debugger] Implement set_breakpoint command"
104 | ```
105 | 
106 | ## Troubleshooting
107 | 
108 | If tests fail:
109 | 
110 | 1. Review the test output carefully
111 | 2. Check for any side effects from your changes
112 | 3. Verify that you've updated all necessary parts of the codebase
113 | 4. Consider running tests with the `-v` flag for more details
114 | 
115 | ## Documentation
116 | 
117 | Update documentation when:
118 | 
119 | 1. Adding new commands or features
120 | 2. Changing existing behavior
121 | 3. Fixing non-obvious bugs
122 | 
123 | ## API Documentation
124 | 
125 | Always use `go doc` instead of searching the internet when finding out Go APIs. This ensures that you're referencing the exact API version in your dependencies:
126 | 
127 | ```sh
128 | # View documentation for a package
129 | go doc github.com/go-delve/delve/service
130 | 
131 | # View documentation for a specific type
132 | go doc github.com/go-delve/delve/service.Config
133 | 
134 | # View documentation for a function or method
135 | go doc github.com/go-delve/delve/service.NewServer
136 | 
137 | # For more detailed documentation including unexported items
138 | go doc -all github.com/go-delve/delve/service
139 | ```
140 | 
141 | This approach ensures you're working with the precise API that's included in your project's dependencies rather than potentially outdated or incompatible online resources.
142 | 
143 | ## Next Steps
144 | 
145 | After implementing a feature:
146 | 
147 | 1. Consider if additional tests would be valuable
148 | 2. Look for opportunities to refactor or improve the code
149 | 3. Update the task status in Phase1-Tasks.md
150 | 4. Move on to the next related task 
```

--------------------------------------------------------------------------------
/pkg/debugger/breakpoints.go:
--------------------------------------------------------------------------------

```go
  1 | package debugger
  2 | 
  3 | import (
  4 | 	"fmt"
  5 | 	"time"
  6 | 
  7 | 	"github.com/go-delve/delve/service/api"
  8 | 	"github.com/sunfmin/mcp-go-debugger/pkg/logger"
  9 | 	"github.com/sunfmin/mcp-go-debugger/pkg/types"
 10 | )
 11 | 
 12 | // SetBreakpoint sets a breakpoint at the specified file and line
 13 | func (c *Client) SetBreakpoint(file string, line int) types.BreakpointResponse {
 14 | 	if c.client == nil {
 15 | 		return types.BreakpointResponse{
 16 | 			Status: "error",
 17 | 			Context: types.DebugContext{
 18 | 				ErrorMessage: "no active debug session",
 19 | 				Timestamp:    getCurrentTimestamp(),
 20 | 			},
 21 | 		}
 22 | 	}
 23 | 
 24 | 	logger.Debug("Setting breakpoint at %s:%d", file, line)
 25 | 	bp, err := c.client.CreateBreakpoint(&api.Breakpoint{
 26 | 		File: file,
 27 | 		Line: line,
 28 | 	})
 29 | 
 30 | 	if err != nil {
 31 | 		return types.BreakpointResponse{
 32 | 			Status: "error",
 33 | 			Context: types.DebugContext{
 34 | 				ErrorMessage: fmt.Sprintf("failed to set breakpoint: %v", err),
 35 | 				Timestamp:    getCurrentTimestamp(),
 36 | 			},
 37 | 		}
 38 | 	}
 39 | 
 40 | 	// Get current state for context
 41 | 	state, err := c.client.GetState()
 42 | 	if err != nil {
 43 | 		logger.Debug("Warning: Failed to get state after setting breakpoint: %v", err)
 44 | 	}
 45 | 
 46 | 	breakpoint := &types.Breakpoint{
 47 | 		DelveBreakpoint: bp,
 48 | 		ID:              bp.ID,
 49 | 		Status:          getBreakpointStatus(bp),
 50 | 		Location:        getBreakpointLocation(bp),
 51 | 		HitCount:        uint64(bp.TotalHitCount),
 52 | 	}
 53 | 
 54 | 	context := c.createDebugContext(state)
 55 | 	context.Operation = "set_breakpoint"
 56 | 
 57 | 	return types.BreakpointResponse{
 58 | 		Status:     "success",
 59 | 		Context:    context,
 60 | 		Breakpoint: *breakpoint,
 61 | 	}
 62 | }
 63 | 
 64 | // ListBreakpoints returns all currently set breakpoints
 65 | func (c *Client) ListBreakpoints() types.BreakpointListResponse {
 66 | 	if c.client == nil {
 67 | 		return types.BreakpointListResponse{
 68 | 			Status: "error",
 69 | 			Context: types.DebugContext{
 70 | 				ErrorMessage: "no active debug session",
 71 | 				Timestamp:    getCurrentTimestamp(),
 72 | 			},
 73 | 		}
 74 | 	}
 75 | 
 76 | 	bps, err := c.client.ListBreakpoints(false)
 77 | 	if err != nil {
 78 | 		return types.BreakpointListResponse{
 79 | 			Status: "error",
 80 | 			Context: types.DebugContext{
 81 | 				ErrorMessage: fmt.Sprintf("failed to list breakpoints: %v", err),
 82 | 				Timestamp:    getCurrentTimestamp(),
 83 | 			},
 84 | 		}
 85 | 	}
 86 | 
 87 | 	var breakpoints []types.Breakpoint
 88 | 	for _, bp := range bps {
 89 | 		breakpoints = append(breakpoints, types.Breakpoint{
 90 | 			DelveBreakpoint: bp,
 91 | 			ID:              bp.ID,
 92 | 			Status:          getBreakpointStatus(bp),
 93 | 			Location:        getBreakpointLocation(bp),
 94 | 			HitCount:        uint64(bp.TotalHitCount),
 95 | 		})
 96 | 	}
 97 | 
 98 | 	// Get current state for context
 99 | 	state, err := c.client.GetState()
100 | 	if err != nil {
101 | 		logger.Debug("Warning: Failed to get state while listing breakpoints: %v", err)
102 | 	}
103 | 
104 | 	context := c.createDebugContext(state)
105 | 	context.Operation = "list_breakpoints"
106 | 
107 | 	return types.BreakpointListResponse{
108 | 		Status:      "success",
109 | 		Context:     context,
110 | 		Breakpoints: breakpoints,
111 | 	}
112 | }
113 | 
114 | // RemoveBreakpoint removes a breakpoint by its ID
115 | func (c *Client) RemoveBreakpoint(id int) types.BreakpointResponse {
116 | 	if c.client == nil {
117 | 		return types.BreakpointResponse{
118 | 			Status: "error",
119 | 			Context: types.DebugContext{
120 | 				ErrorMessage: "no active debug session",
121 | 				Timestamp:    getCurrentTimestamp(),
122 | 			},
123 | 		}
124 | 	}
125 | 
126 | 	// Get breakpoint info before removing
127 | 	bps, err := c.client.ListBreakpoints(false)
128 | 	if err != nil {
129 | 		return types.BreakpointResponse{
130 | 			Status: "error",
131 | 			Context: types.DebugContext{
132 | 				ErrorMessage: fmt.Sprintf("failed to get breakpoint info: %v", err),
133 | 				Timestamp:    getCurrentTimestamp(),
134 | 			},
135 | 		}
136 | 	}
137 | 
138 | 	var targetBp *api.Breakpoint
139 | 	for _, bp := range bps {
140 | 		if bp.ID == id {
141 | 			targetBp = bp
142 | 			break
143 | 		}
144 | 	}
145 | 
146 | 	if targetBp == nil {
147 | 		return types.BreakpointResponse{
148 | 			Status: "error",
149 | 			Context: types.DebugContext{
150 | 				ErrorMessage: fmt.Sprintf("breakpoint %d not found", id),
151 | 				Timestamp:    getCurrentTimestamp(),
152 | 			},
153 | 		}
154 | 	}
155 | 
156 | 	logger.Debug("Removing breakpoint %d at %s:%d", id, targetBp.File, targetBp.Line)
157 | 	_, err = c.client.ClearBreakpoint(id)
158 | 	if err != nil {
159 | 		return types.BreakpointResponse{
160 | 			Status: "error",
161 | 			Context: types.DebugContext{
162 | 				ErrorMessage: fmt.Sprintf("failed to remove breakpoint: %v", err),
163 | 				Timestamp:    getCurrentTimestamp(),
164 | 			},
165 | 		}
166 | 	}
167 | 
168 | 	// Get current state for context
169 | 	state, err := c.client.GetState()
170 | 	if err != nil {
171 | 		logger.Debug("Warning: Failed to get state after removing breakpoint: %v", err)
172 | 	}
173 | 
174 | 	breakpoint := types.Breakpoint{
175 | 		DelveBreakpoint: targetBp,
176 | 		ID:              targetBp.ID,
177 | 		Status:          "removed",
178 | 		Location:        getBreakpointLocation(targetBp),
179 | 		HitCount:        uint64(targetBp.TotalHitCount),
180 | 	}
181 | 
182 | 	context := c.createDebugContext(state)
183 | 	context.Operation = "remove_breakpoint"
184 | 
185 | 	return types.BreakpointResponse{
186 | 		Status:     "success",
187 | 		Context:    context,
188 | 		Breakpoint: breakpoint,
189 | 	}
190 | }
191 | 
192 | func getCurrentTimestamp() time.Time {
193 | 	return time.Now()
194 | }
195 | 
```

--------------------------------------------------------------------------------
/pkg/debugger/execution.go:
--------------------------------------------------------------------------------

```go
  1 | package debugger
  2 | 
  3 | import (
  4 | 	"fmt"
  5 | 	"time"
  6 | 
  7 | 	"github.com/go-delve/delve/service/api"
  8 | 	"github.com/sunfmin/mcp-go-debugger/pkg/logger"
  9 | 	"github.com/sunfmin/mcp-go-debugger/pkg/types"
 10 | )
 11 | 
 12 | // Continue resumes program execution until next breakpoint or program termination
 13 | func (c *Client) Continue() types.ContinueResponse {
 14 | 	if c.client == nil {
 15 | 		return c.createContinueResponse(nil, fmt.Errorf("no active debug session"))
 16 | 	}
 17 | 
 18 | 	logger.Debug("Continuing execution")
 19 | 
 20 | 	// Continue returns a channel that will receive state updates
 21 | 	stateChan := c.client.Continue()
 22 | 
 23 | 	// Wait for the state update from the channel
 24 | 	delveState := <-stateChan
 25 | 	if delveState.Err != nil {
 26 | 		return c.createContinueResponse(nil, fmt.Errorf("continue command failed: %v", delveState.Err))
 27 | 	}
 28 | 
 29 | 	return c.createContinueResponse(delveState, nil)
 30 | }
 31 | 
 32 | // Step executes a single instruction, stepping into function calls
 33 | func (c *Client) Step() types.StepResponse {
 34 | 	if c.client == nil {
 35 | 		return c.createStepResponse(nil, "into", nil, fmt.Errorf("no active debug session"))
 36 | 	}
 37 | 
 38 | 	// Check if program is running or not stopped
 39 | 	delveState, err := c.client.GetState()
 40 | 	if err != nil {
 41 | 		return c.createStepResponse(nil, "into", nil, fmt.Errorf("failed to get state: %v", err))
 42 | 	}
 43 | 
 44 | 	fromLocation := getCurrentLocation(delveState)
 45 | 
 46 | 	if delveState.Running {
 47 | 		logger.Debug("Warning: Cannot step when program is running, waiting for program to stop")
 48 | 		stoppedState, err := waitForStop(c, 2*time.Second)
 49 | 		if err != nil {
 50 | 			return c.createStepResponse(nil, "into", fromLocation, fmt.Errorf("failed to wait for program to stop: %v", err))
 51 | 		}
 52 | 		delveState = stoppedState
 53 | 	}
 54 | 
 55 | 	logger.Debug("Stepping into")
 56 | 	nextState, err := c.client.Step()
 57 | 	if err != nil {
 58 | 		return c.createStepResponse(nil, "into", fromLocation, fmt.Errorf("step into command failed: %v", err))
 59 | 	}
 60 | 
 61 | 	return c.createStepResponse(nextState, "into", fromLocation, nil)
 62 | }
 63 | 
 64 | // StepOver executes the next instruction, stepping over function calls
 65 | func (c *Client) StepOver() types.StepResponse {
 66 | 	if c.client == nil {
 67 | 		return c.createStepResponse(nil, "over", nil, fmt.Errorf("no active debug session"))
 68 | 	}
 69 | 
 70 | 	// Check if program is running or not stopped
 71 | 	delveState, err := c.client.GetState()
 72 | 	if err != nil {
 73 | 		return c.createStepResponse(nil, "over", nil, fmt.Errorf("failed to get state: %v", err))
 74 | 	}
 75 | 
 76 | 	fromLocation := getCurrentLocation(delveState)
 77 | 
 78 | 	if delveState.Running {
 79 | 		logger.Debug("Warning: Cannot step when program is running, waiting for program to stop")
 80 | 		stoppedState, err := waitForStop(c, 2*time.Second)
 81 | 		if err != nil {
 82 | 			return c.createStepResponse(nil, "over", fromLocation, fmt.Errorf("failed to wait for program to stop: %v", err))
 83 | 		}
 84 | 		delveState = stoppedState
 85 | 	}
 86 | 
 87 | 	logger.Debug("Stepping over next line")
 88 | 	nextState, err := c.client.Next()
 89 | 	if err != nil {
 90 | 		return c.createStepResponse(nil, "over", fromLocation, fmt.Errorf("step over command failed: %v", err))
 91 | 	}
 92 | 
 93 | 	return c.createStepResponse(nextState, "over", fromLocation, nil)
 94 | }
 95 | 
 96 | // StepOut executes until the current function returns
 97 | func (c *Client) StepOut() types.StepResponse {
 98 | 	if c.client == nil {
 99 | 		return c.createStepResponse(nil, "out", nil, fmt.Errorf("no active debug session"))
100 | 	}
101 | 
102 | 	// Check if program is running or not stopped
103 | 	delveState, err := c.client.GetState()
104 | 	if err != nil {
105 | 		return c.createStepResponse(nil, "out", nil, fmt.Errorf("failed to get state: %v", err))
106 | 	}
107 | 
108 | 	fromLocation := getCurrentLocation(delveState)
109 | 
110 | 	if delveState.Running {
111 | 		logger.Debug("Warning: Cannot step out when program is running, waiting for program to stop")
112 | 		stoppedState, err := waitForStop(c, 2*time.Second)
113 | 		if err != nil {
114 | 			return c.createStepResponse(nil, "out", fromLocation, fmt.Errorf("failed to wait for program to stop: %v", err))
115 | 		}
116 | 		delveState = stoppedState
117 | 	}
118 | 
119 | 	logger.Debug("Stepping out")
120 | 	nextState, err := c.client.StepOut()
121 | 	if err != nil {
122 | 		return c.createStepResponse(nil, "out", fromLocation, fmt.Errorf("step out command failed: %v", err))
123 | 	}
124 | 
125 | 	return c.createStepResponse(nextState, "out", fromLocation, nil)
126 | }
127 | 
128 | // createContinueResponse creates a ContinueResponse from a DebuggerState
129 | func (c *Client) createContinueResponse(state *api.DebuggerState, err error) types.ContinueResponse {
130 | 	context := c.createDebugContext(state)
131 | 	if err != nil {
132 | 		context.ErrorMessage = err.Error()
133 | 		return types.ContinueResponse{
134 | 			Status:  "error",
135 | 			Context: context,
136 | 		}
137 | 	}
138 | 
139 | 	return types.ContinueResponse{
140 | 		Status:  "success",
141 | 		Context: context,
142 | 	}
143 | }
144 | 
145 | // createStepResponse creates a StepResponse from a DebuggerState
146 | func (c *Client) createStepResponse(state *api.DebuggerState, stepType string, fromLocation *string, err error) types.StepResponse {
147 | 	context := c.createDebugContext(state)
148 | 	if err != nil {
149 | 		context.ErrorMessage = err.Error()
150 | 		return types.StepResponse{
151 | 			Status:  "error",
152 | 			Context: context,
153 | 		}
154 | 	}
155 | 
156 | 	return types.StepResponse{
157 | 		Status:       "success",
158 | 		Context:      context,
159 | 		StepType:     stepType,
160 | 		FromLocation: fromLocation,
161 | 	}
162 | }
163 | 
```

--------------------------------------------------------------------------------
/pkg/types/debug_types.go:
--------------------------------------------------------------------------------

```go
  1 | package types
  2 | 
  3 | import (
  4 | 	"time"
  5 | 
  6 | 	"github.com/go-delve/delve/service/api"
  7 | )
  8 | 
  9 | // DebugContext provides shared context across all debug responses
 10 | type DebugContext struct {
 11 | 	DelveState      *api.DebuggerState `json:"-"`                         // Internal Delve state
 12 | 	Timestamp       time.Time          `json:"timestamp"`                 // Operation timestamp
 13 | 	Operation       string             `json:"operation,omitempty"`       // Last debug operation performed
 14 | 	CurrentLocation *string            `json:"currentLocation,omitempty"` // Current execution position
 15 | 	LocalVariables  []Variable         `json:"localVariables,omitempty"`
 16 | 	// LLM-friendly additions
 17 | 	StopReason   string `json:"stopReason,omitempty"` // Why the program stopped, in human terms
 18 | 	ErrorMessage string `json:"error,omitempty"`      // Error message if any
 19 | }
 20 | 
 21 | // Variable represents a program variable with LLM-friendly additions
 22 | type Variable struct {
 23 | 	// Internal Delve variable - not exposed in JSON
 24 | 	DelveVar *api.Variable `json:"-"`
 25 | 
 26 | 	// LLM-friendly fields
 27 | 	Name  string `json:"name"`  // Variable name
 28 | 	Value string `json:"value"` // Formatted value in human-readable form
 29 | 	Type  string `json:"type"`  // Type in human-readable format
 30 | 	Scope string `json:"scope"` // Variable scope (local, global, etc)
 31 | 	Kind  string `json:"kind"`  // High-level kind description
 32 | }
 33 | 
 34 | // Breakpoint represents a breakpoint with LLM-friendly additions
 35 | type Breakpoint struct {
 36 | 	// Internal Delve breakpoint - not exposed in JSON
 37 | 	DelveBreakpoint *api.Breakpoint `json:"-"`
 38 | 
 39 | 	// LLM-friendly fields
 40 | 	ID          int      `json:"id"`                  // Breakpoint ID
 41 | 	Status      string   `json:"status"`              // Enabled/Disabled/etc in human terms
 42 | 	Location    *string  `json:"location"`            // Breakpoint location
 43 | 	Variables   []string `json:"variables,omitempty"` // Variables in scope
 44 | 	Condition   string   `json:"condition,omitempty"` // Human-readable condition description
 45 | 	HitCount    uint64   `json:"hitCount"`            // Number of times breakpoint was hit
 46 | 	LastHitInfo string   `json:"lastHit,omitempty"`   // Information about last hit in human terms
 47 | }
 48 | 
 49 | // DebuggerOutput represents captured program output with LLM-friendly additions
 50 | type DebuggerOutput struct {
 51 | 	// Internal Delve state - not exposed in JSON
 52 | 	DelveState *api.DebuggerState `json:"-"`
 53 | 
 54 | 	// LLM-friendly fields
 55 | 	Stdout        string       `json:"stdout"`        // Captured standard output
 56 | 	Stderr        string       `json:"stderr"`        // Captured standard error
 57 | 	OutputSummary string       `json:"outputSummary"` // Brief summary of output for LLM
 58 | 	Context       DebugContext `json:"context"`       // Common debugging context
 59 | 	ExitCode      int          `json:"exitCode"`      // Program exit code if available
 60 | }
 61 | 
 62 | // Operation-specific responses
 63 | 
 64 | type LaunchResponse struct {
 65 | 	Context  *DebugContext `json:"context"`
 66 | 	Program  string        `json:"program"`
 67 | 	Args     []string      `json:"args"`
 68 | 	ExitCode int           `json:"exitCode"`
 69 | }
 70 | 
 71 | type BreakpointResponse struct {
 72 | 	Status     string       `json:"status"`
 73 | 	Context    DebugContext `json:"context"`
 74 | 	Breakpoint Breakpoint   `json:"breakpoint"` // The affected breakpoint
 75 | }
 76 | 
 77 | type BreakpointListResponse struct {
 78 | 	Status      string       `json:"status"`
 79 | 	Context     DebugContext `json:"context"`
 80 | 	Breakpoints []Breakpoint `json:"breakpoints"` // All current breakpoints
 81 | }
 82 | 
 83 | type StepResponse struct {
 84 | 	Status       string       `json:"status"`
 85 | 	Context      DebugContext `json:"context"`
 86 | 	StepType     string       `json:"stepType"`    // "into", "over", or "out"
 87 | 	FromLocation *string      `json:"from"`        // Starting location
 88 | 	ChangedVars  []Variable   `json:"changedVars"` // Variables that changed during step
 89 | }
 90 | 
 91 | type EvalVariableResponse struct {
 92 | 	Status   string       `json:"status"`
 93 | 	Context  DebugContext `json:"context"`
 94 | 	Variable Variable     `json:"variable"` // The evald variable
 95 | }
 96 | 
 97 | type ContinueResponse struct {
 98 | 	Status  string       `json:"status"`
 99 | 	Context DebugContext `json:"context"`
100 | }
101 | 
102 | type CloseResponse struct {
103 | 	Status   string       `json:"status"`
104 | 	Context  DebugContext `json:"context"`
105 | 	ExitCode int          `json:"exitCode"` // Program exit code
106 | 	Summary  string       `json:"summary"`  // Session summary for LLM
107 | }
108 | 
109 | type DebuggerOutputResponse struct {
110 | 	Status        string       `json:"status"`
111 | 	Context       DebugContext `json:"context"`
112 | 	Stdout        string       `json:"stdout"`        // Captured standard output
113 | 	Stderr        string       `json:"stderr"`        // Captured standard error
114 | 	OutputSummary string       `json:"outputSummary"` // Brief summary of output for LLM
115 | }
116 | 
117 | type AttachResponse struct {
118 | 	Status  string        `json:"status"`
119 | 	Context *DebugContext `json:"context"`
120 | 	Pid     int           `json:"pid"`
121 | 	Target  string        `json:"target"`
122 | 	Process *Process      `json:"process"`
123 | }
124 | 
125 | type DebugSourceResponse struct {
126 | 	Status      string        `json:"status"`
127 | 	Context     *DebugContext `json:"context"`
128 | 	SourceFile  string        `json:"sourceFile"`
129 | 	DebugBinary string        `json:"debugBinary"`
130 | 	Args        []string      `json:"args"`
131 | }
132 | 
133 | type DebugTestResponse struct {
134 | 	Status       string        `json:"status"`
135 | 	Context      *DebugContext `json:"context"`
136 | 	TestFile     string        `json:"testFile"`
137 | 	TestName     string        `json:"testName"`
138 | 	BuildCommand string        `json:"buildCommand"`
139 | 	BuildOutput  string        `json:"buildOutput"`
140 | 	DebugBinary  string        `json:"debugBinary"`
141 | 	Process      *Process      `json:"process"`
142 | 	TestFlags    []string      `json:"testFlags"`
143 | }
144 | 
145 | // Process represents a debugged process with LLM-friendly additions
146 | type Process struct {
147 | 	Pid         int      `json:"pid"`         // Process ID
148 | 	Name        string   `json:"name"`        // Process name
149 | 	CmdLine     []string `json:"cmdLine"`     // Command line arguments
150 | 	Status      string   `json:"status"`      // Process status (running, stopped, etc.)
151 | 	Summary     string   `json:"summary"`     // Brief description of process state
152 | 	ExitCode    int      `json:"exitCode"`    // Exit code if process has terminated
153 | 	ExitMessage string   `json:"exitMessage"` // Exit message if process has terminated
154 | }
155 | 
```

--------------------------------------------------------------------------------
/pkg/debugger/variables.go:
--------------------------------------------------------------------------------

```go
  1 | package debugger
  2 | 
  3 | import (
  4 | 	"fmt"
  5 | 	"reflect"
  6 | 	"strings"
  7 | 
  8 | 	"github.com/go-delve/delve/service/api"
  9 | 	"github.com/sunfmin/mcp-go-debugger/pkg/types"
 10 | )
 11 | 
 12 | // EvalVariable evaluates a variable expression
 13 | func (c *Client) EvalVariable(name string, depth int) types.EvalVariableResponse {
 14 | 	if c.client == nil {
 15 | 		return c.createEvalVariableResponse(nil, nil, 0, fmt.Errorf("no active debug session"))
 16 | 	}
 17 | 
 18 | 	// Get current state for context
 19 | 	state, err := c.client.GetState()
 20 | 	if err != nil {
 21 | 		return c.createEvalVariableResponse(nil, nil, 0, fmt.Errorf("failed to get state: %v", err))
 22 | 	}
 23 | 
 24 | 	if state.SelectedGoroutine == nil {
 25 | 		return c.createEvalVariableResponse(state, nil, 0, fmt.Errorf("no goroutine selected"))
 26 | 	}
 27 | 
 28 | 	// Create the evaluation scope
 29 | 	scope := api.EvalScope{
 30 | 		GoroutineID: state.SelectedGoroutine.ID,
 31 | 		Frame:       0,
 32 | 	}
 33 | 
 34 | 	// Configure loading with proper struct field handling
 35 | 	loadConfig := api.LoadConfig{
 36 | 		FollowPointers:     true,
 37 | 		MaxVariableRecurse: depth,
 38 | 		MaxStringLen:       1024,
 39 | 		MaxArrayValues:     100,
 40 | 		MaxStructFields:    -1, // Load all struct fields
 41 | 	}
 42 | 
 43 | 	// Evaluate the variable
 44 | 	v, err := c.client.EvalVariable(scope, name, loadConfig)
 45 | 	if err != nil {
 46 | 		return c.createEvalVariableResponse(state, nil, 0, fmt.Errorf("failed to evaluate variable %s: %v", name, err))
 47 | 	}
 48 | 
 49 | 	// Convert to our type
 50 | 	variable := &types.Variable{
 51 | 		DelveVar: v,
 52 | 		Name:     v.Name,
 53 | 		Type:     v.Type,
 54 | 		Kind:     getVariableKind(v),
 55 | 	}
 56 | 
 57 | 	// Format the value based on the variable kind
 58 | 	if v.Kind == reflect.Struct {
 59 | 		// For struct types, format fields
 60 | 		if len(v.Children) > 0 {
 61 | 			fields := make([]string, 0, len(v.Children))
 62 | 			for _, field := range v.Children {
 63 | 				fieldStr := fmt.Sprintf("%s:%s", field.Name, field.Value)
 64 | 				fields = append(fields, fieldStr)
 65 | 			}
 66 | 			variable.Value = "{" + strings.Join(fields, ", ") + "}"
 67 | 		} else {
 68 | 			variable.Value = "{}" // Empty struct
 69 | 		}
 70 | 	} else if v.Kind == reflect.Array || v.Kind == reflect.Slice {
 71 | 		// For array or slice types, format elements
 72 | 		if len(v.Children) > 0 {
 73 | 			elements := make([]string, 0, len(v.Children))
 74 | 			for _, element := range v.Children {
 75 | 				elements = append(elements, element.Value)
 76 | 			}
 77 | 			variable.Value = "[" + strings.Join(elements, ", ") + "]"
 78 | 		} else {
 79 | 			variable.Value = "[]" // Empty array or slice
 80 | 		}
 81 | 	} else {
 82 | 		variable.Value = v.Value
 83 | 	}
 84 | 
 85 | 	return c.createEvalVariableResponse(state, variable, depth, nil)
 86 | }
 87 | 
 88 | // Helper functions for variable information
 89 | func getVariableKind(v *api.Variable) string {
 90 | 	if v == nil {
 91 | 		return "unknown"
 92 | 	}
 93 | 
 94 | 	switch v.Kind {
 95 | 	case reflect.Bool:
 96 | 		return "boolean"
 97 | 	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
 98 | 		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
 99 | 		return "integer"
100 | 	case reflect.Float32, reflect.Float64:
101 | 		return "float"
102 | 	case reflect.String:
103 | 		return "string"
104 | 	case reflect.Array, reflect.Slice:
105 | 		return "array"
106 | 	case reflect.Map:
107 | 		return "map"
108 | 	case reflect.Struct:
109 | 		return "struct"
110 | 	case reflect.Ptr:
111 | 		return "pointer"
112 | 	case reflect.Interface:
113 | 		return "interface"
114 | 	case reflect.Chan:
115 | 		return "channel"
116 | 	case reflect.Func:
117 | 		return "function"
118 | 	default:
119 | 		return "unknown"
120 | 	}
121 | }
122 | 
123 | // Helper function to get struct field information
124 | func getStructFields(v api.Variable) string {
125 | 	if len(v.Children) == 0 {
126 | 		return "none"
127 | 	}
128 | 
129 | 	fields := make([]string, len(v.Children))
130 | 	for i, field := range v.Children {
131 | 		fields[i] = fmt.Sprintf("%s %s", field.Name, field.Type)
132 | 	}
133 | 	return strings.Join(fields, ", ")
134 | }
135 | 
136 | // Helper function to get map key type
137 | func getMapKeyType(v api.Variable) string {
138 | 	if len(v.Children) == 0 {
139 | 		return "unknown"
140 | 	}
141 | 	return v.Children[0].Type
142 | }
143 | 
144 | // Helper function to get map value type
145 | func getMapValueType(v api.Variable) string {
146 | 	if len(v.Children) < 2 {
147 | 		return "unknown"
148 | 	}
149 | 	return v.Children[1].Type
150 | }
151 | 
152 | // getLocalVariables extracts local variables and arguments from the current scope
153 | func (c *Client) getLocalVariables(state *api.DebuggerState) ([]types.Variable, error) {
154 | 	if state == nil || state.SelectedGoroutine == nil {
155 | 		return nil, fmt.Errorf("no active goroutine")
156 | 	}
157 | 
158 | 	// Create evaluation scope for current frame
159 | 	scope := api.EvalScope{
160 | 		GoroutineID: state.SelectedGoroutine.ID,
161 | 		Frame:       0, // 0 represents the current frame
162 | 	}
163 | 
164 | 	// Default load configuration
165 | 	cfg := api.LoadConfig{
166 | 		FollowPointers:     true,
167 | 		MaxVariableRecurse: 1,
168 | 		MaxStringLen:       64,
169 | 		MaxArrayValues:     64,
170 | 		MaxStructFields:    -1,
171 | 	}
172 | 
173 | 	// Convert Delve variables to our format
174 | 	convertToVariable := func(v *api.Variable, scope string) types.Variable {
175 | 		var value string
176 | 
177 | 		// Format the value based on the variable kind
178 | 		if v.Kind == reflect.Struct {
179 | 			// For struct types, format fields
180 | 			if len(v.Children) > 0 {
181 | 				fields := make([]string, 0, len(v.Children))
182 | 				for _, field := range v.Children {
183 | 					fieldStr := fmt.Sprintf("%s:%s", field.Name, field.Value)
184 | 					fields = append(fields, fieldStr)
185 | 				}
186 | 				value = "{" + strings.Join(fields, ", ") + "}"
187 | 			} else {
188 | 				value = "{}" // Empty struct
189 | 			}
190 | 		} else if v.Kind == reflect.Array || v.Kind == reflect.Slice {
191 | 			// For array or slice types, format elements
192 | 			if len(v.Children) > 0 {
193 | 				elements := make([]string, 0, len(v.Children))
194 | 				for _, element := range v.Children {
195 | 					elements = append(elements, element.Value)
196 | 				}
197 | 				value = "[" + strings.Join(elements, ",") + "]"
198 | 			} else {
199 | 				value = "[]" // Empty array or slice
200 | 			}
201 | 		} else {
202 | 			value = v.Value
203 | 		}
204 | 
205 | 		return types.Variable{
206 | 			DelveVar: v,
207 | 			Name:     v.Name,
208 | 			Value:    value,
209 | 			Type:     v.Type,
210 | 			Scope:    scope,
211 | 			Kind:     getVariableKind(v),
212 | 		}
213 | 	}
214 | 
215 | 	var variables []types.Variable
216 | 
217 | 	// Get function arguments
218 | 	args, err := c.client.ListFunctionArgs(scope, cfg)
219 | 	if err != nil {
220 | 		return nil, fmt.Errorf("error getting function arguments: %v", err)
221 | 	}
222 | 
223 | 	// Get local variables
224 | 	locals, err := c.client.ListLocalVariables(scope, cfg)
225 | 	if err != nil {
226 | 		return nil, fmt.Errorf("error getting local variables: %v", err)
227 | 	}
228 | 
229 | 	// Process arguments first
230 | 	for _, arg := range args {
231 | 		variables = append(variables, convertToVariable(&arg, "argument"))
232 | 	}
233 | 
234 | 	// Process local variables
235 | 	for _, local := range locals {
236 | 		variables = append(variables, convertToVariable(&local, "local"))
237 | 	}
238 | 
239 | 	return variables, nil
240 | }
241 | 
242 | // createEvalVariableResponse creates an EvalVariableResponse
243 | func (c *Client) createEvalVariableResponse(state *api.DebuggerState, variable *types.Variable, depth int, err error) types.EvalVariableResponse {
244 | 	context := c.createDebugContext(state)
245 | 	if err != nil {
246 | 		context.ErrorMessage = err.Error()
247 | 		return types.EvalVariableResponse{
248 | 			Status:  "error",
249 | 			Context: context,
250 | 		}
251 | 	}
252 | 
253 | 	return types.EvalVariableResponse{
254 | 		Status:   "success",
255 | 		Context:  context,
256 | 		Variable: *variable,
257 | 	}
258 | }
259 | 
```

--------------------------------------------------------------------------------
/docs/Phase1-Tasks.md:
--------------------------------------------------------------------------------

```markdown
  1 | # MCP Go Debugger - Phase 1 Implementation Tasks
  2 | 
  3 | ## Overview
  4 | 
  5 | This document outlines the detailed tasks for implementing Phase 1 (Core Functionality) of the MCP Go Debugger. The primary goal of Phase 1 is to establish a foundation for the debugger with essential capabilities, setting the stage for more advanced features in subsequent phases.
  6 | 
  7 | ## High-Level Goals
  8 | 
  9 | - Set up Model Context Protocol (MCP) server using mark3labs/mcp-go
 10 | - Embed Delve as a library dependency
 11 | - Implement program launch and attach capabilities
 12 | - Implement basic debugging commands (breakpoints, step, continue)
 13 | - Simple variable inspection
 14 | - Initial testing with sample Go applications
 15 | 
 16 | ## Detailed Tasks
 17 | 
 18 | ### 1. Project Setup and Environment Configuration
 19 | 
 20 | - [x] **1.1** [P0] Create new Go module with proper structure
 21 | - [x] **1.2** [P0] Add mark3labs/mcp-go dependency *(depends on: 1.1)*
 22 | - [x] **1.3** [P0] Add Delve (go-delve/delve) dependencies *(depends on: 1.1)*
 23 | - [x] **1.4** [P1] Set up build system and compilation targets *(depends on: 1.1-1.3)*
 24 | - [x] **1.5** [P1] Create basic documentation structure *(depends on: 1.4)*
 25 | - [ ] **1.6** [P2] Set up testing framework and test fixtures *(depends on: 1.1-1.5)*
 26 | 
 27 | ### 2. Basic MCP Server Implementation
 28 | 
 29 | - [x] **2.1** [P0] Implement basic Model Context Protocol server with mark3labs/mcp-go *(depends on: 1.2)*
 30 | - [x] **2.2** [P0] Configure server name, version, and metadata *(depends on: 2.1)*
 31 | - [x] **2.3** [P1] Implement error handling and logging framework *(depends on: 2.1-2.2)*
 32 | - [x] **2.4** [P1] Set up MCP stdio communication interface *(depends on: 2.1-2.3)*
 33 | - [x] **2.5** [P2] Add server health check capabilities *(depends on: 2.1-2.4)*
 34 | 
 35 | ### 3. Delve Integration
 36 | 
 37 | - [x] **3.1** [P0] Create Delve client wrapper to manage debug sessions *(depends on: 1.3, 2.1)*
 38 | - [x] **3.2** [P0] Implement debug session lifecycle management (create, maintain, close) *(depends on: 3.1)*
 39 | - [x] **3.3** [P0] Implement error handling for Delve operations *(depends on: 3.1-3.2)*
 40 | - [ ] **3.4** [P1] Add support for Delve configuration options *(depends on: 3.1-3.3)*
 41 | - [x] **3.5** [P2] Implement session persistence across commands *(depends on: 3.1-3.4)*
 42 | 
 43 | ### 4. Program Launch and Attach
 44 | 
 45 | - [ ] **4.7** [P1] Implement "debug" tool to compile and debug a source file directly *(depends on: 3.1-3.5)*
 46 | - [ ] **4.8** [P1] Implement "debug_test" tool to compile and debug a test function *(depends on: 3.1-3.5)*
 47 | - [x] **4.1** [P0] Implement "launch" tool to start a program with debugging *(depends on: 3.1-3.5)*
 48 | - [x] **4.2** [P0] Add support for program arguments and environment variables *(depends on: 4.1)*
 49 | - [x] **4.3** [P0] Implement "attach" tool to connect to running process *(depends on: 3.1-3.5)*
 50 | - [x] **4.4** [P1] Add validation for program path and process ID *(depends on: 4.1-4.3, 4.7)*
 51 | - [x] **4.5** [P1] Implement status reporting for running programs *(depends on: 4.1-4.4)*
 52 | - [x] **4.6** [P2] Add support for stopping debugged programs *(depends on: 4.1-4.5)*
 53 | 
 54 | ### 5. Breakpoint Management
 55 | 
 56 | - [x] **5.1** [P0] Implement "set_breakpoint" tool *(depends on: 4.1-4.3)*
 57 | - [x] **5.2** [P0] Add file/line number validation for breakpoints *(depends on: 5.1)*
 58 | - [x] **5.3** [P0] Implement "list_breakpoints" tool to show all breakpoints *(depends on: 5.1-5.2)*
 59 | - [x] **5.4** [P0] Implement "remove_breakpoint" tool *(depends on: 5.1-5.3)*
 60 | - [ ] **5.5** [P1] Add breakpoint ID tracking and management *(depends on: 5.1-5.4)*
 61 | - [ ] **5.6** [P2] Add support for enabling/disabling breakpoints *(depends on: 5.1-5.5)*
 62 | 
 63 | ### 6. Program Control
 64 | 
 65 | - [ ] **6.1** [P0] Implement "continue" tool for program execution *(depends on: 4.1-4.6)*
 66 | - [ ] **6.2** [P0] Add breakpoint hit notification handling *(depends on: 6.1)*
 67 | - [ ] **6.3** [P0] Implement "step" tool for line-by-line execution *(depends on: 6.1-6.2)*
 68 | - [ ] **6.4** [P0] Implement "step_over" tool *(depends on: 6.1-6.3)*
 69 | - [ ] **6.5** [P0] Implement "step_out" tool *(depends on: 6.1-6.4)*
 70 | - [ ] **6.6** [P1] Add execution state tracking and reporting *(depends on: 6.1-6.5)*
 71 | 
 72 | ### 7. Variable Inspection
 73 | 
 74 | - [x] **7.1** [P0] Implement "eval_variable" tool to view variable values *(depends on: 6.1-6.6)*
 75 | - [x] **7.2** [P0] Add support for complex data structures *(depends on: 7.1)*
 76 | - [x] **7.3** [P1] Implement variable formatting and pretty printing *(depends on: 7.1-7.2)*
 77 | - [x] **7.4** [P1] Add support for scope-aware variable lookup *(depends on: 7.1-7.3)*
 78 | - [x] **7.5** [P2] Implement "evaluate" tool for expression evaluation *(depends on: 7.1-7.4)*
 79 | - [x] **7.6** [P1] Implement "list_scope_variables" tool to display all variables in current scope *(depends on: 7.1-7.4)*
 80 | - [x] **7.7** [P1] Implement "get_execution_position" tool to retrieve the current line number and file *(depends on: 7.1-7.4)*
 81 | 
 82 | ### 8. Stack and Goroutine Inspection
 83 | 
 84 | - [ ] **8.1** [P0] Implement "stack_trace" tool *(depends on: 6.1-6.6)*
 85 | - [ ] **8.2** [P0] Add source line information to stack frames *(depends on: 8.1)*
 86 | - [ ] **8.3** [P0] Implement "list_goroutines" tool *(depends on: 8.1-8.2)*
 87 | - [ ] **8.4** [P1] Add goroutine selection for debugging *(depends on: 8.1-8.3)*
 88 | - [ ] **8.5** [P2] Implement goroutine state information *(depends on: 8.1-8.4)*
 89 | 
 90 | ### 9. Testing and Validation
 91 | 
 92 | - [x] **9.1** [P0] Create simple test Go program for debugging *(depends on: All above)*
 93 | - [ ] **9.2** [P0] Test launch and attach workflow *(depends on: 9.1)*
 94 | - [ ] **9.3** [P0] Test breakpoint setting and hitting *(depends on: 9.1-9.2)*
 95 | - [ ] **9.4** [P0] Test program control flow (continue, step) *(depends on: 9.1-9.3)*
 96 | - [ ] **9.5** [P0] Test variable inspection *(depends on: 9.1-9.4)*
 97 | - [ ] **9.6** [P1] Test goroutine debugging with concurrent program *(depends on: 9.1-9.5)*
 98 | - [ ] **9.7** [P1] Document any issues or limitations found *(depends on: 9.1-9.6)*
 99 | 
100 | ### 10. Documentation and Packaging
101 | 
102 | - [ ] **10.1** [P0] Update README with installation instructions *(depends on: All above)*
103 | - [ ] **10.2** [P0] Document all implemented MCP tools *(depends on: 10.1)*
104 | - [ ] **10.3** [P0] Create usage examples for each tool *(depends on: 10.1-10.2)*
105 | - [ ] **10.4** [P1] Document integration with Cursor and Claude Desktop *(depends on: 10.1-10.3)*
106 | - [ ] **10.5** [P1] Create simple tutorial for first-time users *(depends on: 10.1-10.4)*
107 | - [ ] **10.6** [P1] Package binary for easy installation *(depends on: All above)*
108 | - [ ] **10.7** [P2] Create developer documentation for future contributors *(depends on: 10.1-10.6)*
109 | 
110 | ## Priority Levels
111 | 
112 | - **P0**: Must have for Phase 1 - core functionality that cannot be deferred
113 | - **P1**: Should have for Phase 1 - important but could potentially slip to early Phase 2
114 | - **P2**: Nice to have for Phase 1 - enhances the experience but could be deferred
115 | 
116 | ## Next Steps After Phase 1
117 | 
118 | - [ ] Review Phase 1 implementation and gather feedback
119 | - [ ] Identify any issues or limitations that need to be addressed
120 | - [ ] Begin planning for Phase 2 implementation of enhanced features
121 | - [ ] Consider early adopter testing with selected users 
```

--------------------------------------------------------------------------------
/pkg/mcp/server.go:
--------------------------------------------------------------------------------

```go
  1 | package mcp
  2 | 
  3 | import (
  4 | 	"context"
  5 | 	"encoding/json"
  6 | 	"fmt"
  7 | 
  8 | 	"github.com/mark3labs/mcp-go/mcp"
  9 | 	"github.com/mark3labs/mcp-go/server"
 10 | 	"github.com/sunfmin/mcp-go-debugger/pkg/debugger"
 11 | 	"github.com/sunfmin/mcp-go-debugger/pkg/logger"
 12 | )
 13 | 
 14 | type MCPDebugServer struct {
 15 | 	server      *server.MCPServer
 16 | 	debugClient *debugger.Client
 17 | 	version     string
 18 | }
 19 | 
 20 | func NewMCPDebugServer(version string) *MCPDebugServer {
 21 | 	s := &MCPDebugServer{
 22 | 		server:      server.NewMCPServer("Go Debugger MCP", version),
 23 | 		debugClient: debugger.NewClient(),
 24 | 		version:     version,
 25 | 	}
 26 | 
 27 | 	s.registerTools()
 28 | 
 29 | 	return s
 30 | }
 31 | 
 32 | func (s *MCPDebugServer) Server() *server.MCPServer {
 33 | 	return s.server
 34 | }
 35 | 
 36 | func (s *MCPDebugServer) DebugClient() *debugger.Client {
 37 | 	return s.debugClient
 38 | }
 39 | 
 40 | func (s *MCPDebugServer) registerTools() {
 41 | 	s.addDebugSourceFileTool()
 42 | 	s.addDebugTestTool()
 43 | 	s.addLaunchTool()
 44 | 	s.addAttachTool()
 45 | 	s.addCloseTool()
 46 | 	s.addSetBreakpointTool()
 47 | 	s.addListBreakpointsTool()
 48 | 	s.addRemoveBreakpointTool()
 49 | 	s.addContinueTool()
 50 | 	s.addStepTool()
 51 | 	s.addStepOverTool()
 52 | 	s.addStepOutTool()
 53 | 	s.addEvalVariableTool()
 54 | 	s.addGetDebuggerOutputTool()
 55 | }
 56 | 
 57 | func (s *MCPDebugServer) addLaunchTool() {
 58 | 	launchTool := mcp.NewTool("launch",
 59 | 		mcp.WithDescription("Launch a Go application with debugging enabled"),
 60 | 		mcp.WithString("program",
 61 | 			mcp.Required(),
 62 | 			mcp.Description("Path to the Go program"),
 63 | 		),
 64 | 		mcp.WithArray("args",
 65 | 			mcp.Description("Arguments to pass to the program"),
 66 | 		),
 67 | 	)
 68 | 
 69 | 	s.server.AddTool(launchTool, s.Launch)
 70 | }
 71 | 
 72 | func (s *MCPDebugServer) addAttachTool() {
 73 | 	attachTool := mcp.NewTool("attach",
 74 | 		mcp.WithDescription("Attach to a running Go process"),
 75 | 		mcp.WithNumber("pid",
 76 | 			mcp.Required(),
 77 | 			mcp.Description("Process ID to attach to"),
 78 | 		),
 79 | 	)
 80 | 
 81 | 	s.server.AddTool(attachTool, s.Attach)
 82 | }
 83 | 
 84 | func (s *MCPDebugServer) addCloseTool() {
 85 | 	closeTool := mcp.NewTool("close",
 86 | 		mcp.WithDescription("Close the current debugging session"),
 87 | 	)
 88 | 
 89 | 	s.server.AddTool(closeTool, s.Close)
 90 | }
 91 | 
 92 | func (s *MCPDebugServer) addSetBreakpointTool() {
 93 | 	breakpointTool := mcp.NewTool("set_breakpoint",
 94 | 		mcp.WithDescription("Set a breakpoint at a specific file location"),
 95 | 		mcp.WithString("file",
 96 | 			mcp.Required(),
 97 | 			mcp.Description("Path to the file"),
 98 | 		),
 99 | 		mcp.WithNumber("line",
100 | 			mcp.Required(),
101 | 			mcp.Description("Line number"),
102 | 		),
103 | 	)
104 | 
105 | 	s.server.AddTool(breakpointTool, s.SetBreakpoint)
106 | }
107 | 
108 | func (s *MCPDebugServer) addListBreakpointsTool() {
109 | 	listBreakpointsTool := mcp.NewTool("list_breakpoints",
110 | 		mcp.WithDescription("List all currently set breakpoints"),
111 | 	)
112 | 
113 | 	s.server.AddTool(listBreakpointsTool, s.ListBreakpoints)
114 | }
115 | 
116 | func (s *MCPDebugServer) addRemoveBreakpointTool() {
117 | 	removeBreakpointTool := mcp.NewTool("remove_breakpoint",
118 | 		mcp.WithDescription("Remove a breakpoint by its ID"),
119 | 		mcp.WithNumber("id",
120 | 			mcp.Required(),
121 | 			mcp.Description("ID of the breakpoint to remove"),
122 | 		),
123 | 	)
124 | 
125 | 	s.server.AddTool(removeBreakpointTool, s.RemoveBreakpoint)
126 | }
127 | 
128 | func (s *MCPDebugServer) addDebugSourceFileTool() {
129 | 	debugTool := mcp.NewTool("debug",
130 | 		mcp.WithDescription("Debug a Go source file directly"),
131 | 		mcp.WithString("file",
132 | 			mcp.Required(),
133 | 			mcp.Description("Absolute Path to the Go source file"),
134 | 		),
135 | 		mcp.WithArray("args",
136 | 			mcp.Description("Arguments to pass to the program"),
137 | 		),
138 | 	)
139 | 
140 | 	s.server.AddTool(debugTool, s.DebugSourceFile)
141 | }
142 | 
143 | func (s *MCPDebugServer) addDebugTestTool() {
144 | 	debugTestTool := mcp.NewTool("debug_test",
145 | 		mcp.WithDescription("Debug a Go test function"),
146 | 		mcp.WithString("testfile",
147 | 			mcp.Required(),
148 | 			mcp.Description("Absolute Path to the test file"),
149 | 		),
150 | 		mcp.WithString("testname",
151 | 			mcp.Required(),
152 | 			mcp.Description("Name of the test function to debug"),
153 | 		),
154 | 		mcp.WithArray("testflags",
155 | 			mcp.Description("Optional flags to pass to go test"),
156 | 		),
157 | 	)
158 | 
159 | 	s.server.AddTool(debugTestTool, s.DebugTest)
160 | }
161 | 
162 | func (s *MCPDebugServer) addContinueTool() {
163 | 	continueTool := mcp.NewTool("continue",
164 | 		mcp.WithDescription("Continue execution until next breakpoint or program end"),
165 | 	)
166 | 
167 | 	s.server.AddTool(continueTool, s.Continue)
168 | }
169 | 
170 | func (s *MCPDebugServer) addStepTool() {
171 | 	stepTool := mcp.NewTool("step",
172 | 		mcp.WithDescription("Step into the next function call"),
173 | 	)
174 | 
175 | 	s.server.AddTool(stepTool, s.Step)
176 | }
177 | 
178 | func (s *MCPDebugServer) addStepOverTool() {
179 | 	stepOverTool := mcp.NewTool("step_over",
180 | 		mcp.WithDescription("Step over the next function call"),
181 | 	)
182 | 
183 | 	s.server.AddTool(stepOverTool, s.StepOver)
184 | }
185 | 
186 | func (s *MCPDebugServer) addStepOutTool() {
187 | 	stepOutTool := mcp.NewTool("step_out",
188 | 		mcp.WithDescription("Step out of the current function"),
189 | 	)
190 | 
191 | 	s.server.AddTool(stepOutTool, s.StepOut)
192 | }
193 | 
194 | func (s *MCPDebugServer) addEvalVariableTool() {
195 | 	evalVarTool := mcp.NewTool("eval_variable",
196 | 		mcp.WithDescription("Evaluate the value of a variable"),
197 | 		mcp.WithString("name",
198 | 			mcp.Required(),
199 | 			mcp.Description("Name of the variable to evaluate"),
200 | 		),
201 | 		mcp.WithNumber("depth",
202 | 			mcp.Description("Depth for evaluate nested structures (default: 1)"),
203 | 		),
204 | 	)
205 | 
206 | 	s.server.AddTool(evalVarTool, s.EvalVariable)
207 | }
208 | 
209 | func (s *MCPDebugServer) addGetDebuggerOutputTool() {
210 | 	outputTool := mcp.NewTool("get_debugger_output",
211 | 		mcp.WithDescription("Get captured stdout and stderr from the debugged program"),
212 | 	)
213 | 
214 | 	s.server.AddTool(outputTool, s.GetDebuggerOutput)
215 | }
216 | 
217 | func newErrorResult(format string, args ...interface{}) *mcp.CallToolResult {
218 | 	result := mcp.NewToolResultText(fmt.Sprintf("Error: "+format, args...))
219 | 	result.IsError = true
220 | 	return result
221 | }
222 | 
223 | func (s *MCPDebugServer) Launch(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
224 | 	logger.Debug("Received launch request")
225 | 
226 | 	program := request.Params.Arguments["program"].(string)
227 | 
228 | 	var args []string
229 | 	if argsVal, ok := request.Params.Arguments["args"]; ok && argsVal != nil {
230 | 		argsArray := argsVal.([]interface{})
231 | 		args = make([]string, len(argsArray))
232 | 		for i, arg := range argsArray {
233 | 			args[i] = fmt.Sprintf("%v", arg)
234 | 		}
235 | 	}
236 | 
237 | 	response := s.debugClient.LaunchProgram(program, args)
238 | 
239 | 	return newToolResultJSON(response)
240 | }
241 | 
242 | func (s *MCPDebugServer) Attach(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
243 | 	logger.Debug("Received attach request")
244 | 
245 | 	pidFloat := request.Params.Arguments["pid"].(float64)
246 | 	pid := int(pidFloat)
247 | 
248 | 	response := s.debugClient.AttachToProcess(pid)
249 | 
250 | 	return newToolResultJSON(response)
251 | }
252 | 
253 | func (s *MCPDebugServer) Close(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
254 | 	logger.Debug("Received close request")
255 | 
256 | 	response, err := s.debugClient.Close()
257 | 	if err != nil {
258 | 		logger.Error("Failed to close debug session", "error", err)
259 | 		return newErrorResult("failed to close debug session: %v", err), nil
260 | 	}
261 | 
262 | 	s.debugClient = debugger.NewClient()
263 | 
264 | 	return newToolResultJSON(response)
265 | }
266 | 
267 | func (s *MCPDebugServer) SetBreakpoint(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
268 | 	logger.Debug("Received set_breakpoint request")
269 | 
270 | 	file := request.Params.Arguments["file"].(string)
271 | 	line := int(request.Params.Arguments["line"].(float64))
272 | 
273 | 	breakpoint := s.debugClient.SetBreakpoint(file, line)
274 | 
275 | 	return newToolResultJSON(breakpoint)
276 | }
277 | 
278 | func (s *MCPDebugServer) ListBreakpoints(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
279 | 	logger.Debug("Received list_breakpoints request")
280 | 
281 | 	response := s.debugClient.ListBreakpoints()
282 | 
283 | 	return newToolResultJSON(response)
284 | }
285 | 
286 | func (s *MCPDebugServer) RemoveBreakpoint(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
287 | 	logger.Debug("Received remove_breakpoint request")
288 | 
289 | 	id := int(request.Params.Arguments["id"].(float64))
290 | 
291 | 	response := s.debugClient.RemoveBreakpoint(id)
292 | 
293 | 	return newToolResultJSON(response)
294 | }
295 | 
296 | func (s *MCPDebugServer) DebugSourceFile(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
297 | 	logger.Debug("Received debug_source_file request")
298 | 
299 | 	file := request.Params.Arguments["file"].(string)
300 | 
301 | 	var args []string
302 | 	if argsVal, ok := request.Params.Arguments["args"]; ok && argsVal != nil {
303 | 		argsArray := argsVal.([]interface{})
304 | 		args = make([]string, len(argsArray))
305 | 		for i, arg := range argsArray {
306 | 			args[i] = fmt.Sprintf("%v", arg)
307 | 		}
308 | 	}
309 | 
310 | 	response := s.debugClient.DebugSourceFile(file, args)
311 | 
312 | 	return newToolResultJSON(response)
313 | }
314 | 
315 | func (s *MCPDebugServer) Continue(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
316 | 	logger.Debug("Received continue request")
317 | 
318 | 	state := s.debugClient.Continue()
319 | 	return newToolResultJSON(state)
320 | }
321 | 
322 | func (s *MCPDebugServer) Step(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
323 | 	logger.Debug("Received step request")
324 | 
325 | 	state := s.debugClient.Step()
326 | 
327 | 	return newToolResultJSON(state)
328 | }
329 | 
330 | func (s *MCPDebugServer) StepOver(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
331 | 	logger.Debug("Received step_over request")
332 | 
333 | 	state := s.debugClient.StepOver()
334 | 
335 | 	return newToolResultJSON(state)
336 | }
337 | 
338 | func (s *MCPDebugServer) StepOut(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
339 | 	logger.Debug("Received step_out request")
340 | 
341 | 	state := s.debugClient.StepOut()
342 | 	return newToolResultJSON(state)
343 | }
344 | 
345 | func (s *MCPDebugServer) EvalVariable(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
346 | 	logger.Debug("Received evaluate_variable request")
347 | 
348 | 	name := request.Params.Arguments["name"].(string)
349 | 
350 | 	var depth int
351 | 	if depthVal, ok := request.Params.Arguments["depth"]; ok && depthVal != nil {
352 | 		depth = int(depthVal.(float64))
353 | 	} else {
354 | 		depth = 1
355 | 	}
356 | 
357 | 	response := s.debugClient.EvalVariable(name, depth)
358 | 
359 | 	return newToolResultJSON(response)
360 | }
361 | 
362 | func (s *MCPDebugServer) GetDebuggerOutput(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
363 | 	logger.Debug("Received get_debugger_output request")
364 | 
365 | 	output := s.debugClient.GetDebuggerOutput()
366 | 
367 | 	return newToolResultJSON(output)
368 | }
369 | 
370 | func (s *MCPDebugServer) DebugTest(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
371 | 	logger.Debug("Received debug_test request")
372 | 
373 | 	testfile := request.Params.Arguments["testfile"].(string)
374 | 	testname := request.Params.Arguments["testname"].(string)
375 | 
376 | 	var testflags []string
377 | 	if testflagsVal, ok := request.Params.Arguments["testflags"]; ok && testflagsVal != nil {
378 | 		testflagsArray := testflagsVal.([]interface{})
379 | 		testflags = make([]string, len(testflagsArray))
380 | 		for i, flag := range testflagsArray {
381 | 			testflags[i] = fmt.Sprintf("%v", flag)
382 | 		}
383 | 	}
384 | 
385 | 	response := s.debugClient.DebugTest(testfile, testname, testflags)
386 | 
387 | 	return newToolResultJSON(response)
388 | }
389 | 
390 | func newToolResultJSON(data interface{}) (*mcp.CallToolResult, error) {
391 | 	jsonBytes, err := json.Marshal(data)
392 | 	if err != nil {
393 | 		return newErrorResult("failed to serialize data: %v", err), nil
394 | 	}
395 | 	return mcp.NewToolResultText(string(jsonBytes)), nil
396 | }
397 | 
```

--------------------------------------------------------------------------------
/docs/PRD.md:
--------------------------------------------------------------------------------

```markdown
  1 | # MCP Go Debugger PRD
  2 | 
  3 | ## Overview
  4 | 
  5 | MCP Go Debugger is a Model Context Protocol (MCP) server that integrates the Delve debugger for Go applications into Cursor or Claude Desktop. It enables AI assistants to debug Go applications at runtime by providing debugger functionality through an MCP server interface. The implementation will be built in Go using the [mark3labs/mcp-go](https://github.com/mark3labs/mcp-go) framework with Delve embedded directly within the MCP process.
  6 | 
  7 | ## Objectives
  8 | 
  9 | - Provide AI assistants with the ability to debug Go applications at runtime
 10 | - Integrate with Delve, the Go debugger, to leverage its capabilities
 11 | - Offer a simple interface similar to the NodeJS debugger MCP
 12 | - Enable debugging actions like setting breakpoints, inspecting variables, and stepping through code
 13 | - Support both Cursor and Claude Desktop as client interfaces
 14 | - Simplify user experience by embedding Delve within the MCP process
 15 | 
 16 | ## Target Users
 17 | 
 18 | - Go developers using Cursor or Claude Desktop
 19 | - AI assistants that need to debug Go applications
 20 | 
 21 | ## Key Features
 22 | 
 23 | ### 1. Connection to Go Programs
 24 | 
 25 | - Launch Go programs directly from the MCP with debug capabilities enabled
 26 | - Attach to running Go processes that support debugging
 27 | - Debug a Go source file directly (similar to `dlv debug`) by compiling and debugging it
 28 | - Debug a single Go test function directly by compiling and running the test with the debugger
 29 | - Manage the entire debugging lifecycle within a single MCP process
 30 | - Display connection status and information
 31 | 
 32 | ### 2. Breakpoint Management
 33 | 
 34 | - Set breakpoints at specific file locations
 35 | - Set conditional breakpoints
 36 | - List active breakpoints
 37 | - Remove breakpoints
 38 | - Enable/disable breakpoints
 39 | 
 40 | ### 3. Program Control
 41 | 
 42 | - Start/stop execution
 43 | - Step into, over, and out of functions
 44 | - Continue execution until next breakpoint
 45 | - Restart program
 46 | 
 47 | ### 4. State Inspection
 48 | 
 49 | - View variable values at breakpoints
 50 | - Eval call stack
 51 | - Inspect goroutines
 52 | - View thread information
 53 | - Evaluate expressions in current context
 54 | - List all variables in current scope (local variables, function arguments, and package variables)
 55 | - Get current execution position (file name, line number, and function name)
 56 | 
 57 | ### 5. Runtime Analysis
 58 | 
 59 | - Monitor goroutine creation and completion
 60 | - Track memory allocations
 61 | - Profile CPU usage during debugging sessions
 62 | 
 63 | ## Technical Requirements
 64 | 
 65 | ### Embedding Delve
 66 | 
 67 | The MCP will embed Delve directly:
 68 | - Import Delve as a Go library/dependency
 69 | - Create and manage debug sessions programmatically
 70 | - Access Delve's API directly in-process
 71 | - Handle debugger lifecycle (start, stop, restart) within the MCP
 72 | 
 73 | ### MCP Server Implementation
 74 | 
 75 | - Implement using mark3labs/mcp-go framework
 76 | - Expose a standardized Model Context Protocol interface for AI assistants
 77 | - Provide direct access to Delve functionality via MCP tools
 78 | - Handle errors and provide meaningful feedback
 79 | 
 80 | ### Installation and Setup
 81 | 
 82 | - Provide as a compiled Go binary
 83 | - Support for standard Go installation methods (go install)
 84 | - Minimal configuration requirements
 85 | - Clear documentation for setup in both Cursor and Claude Desktop
 86 | 
 87 | ## User Experience
 88 | 
 89 | ### Setup Workflow
 90 | 
 91 | 1. Install MCP Go Debugger
 92 | 2. Configure in Cursor or Claude Desktop
 93 | 3. Use the MCP to either launch a Go application with debugging or attach to a running process
 94 | 4. Interact with the debugging session through the AI assistant
 95 | 
 96 | ### Usage Examples
 97 | 
 98 | #### Example 1: Launching and Debugging an Application
 99 | 
100 | ```
101 | User: "I want to debug my Go application in the current directory."
102 | AI: "I'll help you debug your application. Let me start it with debugging enabled."
103 | [AI uses MCP Go Debugger to launch the application]
104 | "I've started your application with debugging enabled. What part of the code would you like to eval?"
105 | ```
106 | 
107 | #### Example 2: Setting a Breakpoint
108 | 
109 | ```
110 | User: "I'm getting an error in my processTransaction function. Can you help debug it?"
111 | AI: "I'll help debug this issue. Let me set a breakpoint in the processTransaction function."
112 | [AI uses MCP Go Debugger to set breakpoint]
113 | "I've set a breakpoint at the beginning of the processTransaction function. Let's run your application and trigger the function."
114 | ```
115 | 
116 | #### Example 3: Inspecting Variables
117 | 
118 | ```
119 | User: "The application crashed when processing this request."
120 | AI: "Let me eval what's happening when the request is processed."
121 | [AI sets breakpoint and application hits it]
122 | "I can see the issue. The 'amount' variable is negative (-10.5) when it reaches line 42, but the validation check occurs later on line 65."
123 | ```
124 | 
125 | #### Example 4: Debugging a Source File Directly
126 | 
127 | ```
128 | User: "Can you debug this main.go file for me?"
129 | AI: "I'll debug your source file directly."
130 | [AI uses MCP Go Debugger to compile and debug the file]
131 | "I've compiled and started debugging main.go. Let me set a breakpoint in the main function to eval how the program executes."
132 | ```
133 | 
134 | #### Example 5: Debugging a Single Test
135 | 
136 | ```
137 | User: "I need to debug the TestCalculateTotal function in my order_test.go file."
138 | AI: "I'll help you debug that specific test function."
139 | [AI uses MCP Go Debugger to compile and debug the test]
140 | "I've compiled and started debugging the TestCalculateTotal test function. Let me set a breakpoint at the beginning of the test to see how it executes."
141 | ```
142 | 
143 | ## Implementation Plan
144 | 
145 | ### Phase 1: Core Functionality
146 | 
147 | - Set up MCP server using mark3labs/mcp-go
148 | - Embed Delve as a library dependency
149 | - Implement program launch and attach capabilities
150 | - Implement basic debugging commands (breakpoints, step, continue)
151 | - Simple variable inspection
152 | - Initial testing with sample Go applications
153 | 
154 | ### Phase 2: Enhanced Features
155 | 
156 | - Conditional breakpoints
157 | - Advanced state inspection
158 | - Goroutine tracking
159 | - Error analysis and suggestions
160 | - Improved diagnostics and debugging information
161 | 
162 | ### Phase 3: Optimization and Refinement
163 | 
164 | - Performance improvements
165 | - UX enhancements
166 | - Extended documentation
167 | - Integration with additional tools
168 | - Support for complex debugging scenarios
169 | 
170 | ## Success Metrics
171 | 
172 | - Number of successful debugging sessions
173 | - User feedback on debugging effectiveness
174 | - Time saved in debugging complex issues
175 | - Adoption rate among Go developers using Cursor/Claude Desktop
176 | - Reduction in context switching between different tools
177 | 
178 | ## Limitations and Constraints
179 | 
180 | - Requires Go program to be compiled with debug information
181 | - May have performance impact on the running Go application
182 | - Can't debug optimized builds effectively
183 | - Some features of Delve may not be accessible through the MCP interface
184 | - Subject to limitations of mark3labs/mcp-go framework
185 | - May require specific permissions to attach to processes
186 | 
187 | ## Appendix
188 | 
189 | ### Installation Instructions
190 | 
191 | #### Cursor
192 | 
193 | 1. Add to Cursor (`~/.cursor/mcp.json`):
194 | ```json
195 | {
196 |   "mcpServers": {
197 |     "go-debugger": {
198 |       "command": "mcp-go-debugger",
199 |       "args": []
200 |     }
201 |   }
202 | }
203 | ```
204 | 
205 | 2. Verify connection in Cursor interface
206 | 
207 | #### Claude Desktop
208 | 
209 | 1. Add to Claude Desktop:
210 | ```
211 | claude mcp add go-debugger mcp-go-debugger
212 | ```
213 | 
214 | 2. Verify connection:
215 | ```
216 | /mcp
217 | ```
218 | 
219 | ### Usage Instructions
220 | 
221 | 1. Launch and debug a Go program directly through the MCP:
222 | ```
223 | User: "Debug my Go application main.go"
224 | AI: [Uses MCP to launch the application with debugging enabled]
225 | ```
226 | 
227 | 2. Attach to a running Go process:
228 | ```
229 | User: "Attach to my running Go server with PID 12345"
230 | AI: [Uses MCP to attach to the running process for debugging]
231 | ```
232 | 
233 | 3. Debug a Go source file directly:
234 | ```
235 | User: "Debug this main.go file"
236 | AI: [Uses MCP to compile and debug the source file directly]
237 | ```
238 | 
239 | 4. Ask the AI assistant to debug specific issues using the go-debugger MCP
240 | 
241 | ### Implementation Example
242 | 
243 | Basic MCP server implementation using mark3labs/mcp-go with embedded Delve:
244 | 
245 | ```go
246 | package main
247 | 
248 | import (
249 | 	"context"
250 | 	"fmt"
251 | 	"log"
252 | 	"strconv"
253 | 
254 | 	"github.com/go-delve/delve/pkg/terminal"
255 | 	"github.com/go-delve/delve/service"
256 | 	"github.com/go-delve/delve/service/api"
257 | 	"github.com/go-delve/delve/service/debugger"
258 | 	"github.com/mark3labs/mcp-go/mcp"
259 | 	"github.com/mark3labs/mcp-go/server"
260 | )
261 | 
262 | func main() {
263 | 	// Create MCP server
264 | 	s := server.NewMCPServer(
265 | 		"Go Debugger MCP",
266 | 		"1.0.0",
267 | 	)
268 | 
269 | 	// Global variable to hold the debug session
270 | 	var debugClient *service.Client
271 | 	
272 | 	// Add launch tool
273 | 	launchTool := mcp.NewTool("launch",
274 | 		mcp.WithDescription("Launch a Go application with debugging enabled"),
275 | 		mcp.WithString("program",
276 | 			mcp.Required(),
277 | 			mcp.Description("Path to the Go program"),
278 | 		),
279 | 	)
280 | 	
281 | 	s.AddTool(launchTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
282 | 		program := request.Params.Arguments["program"].(string)
283 | 		
284 | 		// Set up and launch the debugger
285 | 		// This is a simplified example - real implementation would handle more config options
286 | 		config := &service.Config{
287 | 			ProcessArgs: []string{program},
288 | 			APIVersion:  2,
289 | 		}
290 | 		
291 | 		var err error
292 | 		debugClient, err = service.NewClient(config)
293 | 		if err != nil {
294 | 			return nil, fmt.Errorf("failed to create debug client: %v", err)
295 | 		}
296 | 		
297 | 		return mcp.NewToolResultText(fmt.Sprintf("Successfully launched %s with debugging enabled", program)), nil
298 | 	})
299 | 	
300 | 	// Add breakpoint tool
301 | 	breakpointTool := mcp.NewTool("set_breakpoint",
302 | 		mcp.WithDescription("Set a breakpoint at a specific file location"),
303 | 		mcp.WithString("file",
304 | 			mcp.Required(),
305 | 			mcp.Description("Path to the file"),
306 | 		),
307 | 		mcp.WithNumber("line",
308 | 			mcp.Required(),
309 | 			mcp.Description("Line number"),
310 | 		),
311 | 	)
312 | 
313 | 	// Add tool handler
314 | 	s.AddTool(breakpointTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
315 | 		if debugClient == nil {
316 | 			return nil, fmt.Errorf("no active debug session, please launch a program first")
317 | 		}
318 | 		
319 | 		file := request.Params.Arguments["file"].(string)
320 | 		line := int(request.Params.Arguments["line"].(float64))
321 | 		
322 | 		bp, err := debugClient.CreateBreakpoint(&api.Breakpoint{
323 | 			File: file,
324 | 			Line: line,
325 | 		})
326 | 		
327 | 		if err != nil {
328 | 			return nil, fmt.Errorf("failed to set breakpoint: %v", err)
329 | 		}
330 | 		
331 | 		return mcp.NewToolResultText(fmt.Sprintf("Breakpoint set at %s:%d (ID: %d)", file, line, bp.ID)), nil
332 | 	})
333 | 
334 | 	// Add other tools...
335 | 
336 | 	// Start the stdio server
337 | 	if err := server.ServeStdio(s); err != nil {
338 | 		log.Fatalf("Server error: %v\n", err)
339 | 	}
340 | }
341 | ```
342 | 
343 | ### API Reference
344 | 
345 | Exposed MCP commands:
346 | - `launch`: Launch a Go program with debugging enabled
347 | - `attach`: Attach to a running Go process
348 | - `debug`: Compile and debug a Go source file directly
349 | - `debug_test`: Compile and debug a Go test function
350 | - `set_breakpoint`: Set a breakpoint at a specific location
351 | - `list_breakpoints`: List all active breakpoints
352 | - `remove_breakpoint`: Remove a specific breakpoint
353 | - `continue`: Continue execution
354 | - `step`: Step to next source line
355 | - `step_out`: Step out of current function
356 | - `step_over`: Step over current line
357 | - `eval_variable`: Eval value of a variable
358 | - `list_scope_variables`: List all variables in current scope
359 | - `list_goroutines`: List all goroutines
360 | - `stack_trace`: Show stack trace at current position
361 | - `evaluate`: Evaluate an expression in current context
```

--------------------------------------------------------------------------------
/docs/Response-Improvements.md:
--------------------------------------------------------------------------------

```markdown
  1 | # MCP Go Debugger Response Improvements
  2 | 
  3 | ## Overview
  4 | 
  5 | This document outlines research and improvements for the MCP Go Debugger tool responses to provide better context and information for LLM interactions. The goal is to standardize response formats and include comprehensive debugging state information that helps LLMs better understand and interact with the debugging process.
  6 | 
  7 | ## Current Challenges
  8 | 
  9 | 1. Inconsistent response formats across different tools
 10 | 2. Limited context about debugger state
 11 | 3. Missing temporal information about operations
 12 | 4. Incomplete variable and execution context
 13 | 5. Empty or missing stdout/stderr capture
 14 | 6. Inconsistent timestamp formatting
 15 | 7. Poor error handling for program termination
 16 | 8. Missing variable change tracking between steps
 17 | 9. Limited breakpoint categorization and information
 18 | 
 19 | ## Test Output Analysis
 20 | 
 21 | Analysis of the TestDebugWorkflow test output reveals several issues:
 22 | 
 23 | 1. When getting debugger output, stdout/stderr are empty with an error message: "failed to get state: Process has exited with status 0"
 24 | 2. Several responses have null values for fields like ScopeVariables even when variables should be available
 25 | 3. Some responses show timestamp as "0001-01-01T00:00:00Z" (zero value) while others have valid timestamps
 26 | 4. Error handling for process exit is suboptimal: "continue command failed: Process has exited with status 0"
 27 | 5. System breakpoints (IDs -1 and -2) are mixed with user breakpoints without clear differentiation
 28 | 6. The ChangedVars field is consistently null even when stepping through code that modifies variables
 29 | 7. Debug context has inconsistent information, with some fields populated in some responses but empty in others
 30 | 
 31 | ## Todo List
 32 | 
 33 | 1. **Improve Output Capture**
 34 |    - Fix empty stdout/stderr issue when retrieving debugger output
 35 |    - Implement buffering of program output during debugging session
 36 |    - Add structured output capture with timestamps
 37 |    - Include outputSummary for better context
 38 | 
 39 | 2. **Standardize Response Context**
 40 |    - Ensure all responses include complete debug context
 41 |    - Fix inconsistent timestamp formatting (eliminate zero values)
 42 |    - Add lastOperation field consistently across all responses
 43 |    - Standardize context object structure across all tools
 44 | 
 45 | 3. **Enhance Variable Information**
 46 |    - Populate ScopeVariables array with all variables in current scope
 47 |    - Track variable changes between steps in changedVars field
 48 |    - Improve variable summaries with human-readable descriptions
 49 |    - Add support for examining complex data structures
 50 | 
 51 | 4. **Improve Error Handling**
 52 |    - Handle program termination gracefully
 53 |    - Provide better context when processes exit
 54 |    - Include clear error messages in context
 55 |    - Create specific response types for different error conditions
 56 | 
 57 | 5. **Refine Breakpoint Management**
 58 |    - Better categorize system vs. user breakpoints
 59 |    - Enhance breakpoint information with descriptions
 60 |    - Track and expose breakpoint hit counts consistently
 61 |    - Add support for conditional breakpoints
 62 | 
 63 | 6. **Enrich Step Execution Feedback**
 64 |    - Add changedVars information when stepping through code
 65 |    - Provide clearer fromLocation and toLocation details
 66 |    - Include operation summaries for each step
 67 |    - Detect entry/exit from functions
 68 | 
 69 | 7. **Implement Cross-Reference Information**
 70 |    - Add references between related variables
 71 |    - Connect variables to their containing functions
 72 |    - Link breakpoints to relevant variables
 73 |    - Create navigation hints between related code elements
 74 | 
 75 | 8. **Add Execution Context**
 76 |    - Include thread and goroutine information
 77 |    - Provide stop reasons and next steps suggestions
 78 |    - Add human-readable summaries of program state
 79 |    - Include stack trace information where relevant
 80 | 
 81 | 9. **Clean Up Response Format**
 82 |    - Remove internal Delve data from JSON output
 83 |    - Ensure consistent structure across all response types
 84 |    - Maintain backward compatibility while improving
 85 |    - Use pointer types for optional fields to reduce null values
 86 | 
 87 | 10. **Enhance Human and LLM Readability**
 88 |     - Add summary fields to all major components
 89 |     - Ensure all location information includes readable context
 90 |     - Provide clear temporal information about debugging state
 91 |     - Create more descriptive operation names
 92 | 
 93 | ## Proposed Response Format
 94 | 
 95 | ### Response Types
 96 | 
 97 | Example JSON responses for each type:
 98 | 
 99 | ```json
100 | // Debug Context Example
101 | {
102 |     "currentPosition": {
103 |         "file": "/path/to/main.go",
104 |         "line": 42,
105 |         "function": "main.processRequest",
106 |         "package": "main",
107 |         "summary": "Inside main.processRequest handling HTTP request"
108 |     },
109 |     "timestamp": "2024-03-24T15:04:05Z",
110 |     "lastOperation": "step_over",
111 |     "errorMessage": "",
112 |     "stopReason": "breakpoint hit in main.processRequest",
113 |     "threads": [
114 |         {
115 |             "id": 1,
116 |             "status": "stopped at breakpoint",
117 |             "location": {
118 |                 "file": "/path/to/main.go",
119 |                 "line": 42,
120 |                 "function": "main.processRequest",
121 |                 "package": "main",
122 |                 "summary": "Processing HTTP request"
123 |             },
124 |             "active": true,
125 |             "summary": "Main thread stopped at breakpoint in request handler"
126 |         }
127 |     ],
128 |     "goroutine": {
129 |         "id": 1,
130 |         "status": "running",
131 |         "waitReason": "waiting for network response",
132 |         "location": {
133 |             "file": "/path/to/main.go",
134 |             "line": 42,
135 |             "function": "main.processRequest",
136 |             "package": "main",
137 |             "summary": "Processing HTTP request"
138 |         },
139 |         "createdAt": {
140 |             "file": "/path/to/main.go",
141 |             "line": 30,
142 |             "function": "main.startWorker",
143 |             "package": "main",
144 |             "summary": "Worker goroutine creation point"
145 |         },
146 |         "userLocation": {
147 |             "file": "/path/to/main.go",
148 |             "line": 42,
149 |             "function": "main.processRequest",
150 |             "package": "main",
151 |             "summary": "User code location"
152 |         },
153 |         "summary": "Main worker goroutine processing HTTP request"
154 |     },
155 |     "operationSummary": "Stepped over function call in main.processRequest"
156 | }
157 | 
158 | // Variable Example
159 | {
160 |     "name": "request",
161 |     "value": "*http.Request{Method:\"GET\", URL:\"/api/users\"}",
162 |     "type": "*http.Request",
163 |     "summary": "HTTP GET request for /api/users endpoint",
164 |     "scope": "local",
165 |     "kind": "pointer",
166 |     "typeInfo": "Pointer to HTTP request structure",
167 |     "references": ["context", "response", "params"]
168 | }
169 | 
170 | // Breakpoint Example
171 | {
172 |     "id": 1,
173 |     "status": "enabled",
174 |     "location": {
175 |         "file": "/path/to/main.go",
176 |         "line": 42,
177 |         "function": "main.processRequest",
178 |         "package": "main",
179 |         "summary": "Start of request processing"
180 |     },
181 |     "description": "Break before processing API request",
182 |     "variables": ["request", "response", "err"],
183 |     "package": "main",
184 |     "condition": "request.Method == \"POST\"",
185 |     "hitCount": 5,
186 |     "lastHitInfo": "Last hit on POST /api/users at 15:04:05"
187 | }
188 | 
189 | // Function Example
190 | {
191 |     "name": "processRequest",
192 |     "signature": "func processRequest(w http.ResponseWriter, r *http.Request) error",
193 |     "parameters": ["w http.ResponseWriter", "r *http.Request"],
194 |     "returnType": "error",
195 |     "package": "main",
196 |     "description": "HTTP request handler for API endpoints",
197 |     "location": {
198 |         "file": "/path/to/main.go",
199 |         "line": 42,
200 |         "function": "main.processRequest",
201 |         "package": "main",
202 |         "summary": "Main request processing function"
203 |     }
204 | }
205 | 
206 | // DebuggerState Example
207 | {
208 |     "status": "stopped at breakpoint",
209 |     "currentThread": {
210 |         "id": 1,
211 |         "status": "stopped at breakpoint",
212 |         "location": {
213 |             "file": "/path/to/main.go",
214 |             "line": 42,
215 |             "function": "main.processRequest",
216 |             "package": "main",
217 |             "summary": "Processing HTTP request"
218 |         },
219 |         "active": true,
220 |         "summary": "Main thread stopped at breakpoint"
221 |     },
222 |     "currentGoroutine": {
223 |         "id": 1,
224 |         "status": "running",
225 |         "location": {
226 |             "file": "/path/to/main.go",
227 |             "line": 42,
228 |             "function": "main.processRequest",
229 |             "package": "main",
230 |             "summary": "Processing HTTP request"
231 |         },
232 |         "summary": "Main goroutine processing request"
233 |     },
234 |     "reason": "Hit breakpoint in request handler",
235 |     "nextSteps": [
236 |         "eval request variable",
237 |         "step into processRequest",
238 |         "continue execution"
239 |     ],
240 |     "summary": "Program paused at start of request processing"
241 | }
242 | 
243 | // Launch Response Example
244 | {
245 |     "status": "success",
246 |     "context": { /* DebugContext object as shown above */ },
247 |     "programName": "./myapp",
248 |     "cmdLine": ["./myapp", "--debug"],
249 |     "buildInfo": {
250 |         "package": "main",
251 |         "goVersion": "go1.21.0"
252 |     }
253 | }
254 | 
255 | // Breakpoint Response Example
256 | {
257 |     "status": "success",
258 |     "context": { /* DebugContext object */ },
259 |     "breakpoint": { /* Breakpoint object as shown above */ },
260 |     "allBreakpoints": [
261 |         { /* Breakpoint object */ }
262 |     ],
263 |     "scopeVariables": [
264 |         { /* Variable object */ }
265 |     ]
266 | }
267 | 
268 | // Step Response Example
269 | {
270 |     "status": "success",
271 |     "context": { /* DebugContext object */ },
272 |     "stepType": "over",
273 |     "fromLocation": {
274 |         "file": "/path/to/main.go",
275 |         "line": 42,
276 |         "function": "main.processRequest",
277 |         "package": "main",
278 |         "summary": "Before function call"
279 |     },
280 |     "toLocation": {
281 |         "file": "/path/to/main.go",
282 |         "line": 43,
283 |         "function": "main.processRequest",
284 |         "package": "main",
285 |         "summary": "After function call"
286 |     },
287 |     "changedVars": [
288 |         { /* Variable object showing changes */ }
289 |     ]
290 | }
291 | 
292 | // Eval Variable Response Example
293 | {
294 |     "status": "success",
295 |     "context": { /* DebugContext object */ },
296 |     "variable": { /* Variable object as shown above */ },
297 |     "scopeInfo": {
298 |         "function": "main.processRequest",
299 |         "package": "main",
300 |         "locals": ["request", "response", "err"]
301 |     }
302 | }
303 | 
304 | // Continue Response Example
305 | {
306 |     "status": "success",
307 |     "context": { /* DebugContext object */ },
308 |     "stoppedAt": {
309 |         "file": "/path/to/main.go",
310 |         "line": 50,
311 |         "function": "main.processRequest",
312 |         "package": "main",
313 |         "summary": "After processing request"
314 |     },
315 |     "stopReason": "breakpoint hit",
316 |     "hitBreakpoint": { /* Breakpoint object */ }
317 | }
318 | 
319 | // Close Response Example
320 | {
321 |     "status": "success",
322 |     "context": { /* DebugContext object */ },
323 |     "exitCode": 0,
324 |     "summary": "Debug session ended normally after processing 100 requests"
325 | }
326 | 
327 | // Debugger Output Response Example
328 | {
329 |     "status": "success",
330 |     "context": { /* DebugContext object */ },
331 |     "stdout": "Processing request ID: 1\nRequest completed successfully\n",
332 |     "stderr": "",
333 |     "outputSummary": "Successfully processed request with ID 1"
334 | }
335 | ```
336 | 
337 | Key Features of the JSON Format:
338 | 1. Internal Delve data stored in non-JSON fields
339 | 2. All exposed fields are human-readable and LLM-friendly
340 | 3. Consistent structure with clear field names
341 | 4. Rich contextual information in summaries
342 | 5. Cross-references between related data
343 | 6. Temporal information about operations
344 | 7. Complete debugging context
345 | 
346 | The JSON format makes it easy to:
347 | 1. Parse and process debugging information
348 | 2. Generate human-readable summaries
349 | 3. Track debugging state changes
350 | 4. Understand the context of operations
351 | 5. Follow program execution flow
352 | 6. Monitor variable changes
353 | 7. Track breakpoint interactions
354 | 
355 | Key Changes:
356 | 1. Removed embedded Delve types in favor of internal fields
357 | 2. Consistent naming across all types
358 | 3. Added summaries to all relevant types
359 | 4. Enhanced cross-referencing between related data
360 | 5. Improved human-readable descriptions
361 | 6. Better temporal information
362 | 7. More consistent structure across all types
363 | 
364 | Benefits:
365 | 1. Cleaner JSON output without internal Delve data
366 | 2. More consistent and predictable structure
367 | 3. Better readability for humans and LLMs
368 | 4. Maintained access to Delve functionality
369 | 5. Improved debugging context
370 | 6. Better relationship tracking
371 | 7. Enhanced temporal awareness
372 | 
373 | ## Implementation Priority
374 | 
375 | Based on test output analysis, these are the highest priority improvements:
376 | 
377 | 1. Fix output capture in GetDebuggerOutput
378 | 2. Ensure consistent timestamp handling
379 | 3. Improve error handling for program termination
380 | 4. Populate ScopeVariables and ChangedVars
381 | 5. Better categorize breakpoints
382 | 6. Standardize context object across all responses
383 | 7. Enhance step execution feedback
384 | 
385 | ## Modified Tools
386 | 
387 | The following core tools will be updated to use the new response format:
388 | 
389 | 1. `launch` - Program launch with debugging
390 | 2. `attach` - Attach to running process
391 | 3. `close` - End debug session
392 | 4. `set_breakpoint` - Set breakpoint
393 | 5. `remove_breakpoint` - Remove breakpoint
394 | 6. `list_breakpoints` - List all breakpoints
395 | 7. `debug_source_file` - Debug source file
396 | 8. `debug_test` - Debug test
397 | 9. `continue` - Continue execution
398 | 10. `step` - Step into
399 | 11. `step_over` - Step over
400 | 12. `step_out` - Step out
401 | 13. `eval_variable` - Eval variable
402 | 14. `get_debugger_output` - Get program output
403 | 
404 | 
405 | ## Implementation Notes
406 | 
407 | 1. All types store Delve data in internal fields with `json:"-"` tag
408 | 2. Use pointer types for optional fields
409 | 3. Implement conversion functions between Delve and LLM-friendly types
410 | 4. Maintain consistent naming conventions
411 | 5. Include rich contextual information
412 | 6. Preserve all debugging capabilities
413 | 7. Focus on human-readable output
```

--------------------------------------------------------------------------------
/pkg/debugger/program.go:
--------------------------------------------------------------------------------

```go
  1 | package debugger
  2 | 
  3 | import (
  4 | 	"context"
  5 | 	"fmt"
  6 | 	"github.com/go-delve/delve/service/api"
  7 | 	"net"
  8 | 	"os"
  9 | 	"path/filepath"
 10 | 	"regexp"
 11 | 	"time"
 12 | 
 13 | 	"github.com/go-delve/delve/pkg/gobuild"
 14 | 	"github.com/go-delve/delve/pkg/logflags"
 15 | 	"github.com/go-delve/delve/pkg/proc"
 16 | 	"github.com/go-delve/delve/service"
 17 | 	"github.com/go-delve/delve/service/debugger"
 18 | 	"github.com/go-delve/delve/service/rpc2"
 19 | 	"github.com/go-delve/delve/service/rpccommon"
 20 | 	"github.com/sunfmin/mcp-go-debugger/pkg/logger"
 21 | 	"github.com/sunfmin/mcp-go-debugger/pkg/types"
 22 | )
 23 | 
 24 | // LaunchProgram starts a new program with debugging enabled
 25 | func (c *Client) LaunchProgram(program string, args []string) types.LaunchResponse {
 26 | 	if c.client != nil {
 27 | 		return c.createLaunchResponse(nil, program, args, fmt.Errorf("debug session already active"))
 28 | 	}
 29 | 
 30 | 	logger.Debug("Starting LaunchProgram for %s", program)
 31 | 
 32 | 	// Ensure program file exists and is executable
 33 | 	absPath, err := filepath.Abs(program)
 34 | 	if err != nil {
 35 | 		return c.createLaunchResponse(nil, program, args, fmt.Errorf("failed to get absolute path: %v", err))
 36 | 	}
 37 | 
 38 | 	if _, err := os.Stat(absPath); os.IsNotExist(err) {
 39 | 		return c.createLaunchResponse(nil, program, args, fmt.Errorf("program file not found: %s", absPath))
 40 | 	}
 41 | 
 42 | 	// Get an available port for the debug server
 43 | 	port, err := getFreePort()
 44 | 	if err != nil {
 45 | 		return c.createLaunchResponse(nil, program, args, fmt.Errorf("failed to find available port: %v", err))
 46 | 	}
 47 | 
 48 | 	// Configure Delve logging
 49 | 	logflags.Setup(false, "", "")
 50 | 
 51 | 	// Create a listener for the debug server
 52 | 	listener, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", port))
 53 | 	if err != nil {
 54 | 		return c.createLaunchResponse(nil, program, args, fmt.Errorf("couldn't start listener: %s", err))
 55 | 	}
 56 | 
 57 | 	// Create pipes for stdout and stderr
 58 | 	stdoutReader, stdoutRedirect, err := proc.Redirector()
 59 | 	if err != nil {
 60 | 		return c.createLaunchResponse(nil, program, args, fmt.Errorf("failed to create stdout redirector: %v", err))
 61 | 	}
 62 | 
 63 | 	stderrReader, stderrRedirect, err := proc.Redirector()
 64 | 	if err != nil {
 65 | 		stdoutRedirect.File.Close()
 66 | 		return c.createLaunchResponse(nil, program, args, fmt.Errorf("failed to create stderr redirector: %v", err))
 67 | 	}
 68 | 
 69 | 	// Create Delve config
 70 | 	config := &service.Config{
 71 | 		Listener:    listener,
 72 | 		APIVersion:  2,
 73 | 		AcceptMulti: true,
 74 | 		ProcessArgs: append([]string{absPath}, args...),
 75 | 		Debugger: debugger.Config{
 76 | 			WorkingDir:     "",
 77 | 			Backend:        "default",
 78 | 			CheckGoVersion: true,
 79 | 			DisableASLR:    true,
 80 | 			Stdout:         stdoutRedirect,
 81 | 			Stderr:         stderrRedirect,
 82 | 		},
 83 | 	}
 84 | 
 85 | 	// Start goroutines to capture output
 86 | 	go c.captureOutput(stdoutReader, "stdout")
 87 | 	go c.captureOutput(stderrReader, "stderr")
 88 | 
 89 | 	// Create and start the debugging server
 90 | 	server := rpccommon.NewServer(config)
 91 | 	if server == nil {
 92 | 		return c.createLaunchResponse(nil, program, args, fmt.Errorf("failed to create debug server"))
 93 | 	}
 94 | 
 95 | 	c.server = server
 96 | 
 97 | 	// Create a channel to signal when the server is ready or fails
 98 | 	serverReady := make(chan error, 1)
 99 | 
100 | 	// Start server in a goroutine
101 | 	go func() {
102 | 		err := server.Run()
103 | 		if err != nil {
104 | 			logger.Debug("Debug server error: %v", err)
105 | 			serverReady <- err
106 | 		}
107 | 	}()
108 | 
109 | 	// Try to connect to the server with a timeout
110 | 	addr := listener.Addr().String()
111 | 	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
112 | 	defer cancel()
113 | 
114 | 	// Try to connect repeatedly until timeout
115 | 	var connected bool
116 | 	for !connected {
117 | 		select {
118 | 		case <-ctx.Done():
119 | 			return c.createLaunchResponse(nil, program, args, fmt.Errorf("timed out waiting for debug server to start"))
120 | 		case err := <-serverReady:
121 | 			return c.createLaunchResponse(nil, program, args, fmt.Errorf("debug server failed to start: %v", err))
122 | 		default:
123 | 			client := rpc2.NewClient(addr)
124 | 			state, err := client.GetState()
125 | 			if err == nil && state != nil {
126 | 				c.client = client
127 | 				c.target = absPath
128 | 				connected = true
129 | 
130 | 				return c.createLaunchResponse(state, program, args, nil)
131 | 			}
132 | 			time.Sleep(100 * time.Millisecond)
133 | 		}
134 | 	}
135 | 
136 | 	return c.createLaunchResponse(nil, program, args, fmt.Errorf("failed to launch program"))
137 | }
138 | 
139 | // AttachToProcess attaches to an existing process with the given PID
140 | func (c *Client) AttachToProcess(pid int) types.AttachResponse {
141 | 	if c.client != nil {
142 | 		return c.createAttachResponse(nil, pid, "", nil, fmt.Errorf("debug session already active"))
143 | 	}
144 | 
145 | 	logger.Debug("Starting AttachToProcess for PID %d", pid)
146 | 
147 | 	// Get an available port for the debug server
148 | 	port, err := getFreePort()
149 | 	if err != nil {
150 | 		return c.createAttachResponse(nil, pid, "", nil, fmt.Errorf("failed to find available port: %v", err))
151 | 	}
152 | 
153 | 	logger.Debug("Setting up Delve logging")
154 | 	// Configure Delve logging
155 | 	logflags.Setup(false, "", "")
156 | 
157 | 	logger.Debug("Creating listener on port %d", port)
158 | 	// Create a listener for the debug server
159 | 	listener, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", port))
160 | 	if err != nil {
161 | 		return c.createAttachResponse(nil, pid, "", nil, fmt.Errorf("couldn't start listener: %s", err))
162 | 	}
163 | 
164 | 	// Note: When attaching to an existing process, we can't easily redirect its stdout/stderr
165 | 	// as those file descriptors are already connected. Output capture is limited for attach mode.
166 | 	logger.Debug("Note: Output redirection is limited when attaching to an existing process")
167 | 
168 | 	logger.Debug("Creating Delve config for attach")
169 | 	// Create Delve config for attaching to process
170 | 	config := &service.Config{
171 | 		Listener:    listener,
172 | 		APIVersion:  2,
173 | 		AcceptMulti: true,
174 | 		ProcessArgs: []string{},
175 | 		Debugger: debugger.Config{
176 | 			AttachPid:      pid,
177 | 			Backend:        "default",
178 | 			CheckGoVersion: true,
179 | 			DisableASLR:    true,
180 | 		},
181 | 	}
182 | 
183 | 	logger.Debug("Creating debug server")
184 | 	// Create and start the debugging server with attach to PID
185 | 	server := rpccommon.NewServer(config)
186 | 	if server == nil {
187 | 		return c.createAttachResponse(nil, pid, "", nil, fmt.Errorf("failed to create debug server"))
188 | 	}
189 | 
190 | 	c.server = server
191 | 
192 | 	// Create a channel to signal when the server is ready or fails
193 | 	serverReady := make(chan error, 1)
194 | 
195 | 	logger.Debug("Starting debug server in goroutine")
196 | 	// Start server in a goroutine
197 | 	go func() {
198 | 		logger.Debug("Running server")
199 | 		err := server.Run()
200 | 		if err != nil {
201 | 			logger.Debug("Debug server error: %v", err)
202 | 			serverReady <- err
203 | 		}
204 | 		logger.Debug("Server run completed")
205 | 	}()
206 | 
207 | 	logger.Debug("Waiting for server to start")
208 | 
209 | 	// Try to connect to the server with a timeout
210 | 	addr := listener.Addr().String()
211 | 
212 | 	// Wait up to 3 seconds for server to be available
213 | 	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
214 | 	defer cancel()
215 | 
216 | 	// Try to connect repeatedly until timeout
217 | 	connected := false
218 | 	for !connected {
219 | 		select {
220 | 		case <-ctx.Done():
221 | 			// Timeout reached
222 | 			return c.createAttachResponse(nil, pid, "", nil, fmt.Errorf("timed out waiting for debug server to start"))
223 | 		case err := <-serverReady:
224 | 			// Server reported an error
225 | 			return c.createAttachResponse(nil, pid, "", nil, fmt.Errorf("debug server failed to start: %v", err))
226 | 		default:
227 | 			// Try to connect
228 | 			client := rpc2.NewClient(addr)
229 | 			// Make a simple API call to test connection
230 | 			state, err := client.GetState()
231 | 			if err == nil && state != nil {
232 | 				// Connection successful
233 | 				c.client = client
234 | 				c.pid = pid
235 | 				connected = true
236 | 				logger.Debug("Successfully attached to process with PID: %d", pid)
237 | 
238 | 				// Get initial state
239 | 				return c.createAttachResponse(state, pid, "", nil, nil)
240 | 			} else {
241 | 				// Failed, wait briefly and retry
242 | 				time.Sleep(100 * time.Millisecond)
243 | 			}
244 | 		}
245 | 	}
246 | 
247 | 	return c.createAttachResponse(nil, pid, "", nil, fmt.Errorf("failed to attach to process"))
248 | }
249 | 
250 | // Close terminates the debug session
251 | func (c *Client) Close() (*types.CloseResponse, error) {
252 | 	if c.client == nil {
253 | 		return &types.CloseResponse{
254 | 			Status: "success",
255 | 			Context: types.DebugContext{
256 | 				Timestamp: time.Now(),
257 | 				Operation: "close",
258 | 			},
259 | 			Summary: "No active debug session to close",
260 | 		}, nil
261 | 	}
262 | 
263 | 	// Signal to stop output capturing goroutines
264 | 	close(c.stopOutput)
265 | 
266 | 	// Create a context with timeout to prevent indefinite hanging
267 | 	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
268 | 	defer cancel()
269 | 
270 | 	// Create error channel
271 | 	errChan := make(chan error, 1)
272 | 
273 | 	// Attempt to detach from the debugger in a separate goroutine
274 | 	go func() {
275 | 		err := c.client.Detach(true)
276 | 		if err != nil {
277 | 			logger.Debug("Warning: Failed to detach from debugged process: %v", err)
278 | 		}
279 | 		errChan <- err
280 | 	}()
281 | 
282 | 	// Wait for completion or timeout
283 | 	var detachErr error
284 | 	select {
285 | 	case detachErr = <-errChan:
286 | 		// Operation completed successfully
287 | 	case <-ctx.Done():
288 | 		logger.Debug("Warning: Detach operation timed out after 5 seconds")
289 | 		detachErr = ctx.Err()
290 | 	}
291 | 
292 | 	// Reset the client
293 | 	c.client = nil
294 | 
295 | 	// Clean up the debug binary if it exists
296 | 	if c.target != "" {
297 | 		gobuild.Remove(c.target)
298 | 		c.target = ""
299 | 	}
300 | 
301 | 	// Create a new channel for server stop operations
302 | 	stopChan := make(chan error, 1)
303 | 
304 | 	// Stop the debug server if it's running
305 | 	if c.server != nil {
306 | 		go func() {
307 | 			err := c.server.Stop()
308 | 			if err != nil {
309 | 				logger.Debug("Warning: Failed to stop debug server: %v", err)
310 | 			}
311 | 			stopChan <- err
312 | 		}()
313 | 
314 | 		// Wait for completion or timeout
315 | 		select {
316 | 		case <-stopChan:
317 | 			// Operation completed
318 | 		case <-time.After(5 * time.Second):
319 | 			logger.Debug("Warning: Server stop operation timed out after 5 seconds")
320 | 		}
321 | 	}
322 | 
323 | 	// Create debug context
324 | 	debugContext := types.DebugContext{
325 | 		Timestamp: time.Now(),
326 | 		Operation: "close",
327 | 	}
328 | 
329 | 	// Get exit code
330 | 	exitCode := 0
331 | 	if detachErr != nil {
332 | 		exitCode = 1
333 | 	}
334 | 
335 | 	// Create close response
336 | 	response := &types.CloseResponse{
337 | 		Status:   "success",
338 | 		Context:  debugContext,
339 | 		ExitCode: exitCode,
340 | 		Summary:  fmt.Sprintf("Debug session closed with exit code %d", exitCode),
341 | 	}
342 | 
343 | 	logger.Debug("Close response: %+v", response)
344 | 	return response, detachErr
345 | }
346 | 
347 | // DebugSourceFile compiles and debugs a Go source file
348 | func (c *Client) DebugSourceFile(sourceFile string, args []string) types.DebugSourceResponse {
349 | 	if c.client != nil {
350 | 		return c.createDebugSourceResponse(nil, sourceFile, "", args, fmt.Errorf("debug session already active"))
351 | 	}
352 | 
353 | 	// Ensure source file exists
354 | 	absPath, err := filepath.Abs(sourceFile)
355 | 	if err != nil {
356 | 		return c.createDebugSourceResponse(nil, sourceFile, "", args, fmt.Errorf("failed to get absolute path: %v", err))
357 | 	}
358 | 
359 | 	if _, err := os.Stat(absPath); os.IsNotExist(err) {
360 | 		return c.createDebugSourceResponse(nil, sourceFile, "", args, fmt.Errorf("source file not found: %s", absPath))
361 | 	}
362 | 
363 | 	// Generate a unique debug binary name
364 | 	debugBinary := gobuild.DefaultDebugBinaryPath("debug_binary")
365 | 
366 | 	logger.Debug("Compiling source file %s to %s", absPath, debugBinary)
367 | 
368 | 	// Compile the source file with output capture
369 | 	cmd, output, err := gobuild.GoBuildCombinedOutput(debugBinary, []string{absPath}, "-gcflags all=-N")
370 | 	if err != nil {
371 | 		logger.Debug("Build command: %s", cmd)
372 | 		logger.Debug("Build output: %s", string(output))
373 | 		gobuild.Remove(debugBinary)
374 | 		return c.createDebugSourceResponse(nil, sourceFile, debugBinary, args, fmt.Errorf("failed to compile source file: %v\nOutput: %s", err, string(output)))
375 | 	}
376 | 
377 | 	// Launch the compiled binary with the debugger
378 | 	response := c.LaunchProgram(debugBinary, args)
379 | 	if response.Context.ErrorMessage != "" {
380 | 		gobuild.Remove(debugBinary)
381 | 		return c.createDebugSourceResponse(nil, sourceFile, debugBinary, args, fmt.Errorf(response.Context.ErrorMessage))
382 | 	}
383 | 
384 | 	// Store the binary path for cleanup
385 | 	c.target = debugBinary
386 | 
387 | 	return c.createDebugSourceResponse(response.Context.DelveState, sourceFile, debugBinary, args, nil)
388 | }
389 | 
390 | // DebugTest compiles and debugs a Go test function
391 | func (c *Client) DebugTest(testFilePath string, testName string, testFlags []string) types.DebugTestResponse {
392 | 	response := types.DebugTestResponse{
393 | 		TestName:  testName,
394 | 		TestFile:  testFilePath,
395 | 		TestFlags: testFlags,
396 | 	}
397 | 	if c.client != nil {
398 | 		return c.createDebugTestResponse(nil, &response, fmt.Errorf("debug session already active"))
399 | 	}
400 | 
401 | 	// Ensure test file exists
402 | 	absPath, err := filepath.Abs(testFilePath)
403 | 	if err != nil {
404 | 		return c.createDebugTestResponse(nil, &response, fmt.Errorf("failed to get absolute path: %v", err))
405 | 	}
406 | 
407 | 	if _, err := os.Stat(absPath); os.IsNotExist(err) {
408 | 		return c.createDebugTestResponse(nil, &response, fmt.Errorf("test file not found: %s", absPath))
409 | 	}
410 | 
411 | 	// Get the directory of the test file
412 | 	testDir := filepath.Dir(absPath)
413 | 	logger.Debug("Test directory: %s", testDir)
414 | 
415 | 	// Generate a unique debug binary name
416 | 	debugBinary := gobuild.DefaultDebugBinaryPath("debug.test")
417 | 
418 | 	logger.Debug("Compiling test package in %s to %s", testDir, debugBinary)
419 | 
420 | 	// Save current directory
421 | 	currentDir, err := os.Getwd()
422 | 	if err != nil {
423 | 		return c.createDebugTestResponse(nil, &response, fmt.Errorf("failed to get current directory: %v", err))
424 | 	}
425 | 
426 | 	// Change to test directory
427 | 	if err := os.Chdir(testDir); err != nil {
428 | 		return c.createDebugTestResponse(nil, &response, fmt.Errorf("failed to change to test directory: %v", err))
429 | 	}
430 | 
431 | 	// Ensure we change back to original directory
432 | 	defer func() {
433 | 		if err := os.Chdir(currentDir); err != nil {
434 | 			logger.Error("Failed to restore original directory: %v", err)
435 | 		}
436 | 	}()
437 | 
438 | 	// Compile the test package with output capture using test-specific build flags
439 | 	cmd, output, err := gobuild.GoTestBuildCombinedOutput(debugBinary, []string{testDir}, "-gcflags all=-N")
440 | 	response.BuildCommand = cmd
441 | 	response.BuildOutput = string(output)
442 | 	if err != nil {
443 | 		gobuild.Remove(debugBinary)
444 | 		return c.createDebugTestResponse(nil, &response, fmt.Errorf("failed to compile test package: %v\nOutput: %s", err, string(output)))
445 | 	}
446 | 
447 | 	// Create args to run the specific test
448 | 	args := []string{
449 | 		"-test.v", // Verbose output
450 | 	}
451 | 
452 | 	// Add specific test pattern if provided
453 | 	if testName != "" {
454 | 		// Escape special regex characters in the test name
455 | 		escapedTestName := regexp.QuoteMeta(testName)
456 | 		// Create a test pattern that matches exactly the provided test name
457 | 		args = append(args, fmt.Sprintf("-test.run=^%s$", escapedTestName))
458 | 	}
459 | 
460 | 	// Add any additional test flags
461 | 	args = append(args, testFlags...)
462 | 
463 | 	logger.Debug("Launching test binary with debugger, test name: %s, args: %v", testName, args)
464 | 	// Launch the compiled test binary with the debugger
465 | 	response2 := c.LaunchProgram(debugBinary, args)
466 | 	if response2.Context.ErrorMessage != "" {
467 | 		gobuild.Remove(debugBinary)
468 | 		return c.createDebugTestResponse(nil, &response, fmt.Errorf(response.Context.ErrorMessage))
469 | 	}
470 | 
471 | 	// Store the binary path for cleanup
472 | 	c.target = debugBinary
473 | 
474 | 	return c.createDebugTestResponse(response2.Context.DelveState, &response, nil)
475 | }
476 | 
477 | // createLaunchResponse creates a response for the launch command
478 | func (c *Client) createLaunchResponse(state *api.DebuggerState, program string, args []string, err error) types.LaunchResponse {
479 | 	context := c.createDebugContext(state)
480 | 	context.Operation = "launch"
481 | 
482 | 	if err != nil {
483 | 		context.ErrorMessage = err.Error()
484 | 	}
485 | 
486 | 	return types.LaunchResponse{
487 | 		Context:  &context,
488 | 		Program:  program,
489 | 		Args:     args,
490 | 		ExitCode: 0,
491 | 	}
492 | }
493 | 
494 | // createAttachResponse creates a response for the attach command
495 | func (c *Client) createAttachResponse(state *api.DebuggerState, pid int, target string, process *types.Process, err error) types.AttachResponse {
496 | 	context := c.createDebugContext(state)
497 | 	context.Operation = "attach"
498 | 
499 | 	if err != nil {
500 | 		context.ErrorMessage = err.Error()
501 | 	}
502 | 
503 | 	return types.AttachResponse{
504 | 		Status:  "success",
505 | 		Context: &context,
506 | 		Pid:     pid,
507 | 		Target:  target,
508 | 		Process: process,
509 | 	}
510 | }
511 | 
512 | // createDebugSourceResponse creates a response for the debug source command
513 | func (c *Client) createDebugSourceResponse(state *api.DebuggerState, sourceFile string, debugBinary string, args []string, err error) types.DebugSourceResponse {
514 | 	context := c.createDebugContext(state)
515 | 	context.Operation = "debug_source"
516 | 
517 | 	if err != nil {
518 | 		context.ErrorMessage = err.Error()
519 | 	}
520 | 
521 | 	return types.DebugSourceResponse{
522 | 		Status:      "success",
523 | 		Context:     &context,
524 | 		SourceFile:  sourceFile,
525 | 		DebugBinary: debugBinary,
526 | 		Args:        args,
527 | 	}
528 | }
529 | 
530 | // createDebugTestResponse creates a response for the debug test command
531 | func (c *Client) createDebugTestResponse(state *api.DebuggerState, response *types.DebugTestResponse, err error) types.DebugTestResponse {
532 | 	context := c.createDebugContext(state)
533 | 	context.Operation = "debug_test"
534 | 	response.Context = &context
535 | 
536 | 	if err != nil {
537 | 		context.ErrorMessage = err.Error()
538 | 		response.Status = "error"
539 | 	}
540 | 
541 | 	return *response
542 | }
543 | 
```

--------------------------------------------------------------------------------
/pkg/mcp/server_test.go:
--------------------------------------------------------------------------------

```go
  1 | package mcp
  2 | 
  3 | import (
  4 | 	"context"
  5 | 	"encoding/json"
  6 | 	"fmt"
  7 | 	"io/ioutil"
  8 | 	"os"
  9 | 	"path/filepath"
 10 | 	"strings"
 11 | 	"testing"
 12 | 	"time"
 13 | 
 14 | 	"github.com/mark3labs/mcp-go/mcp"
 15 | 	"github.com/pmezard/go-difflib/difflib"
 16 | 	"github.com/sunfmin/mcp-go-debugger/pkg/types"
 17 | )
 18 | 
 19 | // We'll use the package-level types from server.go for API responses
 20 | 
 21 | func getTextContent(result *mcp.CallToolResult) string {
 22 | 	if result == nil || len(result.Content) == 0 {
 23 | 		return ""
 24 | 	}
 25 | 
 26 | 	// TextContent is a struct implementing the Content interface
 27 | 	// We need to try to cast it based on the struct's provided functions
 28 | 	if tc, ok := mcp.AsTextContent(result.Content[0]); ok {
 29 | 		return tc.Text
 30 | 	}
 31 | 
 32 | 	return ""
 33 | }
 34 | 
 35 | // Helper function to find the line number for a specific statement in a file
 36 | func findLineNumber(filePath, targetStatement string) int {
 37 | 	content, err := os.ReadFile(filePath)
 38 | 	if err != nil {
 39 | 		panic(fmt.Sprintf("failed to read file: %v", err))
 40 | 	}
 41 | 
 42 | 	lines := strings.Split(string(content), "\n")
 43 | 	for i, line := range lines {
 44 | 		line = strings.TrimSpace(line)
 45 | 		if strings.Contains(line, targetStatement) {
 46 | 			return i + 1 // Line numbers are 1-indexed
 47 | 		}
 48 | 	}
 49 | 
 50 | 	panic(fmt.Sprintf("statement not found: %s", targetStatement))
 51 | }
 52 | 
 53 | // Helper function to unmarshal JSON data and panic if it fails
 54 | func mustUnmarshalJSON(data string, v interface{}) {
 55 | 	if err := json.Unmarshal([]byte(data), v); err != nil {
 56 | 		panic(fmt.Sprintf("failed to unmarshal JSON: %v", err))
 57 | 	}
 58 | }
 59 | 
 60 | // Helper function to get text content from result and unmarshal it to the provided variable
 61 | func mustGetAndUnmarshalJSON(result *mcp.CallToolResult, v interface{}) {
 62 | 	text := getTextContent(result)
 63 | 	if text == "" {
 64 | 		panic("empty text content in result")
 65 | 	}
 66 | 	mustUnmarshalJSON(text, v)
 67 | }
 68 | 
 69 | func expectSuccess(t *testing.T, result *mcp.CallToolResult, err error, response interface{}) {
 70 | 	t.Helper()
 71 | 	if err != nil {
 72 | 		t.Fatalf("Expected success, got error: %v", err)
 73 | 	}
 74 | 
 75 | 	mustGetAndUnmarshalJSON(result, response)
 76 | 
 77 | 	output, _ := json.MarshalIndent(response, "", "  ")
 78 | 	t.Logf("Response: %#+v\n %s", response, string(output))
 79 | }
 80 | 
 81 | func TestDebugWorkflow(t *testing.T) {
 82 | 	// Skip test in short mode
 83 | 	if testing.Short() {
 84 | 		t.Skip("Skipping integration test in short mode")
 85 | 	}
 86 | 
 87 | 	// Create a test Go file with multiple functions and variables for debugging
 88 | 	testFile := createComplexTestGoFile(t)
 89 | 	defer os.RemoveAll(filepath.Dir(testFile))
 90 | 
 91 | 	// Read and print the file content for debugging
 92 | 	fileContent, err := ioutil.ReadFile(testFile)
 93 | 	if err != nil {
 94 | 		t.Fatalf("Failed to read test file: %v", err)
 95 | 	}
 96 | 	t.Logf("Generated test file content:\n%s", string(fileContent))
 97 | 
 98 | 	// Create server
 99 | 	server := NewMCPDebugServer("test-version")
100 | 	ctx := context.Background()
101 | 
102 | 	// Step 1: Launch the debugger
103 | 	launchRequest := mcp.CallToolRequest{}
104 | 	launchRequest.Params.Arguments = map[string]interface{}{
105 | 		"file": testFile,
106 | 		"args": []interface{}{"test-arg1", "test-arg2"},
107 | 	}
108 | 
109 | 	debugResult, err := server.DebugSourceFile(ctx, launchRequest)
110 | 	expectSuccess(t, debugResult, err, &types.DebugSourceResponse{})
111 | 
112 | 	// Give the debugger time to initialize
113 | 	time.Sleep(200 * time.Millisecond)
114 | 
115 | 	// Find line numbers for key statements
116 | 	fmtPrintlnLine := findLineNumber(testFile, "fmt.Println(\"Starting debug test program\")")
117 | 	t.Logf("Found fmt.Println statement at line %d", fmtPrintlnLine)
118 | 
119 | 	countVarLine := findLineNumber(testFile, "count := 10")
120 | 	t.Logf("Found count variable at line %d", countVarLine)
121 | 
122 | 	// Find the calculate function line
123 | 	calculateLine := findLineNumber(testFile, "func calculate(n int) int {")
124 | 	t.Logf("Found calculate function at line %d", calculateLine)
125 | 
126 | 	// Find the line with a := n * 2 inside calculate
127 | 	aVarLine := findLineNumber(testFile, "a := n * 2")
128 | 	t.Logf("Found a variable assignment at line %d", aVarLine)
129 | 
130 | 	// Step 2: Set a breakpoint at the start of calculate function
131 | 	setBreakpointRequest := mcp.CallToolRequest{}
132 | 	setBreakpointRequest.Params.Arguments = map[string]interface{}{
133 | 		"file": testFile,
134 | 		"line": float64(aVarLine), // Line with first statement in calculate function
135 | 	}
136 | 
137 | 	breakpointResult, err := server.SetBreakpoint(ctx, setBreakpointRequest)
138 | 	breakpointResponse := &types.BreakpointResponse{}
139 | 	expectSuccess(t, breakpointResult, err, breakpointResponse)
140 | 
141 | 	if breakpointResponse.Breakpoint.ID <= 0 {
142 | 		t.Errorf("Expected valid breakpoint ID, got: %d", breakpointResponse.Breakpoint.ID)
143 | 	}
144 | 
145 | 	// Step 3: List breakpoints to verify
146 | 	listBreakpointsRequest := mcp.CallToolRequest{}
147 | 	listResult, err := server.ListBreakpoints(ctx, listBreakpointsRequest)
148 | 	listBreakpointsResponse := types.BreakpointListResponse{}
149 | 	expectSuccess(t, listResult, err, &listBreakpointsResponse)
150 | 
151 | 	if len(listBreakpointsResponse.Breakpoints) == 0 {
152 | 		t.Errorf("Expected at least one breakpoint, got none")
153 | 	}
154 | 
155 | 	// Remember the breakpoint ID for later removal
156 | 	firstBreakpointID := listBreakpointsResponse.Breakpoints[0].ID
157 | 
158 | 	// Step 4: Set another breakpoint at the countVarLine
159 | 	setBreakpointRequest2 := mcp.CallToolRequest{}
160 | 	setBreakpointRequest2.Params.Arguments = map[string]interface{}{
161 | 		"file": testFile,
162 | 		"line": float64(countVarLine), // Line with count variable
163 | 	}
164 | 
165 | 	_, err = server.SetBreakpoint(ctx, setBreakpointRequest2)
166 | 	if err != nil {
167 | 		t.Fatalf("Failed to set second breakpoint: %v", err)
168 | 	}
169 | 
170 | 	// Give the debugger more time to initialize fully
171 | 	time.Sleep(300 * time.Millisecond)
172 | 
173 | 	// Step 6: Continue execution to hit first breakpoint
174 | 	continueRequest := mcp.CallToolRequest{}
175 | 	continueResult, err := server.Continue(ctx, continueRequest)
176 | 	expectSuccess(t, continueResult, err, &types.ContinueResponse{})
177 | 
178 | 	// Allow time for the breakpoint to be hit
179 | 	time.Sleep(300 * time.Millisecond)
180 | 
181 | 	// Issue a second continue to reach the breakpoint in the calculate function
182 | 	continueResult2, err := server.Continue(ctx, continueRequest)
183 | 	expectSuccess(t, continueResult2, err, &types.ContinueResponse{})
184 | 
185 | 	// Allow time for the second breakpoint to be hit
186 | 	time.Sleep(300 * time.Millisecond)
187 | 
188 | 	// Step 8: Eval variable 'n' at the first breakpoint in calculate()
189 | 	evalRequest := mcp.CallToolRequest{}
190 | 	evalRequest.Params.Arguments = map[string]interface{}{
191 | 		"name":  "n",
192 | 		"depth": float64(1),
193 | 	}
194 | 
195 | 	evalVariableResult, err := server.EvalVariable(ctx, evalRequest)
196 | 	evalVariableResponse := &types.EvalVariableResponse{}
197 | 	expectSuccess(t, evalVariableResult, err, evalVariableResponse)
198 | 
199 | 	// Verify the variable value is what we expect
200 | 	if evalVariableResponse.Variable.Value != "10" {
201 | 		t.Fatalf("Expected n to be 10, got %s", evalVariableResponse.Variable.Value)
202 | 	}
203 | 
204 | 	// Step 9: Use step over to go to the next line
205 | 	stepOverRequest := mcp.CallToolRequest{}
206 | 	stepResult, err := server.StepOver(ctx, stepOverRequest)
207 | 	expectSuccess(t, stepResult, err, &types.StepResponse{})
208 | 
209 | 	// Allow time for the step to complete
210 | 	time.Sleep(200 * time.Millisecond)
211 | 
212 | 	// Step 10: Eval variable 'a' which should be defined now
213 | 	evalRequest2 := mcp.CallToolRequest{}
214 | 	evalRequest2.Params.Arguments = map[string]interface{}{
215 | 		"name":  "a",
216 | 		"depth": float64(1),
217 | 	}
218 | 
219 | 	evalVariableResult2, err := server.EvalVariable(ctx, evalRequest2)
220 | 	evalVariableResponse2 := &types.EvalVariableResponse{}
221 | 	expectSuccess(t, evalVariableResult2, err, evalVariableResponse2)
222 | 
223 | 	// Verify the variable value is what we expect (a = n * 2, so a = 20)
224 | 	if evalVariableResponse2.Variable.Value != "20" {
225 | 		t.Fatalf("Expected a to be 20, got %s", evalVariableResponse2.Variable.Value)
226 | 	}
227 | 
228 | 	// Step 11: Step over again
229 | 	stepResult2, err := server.StepOver(ctx, stepOverRequest)
230 | 	expectSuccess(t, stepResult2, err, &types.StepResponse{})
231 | 
232 | 	// Allow time for the step to complete
233 | 	time.Sleep(200 * time.Millisecond)
234 | 
235 | 	// Step 12: Remove the first breakpoint
236 | 	removeBreakpointRequest := mcp.CallToolRequest{}
237 | 	removeBreakpointRequest.Params.Arguments = map[string]interface{}{
238 | 		"id": float64(firstBreakpointID),
239 | 	}
240 | 
241 | 	removeResult, err := server.RemoveBreakpoint(ctx, removeBreakpointRequest)
242 | 	expectSuccess(t, removeResult, err, &types.BreakpointResponse{})
243 | 
244 | 	// Step 14: Continue execution to complete the program
245 | 	continueResult, err = server.Continue(ctx, continueRequest)
246 | 	expectSuccess(t, continueResult, err, &types.ContinueResponse{})
247 | 
248 | 	// Allow time for program to complete
249 | 	time.Sleep(300 * time.Millisecond)
250 | 
251 | 	// Check for captured output
252 | 	outputRequest := mcp.CallToolRequest{}
253 | 	outputResult, err := server.GetDebuggerOutput(ctx, outputRequest)
254 | 	var outputResponse = &types.DebuggerOutputResponse{}
255 | 	expectSuccess(t, outputResult, err, outputResponse)
256 | 
257 | 	// Verify that output was captured
258 | 	if outputResponse.Stdout == "" {
259 | 		t.Errorf("Expected stdout to be captured, but got empty output")
260 | 	}
261 | 
262 | 	// Verify output contains expected strings
263 | 	if !strings.Contains(outputResponse.Stdout, "Starting debug test program") {
264 | 		t.Errorf("Expected stdout to contain startup message, got: %s", outputResponse.Stdout)
265 | 	}
266 | 
267 | 	if !strings.Contains(outputResponse.Stdout, "Arguments:") {
268 | 		t.Errorf("Expected stdout to contain arguments message, got: %s", outputResponse.Stdout)
269 | 	}
270 | 
271 | 	// Verify output summary is present and contains expected content
272 | 	if outputResponse.OutputSummary == "" {
273 | 		t.Errorf("Expected output summary to be present, but got empty summary")
274 | 	}
275 | 
276 | 	if !strings.Contains(outputResponse.OutputSummary, "Program output") {
277 | 		t.Errorf("Expected output summary to mention program output, got: %s", outputResponse.OutputSummary)
278 | 	}
279 | 
280 | 	t.Logf("Captured output: %s", outputResponse.Stdout)
281 | 	t.Logf("Output summary: %s", outputResponse.OutputSummary)
282 | 
283 | 	// Clean up by closing the debug session
284 | 	closeRequest := mcp.CallToolRequest{}
285 | 	closeResult, err := server.Close(ctx, closeRequest)
286 | 	expectSuccess(t, closeResult, err, &types.CloseResponse{})
287 | 
288 | 	t.Log("TestDebugWorkflow completed successfully")
289 | }
290 | 
291 | // Helper function to create a more complex Go file for debugging tests
292 | func createComplexTestGoFile(t *testing.T) string {
293 | 	tempDir, err := ioutil.TempDir("", "go-debugger-complex-test")
294 | 	if err != nil {
295 | 		t.Fatalf("Failed to create temp directory: %v", err)
296 | 	}
297 | 
298 | 	goFile := filepath.Join(tempDir, "main.go")
299 | 	content := `package main
300 | 
301 | import (
302 | 	"fmt"
303 | 	"os"
304 | 	"time"
305 | )
306 | 
307 | type Person struct {
308 | 	Name string
309 | 	Age  int
310 | }
311 | 
312 | func (p Person) Greet() string {
313 | 	return fmt.Sprintf("Hello, my name is %s and I am %d years old", p.Name, p.Age)
314 | }
315 | 
316 | func main() {
317 | 	fmt.Println("Starting debug test program")
318 | 	
319 | 	// Process command line arguments
320 | 	args := os.Args[1:]
321 | 	if len(args) > 0 {
322 | 		fmt.Println("Arguments:")
323 | 		for i, arg := range args {
324 | 			fmt.Printf("  %d: %s\n", i+1, arg)
325 | 		}
326 | 	}
327 | 	
328 | 	// Create some variables for debugging
329 | 	count := 10
330 | 	name := "DebugTest"
331 | 	enabled := true
332 | 	
333 | 	// Call a function that we can set breakpoints in
334 | 	result := calculate(count)
335 | 	fmt.Printf("Result of calculation: %d\n", result)
336 | 	
337 | 	// Create and use a struct
338 | 	person := Person{
339 | 		Name: name,
340 | 		Age:  count * 3,
341 | 	}
342 | 	
343 | 	message := person.Greet()
344 | 	fmt.Println(message)
345 | 	
346 | 	// Add a small delay so the program doesn't exit immediately
347 | 	time.Sleep(100 * time.Millisecond)
348 | 	
349 | 	fmt.Println("Program completed, enabled:", enabled)
350 | }
351 | 
352 | func calculate(n int) int {
353 | 	// A function with multiple steps for debugging
354 | 	a := n * 2
355 | 	b := a + 5
356 | 	c := b * b
357 | 	d := processFurther(c)
358 | 	return d
359 | }
360 | 
361 | func processFurther(value int) int {
362 | 	// Another function to test step in/out
363 | 	result := value
364 | 	if value > 100 {
365 | 		result = value / 2
366 | 	} else {
367 | 		result = value * 2
368 | 	}
369 | 	return result
370 | }
371 | `
372 | 	if err := ioutil.WriteFile(goFile, []byte(content), 0644); err != nil {
373 | 		t.Fatalf("Failed to write complex test Go file: %v", err)
374 | 	}
375 | 
376 | 	return goFile
377 | }
378 | 
379 | func TestDebugTest(t *testing.T) {
380 | 	// Skip test in short mode
381 | 	if testing.Short() {
382 | 		t.Skip("Skipping integration test in short mode")
383 | 	}
384 | 
385 | 	// Get paths to our calculator test files
386 | 	testFilePath, err := filepath.Abs("../../testdata/calculator/calculator_test.go")
387 | 	if err != nil {
388 | 		t.Fatalf("Failed to get absolute path to test file: %v", err)
389 | 	}
390 | 
391 | 	// Make sure the test file exists
392 | 	if _, err := os.Stat(testFilePath); os.IsNotExist(err) {
393 | 		t.Fatalf("Test file does not exist: %s", testFilePath)
394 | 	}
395 | 
396 | 	// Find line number for the test function and the Add call
397 | 	testFuncLine := findLineNumber(testFilePath, "func TestAdd(t *testing.T) {")
398 | 
399 | 	addCallLine := findLineNumber(testFilePath, "result := Add(2, 3)")
400 | 
401 | 	t.Logf("Found TestAdd function at line %d, Add call at line %d", testFuncLine, addCallLine)
402 | 
403 | 	// Create server
404 | 	server := NewMCPDebugServer("test-version")
405 | 	ctx := context.Background()
406 | 
407 | 	// Step 1: Launch the debug test
408 | 	debugTestRequest := mcp.CallToolRequest{}
409 | 	debugTestRequest.Params.Arguments = map[string]interface{}{
410 | 		"testfile": testFilePath,
411 | 		"testname": "TestAdd",
412 | 	}
413 | 
414 | 	debugResult, err := server.DebugTest(ctx, debugTestRequest)
415 | 	expectSuccess(t, debugResult, err, &types.DebugTestResponse{})
416 | 
417 | 	// Give the debugger time to initialize
418 | 	time.Sleep(300 * time.Millisecond)
419 | 
420 | 	// Step 2: Set a breakpoint where Add is called in the test function
421 | 	setBreakpointRequest := mcp.CallToolRequest{}
422 | 	setBreakpointRequest.Params.Arguments = map[string]interface{}{
423 | 		"file": testFilePath,
424 | 		"line": float64(addCallLine),
425 | 	}
426 | 
427 | 	breakpointResult, err := server.SetBreakpoint(ctx, setBreakpointRequest)
428 | 	expectSuccess(t, breakpointResult, err, &types.BreakpointResponse{})
429 | 
430 | 	// Step 3: Continue execution to hit breakpoint
431 | 	continueRequest := mcp.CallToolRequest{}
432 | 	continueResult, err := server.Continue(ctx, continueRequest)
433 | 	expectSuccess(t, continueResult, err, &types.ContinueResponse{})
434 | 
435 | 	// Allow time for the breakpoint to be hit
436 | 	time.Sleep(500 * time.Millisecond)
437 | 
438 | 	// First try to eval 't', which should be available in all test functions
439 | 	evalRequest := mcp.CallToolRequest{}
440 | 	evalRequest.Params.Arguments = map[string]interface{}{
441 | 		"name":  "t",
442 | 		"depth": float64(1),
443 | 	}
444 | 
445 | 	evalResult, err := server.EvalVariable(ctx, evalRequest)
446 | 	expectSuccess(t, evalResult, err, &types.EvalVariableResponse{})
447 | 
448 | 	// Now try to step once to execute the Add function call
449 | 	stepRequest := mcp.CallToolRequest{}
450 | 	stepResult, err := server.Step(ctx, stepRequest)
451 | 	expectSuccess(t, stepResult, err, &types.StepResponse{})
452 | 
453 | 	// Allow time for the step to complete
454 | 	time.Sleep(200 * time.Millisecond)
455 | 
456 | 	// Now look for result variable, which should be populated after the Add call
457 | 	evalResultVarRequest := mcp.CallToolRequest{}
458 | 	evalResultVarRequest.Params.Arguments = map[string]interface{}{
459 | 		"name":  "result",
460 | 		"depth": float64(1),
461 | 	}
462 | 
463 | 	resultVarEvalResult, err := server.EvalVariable(ctx, evalResultVarRequest)
464 | 	var evalResultVarResponse = &types.EvalVariableResponse{}
465 | 	expectSuccess(t, resultVarEvalResult, err, evalResultVarResponse)
466 | 
467 | 	if evalResultVarResponse.Variable.Value != "5" {
468 | 		t.Fatalf("Expected result to be 5, got %s", evalResultVarResponse.Variable.Value)
469 | 	}
470 | 
471 | 	// Step 6: Continue execution to complete the test
472 | 	continueResult, err = server.Continue(ctx, continueRequest)
473 | 	expectSuccess(t, continueResult, err, &types.ContinueResponse{})
474 | 
475 | 	// Allow time for program to complete
476 | 	time.Sleep(300 * time.Millisecond)
477 | 
478 | 	// Check for captured output
479 | 	outputRequest := mcp.CallToolRequest{}
480 | 	outputResult, err := server.GetDebuggerOutput(ctx, outputRequest)
481 | 	if err == nil && outputResult != nil {
482 | 		outputText := getTextContent(outputResult)
483 | 		t.Logf("Captured program output: %s", outputText)
484 | 	}
485 | 
486 | 	// Clean up by closing the debug session
487 | 	closeRequest := mcp.CallToolRequest{}
488 | 	closeResult, err := server.Close(ctx, closeRequest)
489 | 	expectSuccess(t, closeResult, err, &types.CloseResponse{})
490 | 
491 | 	t.Log("TestDebugTest completed successfully")
492 | }
493 | 
494 | func prettyJSONDiff(expected, actual interface{}) string {
495 | 	expectedJSON, _ := json.MarshalIndent(expected, "", "  ")
496 | 	actualJSON, _ := json.MarshalIndent(actual, "", "  ")
497 | 
498 | 	diff := difflib.UnifiedDiff{
499 | 		A:        difflib.SplitLines(string(expectedJSON)),
500 | 		B:        difflib.SplitLines(string(actualJSON)),
501 | 		FromFile: "Expected",
502 | 		ToFile:   "Actual",
503 | 		Context:  3,
504 | 	}
505 | 
506 | 	diffText, err := difflib.GetUnifiedDiffString(diff)
507 | 	if err != nil {
508 | 		return fmt.Sprintf("Failed to generate diff: %v", err)
509 | 	}
510 | 
511 | 	return diffText
512 | }
513 | 
514 | func TestEvalComplexVariables(t *testing.T) {
515 | 	// Skip test in short mode
516 | 	if testing.Short() {
517 | 		t.Skip("Skipping integration test in short mode")
518 | 	}
519 | 
520 | 	// Create test file
521 | 	testFile := createComplexTestGoFile(t)
522 | 	//defer os.RemoveAll(filepath.Dir(testFile))
523 | 
524 | 	// Create server
525 | 	server := NewMCPDebugServer("test-version")
526 | 	ctx := context.Background()
527 | 
528 | 	// Launch the debugger
529 | 	launchRequest := mcp.CallToolRequest{}
530 | 	launchRequest.Params.Arguments = map[string]interface{}{
531 | 		"file": testFile,
532 | 		"args": []interface{}{"test-arg1", "test-arg2"},
533 | 	}
534 | 
535 | 	debugResult, err := server.DebugSourceFile(ctx, launchRequest)
536 | 	expectSuccess(t, debugResult, err, &types.DebugSourceResponse{})
537 | 
538 | 	// Give the debugger time to initialize
539 | 	time.Sleep(200 * time.Millisecond)
540 | 
541 | 	// Find line numbers for breakpoints
542 | 	personVarLine := findLineNumber(testFile, "message := person.Greet()")
543 | 	t.Logf("Found person variable declaration at line %d", personVarLine)
544 | 
545 | 	// Set breakpoint at person variable declaration
546 | 	setBreakpointRequest := mcp.CallToolRequest{}
547 | 	setBreakpointRequest.Params.Arguments = map[string]interface{}{
548 | 		"file": testFile,
549 | 		"line": float64(personVarLine),
550 | 	}
551 | 
552 | 	breakpointResult, err := server.SetBreakpoint(ctx, setBreakpointRequest)
553 | 	expectSuccess(t, breakpointResult, err, &types.BreakpointResponse{})
554 | 
555 | 	// Continue to breakpoint
556 | 	continueRequest := mcp.CallToolRequest{}
557 | 	continueResult, err := server.Continue(ctx, continueRequest)
558 | 	expectSuccess(t, continueResult, err, &types.ContinueResponse{})
559 | 
560 | 	// Allow time for breakpoint to be hit
561 | 	time.Sleep(300 * time.Millisecond)
562 | 
563 | 	// Test cases for different variable types
564 | 	testCases := []struct {
565 | 		name        string
566 | 		varName     string
567 | 		depth       int
568 | 		expectedVar types.Variable
569 | 	}{
570 | 		{
571 | 			name:    "Struct variable",
572 | 			varName: "person",
573 | 			depth:   2,
574 | 			expectedVar: types.Variable{
575 | 				Name:  "person",
576 | 				Type:  "main.Person",
577 | 				Scope: "",
578 | 				Kind:  "struct",
579 | 				Value: "{Name:DebugTest, Age:30}",
580 | 			},
581 | 		},
582 | 		{
583 | 			name:    "Struct variable property",
584 | 			varName: "person.Age",
585 | 			depth:   1,
586 | 			expectedVar: types.Variable{
587 | 				Name:  "person.Age",
588 | 				Type:  "int",
589 | 				Scope: "",
590 | 				Kind:  "integer",
591 | 				Value: "30",
592 | 			},
593 | 		},
594 | 		{
595 | 			name:    "Slice variable",
596 | 			varName: "args",
597 | 			depth:   1,
598 | 			expectedVar: types.Variable{
599 | 				Name:  "args",
600 | 				Type:  "[]string",
601 | 				Scope: "",
602 | 				Kind:  "array",
603 | 				Value: "[test-arg1, test-arg2]",
604 | 			},
605 | 		},
606 | 	}
607 | 
608 | 	for _, tc := range testCases {
609 | 		t.Run(tc.name, func(t *testing.T) {
610 | 			evalRequest := mcp.CallToolRequest{}
611 | 			evalRequest.Params.Arguments = map[string]interface{}{
612 | 				"name":  tc.varName,
613 | 				"depth": float64(tc.depth),
614 | 			}
615 | 
616 | 			evalResult, err := server.EvalVariable(ctx, evalRequest)
617 | 			var evalResponse = &types.EvalVariableResponse{}
618 | 			expectSuccess(t, evalResult, err, evalResponse)
619 | 
620 | 			diff := prettyJSONDiff(tc.expectedVar, evalResponse.Variable)
621 | 			if diff != "" {
622 | 				t.Errorf("Variable mismatch: %s", diff)
623 | 			}
624 | 		})
625 | 	}
626 | 
627 | 	// Clean up by closing the debug session
628 | 	closeRequest := mcp.CallToolRequest{}
629 | 	closeResult, err := server.Close(ctx, closeRequest)
630 | 	expectSuccess(t, closeResult, err, &types.CloseResponse{})
631 | }
632 | 
```