gdb-mcp
# gdb-mcp
An [MCP](https://modelcontextprotocol.io) server that lets an AI agent drive a
full **C/C++ debugging session** — set breakpoints, step, inspect memory and
variables, evaluate expressions, and call functions — using **either GDB or
LLDB** behind one unified tool surface.
It works by speaking the [Debug Adapter Protocol](https://microsoft.github.io/debug-adapter-protocol/)
(DAP) to the debugger that ships with your toolchain (`gdb -i dap` or
`lldb-dap`), so a single set of tools drives both debuggers.
Validated end-to-end with **Claude Code**, **Codex CLI**, and **Cursor CLI** —
each agent independently used these tools to debug a live program and extract
correct runtime state.
---
## Features
- **Three ways to start**: launch a prebuilt binary, compile-then-debug a source
file, or attach to a running process (by PID).
- **Execution control**: `continue`, `step` (over / into / out, line or
instruction granularity), and a lock-free `pause` that can interrupt a running
program.
- **Breakpoints**: source (`file:line`) and function breakpoints, with
**conditions**, **hit counts**, and **logpoints**; data breakpoints
(**watchpoints**); stable handles for reliable removal.
- **Inspection**: backtraces, threads, stack frames, scopes, lazy variable
expansion (structs/arrays/STL), **expression evaluation and function calls**
(`foo(x)`), set-variable, read-memory, and disassembly.
- **Source listing** around the current stop, plus a **raw-command escape hatch**
(`dbg_raw_command`) for anything the structured tools don't cover.
- **Agent-friendly output**: verbose DAP payloads are trimmed to compact JSON,
with stop-epoch guards so an agent can't accidentally read a stale frame or
variable reference after the program advances.
## Prerequisites
- **Python 3.11+**
- A DAP-capable debugger:
- **GDB ≥ 14** (ships the `gdb -i dap` interpreter), or
- **`lldb-dap`** (bundled with LLVM / Xcode; on macOS it is found via
`xcrun -f lldb-dap`).
- A C/C++ toolchain (`cc`/`clang`/`gcc`) for the compile-then-debug mode and to
build debuggees with `-g`.
- [`uv`](https://docs.astral.sh/uv/) (recommended) or `pip`.
> **Platform note.** On Linux, run debuggers normally. On **macOS**, live
> debugging needs the debugserver attach permission — if `dbg_start` returns
> *"Not allowed to attach"*, enable Developer Mode once: `sudo DevToolsSecurity -enable`.
> macOS arm64 GDB cannot debug native Mach-O binaries; use LLDB locally and GDB
> on Linux.
## Installation
```bash
git clone https://github.com/birdeclipse/gdb-mcp.git
cd gdb-mcp
uv venv && uv pip install -e ".[dev]" # or: pip install -e ".[dev]"
```
Verify the server starts and advertises its tools:
```bash
uv run gdb-mcp # starts the MCP server on stdio (Ctrl-C to stop)
uv run pytest -m mcp # asserts the server advertises all 20 tools
```
## Registering with an agent
The server runs over stdio. The robust invocation lets `uv` resolve the project
venv regardless of `PATH` (replace `/path/to/gdb-mcp` with your clone path):
```
command: uv
args: ["run", "--directory", "/path/to/gdb-mcp", "gdb-mcp"]
```
Ready-to-edit config snippets live in [`integrations/`](integrations/). Summary:
| Agent | How |
|---|---|
| **Claude Code** | `claude mcp add gdb-mcp -- uv run --directory /path/to/gdb-mcp gdb-mcp` — or merge `integrations/mcp.claude.json` into `.mcp.json` |
| **Codex CLI** | merge `integrations/codex.config.toml` into `~/.codex/config.toml` |
| **Cursor CLI** | copy `integrations/mcp.cursor.json` to `.cursor/mcp.json` (project) or `~/.cursor/mcp.json` |
If you `pip install` the package so `gdb-mcp` is on `PATH`, you can use
`command: gdb-mcp` with empty args instead.
## Tools
All tools except `dbg_start` take a `session_id`.
| Tool | Purpose |
|---|---|
| `dbg_start` | Start a session: `mode` = launch / compile_launch / attach, `debugger` = gdb / lldb |
| `dbg_terminate` | Kill (launch) or detach (attach) and free the session |
| `dbg_continue` | Resume until the next stop / exit |
| `dbg_step` | Step `over` / `into` / `out`; `line` or `instruction` granularity |
| `dbg_pause` | Interrupt a running program (lock-free) |
| `dbg_set_breakpoint` | `file:line` or `function`; optional `condition`, `hit_condition`, `log_message` |
| `dbg_list_breakpoints` | List source / function / data breakpoints with handles |
| `dbg_remove_breakpoint` | Remove by stable `handle` |
| `dbg_set_watchpoint` | Data breakpoint on a variable (`read` / `write` / `rw`) |
| `dbg_backtrace` | Stack frames for a thread |
| `dbg_list_threads` | All threads |
| `dbg_select_frame` | Set the active frame for eval / scopes / variables |
| `dbg_scopes` | Variable scopes (Locals, …) for a frame |
| `dbg_variables` | Expand a `variablesReference` (lazy struct/array/STL expansion) |
| `dbg_evaluate` | Evaluate an expression **or call a function** (`foo(x)`) |
| `dbg_set_variable` | Set a variable to a new value |
| `dbg_read_memory` | Read raw memory (base64) |
| `dbg_disassemble` | Disassemble around an address |
| `dbg_source` | Source lines around a location |
| `dbg_raw_command` | Run a raw gdb/lldb command (escape hatch) |
## Example: a debugging session
A typical agent flow against a program with a `Point pt = {3, 7}` local:
1. `dbg_start(mode="launch", debugger="lldb", program="/path/to/a.out")`
→ stops at entry, returns a `session_id`.
2. `dbg_set_breakpoint(session_id, file="main.c", line=6)` → verified, handle `1`.
3. `dbg_continue(session_id)` → `{state: "stopped", reason: "breakpoint",
frame: {function: "main", file: "main.c", line: 6}}`.
4. `dbg_backtrace(session_id)` → frames.
5. `dbg_scopes(session_id)` → Locals; `dbg_variables(session_id, ref=…)` →
`pt` (expandable) → expand → `{x: 3, y: 7}`.
6. `dbg_evaluate(session_id, "pt.x + pt.y")` → `10`.
7. `dbg_terminate(session_id)`.
## How it works
```
agent ──MCP tool call──▶ MCP server (Python, asyncio)
│ Session Manager (session_id → one debuggee)
│ DAP Client (Content-Length framing,
│ request/response + async events)
▼ spawns
gdb -i dap | lldb-dap
▼
debuggee process
```
Execution tools are event-driven: `continue`/`step` resolve on the debugger's
next `stopped`/`exited` event, not on the request response. See
[`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) for the design and
[`CONTRIBUTING.md`](CONTRIBUTING.md) for development.
## Testing
```bash
uv run pytest # everything
uv run pytest tests/unit # offline (scripted fake adapter) — no debugger
uv run pytest -m mcp # MCP stdio handshake + tool advertisement
uv run pytest -m integration # live debugger on the C fixtures (auto-skips if none)
```
The integration suite probes for a working debugger and **skips cleanly** when
none is available, so it is safe to run anywhere.
## License
[MIT](LICENSE)
TDQS
Scored across 20 tools
Each tool has a clearly distinct purpose. For example, dbg_continue and dbg_step both resume execution but differ explicitly in granularity, and dbg_evaluate vs dbg_raw_command cover expression evaluation and raw commands respectively, leaving no ambiguity.
All tools follow the 'dbg_' prefix followed by a verb_noun pattern (e.g., dbg_set_breakpoint, dbg_list_threads, dbg_source). The naming is uniform and predictable.
20 tools is well-scoped for a debugger MCP server. It covers essential operations without being overwhelming, and each tool earns its place in the set.
The tool surface covers all core debugger actions: session start/terminate, breakpoints, stepping, continue, pause, variable inspection, memory, disassembly, source, threads, and backtrace. There are no obvious gaps for standard debugging workflows.