
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.
Ein MCP (Model Context Protocol)-Server, der jede pybag Windows-Debugger-Funktion als natives MCP-Tool bereitstellt. Er gibt jedem MCP-kompatiblen Client (Claude Desktop, Claude Code, Cowork, OpenAI Codex CLI, Cursor und benutzerdefinierten Agenten) die volle Kontrolle über User-Mode-Prozesse, Kernel-Sitzungen und Absturzspeicheranalyse – alles durch typisierte Werkzeugaufrufe mit strukturierten JSON-Antworten.
git clone https://github.com/your-username/windbg-mcp.git cd windbg-mcp
### 2. Python-Abhängigkeiten installieren```bat
pip install pybag mcp
Laden Sie das Windows SDK herunter und wählen Sie Debugging Tools for Windows während der Installation aus: https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/
Der Server läuft als lokaler stdio-Prozess. Alle unten aufgeführten Clients starten ihn auf dieselbe Weise —
python <path-to>/windbg_mcp.py — aber jeder hat sein eigenes Konfigurationsformat.
Bearbeiten Sie die Claude Desktop-Konfigurationsdatei und fügen Sie den windbg-mcp-Eintrag hinzu:
Speicherort der Konfigurationsdatei:
%APPDATA%\Claude\claude_desktop_config.jsonStarten Sie Claude Desktop neu. Alle 55 Debugger-Tools werden automatisch angezeigt.
---
### Claude Code (CLI)
Führen Sie den folgenden Befehl einmal aus, um den Server zu registrieren. Claude Code speichert den Eintrag in seiner eigenen MCP-Konfiguration und stellt die Tools in jeder nachfolgenden Sitzung zur Verfügung.```bash
claude mcp add windbg-mcp python C:\path\to\windbg-mcp\windbg_mcp.py
Um zu überprüfen, ob der Server registriert wurde:```bash claude mcp list
Um es später zu entfernen:```bash
claude mcp remove windbg-mcp
Es gibt zwei Möglichkeiten, WinDbg MCP zu Cowork hinzuzufügen: über JSON-Konfiguration (schnell) oder
durch Installation als .mcpb-Plugin-Bundle (portabel, teilbar).
3. Speichern und starten Sie Cowork neu. Die Tools sind in Ihrer nächsten Sitzung verfügbar.
#### Option B — Installation als `.mcpb` Plugin-Bundle
Eine `.mcpb`-Datei ist ein Zip-Archiv des Plugin-Verzeichnisses, die Cowork direkt installieren kann. Dies ist die empfohlene Vorgehensweise, wenn Sie den Server mit einem Team oder auf mehreren Rechnern teilen.
**Schritt 1 — Erstellen Sie die `.mcpb`-Datei**
Führen Sie vom Stammverzeichnis des geklonten Repositorys aus:```bat
powershell -Command "Compress-Archive -Path '.\*' -DestinationPath 'windbg-mcp.zip'; Rename-Item 'windbg-mcp.zip' 'windbg-mcp.mcpb'"
Dies erstellt windbg-mcp.mcpb im aktuellen Verzeichnis und bündelt windbg_mcp.py,
manifest.json sowie alle anderen Projektdateien.
Schritt 2 — Installieren in Cowork
windbg-mcp.mcpb aus.manifest.json aus dem Bundle, registriert den MCP-Server und
stellt alle Werkzeuge sofort zur Verfügung — keine manuelle Pfadkonfiguration erforderlich.Die manifest.json in diesem Repository ist bereits korrekt konfiguriert:```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}` wird zur Installationszeit in das Verzeichnis aufgelöst, in das Cowork das Bundle entpackt hat, sodass Sie keine Pfade fest codieren müssen.
---
### OpenAI Codex CLI
Fügen Sie den Server zu Ihrer Codex CLI-Konfigurationsdatei hinzu. Die Datei befindet sich normalerweise unter `~/.codex/config.json` (Linux/macOS) oder `%USERPROFILE%\.codex\config.json` (Windows).```json
{
"mcpServers": {
"windbg-mcp": {
"command": "python",
"args": ["C:\\path\\to\\windbg-mcp\\windbg_mcp.py"]
}
}
}
Sobald gespeichert, starten Sie eine neue Codex-Sitzung. Die WinDbg-Tools stehen dem Modell zum Aufruf zur Verfügung.
4. Speichern. Cursor stellt bei der nächsten Composer-Sitzung eine Verbindung zum Server her.
---
### Continue.dev
Fügen Sie Folgendes zu Ihrer `~/.continue/config.json` (oder zur Arbeitsbereichs-`.continue/config.json`) hinzu:```json
{
"experimental": {
"modelContextProtocolServers": [
{
"transport": {
"type": "stdio",
"command": "python",
"args": ["C:\\path\\to\\windbg-mcp\\windbg_mcp.py"]
}
}
]
}
}
Laden Sie die Continue-Erweiterung neu. Die 55 Debugger-Tools erscheinen in der Tool-Liste.
Wenn Sie Ihren eigenen Agenten oder Ihre eigene Automatisierungspipeline entwickeln, verbinden Sie sich über den standardmäßigen MCP stdio-Transport mit WinDbg MCP. Der Server spricht JSON-RPC 2.0 über stdin/stdout.
mcp SDK)```pythonimport 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())
#### TypeScript / Node.js (unter Verwendung des `@modelcontextprotocol/sdk` Pakets)```typescript
import { 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();
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)
#### Direktes JSON-RPC über stdio (sprachunabhängig)
Der Server kommuniziert über zeilengetrennte JSON-RPC 2.0 Nachrichten. Sie können ihn aus jeder Sprache steuern, indem Sie auf die Standardeingabe (stdin) des Prozesses schreiben und von der Standardausgabe (stdout) lesen:```
→ {"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\", ...}"}]}}
create — Startet einen neuen Prozess unter dem Debugger. Setze initial_break=True (Standard) um am Einstiegspunkt des Prozesses anzuhalten.
attach — Hängt an einen laufenden Prozess an. Geben Sie entweder pid (Ganzzahl) oder name (Prozessdateiname) an. Geben Sie nicht beide an.
kernel_attach — Stellt eine Verbindung zu einem entfernten Kernel-Debugger her. connect_string verwendet die KD-Syntax, z. B. "net:port=55000,key=1.2.3.4".
load_dump — Öffnet eine .dmp-Datei für die Post-Mortem-Analyse. Gibt sofort die Absturzadresse und das nächstgelegene Symbol zurück.
connect — Stellt eine Verbindung zu einem Prozessserver für das entfernte Benutzermodus-Debugging her. options verwendet die DbgEng-Verbindungssyntax, z. B. "tcp:server=192.168.1.10,port=5555".
go — Setzt die Ausführung fort und blockiert bis zum nächsten Debug-Ereignis (Breakpoint, Ausnahme oder Timeout). Gibt die neue RIP und alle während der Ausführung gesammelten Captures zurück.
step_into — Springt in die nächste Anweisung, folgt Aufrufen in aufgerufene Funktionen.
step_over — Überspringt die nächste Anweisung, behandelt Aufrufe als einen einzelnen Schritt.
step_out — Führt aus, bis die aktuelle Funktion zurückkehrt.
goto — Führt aus, bis ein bestimmtes Symbol oder eine Hex-Adresse erreicht ist, z. B. "Kernel32!ExitProcess" oder "0x7fff12340000".
trace — Führt N Einzelschritt-Iterationen durch und zeichnet jede besuchte Anweisung auf.
bp — Setzt einen Software-(Code-)Haltepunkt an einem Symbol oder einer Adresse.
expr: Symbol ("ntdll!NtCreateFile") oder Hex-Adresse ("0x7ff800001234")capture: wenn true (Standard), speichert automatisch den vollständigen Zustand – Register, Stack, Speicher – im Aufnahmepuffer jedes Mal, wenn dieser Haltepunkt ausgelöst wirdaction: "go" (Standard) setzt die Ausführung nach der Aufnahme fort; "break" hält anoneshot: entfernt den Haltepunkt, nachdem er einmal ausgelöst wurdepasscount: wird nur nach N Durchläufen durch die Position ausgelösthw_bp — Setzt einen Hardware-/Daten-Haltepunkt (Watchpoint).
addr: zu überwachende Hex-Adressesize: Überwachungsbreite in Bytes — 1, 2, 4 oder 8 (Standard 4)access: "e" Ausführen, "w" Schreiben (Standard), "r" Lesen/Schreibencapture, action, oneshot: gleiche Semantik wie bplist_bps — Gibt alle aktuell aktiven Haltepunkte mit ihren IDs, Ausdrücken, Typen und Einstellungen zurück.
remove_bp / enable_bp / disable_bp — Verwalten Sie Haltepunkte anhand der von bp oder hw_bp zurückgegebenen id.
Haltepunkte mit capture: true (Standard) speichern automatisch einen vollständigen Debugger-Schnappschuss jedes Mal, wenn sie ausgelöst werden. Der Schnappschuss enthält alle Register, den Aufrufstapel, 64 Bytes Stapelspeicher am RSP und 32 Bytes Code am RIP. Schnappschüsse sammeln sich in einem Puffer und können jederzeit mit get_captures abgerufen werden.
get_captures — Gibt alle seit dem letzten clear_captures gesammelten Aufnahmen zurück. Jede Aufnahme enthält:
registers — alle Registerwerte als Hex-Strings {name: "0x..."}rip — Befehlszeiger zum Zeitpunkt der Aufnahmesymbol_at_rip — nächstgelegenes Symbol zur RIPinstruction — Disassemblierung der Anweisung an der RIPstack — die obersten 10 Aufrufstapel-Frames mit Adressen und Rückkehradressencontext_memory.stack_at_rsp — 64 Bytes am RSP als Hex, formatiert und ASCIIcontext_memory.code_at_rip — 32 Bytes an der RIP als Hex und formatiertclear_captures — Löscht den Aufnahmepuffer. Nützlich vor dem Start eines neuen Durchlaufs.
capture_state — Erstellt sofort einen On-Demand-Schnappschuss des aktuellen Zustands. Verwenden Sie dies, wenn Sie bereits angehalten haben, anstatt zu warten, bis ein Haltepunkt ausgelöst wird.
read_mem — Liest size rohe Bytes von der Adresse addr. Gibt die Daten als hex (kompakt), formatted (durch Leerzeichen getrennte Bytes) und ascii (druckbare Zeichen, . für nicht druckbare) zurück.
write_mem — Schreibt Bytes in den Speicher. data ist ein Hex-String – Leerzeichen und \x-Präfixe werden automatisch entfernt, z. B. "90909090", "\\x90\\x90\\x90\\x90" oder "90 90 90 90".
read_ptr — Liest count aufeinanderfolgende Zeigergrößen-Werte (4 Bytes auf 32-Bit, 8 Bytes auf 64-Bit) ab der Adresse addr.
poi — Dereferenziert einen einzelnen Zeiger an der Adresse addr (Point-of-Interest).
read_str — Liest einen nullterminierten String. Setzen Sie wide=true für UTF-16LE (Windows WCHAR).
dump_mem — Formatierter Dword/Zeiger-Dump, entspricht dd/dp in WinDbg.
mem_info — Gibt die Eigenschaften der Speicherregion für die Seite zurück, die die Adresse addr enthält: Basisadresse, Größe, Typ, Status und Schutzflags.
mem_list — Listet alle virtuellen Speicherregionen im Adressraum des Zielprozesses auf.
get_regs — Gibt jedes verfügbare Register als {name: "0x..."} zurück. Die genaue Menge hängt von der Zielarchitektur ab (x86 vs x64).
get_reg — Gibt ein einzelnes Register zurück, z. B. name="rax", name="eflags".
set_reg — Überschreibt ein Register. value akzeptiert Hex-Strings ("0x1234") oder dezimale Ganzzahl-Strings.
get_pc — Gibt den Befehlszeiger mit Symbolauflösung und den dekodierten Anweisungstext an dieser Adresse zurück.
get_sp — Gibt den aktuellen Stapelzeigerwert zurück.
resolve — Löst einen Symbolnamen in seine virtuelle Adresse auf. Verwenden Sie das Format Module!Funktion, z. B. "Kernel32!WriteFile", "ntdll!NtCreateFile".
find_symbols — Platzhaltersymbolsuche, z. B. "ntdll!*Alloc*", "kernel32!*File*". Gibt alle übereinstimmenden Symbol-Strings zurück.
addr_to_symbol — Löst eine virtuelle Adresse rückwärts zum nächstgelegenen Symbolnamen auf.
disasm — Disassembliert count Anweisungen ab der Adresse addr. Standardmäßig wird die aktuelle RIP verwendet, falls keine Adresse angegeben ist.
whereami — Gibt eine menschenlesbare Beschreibung des Moduls, der Funktion und des Offsets an der angegebenen Adresse zurück.
list_modules — Listet alle im Ziel geladenen Module mit ihrer Basisadresse und Größe auf.
module_info — Gibt den Einstiegspunkt und die Abschnittsliste (Name, virtuelle Adresse, Größe) für ein bestimmtes Modul zurück, z. B. "kernel32.dll", "ntdll.dll".
get_exports — Gibt die vollständige Exporttabelle eines Moduls als Liste von Strings zurück.
get_imports — Gibt die vollständige Importtabelle eines Moduls als Liste von Strings zurück.
list_threads — Listet alle Threads im Zielprozess auf.
get_thread — Gibt den aktuell aktiven Thread-Kontext zurück.
set_thread — Wechselt den aktiven Thread-Kontext anhand der Thread-ID (aus list_threads).
get_stack — Gibt den Aufrufstapel als strukturierte Daten zurück. Jeder Frame enthält die Anweisungsadresse, die Rückkehradresse und den Frame-Zeiger.
get_teb — Gibt die Adresse des Thread Environment Blocks (TEB) für den aktuellen Thread zurück.
get_peb — Gibt die Adresse des Process Environment Blocks (PEB) zurück.
get_handles — Listet alle geöffneten Handles im Zielprozess auf.
get_bitness — Gibt 32 oder 64 in Abhängigkeit von der Zielarchitektur zurück.
raw — Führt einen beliebigen WinDbg-Befehl aus und gibt die Ausgabe als Text zurück. Verwenden Sie dies als Ausweichmöglichkeit für alles, was nicht von den anderen Werkzeugen abgedeckt wird:```
raw(cmd="!heap -stat")
raw(cmd="dt _PEB @$peb")
raw(cmd="!locks")
raw(cmd="lm")
raw(cmd="!address @rsp")
---
## Typische Arbeitsabläufe
### Exploit-Überprüfung```
1. create(path="C:/target/vuln.exe", args="exploit_input.bin")
2. bp(expr="vuln!processInput+0x2A", action="break")
3. go(timeout=15000)
4. get_captures()
In get_captures, inspiziere captures[0].registers.rip:
"0x4141414141414141" — du kontrollierst RIP mit 'A'-BytesÜberprüfe captures[0].context_memory.stack_at_rsp.formatted, um Padding, Rückkehradressen oder Shellcode-Bytes auf dem Stack zu sehen.
### Heap-Spray-Verifizierung```
1. attach(name="target.exe")
2. hw_bp(addr="0x1001F000", size=8, access="w", action="break")
3. go()
4. get_captures() → see what wrote to the spray address
5. read_mem(addr="0x1001EFC0", size=128) → surrounding memory context
### Remote-Kernel-Debugging```
1. kernel_attach(connect_string="net:port=55000,key=1.2.3.4")
2. list_modules() → all loaded kernel modules
3. module_info(name="ntoskrnl.exe") → entry point and sections
4. raw(cmd="!process 0 0") → list all processes from kernel context
5. raw(cmd="!pcr") → processor control region
---
## Tips
**Symbol path** — Wenn die symbol resolution keine Ergebnisse liefert, konfigurieren Sie den Microsoft symbol server:```
raw(cmd=".sympath srv*C:\\symbols*https://msdl.microsoft.com/download/symbols")
raw(cmd=".reload")
Timeout-Anpassung — go() standardmäßig 30 Sekunden. Für Ziele, die länger laufen, bevor sie einen Haltepunkt erreichen:```
go(timeout=120000) # 2 minutes
go(timeout=300000) # 5 minutes
**Adressformat** — Alle `addr`-Parameter akzeptieren Hex-Strings (`"0x1234abcd"`, `"7fff12340000"`) oder einfache Ganzzahlen. Das `0x`-Präfix ist für Hex-Werte optional.
**Shellcode-Überprüfung** — Führen Sie nach einem Capture `read_mem` und `disasm` an der Adresse aus, an der Ihr Shellcode landen soll. Wenn `disasm` Ihre beabsichtigten Anweisungen anzeigt, ist das Payload intakt angekommen.
**Nach `terminate` oder `detach`** — Alle Captures und Breakpoints werden automatisch gelöscht. Rufen Sie `create` oder `attach` auf, um eine neue Sitzung zu starten.
**`capture_state` vs `get_captures`** — Verwenden Sie `capture_state` für eine On-Demand-Momentaufnahme, wenn Sie bereits an einem Breakpoint gestoppt sind. Verwenden Sie `get_captures`, um den Zustand abzurufen, der jedes Mal automatisch gespeichert wurde, wenn während eines `go`-Aufrufs ein Breakpoint ausgelöst wurde.
**Kernel-`raw`-Befehle** — Häufige Kernel-Debugging-Erweiterungen, die gut über `raw` funktionieren:```
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 <addr>") → page table entry for an address
raw(cmd="dt nt!_EPROCESS @$proc") → dump EPROCESS structure
MIT
| Werkzeug | Parameter | Rückgabe |
|---|
status | — | {connected, type, pid, bitness} |
list_processes | — | [{pid, name, description}] |
create | path (erforderlich), args, initial_break | {status, pid, bitness} |
attach | pid oder name (nicht beide), initial_break | {status, pid, bitness} |
kernel_attach | connect_string (erforderlich), initial_break | {status, type, connect_string} |
load_dump | path (erforderlich) | {status, bitness, rip, symbol_at_rip} |
connect | options (erforderlich) | {status, options} |
detach | — | {status} |
terminate | — | {status} |
| Werkzeug | Parameter | Rückgabe |
|---|
go | timeout (ms, Standard 30000) | {status, rip, symbol, new_captures, captures} |
step_into | count (Standard 1) | {rip, instruction, symbol} |
step_over | count (Standard 1) | {rip, instruction, symbol} |
step_out | — | {rip, instruction, symbol} |
goto | expr (erforderlich) | {rip, symbol} |
trace | count (Standard 10) | {instructions: [{rip, instruction, symbol}], count} |
| Werkzeug | Parameter | Rückgabe |
|---|
bp | expr (erforderlich), capture, action, oneshot, passcount | {id, expr, addr, capture} |
hw_bp | addr (erforderlich), size, access, capture, action, oneshot | {id, addr, size, access} |
list_bps | — | [{id, expr, type, capture, action, ...}] |
remove_bp | id (erforderlich) | {status, id} |
enable_bp | id (erforderlich) | {status, id} |
disable_bp | id (erforderlich) | {status, id} |
| Werkzeug | Parameter | Rückgabe |
|---|
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} |
| Werkzeug | Parameter | Rückgabe |
|---|
read_mem | addr (erforderlich), size (Standard 16) | {addr, size, hex, formatted, ascii} |
write_mem | addr (erforderlich), data (erforderlich, Hex-String) | {status, addr, bytes_written} |
read_ptr | addr (erforderlich), count (Standard 1) | {addr, values: ["0x..."]} |
poi | addr (erforderlich) | {addr, value} |
read_str | addr (erforderlich), wide (Standard false) | {addr, value, wide} |
dump_mem | addr (erforderlich), count (Standard 8) | {addr, output} |
mem_info | addr (erforderlich) | {addr, info} |
mem_list | — | [region_description_strings] |
| Werkzeug | Parameter | Rückgabe |
|---|
get_regs | — | {rax, rbx, rcx, rdx, rsi, rdi, rbp, rsp, rip, r8–r15, eflags, ...} |
get_reg | name (erforderlich) | {name, value} |
set_reg | name (erforderlich), value (erforderlich) | {status, name, value} |
get_pc | — | {value, symbol, instruction} |
get_sp | — | {value} |
| Werkzeug | Parameter | Rückgabe |
|---|
resolve | name (erforderlich) | {name, addr} oder {name, addr: null, error} |
find_symbols | pattern (erforderlich) | [symbol_strings] |
addr_to_symbol | addr (erforderlich) | {addr, symbol} |
disasm | addr (Standard: aktuelle RIP), count (Standard 10) | {addr, output} |
whereami | addr (optional, Standard: aktuelle RIP) | {description} |
| Werkzeug | Parameter | Rückgabe |
|---|
list_modules | — | [{name, base, size}] |
module_info | name (erforderlich) | {name, entry_point, sections} |
get_exports | name (erforderlich) | [export_strings] |
get_imports | name (erforderlich) | [import_strings] |
| Werkzeug | Parameter | Rückgabe |
|---|
list_threads | — | [thread_description_strings] |
get_thread | — | {current_thread} |
set_thread | id (erforderlich) | {status, thread} |
get_stack | frames (Standard 20) | {frames: [{frame, addr, return_addr, frame_ptr}], count} |
get_teb | — | {addr} |
get_peb | — | {addr} |
| Werkzeug | Parameter | Rückgabe |
|---|
get_handles | — | [handle_description_strings] |
get_bitness | — | {bits} |
raw | cmd (erforderlich) | {output} |