The Debug MCP Server provides support for Go debugging through Delve (dlv), Go's native debugger with DAP (Debug Adapter Protocol) support. This document explains how to use the Go debugging capabilities.
Before using the Go debugging features, ensure you have:
- Go 1.18 or higher installed from go.dev/dl
- Delve 1.6.0+ installed (that is when
dlv daplanded; current releases are 1.2x):go install github.com/go-delve/delve/cmd/dlv@latest
Verify your installation:
go version # Should show 1.18 or higher
dlv version # Should show Delve version
dlv dap --help # Should show DAP help (confirms DAP support)If dlv is not on your PATH (a common case when GOPATH/bin is not exported), point the
server at it with the DLV_PATH environment variable — set it to the Delve binary, not
its directory. The session's executablePath takes precedence over DLV_PATH; with neither
set, the adapter looks for dlv/dlv-dap on PATH and then in GOPATH/bin.
Launch only. Go has no attach implementation — attach_to_process fails fast with
"Attach mode is not implemented" for Go sessions. Debug Go by launching the program,
the test binary, or a prebuilt executable with the modes below.
First, create a Go debug session:
create_debug_session { "language": "go", "name": "My Go Debug Session" }
This returns a session ID that you'll use for all subsequent debugging commands.
Before debugging, compile your Go program with debug symbols:
go build -gcflags="all=-N -l" -o myprogram main.goThe -gcflags="all=-N -l" flags disable optimizations and inlining, which are required for accurate debugging.
Set breakpoints in your code before starting execution:
set_breakpoint { "sessionId": "your-session-id", "file": "/path/to/your/main.go", "line": 15 }
You can also set conditional breakpoints:
set_breakpoint { "sessionId": "your-session-id", "file": "/path/to/your/main.go", "line": 20,
"condition": "x > 10" }
Start debugging your Go program. You can use different launch modes:
start_debugging { "sessionId": "your-session-id", "scriptPath": "/path/to/your/main.go",
"dapLaunchArgs": { "mode": "debug", "program": "/path/to/your/main.go",
"stopOnEntry": false } }
start_debugging { "sessionId": "your-session-id", "scriptPath": "/path/to/your/compiled/binary",
"dapLaunchArgs": { "mode": "exec",
"program": "/path/to/your/compiled/binary" } }
start_debugging { "sessionId": "your-session-id", "scriptPath": "/path/to/your/test/directory",
"dapLaunchArgs": { "mode": "test",
"program": "/path/to/your/test/directory" } }
When execution pauses at a breakpoint, you can:
step_over { "sessionId": "your-session-id" }
step_into { "sessionId": "your-session-id" }
step_out { "sessionId": "your-session-id" }
continue_execution { "sessionId": "your-session-id" }
When paused, you can examine the program's state:
get_local_variables { "sessionId": "your-session-id" }
get_stack_trace { "sessionId": "your-session-id" }
evaluate_expression { "sessionId": "your-session-id", "expression": "x + y * 2" }
When finished debugging, close the session:
close_debug_session { "sessionId": "your-session-id" }
Delve supports several launch modes:
debug: Compile and debug a main packagetest: Compile and debug a test binaryexec: Debug a pre-compiled binaryreplay: Replay a recorded tracecore: Debug a core dump
Go programs use goroutines for concurrency. Delve natively handles goroutines, and the MCP tools reflect this:
- Stack traces show the goroutine context for each frame
- Internal runtime and testing frames (paths containing
/runtime/or/testing/) are filtered out by default (useincludeInternals: trueinget_stack_traceto see them) - Variable inspection works within the current goroutine's stack frame
Note: goroutines reach the MCP tools as DAP threads. list_threads returns them (Delve already hides system goroutines — the adapter forces hideSystemGoroutines: true), and get_stack_trace accepts a threadId from that list to inspect one specific goroutine; an explicit id is authoritative and is never silently switched. What is not exposed is Delve's goroutine-scoped machinery: set_breakpoint has no goroutine filter, and stepping and continue_execution take no thread argument — they act on the session's current stopped thread.
The Go adapter supports exception breakpoints with two built-in filters: unrecovered-panic (break on unrecovered panics) and runtime-fatal-throw (break on fatal runtime errors). Delve has no caught/uncaught distinction, so both the uncaught and all exception break modes arm the same two filters; launch sessions default to breakOnExceptions: "uncaught", which arms both, and launch is the only mode Go has. These are declared in the adapter's capabilities and sent to Delve during session configuration. Custom exception breakpoint configuration can also be provided via dapLaunchArgs.
Delve 1.27 and later send a DAP exited event with the code. Older Delve — the only kind a Go older than 1.25 can run — never does; it prints Process N has exited with status S to the console instead — under noDebug before terminated, and in debug mode only in reply to the disconnect request. mcp-debugger reads that line back (issue #753). Either way exitCode appears in list_debug_sessions and in the start_debugging run-to-completion summary exactly as for every other language. The line itself is visible in get_output (with Delve's Detaching) under noDebug, and in debug mode on Delve older than 1.27; Delve 1.27+ in debug mode prints it only as the session is torn down, after output capture has ended. A session closed while the program is still running reports no code — Delve prints Detaching and terminating target process instead, and nothing is guessed.
- Always build with debug flags: Use
-gcflags="all=-N -l"to disable optimizations - Absolute paths: Use absolute paths for file references in breakpoints
- Internal frame filtering: Frames from Go runtime/testing paths are filtered by default for cleaner stack traces
- Stop on entry: Delve has a quirk with "unknown goroutine 1" --
stopOnEntry=falseis a global session-manager default (not Go-specific), but the Go adapter policy also enforces this default to avoid Delve's goroutine issue when the user has not explicitly specifiedstopOnEntry - Variable inspection: Delve's DAP output automatically dereferences pointers, and represents slices with length/capacity and maps as key-value pairs
- Test debugging: Use
mode: "test"to debug Go test functions
Here's a complete example debugging a simple Go program:
// main.go
package main
import "fmt"
func main() {
x := 10
y := 20
sum := add(x, y)
fmt.Printf("Sum: %d\n", sum)
}
func add(a, b int) int {
return a + b
}-
Build with debug symbols:
go build -gcflags="all=-N -l" -o myprogram main.go -
Create debug session with
language: "go" -
Set breakpoint at line 7 (inside
main) -
Start debugging with
mode: "exec"and an absolutescriptPathto the built binary (host mode rejects a relative path such as./myprogram) -
Step through the code and inspect variables
- Ensure Delve is installed:
go install github.com/go-delve/delve/cmd/dlv@latest - Check that
dlvis in your PATH:which dlv - If it is installed but not on
PATH(e.g.~/go/binis not exported), setDLV_PATHto the binary — for exampleexport DLV_PATH="$HOME/go/bin/dlv"— or passexecutablePathincreate_debug_session - Verify DAP support:
dlv dap --help
- Ensure Go 1.18+ is installed:
go version - Check that
gois in your PATH:which go
- Ensure you built with
-gcflags="all=-N -l" - Use absolute paths for file references
- Check that the line number corresponds to an executable statement
- This is a Delve quirk when
stopOnEntry=true - The adapter defaults
stopOnEntry=falseto avoid this - If you need to stop on entry, the error is harmless and execution continues
- Expected: the Go adapter declares launch support only
- Use
start_debuggingwithmode: "exec"against the already-built binary instead of attaching to the running process