Skip to main content
Glama

cdbmcp

A standalone MCP (Model Context Protocol) server for Windows that attaches to a running C++ process and exposes three toolsevaluate, 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.

Related MCP server: MCP Server for WinDbg Crash Analysis

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\x64 paths, --cdb-path, or CDB_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.py drives cdbmcp over stdio and python tests/demo_http_session.py drives 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.

⚠️ .call runs 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 (_Hash chain) and vector internals are walked directly via the C++ EE (no inferior calls); dx/NatVis is the fallback for anything else.

  • Interrupt: -exec interruptGenerateConsoleCtrlEvent (Ctrl-Break to cdb's process group). A raw 0x03 byte 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/-gPDB + _NT_SYMBOL_PATH / --symbol-path.

  • Attach overhead: a live C++ target raises first-chance exceptions constantly; cdbmcp runs sxn eh so the engine does not break on them.

  • Transports: stdio (default) and HTTP (streamable-http) with shared-secret bearer auth.

F
license - not found
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

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/cdbmcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server