From 79c646cf87c8b46f83d3eb4a85cff67c5d2f1a3e Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Tue, 27 Jan 2026 15:26:22 -0700 Subject: [PATCH] Add comprehensive documentation for MCP server - Update README.md with accurate lokkju fork info and troubleshooting - Add CLAUDE.md development guide for future sessions - Document GDB protocol details, architecture, and common issues --- CLAUDE.md | 371 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 295 +++++++++++++++++++++++++++++-------------- 2 files changed, 571 insertions(+), 95 deletions(-) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..046c756 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,371 @@ +# DOSBox-X MCP Server - Development Guide + +## Project Overview + +An MCP server that enables Claude to programmatically debug DOS binaries running in DOSBox-X via the GDB Remote Serial Protocol. The primary use case is reverse engineering the unpublished Bezier algorithm in RIPTERM.EXE. + +--- + +## Quick Reference + +### Running the System + +```bash +# Start DOSBox-X container (must be running for MCP tools to work) +xhost +local:docker && docker compose up -d + +# Verify GDB server is listening +nc -zv localhost 1234 + +# Check container logs +docker compose logs -f + +# Stop container +docker compose down +``` + +### Testing the MCP Server + +```bash +# Run full test suite +uv run pytest + +# Run specific test file +uv run pytest tests/test_gdb_client.py -v + +# Test MCP tools via CLI (important: --allowedTools required!) +claude -p "attach to localhost:1234 and read registers" \ + --allowedTools "mcp__dosbox-mcp__*" +``` + +### Common Development Commands + +```bash +# Run server directly (for debugging) +uv run dosbox-mcp + +# Lint and format +uv run ruff check src/ && uv run ruff format src/ + +# Rebuild container after Dockerfile changes +docker compose build --no-cache +``` + +--- + +## Architecture + +### Component Overview + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Claude Code │ +└─────────────────────────────────────────────────────────────────┘ + │ MCP (stdio) + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ FastMCP Server │ +│ server.py - registers tools from tools/ modules │ +└─────────────────────────────────────────────────────────────────┘ + │ + ┌───────────────┼───────────────┐ + ▼ ▼ ▼ +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ state.py │ │ types.py │ │ utils.py │ +│ Global client │ │ Registers │ │ parse_address │ +│ and manager │ │ Breakpoint │ │ format helpers │ +└─────────────────┘ │ MemoryRegion │ └─────────────────┘ + │ └─────────────────┘ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ gdb_client.py │ +│ GDB Remote Serial Protocol client │ +│ - Packet formatting ($cmd#checksum) │ +│ - Register/memory read/write │ +│ - Breakpoint management │ +│ - Execution control (step, continue) │ +└─────────────────────────────────────────────────────────────────┘ + │ TCP :1234 + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ DOSBox-X Container (lokkju fork) │ +│ Built with --enable-remotedebug │ +│ Ports: 1234 (GDB), 4444 (QMP) │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### Key Files + +| File | Purpose | +|------|---------| +| `server.py` | FastMCP entry point, registers all tools | +| `gdb_client.py` | GDB protocol implementation (~400 lines) | +| `state.py` | Singleton instances (manager, client) to avoid circular imports | +| `types.py` | Dataclasses: Registers, Breakpoint, MemoryRegion, etc. | +| `utils.py` | Address parsing, format conversion | +| `tools/execution.py` | launch, attach, continue, step, quit | +| `tools/breakpoints.py` | breakpoint_set, list, delete | +| `tools/inspection.py` | registers, memory_read/write, disassemble, stack | +| `tools/peripheral.py` | screenshot, serial_send (stubs) | + +--- + +## GDB Remote Serial Protocol + +### Packet Format + +``` +Send: $#<2-digit-hex-checksum> +Receive: +$# + (+ is ACK, - is NAK) +``` + +### Key Commands + +| Command | Description | Example | +|---------|-------------|---------| +| `qSupported` | Handshake/capabilities | First packet after connect | +| `g` | Read all registers | Returns hex blob | +| `G` | Write all registers | | +| `m,` | Read memory | `m7c00,10` | +| `M,:` | Write memory | | +| `Z0,,1` | Set software breakpoint | `Z0,7c00,1` | +| `z0,,1` | Clear breakpoint | | +| `c` | Continue execution | | +| `s` | Step one instruction | | +| `?` | Query stop reason | Returns `S05` (SIGTRAP) etc. | + +### Register Order (x86) + +GDB returns registers in this order: +``` +EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI, EIP, EFLAGS, +CS, SS, DS, ES, FS, GS +``` + +Each is 32-bit little-endian hex (8 chars), except segments (16-bit, 4 chars). + +--- + +## DOSBox-X Configuration + +### Critical: Use `[dosbox]` Section + +The lokkju fork's GDB/QMP config goes in `[dosbox]`, NOT `[debug]`: + +```ini +[dosbox] +gdbserver=true +gdbserver port=1234 +qmpserver=true +qmpserver port=4444 +``` + +### Why lokkju/dosbox-x-remotedebug? + +Mainline DOSBox-X has no GDB stub. The lokkju fork (branch: `remotedebug`) adds: +- `--enable-remotedebug` configure flag +- `gdbserver.cpp` and `qmp.cpp` sources +- Runtime config options + +Build command in Dockerfile: +```bash +./configure --enable-remotedebug --enable-debug --enable-sdl2 +``` + +--- + +## Real Mode Addressing + +DOS uses segment:offset addressing (20-bit physical): + +```python +physical = (segment << 4) + offset + +# Example: 1234:5678 +# physical = 0x12340 + 0x5678 = 0x179B8 +``` + +The `utils.py` module handles parsing: +- `"1234:5678"` → segment:offset +- `"0x12345"` or `"12345"` → flat hex +- `"#65536"` → decimal +- `"CS:IP"` → register-based (requires current register state) + +--- + +## Adding New Tools + +### 1. Create the Tool Function + +In appropriate `tools/*.py` file: + +```python +from ..state import client, manager +from ..types import SomeType +from ..utils import parse_address + +def my_new_tool(address: str, count: int = 10) -> dict: + """Short description for MCP. + + Args: + address: Memory address (segment:offset or flat hex) + count: Number of items (default: 10) + + Returns: + Result dictionary with data + """ + if not client.connected: + return {"error": "Not connected. Use attach() first."} + + addr = parse_address(address, client.read_registers()) + # ... implementation ... + return {"address": f"{addr:05x}", "data": result} +``` + +### 2. Export from `tools/__init__.py` + +```python +from .mymodule import my_new_tool + +__all__ = [ + # ...existing... + "my_new_tool", +] +``` + +### 3. Register in `server.py` + +```python +mcp.tool()(tools.my_new_tool) +``` + +### 4. Add Tests + +In `tests/test_tools.py` or new file: + +```python +def test_my_new_tool(): + # Mock the GDB client + ... +``` + +--- + +## Unfinished Work + +### Peripheral Tools (Stubs) + +`screenshot` and `serial_send` are placeholders. Implementation ideas: + +**screenshot**: DOSBox-X can capture via: +- Built-in screenshot command (key binding) +- QMP protocol on port 4444 +- X11 window capture + +**serial_send**: Configure serial port in dosbox.conf: +```ini +[serial] +serial1=nullmodem server:1234 +``` +Then send data via socket. + +### Step Over Implementation + +`step_over` in `execution.py` is basic — it steps but doesn't properly handle CALL instructions. A full implementation would: +1. Disassemble current instruction +2. If CALL, set temp breakpoint after it +3. Continue instead of step +4. Remove temp breakpoint + +### Disassembler + +Current disassembler in `inspection.py` is minimal (pattern matching common opcodes). For full disassembly, consider: +- Capstone library (`pip install capstone`) +- External tool like `ndisasm` + +--- + +## Testing Against Real DOSBox-X + +### Manual GDB Connection + +```bash +# From host, after container is running +nc localhost 1234 + +# Type (no newline): +$qSupported#37 + +# Should receive capabilities response +``` + +### Python Quick Test + +```python +from dosbox_mcp.gdb_client import GDBClient + +client = GDBClient() +client.connect("localhost", 1234) +regs = client.read_registers() +print(f"CS:IP = {regs.cs:04x}:{regs.ip:04x}") +client.disconnect() +``` + +--- + +## Debugging Tips + +### Enable Verbose Logging + +```python +import logging +logging.getLogger("dosbox_mcp").setLevel(logging.DEBUG) +``` + +### Check Container Logs + +```bash +# GDB connections show up here +docker logs dosbox-mcp 2>&1 | grep -i gdb +``` + +### Common Issues + +| Symptom | Cause | Fix | +|---------|-------|-----| +| "Connection refused" | Container not running | `docker compose up -d` | +| "Connection closed" | Wrong DOSBox build | Verify lokkju fork | +| Config not applied | Wrong section | Use `[dosbox]` not `[debug]` | +| Timeout on continue | No breakpoint hit | Set breakpoint first | +| "unhealthy" status | Missing `nc` in image | Cosmetic, ignore or add netcat | + +--- + +## Future Enhancements + +1. **RIPTERM Bezier Tracing** + - Set breakpoints at drawing routines + - Capture coordinate pairs from registers/stack + - Build coordinate trace for algorithm analysis + +2. **QMP Integration** + - Port 4444 provides machine control + - Could enable screenshots, state snapshots + +3. **Memory Watchpoints** + - GDB protocol supports `Z2`/`z2` for write watchpoints + - Useful for tracking variable changes + +4. **Scripted Automation** + - Create `examples/ripterm_bezier.py` for the target use case + - Document coordinate capture methodology + +--- + +## Related Resources + +- [GDB Remote Protocol Docs](https://sourceware.org/gdb/current/onlinedocs/gdb.html/Remote-Protocol.html) +- [lokkju fork PR/issue](https://github.com/joncampbell123/dosbox-x/issues/752) — Original discussion +- [FastMCP Docs](https://gofastmcp.com/) — MCP server framework +- [RIPscrip Spec](https://wiki.preterhuman.net/RIPscrip_Graphics_Protocol_Specification) diff --git a/README.md b/README.md index 4a5b24e..e86fdfd 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,14 @@ AI-assisted debugging of DOS binaries via the Model Context Protocol (MCP). This MCP server enables Claude to programmatically debug DOS programs running in DOSBox-X by providing tools for: -- Setting breakpoints -- Reading/writing registers and memory -- Stepping through code -- Tracing execution +- Setting breakpoints and tracing execution +- Reading/writing CPU registers and memory +- Disassembling code at any address +- Step-by-step instruction execution ## Primary Use Case -**Reverse engineering classic DOS programs** - specifically, tracing the unpublished Bezier curve algorithm in RIPTERM.EXE for the RIPscrip graphics protocol research project. +**Reverse engineering classic DOS programs** — specifically, tracing the unpublished Bezier curve algorithm in RIPTERM.EXE for the [RIPscrip graphics protocol research project](https://github.com/ryanmalloy/rpmesh). ## Quick Start @@ -19,168 +19,243 @@ This MCP server enables Claude to programmatically debug DOS programs running in - Python 3.11+ - [uv](https://github.com/astral-sh/uv) package manager -- Docker (for DOSBox-X container) -- X11 display (for DOSBox GUI) +- Docker and Docker Compose +- X11 display (for DOSBox GUI) or use headless mode ### Installation ```bash # Clone the repository -git clone https://github.com/yourusername/dosbox-mcp.git +git clone https://github.com/ryanmalloy/dosbox-mcp.git cd dosbox-mcp # Install dependencies uv sync -# Build Docker image -make build +# Build Docker image (uses lokkju/dosbox-x-remotedebug fork) +docker compose build -# Create DOS directory -make init +# Create DOS directory for your binaries +mkdir -p dos ``` ### Running ```bash -# Allow X11 access for Docker +# Allow X11 access for Docker (Linux) xhost +local:docker -# Start DOSBox-X -make up +# Start DOSBox-X with GDB server +docker compose up -d -# Register MCP server with Claude Code -make register +# Verify GDB port is listening +nc -zv localhost 1234 + +# View logs +docker compose logs -f ``` -### Usage with Claude +### Register with Claude Code -Once registered, Claude can use these tools: +```bash +# Add to Claude Code +claude mcp add dosbox-mcp -- uv run --directory /path/to/dosbox-mcp dosbox-mcp +# Verify registration +claude mcp list ``` -# Launch DOSBox-X with a binary -launch("/path/to/GAME.EXE") -# Connect to debugger +## Usage with Claude + +Once registered, Claude can debug DOS binaries: + +```python +# Connect to the running DOSBox-X GDB server attach("localhost", 1234) -# Set a breakpoint -breakpoint_set("1234:0100") - -# Run until breakpoint -continue_execution() - -# Read registers +# Read current CPU state registers() -# Read memory -memory_read("DS:0100", 64) +# Read memory at CS:IP +memory_read("CS:IP", 64) -# Step through code +# Set a breakpoint at the boot sector +breakpoint_set("0x7C00") + +# Continue execution until breakpoint +continue_execution() + +# Step through instructions step(10) -# Clean up +# Disassemble at current location +disassemble("CS:IP", 20) + +# Disconnect when done quit() ``` ## Tools Reference -### Execution Control +### Execution Control (6 tools) | Tool | Description | |------|-------------| -| `launch` | Start DOSBox-X with optional binary | -| `attach` | Connect to GDB stub | -| `continue_execution` | Run until breakpoint | -| `step` | Step N instructions | +| `launch` | Start DOSBox-X container with optional binary | +| `attach` | Connect to GDB stub at host:port | +| `continue_execution` | Run until breakpoint or signal | +| `step` | Step N instructions (default: 1) | | `step_over` | Step over CALL instructions | -| `quit` | Stop DOSBox-X | +| `quit` | Disconnect and optionally stop DOSBox-X | -### Breakpoints +### Breakpoints (3 tools) | Tool | Description | |------|-------------| -| `breakpoint_set` | Set breakpoint at address | -| `breakpoint_list` | List all breakpoints | -| `breakpoint_delete` | Remove breakpoint(s) | +| `breakpoint_set` | Set software breakpoint at address | +| `breakpoint_list` | List all active breakpoints | +| `breakpoint_delete` | Remove breakpoint by ID or all | -### Inspection +### Inspection (6 tools) | Tool | Description | |------|-------------| -| `registers` | Read all CPU registers | -| `memory_read` | Read memory region | -| `memory_write` | Write to memory | -| `disassemble` | Simple disassembly view | -| `stack` | Dump stack contents | -| `status` | Get debugger status | +| `registers` | Read all CPU registers (32-bit, 16-bit, segments, flags) | +| `memory_read` | Read memory in hex/ASCII/dump format | +| `memory_write` | Write hex or ASCII data to memory | +| `disassemble` | Disassemble instructions with opcode hints | +| `stack` | Dump stack contents from SS:SP | +| `status` | Get debugger connection status | -### Address Formats +### Peripheral (2 tools — stubs) + +| Tool | Description | +|------|-------------| +| `screenshot` | Capture DOSBox-X display (not yet implemented) | +| `serial_send` | Send data to serial port (not yet implemented) | + +## Address Formats The server supports multiple address formats: -- **Segment:offset**: `1234:5678` (standard DOS format) -- **Flat hex**: `0x12345` or `12345h` -- **Decimal**: `#65536` -- **Register-based**: `DS:SI`, `CS:IP` +| Format | Example | Description | +|--------|---------|-------------| +| Segment:offset | `1234:5678` | Standard DOS format | +| Flat hex | `0x12345` or `12345` | Physical address | +| Decimal | `#65536` | Decimal address | +| Register-based | `CS:IP`, `DS:SI` | Uses current register values | ## Architecture ``` ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ │ Claude Code │────▶│ DOSBox-X MCP │────▶│ DOSBox-X │ -│ │ MCP │ Server │ GDB │ (GDB stub) │ +│ │ MCP │ Server │ GDB │ Container │ └─────────────────┘ └──────────────────┘ └─────────────────┘ - │ - ▼ - ┌──────────────────┐ - │ GDB Remote │ - │ Protocol │ - │ (TCP :1234) │ - └──────────────────┘ + │ │ │ + │ │ │ + ▼ ▼ ▼ + FastMCP 2.x GDB Remote lokkju/dosbox-x- + (stdio) Protocol remotedebug fork + (TCP :1234) (--enable-remotedebug) ``` -## Development +### Why lokkju/dosbox-x-remotedebug? -```bash -# Run tests -make test +Mainline DOSBox-X doesn't include a GDB server. The [lokkju/dosbox-x-remotedebug](https://github.com/lokkju/dosbox-x-remotedebug) fork adds: -# Lint code -make lint - -# Format code -make format - -# View logs -make logs -``` +- `--enable-remotedebug` build flag +- GDB server on port 1234 (configurable) +- QMP server on port 4444 for machine control +- Config options in `[dosbox]` section: `gdbserver=true`, `gdbserver port=1234` ## Project Structure ``` dosbox-mcp/ ├── src/dosbox_mcp/ -│ ├── server.py # FastMCP server (tools) -│ ├── gdb_client.py # GDB protocol client -│ ├── dosbox.py # DOSBox-X management -│ ├── types.py # Type definitions -│ └── utils.py # Utilities -├── tests/ -├── examples/ -├── Dockerfile # DOSBox-X with GDB -└── docker-compose.yml +│ ├── server.py # FastMCP server entry point +│ ├── gdb_client.py # GDB Remote Serial Protocol client +│ ├── dosbox.py # DOSBox-X process/container management +│ ├── state.py # Shared global state (manager, client) +│ ├── types.py # Type definitions (Registers, Breakpoint, etc.) +│ ├── utils.py # Address parsing, format conversion +│ └── tools/ # MCP tool implementations +│ ├── execution.py # launch, attach, continue, step, quit +│ ├── breakpoints.py # breakpoint_set, list, delete +│ ├── inspection.py # registers, memory, disassemble, stack +│ └── peripheral.py # screenshot, serial (stubs) +├── tests/ # pytest test suite +├── config/ +│ └── dosbox.conf # DOSBox-X configuration with GDB enabled +├── dos/ # Mount point for DOS binaries +├── Dockerfile # Multi-stage build for dosbox-x-remotedebug +├── docker-compose.yml # Container orchestration +└── pyproject.toml # Python package definition +``` + +## Configuration + +### DOSBox-X Config (`config/dosbox.conf`) + +```ini +[dosbox] +memsize=16 +gdbserver=true +gdbserver port=1234 +qmpserver=true +qmpserver port=4444 + +[autoexec] +MOUNT C /dos +C: +``` + +### Environment Variables (`.env`) + +```bash +GDB_PORT=1234 # Host port for GDB +QMP_PORT=4444 # Host port for QMP +DOS_DIR=./dos # DOS files mount point +CONFIG_FILE=./config/dosbox.conf +``` + +## Development + +```bash +# Run tests +uv run pytest + +# Run tests with coverage +uv run pytest --cov=dosbox_mcp + +# Lint code +uv run ruff check src/ + +# Format code +uv run ruff format src/ + +# Type check (optional) +uv run mypy src/ ``` ## Technical Details -### GDB Remote Protocol +### GDB Remote Serial Protocol -This server implements a client for the [GDB Remote Serial Protocol](https://sourceware.org/gdb/current/onlinedocs/gdb.html/Remote-Protocol.html), which provides: +The server implements a client for the [GDB Remote Serial Protocol](https://sourceware.org/gdb/current/onlinedocs/gdb.html/Remote-Protocol.html): -- Register read/write (`g`, `G`, `p`, `P`) -- Memory read/write (`m`, `M`) -- Software breakpoints (`Z0`, `z0`) -- Execution control (`c`, `s`, `?`) +| Command | Description | +|---------|-------------| +| `qSupported` | Capability negotiation (handshake) | +| `g` / `G` | Read/write all registers | +| `p` / `P` | Read/write single register | +| `m` / `M` | Read/write memory | +| `Z0` / `z0` | Set/clear software breakpoint | +| `c` / `s` | Continue / step | +| `?` | Query stop reason | + +Packets use format: `$#<2-digit checksum>` ### Real Mode Addressing @@ -190,13 +265,43 @@ DOS uses real mode with segment:offset addressing: Physical Address = (Segment << 4) + Offset ``` -This gives a 20-bit address space (1MB), though only 640KB is conventional memory. +This gives a 20-bit address space (1MB). The `Registers` class provides helpers like `cs_ip` and `ss_sp` for common address calculations. + +## Troubleshooting + +### GDB Connection Refused + +```bash +# Check if container is running +docker ps | grep dosbox + +# Check if GDB port is listening +docker exec dosbox-mcp nc -zv localhost 1234 + +# Check logs for GDB server startup +docker logs dosbox-mcp 2>&1 | grep -i gdb +``` + +### Container Shows "unhealthy" + +The healthcheck uses `nc` which may not be installed in the slim image. This is cosmetic — the GDB server still works. To fix, add `netcat-openbsd` to the Dockerfile runtime stage. + +### X11 Display Issues + +```bash +# Allow Docker X11 access +xhost +local:docker + +# Or use headless mode +docker compose --profile headless up -d dosbox-headless +``` ## Related Projects -- [RIPscrip Research](../rpmesh/) - Parent project for RIPscrip graphics protocol -- [dosbox-x-gdb](https://github.com/hezi/dosbox-x-gdb) - DOSBox-X fork with GDB support -- [FastMCP](https://gofastmcp.com/) - MCP server framework +- [RIPscrip Research (rpmesh)](https://github.com/ryanmalloy/rpmesh) — Parent project +- [lokkju/dosbox-x-remotedebug](https://github.com/lokkju/dosbox-x-remotedebug) — DOSBox-X fork with GDB +- [FastMCP](https://gofastmcp.com/) — MCP server framework +- [GDB Remote Protocol](https://sourceware.org/gdb/current/onlinedocs/gdb.html/Remote-Protocol.html) ## License