Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package mcp-dap-server for openSUSE:Factory checked in at 2026-03-11 20:57:04 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/mcp-dap-server (Old) and /work/SRC/openSUSE:Factory/.mcp-dap-server.new.8177 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "mcp-dap-server" Wed Mar 11 20:57:04 2026 rev:2 rq:1338284 version:0.0.0+git20250722.f375340 Changes: -------- --- /work/SRC/openSUSE:Factory/mcp-dap-server/mcp-dap-server.changes 2025-07-18 16:00:10.018572396 +0200 +++ /work/SRC/openSUSE:Factory/.mcp-dap-server.new.8177/mcp-dap-server.changes 2026-03-11 20:59:04.039194712 +0100 @@ -1,0 +2,10 @@ +Wed Dec 10 04:16:42 UTC 2025 - Jeff Kowalczyk <[email protected]> + +- Update to version 0.0.0+git20250722.f375340: + * tools: create debuggerSession type, remove globals + * tools: add test for next and fix bugs + * tools: return frame ID in context + * tools: add test for getScopes and fix implementation + * docs: add examples for gemini cli and claude code + +------------------------------------------------------------------- Old: ---- mcp-dap-server-0.0.0+git20250716.7ffcc1d.tar.gz New: ---- mcp-dap-server-0.0.0+git20250722.f375340.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ mcp-dap-server.spec ++++++ --- /var/tmp/diff_new_pack.PGpL2j/_old 2026-03-11 20:59:04.839227660 +0100 +++ /var/tmp/diff_new_pack.PGpL2j/_new 2026-03-11 20:59:04.843227825 +0100 @@ -17,7 +17,7 @@ Name: mcp-dap-server -Version: 0.0.0+git20250716.7ffcc1d +Version: 0.0.0+git20250722.f375340 Release: 0 Summary: MCP server to communicate with DAP servers License: MIT ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.PGpL2j/_old 2026-03-11 20:59:04.891229802 +0100 +++ /var/tmp/diff_new_pack.PGpL2j/_new 2026-03-11 20:59:04.899230131 +0100 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/go-delve/mcp-dap-server.git</param> - <param name="changesrevision">7ffcc1dfb25611bc0510478289346eca50a55df9</param></service></servicedata> + <param name="changesrevision">f375340401a3af805b7a32a52d102f60ffdff793</param></service></servicedata> (No newline at EOF) ++++++ mcp-dap-server-0.0.0+git20250716.7ffcc1d.tar.gz -> mcp-dap-server-0.0.0+git20250722.f375340.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mcp-dap-server-0.0.0+git20250716.7ffcc1d/README.md new/mcp-dap-server-0.0.0+git20250722.f375340/README.md --- old/mcp-dap-server-0.0.0+git20250716.7ffcc1d/README.md 2025-07-17 02:30:27.000000000 +0200 +++ new/mcp-dap-server-0.0.0+git20250722.f375340/README.md 2025-07-23 00:50:02.000000000 +0200 @@ -67,6 +67,8 @@ ### Example MCP Client Configuration +This configuration should work (or serve as a starting point) for Agents such as (Gemini CLI)[https://developers.google.com/gemini-code-assist/docs/use-agentic-chat-pair-programmer#configure-mcp-servers]. + ```json { "mcpServers": { @@ -79,6 +81,14 @@ } ``` +### Example configuration using Claude Code + +[Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview) + +``` +claude mcp add --transport sse mcp-dap-server http://localhost:8080 +``` + ## Available Tools ### Session Management @@ -168,9 +178,13 @@ - `levels` (number, optional): Number of frames to retrieve #### `scopes` -Gets variable scopes for a stack frame. +Gets variable scopes for a stack frame and automatically fetches all variables within each scope. - **Parameters**: - `frameId` (number): Stack frame ID +- **Returns**: A formatted display showing: + - All available scopes (Locals, Arguments, Globals, etc.) + - Variables within each scope with their names, types, and values + - Variable references for compound types that can be further inspected #### `variables` Gets variables in a scope. Binary files old/mcp-dap-server-0.0.0+git20250716.7ffcc1d/mcp-dap-server and new/mcp-dap-server-0.0.0+git20250722.f375340/mcp-dap-server differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mcp-dap-server-0.0.0+git20250716.7ffcc1d/testdata/go/scopes/main.go new/mcp-dap-server-0.0.0+git20250722.f375340/testdata/go/scopes/main.go --- old/mcp-dap-server-0.0.0+git20250716.7ffcc1d/testdata/go/scopes/main.go 1970-01-01 01:00:00.000000000 +0100 +++ new/mcp-dap-server-0.0.0+git20250722.f375340/testdata/go/scopes/main.go 2025-07-23 00:50:02.000000000 +0200 @@ -0,0 +1,68 @@ +package main + +import "fmt" + +// Global variables for testing global scope +var globalString = "I am a global string" +var globalInt = 42 + +type Person struct { + Name string + Age int +} + +func main() { + // Local variables in main + localVar := "main local" + number := 100 + + // Use the local variables + fmt.Println(localVar, number) + + // Call a function with arguments + result := greet("Alice", 30) + fmt.Println(result) + + // Create a struct + person := Person{Name: "Bob", Age: 25} + processPerson(person) + + // Test with slice and map + numbers := []int{1, 2, 3, 4, 5} + data := map[string]int{"one": 1, "two": 2, "three": 3} + + processCollection(numbers, data) +} + +func greet(name string, age int) string { + // Function with arguments and local variables + greeting := fmt.Sprintf("Hello %s, you are %d years old", name, age) + prefix := "Greeting: " + + fullGreeting := prefix + greeting + + // Breakpoint location for testing scopes + return fullGreeting // Set breakpoint here (line 42) +} + +func processPerson(p Person) { + // Local variables + description := fmt.Sprintf("%s is %d years old", p.Name, p.Age) + isAdult := p.Age >= 18 + + // Breakpoint location for testing scopes with struct parameter + fmt.Println(description, "Adult:", isAdult) // Set breakpoint here (line 54) +} + +func processCollection(nums []int, dict map[string]int) { + // Local variables + sum := 0 + for _, n := range nums { + sum += n + } + + count := len(dict) + + // Breakpoint location for testing scopes with collection parameters + fmt.Printf("Sum: %d, Map entries: %d\n", sum, count) // Set breakpoint here (line 67) +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mcp-dap-server-0.0.0+git20250716.7ffcc1d/testdata/go/step/main.go new/mcp-dap-server-0.0.0+git20250722.f375340/testdata/go/step/main.go --- old/mcp-dap-server-0.0.0+git20250716.7ffcc1d/testdata/go/step/main.go 1970-01-01 01:00:00.000000000 +0100 +++ new/mcp-dap-server-0.0.0+git20250722.f375340/testdata/go/step/main.go 2025-07-23 00:50:02.000000000 +0200 @@ -0,0 +1,30 @@ +package main + +import "fmt" + +func main() { + // Line 6: Initialize first variable + x := 10 + + // Line 9: Initialize second variable + y := 20 + + // Line 12: Perform calculation + sum := x + y + + // Line 15: Create a string + message := fmt.Sprintf("Sum is: %d", sum) + + // Line 18: Print result + fmt.Println(message) + + // Line 21: Modify variables + x = x * 2 + y = y + 5 + + // Line 25: Another calculation + product := x * y + + // Line 28: Final print + fmt.Printf("Product is: %d\n", product) +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mcp-dap-server-0.0.0+git20250716.7ffcc1d/tools.go new/mcp-dap-server-0.0.0+git20250722.f375340/tools.go --- old/mcp-dap-server-0.0.0+git20250716.7ffcc1d/tools.go 2025-07-17 02:30:27.000000000 +0200 +++ new/mcp-dap-server-0.0.0+git20250722.f375340/tools.go 2025-07-23 00:50:02.000000000 +0200 @@ -14,118 +14,119 @@ "github.com/modelcontextprotocol/go-sdk/mcp" ) -var ( +type debuggerSession struct { cmd *exec.Cmd client *DAPClient -) +} // registerTools registers the debugger tools with the MCP server. // It adds two tools: start-debugger for starting a DAP server and stop-debugger for stopping it. func registerTools(server *mcp.Server) { + ds := &debuggerSession{} mcp.AddTool(server, &mcp.Tool{ Name: "start-debugger", Description: "Starts a debugger exposed via a DAP server. You can provide the port you would like the debugger DAP server to listen on.", - }, startDebugger) + }, ds.startDebugger) mcp.AddTool(server, &mcp.Tool{ Name: "stop-debugger", Description: "Stops an already running debugger.", - }, stopDebugger) + }, ds.stopDebugger) mcp.AddTool(server, &mcp.Tool{ Name: "debug-program", Description: "Tells the debugger running via DAP to debug a local program.", - }, debugProgram) + }, ds.debugProgram) mcp.AddTool(server, &mcp.Tool{ Name: "exec-program", Description: "Tells the debugger running via DAP to debug a local program that has already been compiled. The path to the program must be an absolute path, or the program must be in $PATH.", - }, execProgram) + }, ds.execProgram) mcp.AddTool(server, &mcp.Tool{ Name: "set-breakpoints", Description: "Sets breakpoints in a source file at specified line numbers.", - }, setBreakpoints) + }, ds.setBreakpoints) mcp.AddTool(server, &mcp.Tool{ Name: "set-function-breakpoints", Description: "Sets breakpoints on functions by name.", - }, setFunctionBreakpoints) + }, ds.setFunctionBreakpoints) mcp.AddTool(server, &mcp.Tool{ Name: "configuration-done", Description: "Indicates that the configuration phase is complete and debugging can begin.", - }, configurationDone) + }, ds.configurationDone) mcp.AddTool(server, &mcp.Tool{ Name: "continue", Description: "Continues execution of the debugged program.", - }, continueExecution) + }, ds.continueExecution) mcp.AddTool(server, &mcp.Tool{ Name: "next", Description: "Steps over the next line of code.", - }, nextStep) + }, ds.nextStep) mcp.AddTool(server, &mcp.Tool{ Name: "step-in", Description: "Steps into a function call.", - }, stepIn) + }, ds.stepIn) mcp.AddTool(server, &mcp.Tool{ Name: "step-out", Description: "Steps out of the current function.", - }, stepOut) + }, ds.stepOut) mcp.AddTool(server, &mcp.Tool{ Name: "pause", Description: "Pauses execution of a thread.", - }, pauseExecution) + }, ds.pauseExecution) mcp.AddTool(server, &mcp.Tool{ Name: "threads", Description: "Lists all threads in the debugged program.", - }, listThreads) + }, ds.listThreads) mcp.AddTool(server, &mcp.Tool{ Name: "stack-trace", Description: "Gets the stack trace for a thread.", - }, getStackTrace) + }, ds.getStackTrace) mcp.AddTool(server, &mcp.Tool{ Name: "scopes", Description: "Gets the scopes for a stack frame.", - }, getScopes) + }, ds.getScopes) mcp.AddTool(server, &mcp.Tool{ Name: "variables", Description: "Gets variables in a scope.", - }, getVariables) + }, ds.getVariables) mcp.AddTool(server, &mcp.Tool{ Name: "evaluate", Description: "Evaluates an expression in the context of a stack frame.", - }, evaluateExpression) + }, ds.evaluateExpression) mcp.AddTool(server, &mcp.Tool{ Name: "disconnect", Description: "Disconnects from the debugger.", - }, disconnect) + }, ds.disconnect) mcp.AddTool(server, &mcp.Tool{ Name: "exception-info", Description: "Gets information about an exception in a thread.", - }, getExceptionInfo) + }, ds.getExceptionInfo) mcp.AddTool(server, &mcp.Tool{ Name: "set-variable", Description: "Sets the value of a variable in the debugged program.", - }, setVariable) + }, ds.setVariable) mcp.AddTool(server, &mcp.Tool{ Name: "restart", Description: "Restarts the debugging session.", - }, restartDebugger) + }, ds.restartDebugger) mcp.AddTool(server, &mcp.Tool{ Name: "terminate", Description: "Terminates the debuggee process.", - }, terminateDebugger) + }, ds.terminateDebugger) mcp.AddTool(server, &mcp.Tool{ Name: "loaded-sources", Description: "Gets the list of all loaded source files.", - }, getLoadedSources) + }, ds.getLoadedSources) mcp.AddTool(server, &mcp.Tool{ Name: "modules", Description: "Gets the list of all loaded modules.", - }, getModules) + }, ds.getModules) mcp.AddTool(server, &mcp.Tool{ Name: "disassemble", Description: "Disassembles code at a memory reference.", - }, disassembleCode) + }, ds.disassembleCode) mcp.AddTool(server, &mcp.Tool{ Name: "attach", Description: "Attaches the debugger to a running process.", - }, attachDebugger) + }, ds.attachDebugger) } // StartDebuggerParams defines the parameters for starting a debugger. @@ -136,18 +137,18 @@ // startDebugger starts a debugger DAP server on the specified port. // It launches the delve debugger in DAP mode and configures it to listen on the given port. // If the port doesn't start with ":", it will be prefixed automatically. -func startDebugger(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[StartDebuggerParams]) (*mcp.CallToolResultFor[any], error) { +func (ds *debuggerSession) startDebugger(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[StartDebuggerParams]) (*mcp.CallToolResultFor[any], error) { port := params.Arguments.Port if !strings.HasPrefix(port, ":") { port = ":" + port } - cmd = exec.Command("dlv", "dap", "--listen", port, "--log", "--log-output", "dap") - cmd.Stderr = os.Stderr - stdout, err := cmd.StdoutPipe() + ds.cmd = exec.Command("dlv", "dap", "--listen", port, "--log", "--log-output", "dap") + ds.cmd.Stderr = os.Stderr + stdout, err := ds.cmd.StdoutPipe() if err != nil { return nil, err } - if err := cmd.Start(); err != nil { + if err := ds.cmd.Start(); err != nil { return nil, err } r := bufio.NewReader(stdout) @@ -162,12 +163,12 @@ } } - client = newDAPClient("localhost" + port) - if err := client.InitializeRequest(); err != nil { + ds.client = newDAPClient("localhost" + port) + if err := ds.client.InitializeRequest(); err != nil { return nil, err } // Read response to discover server capabilities - msg, err := client.ReadMessage() + msg, err := ds.client.ReadMessage() if err != nil { return nil, err } @@ -204,21 +205,21 @@ // stopDebugger stops the currently running debugger process. // It kills the debugger process and waits for it to exit. // If no debugger is running, it returns a message indicating this. -func stopDebugger(ctx context.Context, _ *mcp.ServerSession, _ *mcp.CallToolParamsFor[StopDebuggerParams]) (*mcp.CallToolResultFor[any], error) { - if cmd == nil { +func (ds *debuggerSession) stopDebugger(ctx context.Context, _ *mcp.ServerSession, _ *mcp.CallToolParamsFor[StopDebuggerParams]) (*mcp.CallToolResultFor[any], error) { + if ds.cmd == nil { return &mcp.CallToolResultFor[any]{ Content: []mcp.Content{&mcp.TextContent{Text: "No debugger currently executing."}}, }, nil } // Close the DAP client connection if it exists - if client != nil { - client.Close() - client = nil + if ds.client != nil { + ds.client.Close() + ds.client = nil } // Kill the debugger process - if err := cmd.Process.Kill(); err != nil { + if err := ds.cmd.Process.Kill(); err != nil { // Ignore the error if the process has already exited if !strings.Contains(err.Error(), "process already finished") { return nil, err @@ -226,8 +227,8 @@ } // Wait for the process to finish - cmd.Wait() // Ignore error as process might have been killed - cmd = nil + ds.cmd.Wait() // Ignore error as process might have been killed + ds.cmd = nil return &mcp.CallToolResultFor[any]{ Content: []mcp.Content{&mcp.TextContent{Text: "Debugger stopped."}}, @@ -244,12 +245,12 @@ // It sends a launch request to the DAP server with the given program path, // then reads the response to verify the launch was successful. // Returns an error if the launch fails or if the DAP server reports failure. -func debugProgram(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[DebugProgramParams]) (*mcp.CallToolResultFor[any], error) { +func (ds *debuggerSession) debugProgram(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[DebugProgramParams]) (*mcp.CallToolResultFor[any], error) { path := params.Arguments.Path - if err := client.LaunchRequest("debug", path, true); err != nil { + if err := ds.client.LaunchRequest("debug", path, true); err != nil { return nil, err } - if err := readAndValidateResponse(client, "unable to launch program to debug via DAP server"); err != nil { + if err := readAndValidateResponse(ds.client, "unable to launch program to debug via DAP server"); err != nil { return nil, err } @@ -258,12 +259,12 @@ }, nil } -func execProgram(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[DebugProgramParams]) (*mcp.CallToolResultFor[any], error) { +func (ds *debuggerSession) execProgram(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[DebugProgramParams]) (*mcp.CallToolResultFor[any], error) { path := params.Arguments.Path - if err := client.LaunchRequest("exec", path, true); err != nil { + if err := ds.client.LaunchRequest("exec", path, true); err != nil { return nil, err } - if err := readAndValidateResponse(client, "unable to exec program to debug via DAP server"); err != nil { + if err := readAndValidateResponse(ds.client, "unable to exec program to debug via DAP server"); err != nil { return nil, err } @@ -300,14 +301,14 @@ } // setBreakpoints sets breakpoints in a source file at specified line numbers. -func setBreakpoints(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[SetBreakpointsParams]) (*mcp.CallToolResultFor[any], error) { - if client == nil { +func (ds *debuggerSession) setBreakpoints(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[SetBreakpointsParams]) (*mcp.CallToolResultFor[any], error) { + if ds.client == nil { return nil, fmt.Errorf("debugger not started") } - if err := client.SetBreakpointsRequest(params.Arguments.File, params.Arguments.Lines); err != nil { + if err := ds.client.SetBreakpointsRequest(params.Arguments.File, params.Arguments.Lines); err != nil { return nil, err } - msg, err := client.ReadMessage() + msg, err := ds.client.ReadMessage() if err != nil { return nil, err } @@ -340,14 +341,14 @@ } // setFunctionBreakpoints sets breakpoints on functions by name. -func setFunctionBreakpoints(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[SetFunctionBreakpointsParams]) (*mcp.CallToolResultFor[any], error) { - if client == nil { +func (ds *debuggerSession) setFunctionBreakpoints(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[SetFunctionBreakpointsParams]) (*mcp.CallToolResultFor[any], error) { + if ds.client == nil { return nil, fmt.Errorf("debugger not started") } - if err := client.SetFunctionBreakpointsRequest(params.Arguments.Functions); err != nil { + if err := ds.client.SetFunctionBreakpointsRequest(params.Arguments.Functions); err != nil { return nil, err } - if err := readAndValidateResponse(client, "unable to set function breakpoints"); err != nil { + if err := readAndValidateResponse(ds.client, "unable to set function breakpoints"); err != nil { return nil, err } @@ -361,14 +362,14 @@ } // configurationDone indicates that configuration is complete and debugging can begin. -func configurationDone(ctx context.Context, _ *mcp.ServerSession, _ *mcp.CallToolParamsFor[ConfigurationDoneParams]) (*mcp.CallToolResultFor[any], error) { - if client == nil { +func (ds *debuggerSession) configurationDone(ctx context.Context, _ *mcp.ServerSession, _ *mcp.CallToolParamsFor[ConfigurationDoneParams]) (*mcp.CallToolResultFor[any], error) { + if ds.client == nil { return nil, fmt.Errorf("debugger not started") } - if err := client.ConfigurationDoneRequest(); err != nil { + if err := ds.client.ConfigurationDoneRequest(); err != nil { return nil, err } - if err := readAndValidateResponse(client, "unable to complete configuration"); err != nil { + if err := readAndValidateResponse(ds.client, "unable to complete configuration"); err != nil { return nil, err } @@ -383,15 +384,15 @@ } // continueExecution continues execution of the debugged program. -func continueExecution(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[ContinueParams]) (*mcp.CallToolResultFor[any], error) { - if client == nil { +func (ds *debuggerSession) continueExecution(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[ContinueParams]) (*mcp.CallToolResultFor[any], error) { + if ds.client == nil { return nil, fmt.Errorf("debugger not started") } - if err := client.ContinueRequest(params.Arguments.ThreadID); err != nil { + if err := ds.client.ContinueRequest(params.Arguments.ThreadID); err != nil { return nil, err } for { - msg, err := client.ReadMessage() + msg, err := ds.client.ReadMessage() if err != nil { return nil, err } @@ -430,20 +431,36 @@ } // nextStep steps over the next line of code. -func nextStep(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[NextParams]) (*mcp.CallToolResultFor[any], error) { - if client == nil { +func (ds *debuggerSession) nextStep(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[NextParams]) (*mcp.CallToolResultFor[any], error) { + if ds.client == nil { return nil, fmt.Errorf("debugger not started") } - if err := client.NextRequest(params.Arguments.ThreadID); err != nil { + if err := ds.client.NextRequest(params.Arguments.ThreadID); err != nil { return nil, err } - if err := readAndValidateResponse(client, "unable to step to next line"); err != nil { - return nil, err + for { + msg, err := ds.client.ReadMessage() + if err != nil { + return nil, err + } + switch resp := msg.(type) { + case dap.ResponseMessage: + if !resp.GetResponse().Success { + return nil, fmt.Errorf("%s: %s", "unable to step to next line", resp.GetResponse().Message) + } + case *dap.StoppedEvent: + msg := resp.Body + var response string + response = formatStoppedResponse(msg) + return &mcp.CallToolResultFor[any]{ + Content: []mcp.Content{&mcp.TextContent{Text: "Stepped to next line...\n" + response}}, + }, nil + case *dap.TerminatedEvent: + return &mcp.CallToolResultFor[any]{ + Content: []mcp.Content{&mcp.TextContent{Text: "Stepped to program termination"}}, + }, nil + } } - - return &mcp.CallToolResultFor[any]{ - Content: []mcp.Content{&mcp.TextContent{Text: "Stepped to next line"}}, - }, nil } // StepInParams defines the parameters for stepping into a function. @@ -452,20 +469,36 @@ } // stepIn steps into a function call. -func stepIn(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[StepInParams]) (*mcp.CallToolResultFor[any], error) { - if client == nil { +func (ds *debuggerSession) stepIn(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[StepInParams]) (*mcp.CallToolResultFor[any], error) { + if ds.client == nil { return nil, fmt.Errorf("debugger not started") } - if err := client.StepInRequest(params.Arguments.ThreadID); err != nil { + if err := ds.client.StepInRequest(params.Arguments.ThreadID); err != nil { return nil, err } - if err := readAndValidateResponse(client, "unable to step into function"); err != nil { - return nil, err + for { + msg, err := ds.client.ReadMessage() + if err != nil { + return nil, err + } + switch resp := msg.(type) { + case dap.ResponseMessage: + if !resp.GetResponse().Success { + return nil, fmt.Errorf("%s: %s", "unable to step into function", resp.GetResponse().Message) + } + case *dap.StoppedEvent: + msg := resp.Body + var response string + response = formatStoppedResponse(msg) + return &mcp.CallToolResultFor[any]{ + Content: []mcp.Content{&mcp.TextContent{Text: "Stepped into function...\n" + response}}, + }, nil + case *dap.TerminatedEvent: + return &mcp.CallToolResultFor[any]{ + Content: []mcp.Content{&mcp.TextContent{Text: "Stepped to program termination"}}, + }, nil + } } - - return &mcp.CallToolResultFor[any]{ - Content: []mcp.Content{&mcp.TextContent{Text: "Stepped into function"}}, - }, nil } // StepOutParams defines the parameters for stepping out of a function. @@ -474,20 +507,36 @@ } // stepOut steps out of the current function. -func stepOut(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[StepOutParams]) (*mcp.CallToolResultFor[any], error) { - if client == nil { +func (ds *debuggerSession) stepOut(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[StepOutParams]) (*mcp.CallToolResultFor[any], error) { + if ds.client == nil { return nil, fmt.Errorf("debugger not started") } - if err := client.StepOutRequest(params.Arguments.ThreadID); err != nil { + if err := ds.client.StepOutRequest(params.Arguments.ThreadID); err != nil { return nil, err } - if err := readAndValidateResponse(client, "unable to step out of function"); err != nil { - return nil, err + for { + msg, err := ds.client.ReadMessage() + if err != nil { + return nil, err + } + switch resp := msg.(type) { + case dap.ResponseMessage: + if !resp.GetResponse().Success { + return nil, fmt.Errorf("%s: %s", "unable to step out of function", resp.GetResponse().Message) + } + case *dap.StoppedEvent: + msg := resp.Body + var response string + response = formatStoppedResponse(msg) + return &mcp.CallToolResultFor[any]{ + Content: []mcp.Content{&mcp.TextContent{Text: "Stepped out of function...\n" + response}}, + }, nil + case *dap.TerminatedEvent: + return &mcp.CallToolResultFor[any]{ + Content: []mcp.Content{&mcp.TextContent{Text: "Stepped to program termination"}}, + }, nil + } } - - return &mcp.CallToolResultFor[any]{ - Content: []mcp.Content{&mcp.TextContent{Text: "Stepped out of function"}}, - }, nil } // PauseParams defines the parameters for pausing execution. @@ -496,14 +545,14 @@ } // pauseExecution pauses execution of a thread. -func pauseExecution(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[PauseParams]) (*mcp.CallToolResultFor[any], error) { - if client == nil { +func (ds *debuggerSession) pauseExecution(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[PauseParams]) (*mcp.CallToolResultFor[any], error) { + if ds.client == nil { return nil, fmt.Errorf("debugger not started") } - if err := client.PauseRequest(params.Arguments.ThreadID); err != nil { + if err := ds.client.PauseRequest(params.Arguments.ThreadID); err != nil { return nil, err } - if err := readAndValidateResponse(client, "unable to pause execution"); err != nil { + if err := readAndValidateResponse(ds.client, "unable to pause execution"); err != nil { return nil, err } @@ -517,14 +566,14 @@ } // listThreads lists all threads in the debugged program. -func listThreads(ctx context.Context, _ *mcp.ServerSession, _ *mcp.CallToolParamsFor[ThreadsParams]) (*mcp.CallToolResultFor[any], error) { - if client == nil { +func (ds *debuggerSession) listThreads(ctx context.Context, _ *mcp.ServerSession, _ *mcp.CallToolParamsFor[ThreadsParams]) (*mcp.CallToolResultFor[any], error) { + if ds.client == nil { return nil, fmt.Errorf("debugger not started") } - if err := client.ThreadsRequest(); err != nil { + if err := ds.client.ThreadsRequest(); err != nil { return nil, err } - msg, err := client.ReadMessage() + msg, err := ds.client.ReadMessage() if err != nil { return nil, err } @@ -552,8 +601,8 @@ } // getStackTrace gets the stack trace for a thread. -func getStackTrace(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[StackTraceParams]) (*mcp.CallToolResultFor[any], error) { - if client == nil { +func (ds *debuggerSession) getStackTrace(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[StackTraceParams]) (*mcp.CallToolResultFor[any], error) { + if ds.client == nil { return nil, fmt.Errorf("debugger not started") } @@ -562,13 +611,13 @@ levels = 20 } - if err := client.StackTraceRequest(params.Arguments.ThreadID, params.Arguments.StartFrame, levels); err != nil { + if err := ds.client.StackTraceRequest(params.Arguments.ThreadID, params.Arguments.StartFrame, levels); err != nil { return nil, err } // Read messages until we get the stack trace response for { - msg, err := client.ReadMessage() + msg, err := ds.client.ReadMessage() if err != nil { return nil, err } @@ -583,7 +632,7 @@ stackTrace.WriteString(fmt.Sprintf("Stack trace for thread %d:\n", params.Arguments.ThreadID)) for i, frame := range resp.Body.StackFrames { - stackTrace.WriteString(fmt.Sprintf("\n#%d %s", i, frame.Name)) + stackTrace.WriteString(fmt.Sprintf("\n#%d (Frame ID: %d) %s", i, frame.Id, frame.Name)) if frame.Source != nil && frame.Source.Path != "" { stackTrace.WriteString(fmt.Sprintf("\n at %s:%d", frame.Source.Path, frame.Line)) if frame.Column > 0 { @@ -624,24 +673,61 @@ } // getScopes gets the scopes for a stack frame. -func getScopes(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[ScopesParams]) (*mcp.CallToolResultFor[any], error) { - if client == nil { +// It retrieves all available scopes (e.g., Locals, Arguments, Globals) for the specified frame +// and automatically fetches the variables within each scope. +// The response includes: +// - Scope names and their variable references +// - All variables within each scope with their names, types, and values +// Returns a formatted text representation of the scopes and their variables. +func (ds *debuggerSession) getScopes(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[ScopesParams]) (*mcp.CallToolResultFor[any], error) { + if ds.client == nil { return nil, fmt.Errorf("debugger not started") } - if err := client.ScopesRequest(params.Arguments.FrameID); err != nil { + if err := ds.client.ScopesRequest(params.Arguments.FrameID); err != nil { return nil, err } - msg, err := client.ReadMessage() + msg, err := ds.client.ReadMessage() if err != nil { return nil, err } - if resp, ok := msg.(dap.ResponseMessage); ok { - if !resp.GetResponse().Success { - return nil, fmt.Errorf("unable to get scopes: %s", resp.GetResponse().Message) + if resp, ok := msg.(*dap.ScopesResponse); ok { + if !resp.Success { + return nil, fmt.Errorf("unable to get scopes: %s", resp.Message) } + + var result strings.Builder + result.WriteString(fmt.Sprintf("Scopes for frame %d:\n", params.Arguments.FrameID)) + + for _, scope := range resp.Body.Scopes { + result.WriteString(fmt.Sprintf("\n%s (ref: %d", scope.Name, scope.VariablesReference)) + if scope.Expensive { + result.WriteString(", expensive") + } + result.WriteString(")\n") + + // If the scope has variables, we can fetch them + if scope.VariablesReference > 0 { + // Request variables for this scope + if err := ds.client.VariablesRequest(scope.VariablesReference); err == nil { + if varMsg, err := ds.client.ReadMessage(); err == nil { + if varResp, ok := varMsg.(*dap.VariablesResponse); ok && varResp.Success { + // Format variables + for _, variable := range varResp.Body.Variables { + result.WriteString(fmt.Sprintf(" %s", variable.Name)) + if variable.Type != "" { + result.WriteString(fmt.Sprintf(" (%s)", variable.Type)) + } + result.WriteString(fmt.Sprintf(" = %s\n", variable.Value)) + } + } + } + } + } + } + return &mcp.CallToolResultFor[any]{ - Content: []mcp.Content{&mcp.TextContent{Text: "Retrieved scopes"}}, + Content: []mcp.Content{&mcp.TextContent{Text: result.String()}}, }, nil } @@ -654,14 +740,14 @@ } // getVariables gets variables in a scope. -func getVariables(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[VariablesParams]) (*mcp.CallToolResultFor[any], error) { - if client == nil { +func (ds *debuggerSession) getVariables(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[VariablesParams]) (*mcp.CallToolResultFor[any], error) { + if ds.client == nil { return nil, fmt.Errorf("debugger not started") } - if err := client.VariablesRequest(params.Arguments.VariablesReference); err != nil { + if err := ds.client.VariablesRequest(params.Arguments.VariablesReference); err != nil { return nil, err } - msg, err := client.ReadMessage() + msg, err := ds.client.ReadMessage() if err != nil { return nil, err } @@ -686,8 +772,8 @@ } // evaluateExpression evaluates an expression in the context of a stack frame. -func evaluateExpression(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[EvaluateParams]) (*mcp.CallToolResultFor[any], error) { - if client == nil { +func (ds *debuggerSession) evaluateExpression(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[EvaluateParams]) (*mcp.CallToolResultFor[any], error) { + if ds.client == nil { return nil, fmt.Errorf("debugger not started") } @@ -696,14 +782,14 @@ context = "repl" } - if err := client.EvaluateRequest(params.Arguments.Expression, params.Arguments.FrameID, context); err != nil { + if err := ds.client.EvaluateRequest(params.Arguments.Expression, params.Arguments.FrameID, context); err != nil { return nil, err } // Read messages until we get the EvaluateResponse // Events can come at any time, so we need to handle them for { - msg, err := client.ReadMessage() + msg, err := ds.client.ReadMessage() if err != nil { return nil, err } @@ -737,14 +823,14 @@ } // setVariable sets the value of a variable in the debugged program. -func setVariable(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[SetVariableParams]) (*mcp.CallToolResultFor[any], error) { - if client == nil { +func (ds *debuggerSession) setVariable(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[SetVariableParams]) (*mcp.CallToolResultFor[any], error) { + if ds.client == nil { return nil, fmt.Errorf("debugger not started") } - if err := client.SetVariableRequest(params.Arguments.VariablesReference, params.Arguments.Name, params.Arguments.Value); err != nil { + if err := ds.client.SetVariableRequest(params.Arguments.VariablesReference, params.Arguments.Name, params.Arguments.Value); err != nil { return nil, err } - msg, err := client.ReadMessage() + msg, err := ds.client.ReadMessage() if err != nil { return nil, err } @@ -767,11 +853,11 @@ } // restartDebugger restarts the debugging session. -func restartDebugger(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[RestartParams]) (*mcp.CallToolResultFor[any], error) { - if client == nil { +func (ds *debuggerSession) restartDebugger(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[RestartParams]) (*mcp.CallToolResultFor[any], error) { + if ds.client == nil { return nil, fmt.Errorf("debugger not started") } - if err := client.RestartRequest(map[string]any{ + if err := ds.client.RestartRequest(map[string]any{ "arguments": map[string]any{ "request": "launch", "mode": "exec", @@ -781,7 +867,7 @@ }); err != nil { return nil, err } - if err := readAndValidateResponse(client, "unable to restart debugger"); err != nil { + if err := readAndValidateResponse(ds.client, "unable to restart debugger"); err != nil { return nil, err } @@ -795,14 +881,14 @@ } // terminateDebugger terminates the debuggee process. -func terminateDebugger(ctx context.Context, _ *mcp.ServerSession, _ *mcp.CallToolParamsFor[TerminateParams]) (*mcp.CallToolResultFor[any], error) { - if client == nil { +func (ds *debuggerSession) terminateDebugger(ctx context.Context, _ *mcp.ServerSession, _ *mcp.CallToolParamsFor[TerminateParams]) (*mcp.CallToolResultFor[any], error) { + if ds.client == nil { return nil, fmt.Errorf("debugger not started") } - if err := client.TerminateRequest(); err != nil { + if err := ds.client.TerminateRequest(); err != nil { return nil, err } - if err := readAndValidateResponse(client, "unable to terminate debugger"); err != nil { + if err := readAndValidateResponse(ds.client, "unable to terminate debugger"); err != nil { return nil, err } @@ -816,14 +902,14 @@ } // getLoadedSources gets the list of all loaded source files. -func getLoadedSources(ctx context.Context, _ *mcp.ServerSession, _ *mcp.CallToolParamsFor[LoadedSourcesParams]) (*mcp.CallToolResultFor[any], error) { - if client == nil { +func (ds *debuggerSession) getLoadedSources(ctx context.Context, _ *mcp.ServerSession, _ *mcp.CallToolParamsFor[LoadedSourcesParams]) (*mcp.CallToolResultFor[any], error) { + if ds.client == nil { return nil, fmt.Errorf("debugger not started") } - if err := client.LoadedSourcesRequest(); err != nil { + if err := ds.client.LoadedSourcesRequest(); err != nil { return nil, err } - msg, err := client.ReadMessage() + msg, err := ds.client.ReadMessage() if err != nil { return nil, err } @@ -845,14 +931,14 @@ } // getModules gets the list of all loaded modules. -func getModules(ctx context.Context, _ *mcp.ServerSession, _ *mcp.CallToolParamsFor[ModulesParams]) (*mcp.CallToolResultFor[any], error) { - if client == nil { +func (ds *debuggerSession) getModules(ctx context.Context, _ *mcp.ServerSession, _ *mcp.CallToolParamsFor[ModulesParams]) (*mcp.CallToolResultFor[any], error) { + if ds.client == nil { return nil, fmt.Errorf("debugger not started") } - if err := client.ModulesRequest(); err != nil { + if err := ds.client.ModulesRequest(); err != nil { return nil, err } - msg, err := client.ReadMessage() + msg, err := ds.client.ReadMessage() if err != nil { return nil, err } @@ -877,14 +963,14 @@ } // disassembleCode disassembles code at a memory reference. -func disassembleCode(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[DisassembleParams]) (*mcp.CallToolResultFor[any], error) { - if client == nil { +func (ds *debuggerSession) disassembleCode(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[DisassembleParams]) (*mcp.CallToolResultFor[any], error) { + if ds.client == nil { return nil, fmt.Errorf("debugger not started") } - if err := client.DisassembleRequest(params.Arguments.MemoryReference, params.Arguments.InstructionOffset, params.Arguments.InstructionCount); err != nil { + if err := ds.client.DisassembleRequest(params.Arguments.MemoryReference, params.Arguments.InstructionOffset, params.Arguments.InstructionCount); err != nil { return nil, err } - msg, err := client.ReadMessage() + msg, err := ds.client.ReadMessage() if err != nil { return nil, err } @@ -908,14 +994,14 @@ } // attachDebugger attaches the debugger to a running process. -func attachDebugger(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[AttachParams]) (*mcp.CallToolResultFor[any], error) { - if client == nil { +func (ds *debuggerSession) attachDebugger(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[AttachParams]) (*mcp.CallToolResultFor[any], error) { + if ds.client == nil { return nil, fmt.Errorf("debugger not started") } - if err := client.AttachRequest(params.Arguments.Mode, params.Arguments.ProcessID); err != nil { + if err := ds.client.AttachRequest(params.Arguments.Mode, params.Arguments.ProcessID); err != nil { return nil, err } - if err := readAndValidateResponse(client, "unable to attach to process"); err != nil { + if err := readAndValidateResponse(ds.client, "unable to attach to process"); err != nil { return nil, err } @@ -930,20 +1016,20 @@ } // disconnect disconnects from the debugger. -func disconnect(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[DisconnectParams]) (*mcp.CallToolResultFor[any], error) { - if client == nil { +func (ds *debuggerSession) disconnect(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[DisconnectParams]) (*mcp.CallToolResultFor[any], error) { + if ds.client == nil { return nil, fmt.Errorf("debugger not started") } - if err := client.DisconnectRequest(params.Arguments.TerminateDebuggee); err != nil { + if err := ds.client.DisconnectRequest(params.Arguments.TerminateDebuggee); err != nil { return nil, err } - if err := readAndValidateResponse(client, "unable to disconnect"); err != nil { + if err := readAndValidateResponse(ds.client, "unable to disconnect"); err != nil { return nil, err } // Clean up client connection - client.Close() - client = nil + ds.client.Close() + ds.client = nil return &mcp.CallToolResultFor[any]{ Content: []mcp.Content{&mcp.TextContent{Text: "Disconnected from debugger"}}, @@ -956,14 +1042,14 @@ } // getExceptionInfo gets information about an exception in a thread. -func getExceptionInfo(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[ExceptionInfoParams]) (*mcp.CallToolResultFor[any], error) { - if client == nil { +func (ds *debuggerSession) getExceptionInfo(ctx context.Context, _ *mcp.ServerSession, params *mcp.CallToolParamsFor[ExceptionInfoParams]) (*mcp.CallToolResultFor[any], error) { + if ds.client == nil { return nil, fmt.Errorf("debugger not started") } - if err := client.ExceptionInfoRequest(params.Arguments.ThreadID); err != nil { + if err := ds.client.ExceptionInfoRequest(params.Arguments.ThreadID); err != nil { return nil, err } - msg, err := client.ReadMessage() + msg, err := ds.client.ReadMessage() if err != nil { return nil, err } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mcp-dap-server-0.0.0+git20250716.7ffcc1d/tools_test.go new/mcp-dap-server-0.0.0+git20250722.f375340/tools_test.go --- old/mcp-dap-server-0.0.0+git20250716.7ffcc1d/tools_test.go 2025-07-17 02:30:27.000000000 +0200 +++ new/mcp-dap-server-0.0.0+git20250722.f375340/tools_test.go 2025-07-23 00:50:02.000000000 +0200 @@ -2,6 +2,7 @@ import ( "context" + "fmt" "net/http" "net/http/httptest" "os" @@ -442,5 +443,412 @@ } // Stop debugger + ts.stopDebugger(t) +} + +func TestScopes(t *testing.T) { + // Setup test infrastructure + ts := setupMCPServerAndClient(t) + defer ts.cleanup() + + // Compile test program + binaryPath, cleanupBinary := compileTestProgram(t, ts.cwd, "helloworld") + defer cleanupBinary() + + // Start debugger and execute program + ts.startDebuggerAndExecuteProgram(t, "9093", binaryPath) + + // Set breakpoint and continue + f := filepath.Join(ts.cwd, "testdata", "go", "helloworld", "main.go") + ts.setBreakpointAndContinue(t, f, 7) + + // Get stacktrace first to ensure we have valid frame IDs + stacktraceResult, err := ts.session.CallTool(ts.ctx, &mcp.CallToolParams{ + Name: "stack-trace", + Arguments: map[string]any{ + "threadID": 1, + }, + }) + if err != nil { + t.Fatalf("Failed to get stacktrace: %v", err) + } + t.Logf("Stacktrace result: %v", stacktraceResult) + + // Test getting scopes for frame ID 1000 (the topmost frame) + scopesResult, err := ts.session.CallTool(ts.ctx, &mcp.CallToolParams{ + Name: "scopes", + Arguments: map[string]any{ + "frameId": 1000, + }, + }) + if err != nil { + t.Fatalf("Failed to get scopes: %v", err) + } + t.Logf("Scopes result: %v", scopesResult) + + // Check if scopes returned an error + if scopesResult.IsError { + errorMsg := "Unknown error" + if len(scopesResult.Content) > 0 { + if textContent, ok := scopesResult.Content[0].(*mcp.TextContent); ok { + errorMsg = textContent.Text + } + } + t.Fatalf("Scopes returned error: %s", errorMsg) + } + + // Verify we got scopes data + if len(scopesResult.Content) == 0 { + t.Fatalf("Expected scopes data, got empty content") + } + + // Extract scopes content + scopesStr := "" + for _, content := range scopesResult.Content { + if textContent, ok := content.(*mcp.TextContent); ok { + scopesStr += textContent.Text + } + } + + t.Logf("Scopes output:\n%s", scopesStr) + + // Verify scopes contains expected information + // For the helloworld program at line 7, we should have locals scope + if !strings.Contains(scopesStr, "Locals") { + t.Errorf("Expected scopes to contain 'Locals', got: %s", scopesStr) + } + + // The greeting variable should be in the locals scope + if !strings.Contains(scopesStr, "greeting") { + t.Errorf("Expected scopes to contain 'greeting' variable, got: %s", scopesStr) + } + + // Stop debugger + ts.stopDebugger(t) +} + +func TestScopesComprehensive(t *testing.T) { + // Setup test infrastructure + ts := setupMCPServerAndClient(t) + defer ts.cleanup() + + // Compile test program + binaryPath, cleanupBinary := compileTestProgram(t, ts.cwd, "scopes") + defer cleanupBinary() + + // Start debugger and execute program + ts.startDebuggerAndExecuteProgram(t, "9094", binaryPath) + + // Set all breakpoints at once + f := filepath.Join(ts.cwd, "testdata", "go", "scopes", "main.go") + + // Set breakpoint in greet function at line 42 + setBreakpointResult, err := ts.session.CallTool(ts.ctx, &mcp.CallToolParams{ + Name: "set-breakpoints", + Arguments: map[string]any{ + "file": f, + "lines": []int{42, 54, 67}, + }, + }) + if err != nil { + t.Fatalf("Failed to set breakpoints: %v", err) + } + t.Logf("Set breakpoints result: %v", setBreakpointResult) + + // Continue to first breakpoint + _, err = ts.session.CallTool(ts.ctx, &mcp.CallToolParams{ + Name: "continue", + Arguments: map[string]any{ + "threadID": 1, + }, + }) + if err != nil { + t.Fatalf("Failed to continue: %v", err) + } + + // Get stacktrace to ensure we have valid frame IDs + _, err = ts.session.CallTool(ts.ctx, &mcp.CallToolParams{ + Name: "stack-trace", + Arguments: map[string]any{ + "threadID": 1, + }, + }) + if err != nil { + t.Fatalf("Failed to get stacktrace: %v", err) + } + + // Get scopes for the greet function frame + scopesResult, err := ts.session.CallTool(ts.ctx, &mcp.CallToolParams{ + Name: "scopes", + Arguments: map[string]any{ + "frameId": 1000, // topmost frame (greet function) + }, + }) + if err != nil { + t.Fatalf("Failed to get scopes: %v", err) + } + + scopesStr := "" + for _, content := range scopesResult.Content { + if textContent, ok := content.(*mcp.TextContent); ok { + scopesStr += textContent.Text + } + } + t.Logf("Scopes in greet function:\n%s", scopesStr) + + // Verify function arguments + if !strings.Contains(scopesStr, "name") || !strings.Contains(scopesStr, "\"Alice\"") { + t.Errorf("Expected to find argument 'name' with value 'Alice'") + } + if !strings.Contains(scopesStr, "age") || !strings.Contains(scopesStr, "30") { + t.Errorf("Expected to find argument 'age' with value 30") + } + + // Verify local variables + if !strings.Contains(scopesStr, "greeting") { + t.Errorf("Expected to find local variable 'greeting'") + } + if !strings.Contains(scopesStr, "prefix") && !strings.Contains(scopesStr, "Greeting: ") { + t.Errorf("Expected to find local variable 'prefix' with value 'Greeting: '") + } + + // Test 2: Struct parameter and local variables + // Continue to next breakpoint in processPerson at line 54 + _, err = ts.session.CallTool(ts.ctx, &mcp.CallToolParams{ + Name: "continue", + Arguments: map[string]any{ + "threadID": 1, + }, + }) + if err != nil { + t.Fatalf("Failed to continue: %v", err) + } + + // Get stack trace for processPerson function + _, err = ts.session.CallTool(ts.ctx, &mcp.CallToolParams{ + Name: "stack-trace", + Arguments: map[string]any{ + "threadID": 1, + }, + }) + if err != nil { + t.Fatalf("Failed to get stacktrace: %v", err) + } + + // Get scopes for processPerson function + scopesResult2, err := ts.session.CallTool(ts.ctx, &mcp.CallToolParams{ + Name: "scopes", + Arguments: map[string]any{ + "frameId": 1000, // topmost frame (processPerson function) + }, + }) + if err != nil { + t.Fatalf("Failed to get scopes: %v", err) + } + + scopesStr2 := "" + for _, content := range scopesResult2.Content { + if textContent, ok := content.(*mcp.TextContent); ok { + scopesStr2 += textContent.Text + } + } + t.Logf("Scopes in processPerson function:\n%s", scopesStr2) + + // Verify struct parameter + if !strings.Contains(scopesStr2, "p") { + t.Errorf("Expected to find parameter 'p' (Person struct)") + } + if !strings.Contains(scopesStr2, "description") { + t.Errorf("Expected to find local variable 'description'") + } + if !strings.Contains(scopesStr2, "isAdult") { + t.Errorf("Expected to find local variable 'isAdult'") + } + + // Test 3: Collection parameters + // Continue to next breakpoint in processCollection at line 67 + _, err = ts.session.CallTool(ts.ctx, &mcp.CallToolParams{ + Name: "continue", + Arguments: map[string]any{ + "threadID": 1, + }, + }) + if err != nil { + t.Fatalf("Failed to continue: %v", err) + } + + // Get stack trace for processCollection function + _, err = ts.session.CallTool(ts.ctx, &mcp.CallToolParams{ + Name: "stack-trace", + Arguments: map[string]any{ + "threadID": 1, + }, + }) + if err != nil { + t.Fatalf("Failed to get stacktrace: %v", err) + } + + // Get scopes for processCollection function + scopesResult3, err := ts.session.CallTool(ts.ctx, &mcp.CallToolParams{ + Name: "scopes", + Arguments: map[string]any{ + "frameId": 1000, // topmost frame (processCollection function) + }, + }) + if err != nil { + t.Fatalf("Failed to get scopes: %v", err) + } + + scopesStr3 := "" + for _, content := range scopesResult3.Content { + if textContent, ok := content.(*mcp.TextContent); ok { + scopesStr3 += textContent.Text + } + } + t.Logf("Scopes in processCollection function:\n%s", scopesStr3) + + // Verify collection parameters and locals + if !strings.Contains(scopesStr3, "nums") { + t.Errorf("Expected to find parameter 'nums' (slice)") + } + if !strings.Contains(scopesStr3, "dict") { + t.Errorf("Expected to find parameter 'dict' (map)") + } + if !strings.Contains(scopesStr3, "sum") { + t.Errorf("Expected to find local variable 'sum'") + } + if !strings.Contains(scopesStr3, "count") { + t.Errorf("Expected to find local variable 'count'") + } + + // Stop debugger + ts.stopDebugger(t) +} + +func TestNextStep(t *testing.T) { + // Setup test infrastructure + ts := setupMCPServerAndClient(t) + defer ts.cleanup() + + // Compile test program + binaryPath, cleanupBinary := compileTestProgram(t, ts.cwd, "step") + defer cleanupBinary() + + // Start debugger and execute program + ts.startDebuggerAndExecuteProgram(t, "9090", binaryPath) + + // Set breakpoint at line 7 (x := 10) + f := filepath.Join(ts.cwd, "testdata", "go", "step", "main.go") + ts.setBreakpointAndContinue(t, f, 7) + + // Helper function to perform next step + performNextStep := func(threadID int) error { + args := map[string]any{ + "threadId": threadID, + } + result, err := ts.session.CallTool(ts.ctx, &mcp.CallToolParams{ + Name: "next", + Arguments: args, + }) + if err != nil { + return err + } + // Verify we get a response + if len(result.Content) == 0 { + return fmt.Errorf("expected content in next step response") + } + return nil + } + + // Get initial stack trace to find thread ID + stackTraceArgs := map[string]any{ + "threadId": 1, + } + stackTraceResult, err := ts.session.CallTool(ts.ctx, &mcp.CallToolParams{ + Name: "stack-trace", + Arguments: stackTraceArgs, + }) + if err != nil { + t.Fatalf("Failed to get stack trace: %v", err) + } + + // Step to line 10 (y := 20) + err = performNextStep(1) + if err != nil { + t.Fatalf("Failed to perform next step: %v", err) + } + + // Get stack trace to verify we're at line 10 + stackTraceResult, err = ts.session.CallTool(ts.ctx, &mcp.CallToolParams{ + Name: "stack-trace", + Arguments: stackTraceArgs, + }) + if err != nil { + t.Fatalf("Failed to get stack trace after next: %v", err) + } + stacktraceStr := stackTraceResult.Content[0].(*mcp.TextContent).Text + if !strings.Contains(stacktraceStr, "main.go:10") { + t.Errorf("Expected to be at line 10 after next step, got: %s", stacktraceStr) + } + + // Step to line 13 (sum := x + y) + err = performNextStep(1) + if err != nil { + t.Fatalf("Failed to perform second next step: %v", err) + } + + // Verify we're at line 13 + stackTraceResult, err = ts.session.CallTool(ts.ctx, &mcp.CallToolParams{ + Name: "stack-trace", + Arguments: stackTraceArgs, + }) + if err != nil { + t.Fatalf("Failed to get stack trace after second next: %v", err) + } + stacktraceStr = stackTraceResult.Content[0].(*mcp.TextContent).Text + if !strings.Contains(stacktraceStr, "main.go:13") { + t.Errorf("Expected to be at line 13 after second next step, got: %s", stacktraceStr) + } + + // Step to line 16 (message := fmt.Sprintf...) + err = performNextStep(1) + if err != nil { + t.Fatalf("Failed to perform third next step: %v", err) + } + + // Get fresh stack trace to get current frame ID + stackTraceResult, err = ts.session.CallTool(ts.ctx, &mcp.CallToolParams{ + Name: "stack-trace", + Arguments: stackTraceArgs, + }) + if err != nil { + t.Fatalf("Failed to get stack trace before scopes: %v", err) + } + + // Get variables to verify state + scopesArgs := map[string]any{ + "frameId": 1000, + } + scopesResult, err := ts.session.CallTool(ts.ctx, &mcp.CallToolParams{ + Name: "scopes", + Arguments: scopesArgs, + }) + if err != nil { + t.Fatalf("Failed to get scopes: %v", err) + } + scopesStr := scopesResult.Content[0].(*mcp.TextContent).Text + + // Verify variables exist and have expected values + if !strings.Contains(scopesStr, "x (int) = 10") { + t.Errorf("Expected x to be 10 in scopes, got:\n%s", scopesStr) + } + if !strings.Contains(scopesStr, "y (int) = 20") { + t.Errorf("Expected y to be 20 in scopes, got:\n%s", scopesStr) + } + if !strings.Contains(scopesStr, "sum (int) = 30") { + t.Errorf("Expected sum to be 30 in scopes, got:\n%s", scopesStr) + } + + // Stop debugger ts.stopDebugger(t) } ++++++ vendor.tar.gz ++++++
