cdbmcp
Enables AI clients to inspect live memory of a running C++ process on Windows using cdb.exe, providing tools to evaluate expressions, retrieve map entries, and enumerate containers without modifying target code.
README
cdbmcp
A standalone MCP (Model Context Protocol) server for Windows 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 cdb.exe
subprocess.
It is a faithful Windows port of gdbmcp: zero source-level intrusion (no target code or build is modified). The AI writes the field/path it wants as a C++ expression; cdbmcp passes it verbatim to cdb, which resolves symbols from the target's PDB and returns the value.
MCP client ──stdio or HTTP──▶ cdbmcp (Python) ──text cmds──▶ cdb.exe ──debug attach──▶ target C++ process (+PDB)
What works (and what doesn't) — read this first
cdb's C++ expression evaluator is powerful but has the same hard limit as GDB on a stock MSVC STL target: inlined STL methods cannot be called.
| Want | Works? | How |
|---|---|---|
| Read a field / follow pointers | ✅ | obj->member.field |
| Call an out-of-line function (singleton accessor) | ⚠️ opt-in | needs --allow-calls; Instance()->field (runs target code) |
Dump a whole struct / container (??) |
✅ (text) | default evaluate |
| List elements of any container | ✅ | enumerate (STL walker first, dx/NatVis fallback) |
Call inlined STL (map::find, map::operator[], vector::operator[]) |
❌ | use map_get / enumerate, or an out-of-line accessor |
These limits come from the C++ evaluator / MSVC STL, not from cdbmcp — cdbmcp only passes the expression through.
Target pause model
cdb attaches with a debug attach, which stops the target at the initial
breakpoint. cdbmcp keeps the target running between queries and briefly
interrupts it (Ctrl-Break) per tool call — a few milliseconds for a pure data
read, longer if the expression triggers an inferior function call. A wall-clock
watchdog (which forces a break into the engine) aborts any call that does not
return. On cdbmcp exit (stdin closed, Ctrl-C/Break, console close) it
detaches (qd, never kills) the target, which resumes normal execution.
Set "pause_mode": "frozen" to instead break once at attach and keep the target
stopped for the whole session (debug fallback; freezes the live process).
Requirements
- Windows x64.
- cdb.exe (Windows Debugging Tools / Windows Kits). Auto-detected from the
usual
Windows Kits\10\Debuggers\x64paths,--cdb-path, orCDB_PATH. - A target built with debug info (a matching PDB) for symbol resolution.
- Privilege to attach to the target (same user / same integrity, or run elevated if the target is elevated). Only one debugger may attach at a time.
Install
pip install -e .
(Adds the mcp dependency and an entry point cdbmcp.)
Run
cdbmcp is always a single standalone process that attaches to the target PID at startup and stays attached for its whole life (it never respawns cdb per query). The only difference between the two modes is the transport:
┌──────────────────────────────────────────┐
AI client ─stdio/──▶│ cdbmcp.exe (standalone, long-running) │
HTTP │ · attaches to target PID at startup │──▶ live C++ target (+PDB)
(Claude Code / │ · resident cdb, read-only │
Cursor / remote / │ · stdio OR streamable-http + bearer │
many at once) └──────────────────────────────────────────┘
- stdio (default): the MCP client launches cdbmcp as its own subprocess and talks over stdin/stdout. Simplest for a local Claude Code / Cursor.
- HTTP: you start cdbmcp yourself as a long-running service; many and/or
remote clients then connect to
http://<host>:<port>/mcp. This is the model when cdbmcp should be one shared process serving several AI clients.
Common flags (both modes): --pid <PID> (required), --cdb-path D:\Debuggers\cdb.exe,
--symbol-path "srv*C:\Symbols;D:\your\app", --pause-mode per_query|frozen,
--config config.json. Run python -m cdbmcp --help for the full list.
stdio
python -m cdbmcp --pid <PID> --cdb-path D:\Debuggers\cdb.exe
Client config (Claude Code / Cursor) — client launches cdbmcp:
{
"mcpServers": {
"cdbmcp": { "command": "python",
"args": ["-m", "cdbmcp", "--pid", "12345",
"--cdb-path", "D:\\Debuggers\\cdb.exe"] }
}
}
HTTP (standalone service, multi/remote client)
Start cdbmcp once as a service (it attaches immediately and stays up):
python -m cdbmcp --transport http --host 0.0.0.0 --port 8000 ^
--pid <PID> --cdb-path D:\Debuggers\cdb.exe ^
--symbol-path "srv*C:\Symbols;D:\your\app" ^
--auth-token SECRET
Endpoint: POST http://<host>:8000/mcp (MCP streamable-http). Clients connect
to the URL with the bearer token:
{
"mcpServers": {
"cdbmcp": { "url": "http://your-host:8000/mcp",
"headers": { "Authorization": "Bearer SECRET" } }
}
}
Security: evaluate reads arbitrary target memory, so any remote exposure
must be authenticated. Without --auth-token / CDBMCP_TOKEN cdbmcp warns
and forces 127.0.0.1-only. For production put a reverse proxy (nginx/caddy,
TLS) in front and bind loopback — otherwise the token travels in cleartext.
Queries are serialized through one cdb session, so concurrent clients queue
cleanly rather than stepping on each other.
The tools
All three are called the same way — a tools/call JSON-RPC over stdio or HTTP,
after the standard initialize / notifications/initialized handshake.
End-to-end demos (build the fixture first, see below):
python tests/demo_ai_session.pydrives cdbmcp over stdio andpython tests/demo_http_session.pydrives a standalone HTTP cdbmcp, each running real AI-style queries against the fixture.
evaluate(expression, format="text")
Evaluate a read-only C++ expression. text is a WinDbg-style recursive dump;
json returns a typed tree {name, type, value|children} parsed from
cdb's dx/NatVis — scalars coerced to int/float/bool, std::string shown as
its content, structs/containers recursed (depth/width capped), STL bookkeeping
([Raw View]/size/capacity/allocator…) pruned. On error returns a plain
diagnostic string; server, debugger and target never crash.
Inferior function calls (--allow-calls)
cdb's C++ evaluator (??) cannot call functions, so an expression like
GameWorld::Instance()->players fails with "Extra character error". With
--allow-calls (or "allow_inferior_calls": true in config) cdbmcp resolves a
leading accessor call via cdb .call (it runs the function in the target,
reads the returned pointer) and rewrites the rest to member access — so
GameWorld::Instance()->players, map_get("GameWorld::Instance()->players", "1001")
etc. work. Nested calls (A()->B()) are not flattened; prefer the leading
accessor + member access + the container tools.
⚠️
.callruns target code (side effects, possible deadlock) and breaks the read-only guarantee — hence OFF by default. cdbmcp prints a warning when it is on. Only the leading call is executed; everything after it stays member-access-only.
map_get(map_expr, key_expr, format="text")
Look up one element by key in std::map / multimap without calling any
inlined STL method. std::map/multimap (red-black tree) and
std::unordered_map/unordered_multimap (_Hash element chain) are walked
directly and the key is compared in-engine (first == key_expr). Struct keys
without a usable == fall back to a dx dump. Use a 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 (tree), unordered_* (_Hash chain) and vector are all walked
directly (no inferior calls); anything unrecognised falls back to dx/NatVis.
Test fixture
tests/fixtures/target.cpp is a tiny C++ process with a singleton GameWorld
holding std::map<int,Player>, std::unordered_map<int,Item> and
std::vector<Item>, plus out-of-line accessors.
cd tests\fixtures
build.bat :: produces target.exe + target.pdb
target.exe :: prints PID=..., then idles
:: in another shell:
python -m cdbmcp --pid <that PID>
Architecture / code map
| File | Role |
|---|---|
cdbmcp/cdb_controller.py |
Resident cdb subprocess; attach / interrupt / go / detach; query serialization; watchdog break |
cdbmcp/cdb_protocol.py |
Byte-oriented reader with sentinel-based delimiting (cdb has no GDB/MI) |
cdbmcp/evaluator.py |
evaluate: validate → interrupt → ?? (text) / dx (json tree) → resume; resolve_calls (.call, opt-in); error mapping |
cdbmcp/container_query.py |
map_get / enumerate: STL walker first, dx/NatVis fallback |
cdbmcp/stl_walker.py |
Walk MSVC STL tree / unordered (_Hash) / vector internals via the C++ EE — no inferior calls |
cdbmcp/formatters.py |
text/json rendering; prompt/banner cleanup; dx tree → typed JSON parser |
cdbmcp/watchdog.py |
Cancellable wall-clock timer → forced break |
cdbmcp/errors.py |
Error classification + friendly messages |
cdbmcp/auth.py |
Shared-secret bearer auth + pure-ASGI middleware (HTTP transport) |
cdbmcp/http_transport.py |
Streamable-HTTP transport (uvicorn) with bearer auth |
cdbmcp/lifecycle.py |
atexit + console-control handlers → clean detach |
cdbmcp/mcp_server.py |
FastMCP server registering the three tools |
cdbmcp/config.py |
Config load (JSON + CLI + cdb discovery) |
cdbmcp/__main__.py |
CLI entry: parse args, attach, serve stdio or HTTP |
Differences from gdbmcp
- Engine: GDB/MI subprocess → resident cdb.exe subprocess (text protocol with sentinel delimiting, since cdb has no MI).
- STL layout: libstdc++/libc++ → MSVC STL. Tree (
_Tree), unordered (_Hashchain) and vector internals are walked directly via the C++ EE (no inferior calls);dx/NatVis is the fallback for anything else. - Interrupt:
-exec interrupt→GenerateConsoleCtrlEvent(Ctrl-Break to cdb's process group). A raw0x03byte on cdb's piped stdin does NOT break a running target — console control events don't flow through pipes — so cdbmcp launches cdb in its own process group and signals it (allocating a console when cdbmcp itself has none, e.g. under an MCP client). - Symbols: DWARF/
-g→ PDB +_NT_SYMBOL_PATH/--symbol-path. - Attach overhead: a live C++ target raises first-chance exceptions
constantly; cdbmcp runs
sxn ehso the engine does not break on them. - Transports: stdio (default) and HTTP (streamable-http) with shared-secret bearer auth.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.