# 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)