
An MCP (Model Context Protocol) server that turns all pybag Windows debugger functions into native MCP tools. It lets MCP-compatible clients (Claude Desktop, Claude Code, Cowork, OpenAI Codex CLI, Cursor, and custom agents) control user-mode processes, kernel sessions, and crash dump analysis via structured JSON calls.
Um servidor MCP (Model Context Protocol) que expõe cada função do depurador Windows pybag como uma ferramenta MCP nativa. Ele concede a qualquer cliente compatível com MCP (Claude Desktop, Claude Code, Cowork, OpenAI Codex CLI, Cursor e agentes personalizados) controle total sobre processos em modo de usuário, sessões de kernel e análise de dumps de falha — tudo por meio de chamadas de ferramentas tipadas com respostas JSON estruturadas.
git clone https://github.com/your-username/windbg-mcp.git cd windbg-mcp
### 2. Instalar dependências Python```bat
pip install pybag mcp
Baixe o Windows SDK e selecione Debugging Tools for Windows durante a instalação: https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/
O servidor é executado como um processo stdio local. Todos os clientes abaixo o iniciam da mesma forma —
python <path-to>/windbg_mcp.py — mas cada um tem seu próprio formato de configuração.
Edite o arquivo de configuração do Claude Desktop e adicione a entrada windbg-mcp:
Local do arquivo de configuração:
%APPDATA%\Claude\claude_desktop_config.jsonReinicie o Claude Desktop. Todas as 55 ferramentas de depuração aparecerão automaticamente.
---
### Claude Code (CLI)
Execute o seguinte comando uma vez para registrar o servidor. O Claude Code armazena a entrada
em sua própria configuração MCP e disponibiliza as ferramentas em cada sessão subsequente.```bash
claude mcp add windbg-mcp python C:\path\to\windbg-mcp\windbg_mcp.py
Para verificar se o servidor foi registrado:```bash claude mcp list
Para removê-lo mais tarde:```bash
claude mcp remove windbg-mcp
Existem duas formas de adicionar o WinDbg MCP ao Cowork: através da configuração JSON (rápida) ou instalando-o como um pacote de plugin .mcpb (portátil, compartilhável).
3. Salve e reinicie o Cowork. As ferramentas estarão disponíveis na sua próxima sessão.
#### Opção B — Instalar como um Pacote de Plugin `.mcpb`
Um arquivo `.mcpb` é um arquivo zip do diretório do plugin que o Cowork pode instalar diretamente. Esta é a abordagem recomendada ao compartilhar o servidor com uma equipe ou entre máquinas.
**Passo 1 — Construir o arquivo `.mcpb`**
A partir da raiz do repositório clonado, execute:```bat
powershell -Command "Compress-Archive -Path '.\*' -DestinationPath 'windbg-mcp.zip'; Rename-Item 'windbg-mcp.zip' 'windbg-mcp.mcpb'"
Isto cria windbg-mcp.mcpb no diretório atual, agrupando windbg_mcp.py,
manifest.json e quaisquer outros arquivos do projeto.
Passo 2 — Instalar no Cowork
windbg-mcp.mcpb.manifest.json do pacote, registra o servidor MCP e
disponibiliza todas as ferramentas imediatamente — sem necessidade de configuração manual de caminho.O manifest.json incluído neste repositório já está configurado corretamente:```json
{
"manifest_version": "0.2",
"name": "windbg-mcp",
"version": "1.0.0",
"description": "WinDbg MCP — full Windows debugger control via MCP tools",
"server": {
"type": "python",
"entry_point": "windbg_mcp.py",
"mcp_config": {
"command": "python",
"args": ["${__dirname}/windbg_mcp.py"]
}
}
}
`${__dirname}` é resolvido no momento da instalação para o diretório onde o Cowork descompactou o pacote, então você não precisa codificar caminhos de forma fixa.
---
### OpenAI Codex CLI
Adicione o servidor ao seu arquivo de configuração do Codex CLI. O arquivo geralmente está localizado em `~/.codex/config.json` (Linux/macOS) ou `%USERPROFILE%\.codex\config.json` (Windows).```json
{
"mcpServers": {
"windbg-mcp": {
"command": "python",
"args": ["C:\\path\\to\\windbg-mcp\\windbg_mcp.py"]
}
}
}
Após salvar, inicie uma nova sessão do Codex. As ferramentas WinDbg estarão disponíveis para o modelo chamar.
{
"mcpServers": {
"windsurf": {
"type": "stdio",
"command": "windsurf",
"args": ["mcp"]
}
}
}
``````json
{
"windbg-mcp": {
"command": "python",
"args": ["C:\\path\\to\\windbg-mcp\\windbg_mcp.py"]
}
}
Adicione o seguinte ao seu ~/.continue/config.json (ou ao nível do espaço de trabalho
.continue/config.json):```json
{
"experimental": {
"modelContextProtocolServers": [
{
"transport": {
"type": "stdio",
"command": "python",
"args": ["C:\path\to\windbg-mcp\windbg_mcp.py"]
}
}
]
}
}
Recarregue a extensão Continue. As 55 ferramentas do depurador aparecerão na lista de ferramentas.
---
### Agentes Personalizados e o SDK MCP
Se você está construindo seu próprio agente ou pipeline de automação, conecte-se ao WinDbg MCP
através do transporte stdio padrão do MCP. O servidor fala JSON-RPC 2.0 via stdin/stdout.
#### Python (usando o SDK `mcp`)```python
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
server_params = StdioServerParameters(
command="python",
args=[r"C:\path\to\windbg-mcp\windbg_mcp.py"],
)
async def main():
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
# List all available tools
tools = await session.list_tools()
print([t.name for t in tools.tools])
# Load a crash dump
result = await session.call_tool(
"load_dump",
arguments={"path": r"C:\crashes\crash.dmp"},
)
print(result.content)
# Read 64 bytes at RSP
result = await session.call_tool(
"read_mem",
arguments={"addr": "0x00000000001FF000", "size": 64},
)
print(result.content)
asyncio.run(main())
@modelcontextprotocol/sdk)```typescriptimport { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
const transport = new StdioClientTransport({ command: "python", args: ["C:\path\to\windbg-mcp\windbg_mcp.py"], });
const client = new Client({ name: "my-agent", version: "1.0.0" }, {}); await client.connect(transport);
// Call a tool const result = await client.callTool({ name: "load_dump", arguments: { path: "C:\crashes\crash.dmp" }, }); console.log(result.content);
await client.close();
#### LangChain / LangGraph```python
from langchain_mcp_adapters.tools import load_mcp_tools
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
server_params = StdioServerParameters(
command="python",
args=[r"C:\path\to\windbg-mcp\windbg_mcp.py"],
)
async def get_tools():
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
return await load_mcp_tools(session)
O servidor se comunica por meio de mensagens JSON-RPC 2.0 delimitadas por nova linha. Você pode controlá-lo a partir de qualquer linguagem escrevendo no stdin do processo e lendo do stdout:``` → {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"my-client","version":"1.0"}}} ← {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{...},"serverInfo":{"name":"WinDbg MCP","version":"1.0.0"}}}
→ {"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"load_dump","arguments":{"path":"C:\crashes\crash.dmp"}}} ← {"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"{"status": "ok", ...}"}]}}
## Ferramentas Disponíveis (55 no total)
### Gerenciamento de Sessão
| Ferramenta | Parâmetros | Retorno |
|------|-----------|---------|
| `status` | — | `{connected, type, pid, bitness}` |
| `list_processes` | — | `[{pid, name, description}]` |
| `create` | `path` (obrigatório), `args`, `initial_break` | `{status, pid, bitness}` |
| `attach` | `pid` **ou** `name` (não ambos), `initial_break` | `{status, pid, bitness}` |
| `kernel_attach` | `connect_string` (obrigatório), `initial_break` | `{status, type, connect_string}` |
| `load_dump` | `path` (obrigatório) | `{status, bitness, rip, symbol_at_rip}` |
| `connect` | `options` (obrigatório) | `{status, options}` |
| `detach` | — | `{status}` |
| `terminate` | — | `{status}` |
**`create`** — Inicia um novo processo sob o depurador. Defina `initial_break=True` (padrão) para interromper no ponto de entrada do processo.
**`attach`** — Anexa a um processo em execução. Forneça `pid` (inteiro) ou `name` (nome do arquivo do processo). Não forneça ambos.
**`kernel_attach`** — Conecta a um depurador de kernel remoto. `connect_string` usa a sintaxe KD, ex.: `"net:port=55000,key=1.2.3.4"`.
**`load_dump`** — Abre um arquivo `.dmp` para análise post-mortem. Retorna imediatamente o endereço da falha e o símbolo mais próximo.
**`connect`** — Conecta a um servidor de processos para depuração remota em modo de usuário. `options` usa a sintaxe de conexão DbgEng, ex.: `"tcp:server=192.168.1.10,port=5555"`.
---
### Controle de Execução
| Ferramenta | Parâmetros | Retorno |
|------|-----------|---------|
| `go` | `timeout` (ms, padrão 30000) | `{status, rip, symbol, new_captures, captures}` |
| `step_into` | `count` (padrão 1) | `{rip, instruction, symbol}` |
| `step_over` | `count` (padrão 1) | `{rip, instruction, symbol}` |
| `step_out` | — | `{rip, instruction, symbol}` |
| `goto` | `expr` (obrigatório) | `{rip, symbol}` |
| `trace` | `count` (padrão 10) | `{instructions: [{rip, instruction, symbol}], count}` |
**`go`** — Retoma a execução e bloqueia até o próximo evento de depuração (ponto de interrupção, exceção ou tempo limite). Retorna o novo RIP e quaisquer capturas coletadas durante a execução.
**`step_into`** — Executa passo a passo dentro da próxima instrução, seguindo chamadas para funções chamadas.
**`step_over`** — Executa passo a passo sobre a próxima instrução, tratando chamadas como um único passo.
**`step_out`** — Executa até que a função atual retorne.
**`goto`** — Executa até que um símbolo ou endereço hexadecimal específico seja alcançado, ex.: `"Kernel32!ExitProcess"` ou `"0x7fff12340000"`.
**`trace`** — Realiza N iterações de passo único e registra cada instrução visitada.
---
### Pontos de Interrupção
| Ferramenta | Parâmetros | Retorno |
|------|-----------|---------|
| `bp` | `expr` (obrigatório), `capture`, `action`, `oneshot`, `passcount` | `{id, expr, addr, capture}` |
| `hw_bp` | `addr` (obrigatório), `size`, `access`, `capture`, `action`, `oneshot` | `{id, addr, size, access}` |
| `list_bps` | — | `[{id, expr, type, capture, action, ...}]` |
| `remove_bp` | `id` (obrigatório) | `{status, id}` |
| `enable_bp` | `id` (obrigatório) | `{status, id}` |
| `disable_bp` | `id` (obrigatório) | `{status, id}` |
**`bp`** — Define um ponto de interrupção de software (código) em um símbolo ou endereço.
- `expr`: símbolo (`"ntdll!NtCreateFile"`) ou endereço hexadecimal (`"0x7ff800001234"`)
- `capture`: quando `true` (padrão), salva automaticamente o estado completo — registradores, pilha, memória — no buffer de captura cada vez que este ponto de interrupção é acionado
- `action`: `"go"` (padrão) continua a execução após a captura; `"break"` interrompe
- `oneshot`: remove o ponto de interrupção após ser acionado uma vez
- `passcount`: aciona apenas após N passagens pelo local
**`hw_bp`** — Define um ponto de interrupção de hardware / dados (watchpoint).
- `addr`: endereço hexadecimal a ser observado
- `size`: largura da observação em bytes — `1`, `2`, `4` ou `8` (padrão `4`)
- `access`: `"e"` execução, `"w"` escrita (padrão), `"r"` leitura/escrita
- `capture`, `action`, `oneshot`: mesmas semânticas de `bp`
**`list_bps`** — Retorna todos os pontos de interrupção atualmente ativos com seus IDs, expressões, tipos e configurações.
**`remove_bp` / `enable_bp` / `disable_bp`** — Gerencia pontos de interrupção pelo `id` retornado de `bp` ou `hw_bp`.
---
### Capturas de Estado
Pontos de interrupção com `capture: true` (o padrão) salvam automaticamente uma
imagem completa do depurador toda vez que são acionados. A imagem inclui todos
os registradores, a pilha de chamadas, 64 bytes da memória da pilha em RSP e
32 bytes de código em RIP. As imagens são acumuladas em um buffer e podem ser
recuperadas a qualquer momento com `get_captures`.
| Ferramenta | Parâmetros | Retorno |
|------|-----------|---------|
| `get_captures` | — | `{count, captures: [{bp_id, expr, timestamp, registers, rip, symbol_at_rip, instruction, stack, context_memory}]}` |
| `clear_captures` | — | `{status}` |
| `capture_state` | — | `{timestamp, registers, rip, symbol_at_rip, instruction, disasm_5, stack_at_rsp, call_stack}` |
**`get_captures`** — Retorna todas as capturas coletadas desde o último `clear_captures`. Cada captura contém:
- `registers` — todos os valores dos registradores como strings hexadecimais `{name: "0x..."}`
- `rip` — ponteiro de instrução no momento da captura
- `symbol_at_rip` — símbolo mais próximo de RIP
- `instruction` — desmontagem da instrução em RIP
- `stack` — 10 quadros superiores da pilha de chamadas com endereços e endereços de retorno
- `context_memory.stack_at_rsp` — 64 bytes em RSP como hexadecimal, formatado e ASCII
- `context_memory.code_at_rip` — 32 bytes em RIP como hexadecimal e formatado
**`clear_captures`** — Limpa o buffer de captura. Útil antes de iniciar uma nova execução.
**`capture_state`** — Tira uma imagem imediata sob demanda do estado atual. Use quando já estiver interrompido, em vez de esperar um ponto de interrupção ser acionado.
---
### Memória
| Ferramenta | Parâmetros | Retorno |
|------|-----------|---------|
| `read_mem` | `addr` (obrigatório), `size` (padrão 16) | `{addr, size, hex, formatted, ascii}` |
| `write_mem` | `addr` (obrigatório), `data` (obrigatório, string hex) | `{status, addr, bytes_written}` |
| `read_ptr` | `addr` (obrigatório), `count` (padrão 1) | `{addr, values: ["0x..."]}` |
| `poi` | `addr` (obrigatório) | `{addr, value}` |
| `read_str` | `addr` (obrigatório), `wide` (padrão false) | `{addr, value, wide}` |
| `dump_mem` | `addr` (obrigatório), `count` (padrão 8) | `{addr, output}` |
| `mem_info` | `addr` (obrigatório) | `{addr, info}` |
| `mem_list` | — | `[region_description_strings]` |
**`read_mem`** — Lê `size` bytes brutos de `addr`. Retorna os dados como `hex` (compacto), `formatted` (bytes separados por espaço) e `ascii` (caracteres imprimíveis, `.` para não imprimíveis).
**`write_mem`** — Escreve bytes na memória. `data` é uma string hexadecimal — espaços e prefixos `\x` são removidos automaticamente, ex.: `"90909090"`, `"\\x90\\x90\\x90\\x90"` ou `"90 90 90 90"`.
**`read_ptr`** — Lê `count` valores consecutivos do tamanho de um ponteiro (4 bytes em 32 bits, 8 bytes em 64 bits) começando em `addr`.
**`poi`** — Desreferencia um único ponteiro em `addr` (ponteiro de interesse).
**`read_str`** — Lê uma string terminada em nulo. Defina `wide=true` para UTF-16LE (WCHAR do Windows).
**`dump_mem`** — Despejo formatado de dword/ponteiro, equivalente a `dd`/`dp` no WinDbg.
**`mem_info`** — Retorna as propriedades da região de memória para a página que contém `addr`: endereço base, tamanho, tipo, estado e flags de proteção.
**`mem_list`** — Lista todas as regiões de memória virtual no espaço de endereço do processo alvo.
---
### Registradores
| Ferramenta | Parâmetros | Retorno |
|------|-----------|---------|
| `get_regs` | — | `{rax, rbx, rcx, rdx, rsi, rdi, rbp, rsp, rip, r8–r15, eflags, ...}` |
| `get_reg` | `name` (obrigatório) | `{name, value}` |
| `set_reg` | `name` (obrigatório), `value` (obrigatório) | `{status, name, value}` |
| `get_pc` | — | `{value, symbol, instruction}` |
| `get_sp` | — | `{value}` |
**`get_regs`** — Retorna todos os registradores disponíveis como `{name: "0x..."}`. O conjunto exato depende da arquitetura alvo (x86 vs x64).
**`get_reg`** — Retorna um único registrador, ex.: `name="rax"`, `name="eflags"`.
**`set_reg`** — Sobrescreve um registrador. `value` aceita strings hexadecimais (`"0x1234"`) ou strings de inteiros decimais.
**`get_pc`** — Retorna o ponteiro de instrução com resolução de símbolo e o texto da instrução decodificada naquele endereço.
**`get_sp`** — Retorna o valor atual do ponteiro da pilha.
---
### Símbolos e Desmontagem
| Ferramenta | Parâmetros | Retorno |
|------|-----------|---------|
| `resolve` | `name` (obrigatório) | `{name, addr}` ou `{name, addr: null, error}` |
| `find_symbols` | `pattern` (obrigatório) | `[symbol_strings]` |
| `addr_to_symbol` | `addr` (obrigatório) | `{addr, symbol}` |
| `disasm` | `addr` (padrão: RIP atual), `count` (padrão 10) | `{addr, output}` |
| `whereami` | `addr` (opcional, padrão: RIP atual) | `{description}` |
**`resolve`** — Resolve um nome de símbolo para seu endereço virtual. Use o formato `Module!Function`, ex.: `"Kernel32!WriteFile"`, `"ntdll!NtCreateFile"`.
**`find_symbols`** — Pesquisa de símbolos com curinga, ex.: `"ntdll!*Alloc*"`, `"kernel32!*File*"`. Retorna todas as strings de símbolos correspondentes.
**`addr_to_symbol`** — Resolve inversamente um endereço virtual para o nome do símbolo mais próximo.
**`disasm`** — Desmonta `count` instruções começando em `addr`. Padrão é o RIP atual se nenhum endereço for fornecido.
**`whereami`** — Retorna uma descrição legível por humanos do módulo, função e deslocamento no endereço fornecido.
---
### Módulos
| Ferramenta | Parâmetros | Retorno |
|------|-----------|---------|
| `list_modules` | — | `[{name, base, size}]` |
| `module_info` | `name` (obrigatório) | `{name, entry_point, sections}` |
| `get_exports` | `name` (obrigatório) | `[export_strings]` |
| `get_imports` | `name` (obrigatório) | `[import_strings]` |
**`list_modules`** — Lista todos os módulos carregados no alvo, com seu endereço base e tamanho.
**`module_info`** — Retorna o ponto de entrada e a lista de seções (nome, endereço virtual, tamanho) para um módulo específico, ex.: `"kernel32.dll"`, `"ntdll.dll"`.
**`get_exports`** — Retorna a tabela de exportação completa de um módulo como uma lista de strings.
**`get_imports`** — Retorna a tabela de importação completa de um módulo como uma lista de strings.
---
### Threads e Pilha
| Ferramenta | Parâmetros | Retorno |
|------|-----------|---------|
| `list_threads` | — | `[thread_description_strings]` |
| `get_thread` | — | `{current_thread}` |
| `set_thread` | `id` (obrigatório) | `{status, thread}` |
| `get_stack` | `frames` (padrão 20) | `{frames: [{frame, addr, return_addr, frame_ptr}], count}` |
| `get_teb` | — | `{addr}` |
| `get_peb` | — | `{addr}` |
**`list_threads`** — Lista todas as threads no processo alvo.
**`get_thread`** — Retorna o contexto da thread atualmente ativa.
**`set_thread`** — Alterna o contexto da thread ativa pelo ID da thread (de `list_threads`).
**`get_stack`** — Retorna a pilha de chamadas como dados estruturados. Cada quadro inclui o endereço da instrução, endereço de retorno e ponteiro do quadro.
**`get_teb`** — Retorna o endereço do Bloco de Ambiente da Thread para a thread atual.
**`get_peb`** — Retorna o endereço do Bloco de Ambiente do Processo.
---
### Processo e Utilitários
| Ferramenta | Parâmetros | Retorno |
|------|-----------|---------|
| `get_handles` | — | `[handle_description_strings]` |
| `get_bitness` | — | `{bits}` |
| `raw` | `cmd` (obrigatório) | `{output}` |
**`get_handles`** — Lista todos os identificadores abertos no processo alvo.
**`get_bitness`** — Retorna `32` ou `64` dependendo da arquitetura alvo.
**`raw`** — Executa qualquer comando do WinDbg e retorna a saída como texto. Use isso como uma válvula de escape para qualquer coisa não coberta pelas outras ferramentas.```
raw(cmd="!heap -stat")
raw(cmd="dt _PEB @$peb")
raw(cmd="!locks")
raw(cmd="lm")
raw(cmd="!address @rsp")
Em `get_captures`, inspecione `captures[0].registers.rip`:
- `"0x4141414141414141"` — você controla o RIP com bytes 'A'
- Qualquer valor que corresponda ao seu padrão — controlado
- Um endereço de aparência válida — travamento mas ainda não controlado
Verifique `captures[0].context_memory.stack_at_rsp.formatted` para ver padding, endereços de retorno ou bytes de shellcode na pilha.
---
### Análise de crash dump```
1. load_dump(path="C:/crashes/crash.dmp")
2. get_regs() → full register state at crash time
3. get_stack(frames=30) → call stack at crash
4. get_sp() → read RSP value
5. read_mem(addr=<rsp>, size=64) → stack contents
6. disasm() → instructions at the crash address
---
### ASLR check```
1. create(path="C:/target/target.exe")
2. resolve(name="kernel32!WriteFile") → record base address
3. terminate()
4. create(path="C:/target/target.exe")
5. resolve(name="kernel32!WriteFile") → compare: changed = ASLR on, same = ASLR off
---
### Inspeção de threads```
1. attach(pid=1234)
2. list_threads() → all thread IDs
3. set_thread(id=2) → switch context
4. get_stack(frames=20) → call stack for that thread
5. get_regs() → registers for that thread
6. get_teb() → TEB address
Caminho do símbolo — Se a resolução de símbolos não retornar resultados, configure o servidor de símbolos da Microsoft:``` raw(cmd=".sympath srvC:\symbolshttps://msdl.microsoft.com/download/symbols") raw(cmd=".reload")
**Ajuste de timeout** — `go()` tem como padrão 30 segundos. Para alvos que executam por mais tempo antes de atingir um breakpoint:```
go(timeout=120000) # 2 minutes
go(timeout=300000) # 5 minutes
Formato do endereço — Todos os parâmetros addr aceitam strings hex ("0x1234abcd", "7fff12340000") ou inteiros simples. O prefixo 0x é opcional para valores hex.
Verificação de shellcode — Após uma captura, use read_mem e disasm no endereço onde seu shellcode deve cair. Se disasm mostrar suas instruções pretendidas, o payload chegou intacto.
Após terminate ou detach — Todas as capturas e breakpoints são limpos automaticamente. Chame create ou attach para iniciar uma nova sessão.
capture_state vs get_captures — Use capture_state para um snapshot sob demanda quando já estiver parado em um breakpoint. Use get_captures para recuperar o estado que foi salvo automaticamente cada vez que um breakpoint foi acionado durante uma chamada go.
Comandos raw do kernel — Extensões comuns de depuração do kernel que funcionam bem através de raw:```
raw(cmd="!process 0 0") → list all processes
raw(cmd="!thread") → current thread details
raw(cmd="!irql") → current IRQL
raw(cmd="!pcr") → processor control region
raw(cmd="!pte ") → page table entry for an address
raw(cmd="dt nt!_EPROCESS @$proc") → dump EPROCESS structure
---
## Licença
MIT