gdbmcp
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@gdbmcpevaluate world->players[0].hp"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
gdbmcp
A standalone MCP (Model Context Protocol) server for Linux that attaches
to a running C++ process and exposes three tools — evaluate,
map_get, enumerate — to inspect the target's live memory via a resident
GDB subprocess.
Zero source-level intrusion: no target code or build is modified. The AI writes the field/path it wants as a C++ expression; gdbmcp passes it verbatim to GDB, which resolves symbols from the target's DWARF debug info and returns the value.
MCP client ──stdio JSON-RPC──▶ gdbmcp (Python, single ELF) ──GDB/MI──▶ gdb ──ptrace──▶ target C++ process (-g)What works (and what doesn't) — read this first
GDB's expression evaluator is powerful but has hard limits on a stock
libstdc++/libc++ target. evaluate surfaces these as error strings; prefer the
working forms.
Want | Works? | How |
Read a field / follow pointers | ✅ |
|
Call an out-of-line function (e.g. a singleton accessor) | ✅ |
|
Index a | ✅ via raw buffer |
|
Index a raw array / pointer | ✅ |
|
Call inlined STL methods ( | ❌ | gdb can't call inlined functions — use an out-of-line accessor on the target |
Construct a custom key: | ❌ syntax error | use C-style cast |
| ✅ (text format) | default |
Why: std::map::find / std::vector::operator[] are defined inline in the
STL headers; GDB cannot inferior-call an inlined function. The robust pattern on
a real target is to expose out-of-line accessor methods taking primitive
arguments (e.g. FindPlayer(int id) instead of players.find(EntityID(id))),
and to reach vector elements through _M_impl._M_start[i].
If you can't add an accessor (e.g. a third-party binary), use the dedicated container tools below — they walk the container's internal layout by direct memory reads, no inferior call:
Want | Tool | How |
Look up one element by key in a map/unordered_map |
| walks the rb-tree / hash buckets, matches the key field-wise |
List elements of any container |
| walks map/set (tree), unordered_* (hash), vector (buffer) |
These limitations come from GDB/libstdc++, not from gdbmcp — gdbmcp only passes the expression through.
Related MCP server: gdb and rr Debugging
Target pause model
GDB attaches via ptrace, which SIGSTOPs the target. gdbmcp keeps the target
running between queries and briefly interrupts it (all-stop) per evaluate
call — typically a few milliseconds for a pure data read, longer if the
expression triggers an inferior function call. A wall-clock watchdog (plus GDB
≥14.1's native direct-call-timeout) aborts any call that does not return.
On gdbmcp exit (stdin closed, SIGTERM, SIGINT), it detaches (never kills) the target, which resumes normal execution.
Requirements
Linux only.
gdb ≥ 14.1 recommended (native inferior-call timeout). Minimum gdb ≥ 10 (watchdog-only). Must be the full gdb package with Python support (
gdb --configurationshows--with-python) — a strippedgdb-minimalcannot load STL pretty-printers. Install e.g.apt install gdb.ptrace permission to attach to the target.
kernel.yama.ptrace_scopedefaults to1on most distros (only a parent may trace its children). Pick one:run gdbmcp as root, or
sudo setcap cap_sys_ptrace+ep ./dist/gdbmcp, orset
/proc/sys/kernel/yama/ptrace_scopeto0(system-wide; security trade-off).in Docker:
--cap-add SYS_PTRACE.
The target must be compiled with debug info (
-g, not stripped) for symbol resolution.
Build the single-file executable
./packaging/build.sh # uses .venv, installs deps + pyinstaller, builds dist/gdbmcpThe result dist/gdbmcp is a self-contained ELF — no Python needed on the host.
gdb itself is a separate system dependency (see above). Build on the oldest glibc
you intend to support for forward portability.
Run
gdbmcp speaks MCP over stdio (default) or HTTP (streamable-http).
stdio (default — for Claude Code / Cursor / cline / claude.ai)
The MCP client launches gdbmcp as a subprocess and talks over its stdin/stdout:
./dist/gdbmcp --pid <PID> [--config config.json] [--gdb-path /usr/bin/gdb]Example client config:
{
"mcpServers": {
"gdbmcp": { "command": "/path/to/gdbmcp", "args": ["--pid", "12345"] }
}
}HTTP (for remote / multi-client access)
./dist/gdbmcp --pid <PID> --transport http --host 0.0.0.0 --port 8000 [--auth-token SECRET]Endpoint: POST http://<host>:<port>/mcp (MCP streamable-http). Bind defaults to
0.0.0.0:8000.
Authentication (HTTP only — important)
evaluate reads arbitrary target memory. Any HTTP exposure must be authenticated.
Set a bearer token via --auth-token or the GDBMCP_TOKEN env var; requests must
then carry Authorization: Bearer <token> or they get 401. Without a token on
the HTTP transport, gdbmcp prints a warning and runs unauthenticated — do not do
this on a reachable network.
This is a shared-secret scheme (constant-time compare). For production, put a
reverse proxy (nginx/caddy) in front to terminate TLS and bind gdbmcp to
127.0.0.1, so the token is never sent in cleartext. (gdbmcp does not bundle a
TLS terminator; OAuth is not implemented — see auth.py if you need either.)
config.json (optional)
See config.example.json. Notable fields: gdb_path,
timeouts.{attach,call,eval}_seconds, output.{default_format,max_depth, max_children,string_truncate}, stl_flavor.
The evaluate tool
expression(string, required) — a C++ expression.format("text" | "json", default "text") —textis gdb-style;jsonreturns a typed tree{name, type, value|children}(scalars coerced to int/float/bool/str).
On error, returns a plain-language diagnostic string (unknown symbol, null dereference, timeout, target exited, invalid expression). Failures never crash the server, GDB, or the target.
The map_get and enumerate tools (no-accessor container access)
When the target has no out-of-line accessor, these read containers directly:
map_get(map_expr, key_expr, format="text") — look up one element by key in
std::map / std::unordered_map / std::multimap / std::multiset. Walks the
red-black tree / hash buckets by memory reads and matches the key (deep,
field-wise, so struct keys compare by fields). Use C-style cast for struct keys:
map_get("world->m_players", "(EntityID)1002").
enumerate(container_expr, limit=100, format="text") — list up to limit
elements of any STL container (map/set/multimap/multiset, unordered_*,
vector). Map items carry both key and value; vector/set items carry the value.
Both never call inlined STL methods, so they work on any target regardless of accessors. Layout is libstdc++-specific (stable across its ABI versions).
Quick examples (MCP JSON-RPC)
All three tools are called the same way — a tools/call JSON-RPC over whichever
transport you started (stdio or HTTP). After the standard initialize /
notifications/initialized handshake:
evaluate — read a single value/expression (prefers an out-of-line accessor):
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": { "name": "evaluate",
"arguments": { "expression": "GameWorld::GetInstance()->find_player(1001)->m_hp",
"format": "text" } } }
// -> { "result": { "isError": false, "content": [ { "type": "text", "text": "int = 100" } ] } }map_get — look up one map element by key WITHOUT an accessor:
{ "jsonrpc": "2.0", "id": 2, "method": "tools/call",
"params": { "name": "map_get",
"arguments": { "map_expr": "GameWorld::GetInstance()->m_players",
"key_expr": "(EntityID)1002", "format": "text" } } }
// -> key matched -> value is the Player struct dump (Bob ...)enumerate — list elements of any container:
{ "jsonrpc": "2.0", "id": 3, "method": "tools/call",
"params": { "name": "enumerate",
"arguments": { "container_expr": "GameWorld::GetInstance()->find_player(1001)->m_items",
"limit": 5, "format": "text" } } }
// -> size=2, lists Sword / PotionUse format: "json" on any tool for structured output. On any failure the tool
returns a plain-language diagnostic string (isError: false, the text describes
the problem); the server, GDB, and target never crash.
Test
.venv/bin/python -m pytest # unit + integration + full-stack e2eTests attach to a compiled fixture (tests/fixtures/target.cpp) via real gdb.
Architecture / code map
File | Role |
| Resident gdb subprocess; GDB/MI send/recv with token correlation; attach/interrupt/continue/detach; watchdog abort path; init + libstdc++ printer registration |
| GDB/MI stream parser (result/async/stream records; nested tuples & lists) |
|
|
|
|
| text/JSON rendering of the value tree; scalar coercion; depth/width caps |
| Error classification + human-friendly messages |
| Cancellable wall-clock timer that aborts runaway inferior calls |
| FastMCP server registering the |
| Shared-secret bearer-token verifier for the HTTP transport |
| atexit + signal handlers → clean detach on exit |
| Config load (JSON + CLI overrides) |
| CLI entry: parse args, attach, serve stdio |
Out of scope (v1)
non-stop mode (smaller pause blast radius), process-name auto-discovery,
a custom GDB Python pretty-printer that emits JSON (currently the JSON path parses GDB's var-object output), memory-write tooling, and bundling gdb into the executable.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/luckinbyte/gdbmcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server