Skip to main content
Glama
README.md
# 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

B3.4/5.0

Scored across 20 tools

Disambiguation5/5

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.

Naming Consistency5/5

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.

Tool Count5/5

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.

Completeness5/5

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.

Maintenance

ActivityInactive
ResponsivenessNo issues