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
This commit is contained in:
Ryan Malloy 2026-01-27 15:26:22 -07:00
parent b022f6fb9e
commit 79c646cf87
2 changed files with 571 additions and 95 deletions

371
CLAUDE.md Normal file
View File

@ -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: $<command>#<2-digit-hex-checksum>
Receive: +$<response>#<checksum>
(+ is ACK, - is NAK)
```
### Key Commands
| Command | Description | Example |
|---------|-------------|---------|
| `qSupported` | Handshake/capabilities | First packet after connect |
| `g` | Read all registers | Returns hex blob |
| `G<hex>` | Write all registers | |
| `m<addr>,<len>` | Read memory | `m7c00,10` |
| `M<addr>,<len>:<hex>` | Write memory | |
| `Z0,<addr>,1` | Set software breakpoint | `Z0,7c00,1` |
| `z0,<addr>,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)

295
README.md
View File

@ -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: This MCP server enables Claude to programmatically debug DOS programs running in DOSBox-X by providing tools for:
- Setting breakpoints - Setting breakpoints and tracing execution
- Reading/writing registers and memory - Reading/writing CPU registers and memory
- Stepping through code - Disassembling code at any address
- Tracing execution - Step-by-step instruction execution
## Primary Use Case ## 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 ## Quick Start
@ -19,168 +19,243 @@ This MCP server enables Claude to programmatically debug DOS programs running in
- Python 3.11+ - Python 3.11+
- [uv](https://github.com/astral-sh/uv) package manager - [uv](https://github.com/astral-sh/uv) package manager
- Docker (for DOSBox-X container) - Docker and Docker Compose
- X11 display (for DOSBox GUI) - X11 display (for DOSBox GUI) or use headless mode
### Installation ### Installation
```bash ```bash
# Clone the repository # Clone the repository
git clone https://github.com/yourusername/dosbox-mcp.git git clone https://github.com/ryanmalloy/dosbox-mcp.git
cd dosbox-mcp cd dosbox-mcp
# Install dependencies # Install dependencies
uv sync uv sync
# Build Docker image # Build Docker image (uses lokkju/dosbox-x-remotedebug fork)
make build docker compose build
# Create DOS directory # Create DOS directory for your binaries
make init mkdir -p dos
``` ```
### Running ### Running
```bash ```bash
# Allow X11 access for Docker # Allow X11 access for Docker (Linux)
xhost +local:docker xhost +local:docker
# Start DOSBox-X # Start DOSBox-X with GDB server
make up docker compose up -d
# Register MCP server with Claude Code # Verify GDB port is listening
make register 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) attach("localhost", 1234)
# Set a breakpoint # Read current CPU state
breakpoint_set("1234:0100")
# Run until breakpoint
continue_execution()
# Read registers
registers() registers()
# Read memory # Read memory at CS:IP
memory_read("DS:0100", 64) 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) step(10)
# Clean up # Disassemble at current location
disassemble("CS:IP", 20)
# Disconnect when done
quit() quit()
``` ```
## Tools Reference ## Tools Reference
### Execution Control ### Execution Control (6 tools)
| Tool | Description | | Tool | Description |
|------|-------------| |------|-------------|
| `launch` | Start DOSBox-X with optional binary | | `launch` | Start DOSBox-X container with optional binary |
| `attach` | Connect to GDB stub | | `attach` | Connect to GDB stub at host:port |
| `continue_execution` | Run until breakpoint | | `continue_execution` | Run until breakpoint or signal |
| `step` | Step N instructions | | `step` | Step N instructions (default: 1) |
| `step_over` | Step over CALL instructions | | `step_over` | Step over CALL instructions |
| `quit` | Stop DOSBox-X | | `quit` | Disconnect and optionally stop DOSBox-X |
### Breakpoints ### Breakpoints (3 tools)
| Tool | Description | | Tool | Description |
|------|-------------| |------|-------------|
| `breakpoint_set` | Set breakpoint at address | | `breakpoint_set` | Set software breakpoint at address |
| `breakpoint_list` | List all breakpoints | | `breakpoint_list` | List all active breakpoints |
| `breakpoint_delete` | Remove breakpoint(s) | | `breakpoint_delete` | Remove breakpoint by ID or all |
### Inspection ### Inspection (6 tools)
| Tool | Description | | Tool | Description |
|------|-------------| |------|-------------|
| `registers` | Read all CPU registers | | `registers` | Read all CPU registers (32-bit, 16-bit, segments, flags) |
| `memory_read` | Read memory region | | `memory_read` | Read memory in hex/ASCII/dump format |
| `memory_write` | Write to memory | | `memory_write` | Write hex or ASCII data to memory |
| `disassemble` | Simple disassembly view | | `disassemble` | Disassemble instructions with opcode hints |
| `stack` | Dump stack contents | | `stack` | Dump stack contents from SS:SP |
| `status` | Get debugger status | | `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: The server supports multiple address formats:
- **Segment:offset**: `1234:5678` (standard DOS format) | Format | Example | Description |
- **Flat hex**: `0x12345` or `12345h` |--------|---------|-------------|
- **Decimal**: `#65536` | Segment:offset | `1234:5678` | Standard DOS format |
- **Register-based**: `DS:SI`, `CS:IP` | Flat hex | `0x12345` or `12345` | Physical address |
| Decimal | `#65536` | Decimal address |
| Register-based | `CS:IP`, `DS:SI` | Uses current register values |
## Architecture ## Architecture
``` ```
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Claude Code │────▶│ DOSBox-X MCP │────▶│ DOSBox-X │ │ Claude Code │────▶│ DOSBox-X MCP │────▶│ DOSBox-X │
│ │ MCP │ Server │ GDB │ (GDB stub) │ │ MCP │ Server │ GDB │ Container
└─────────────────┘ └──────────────────┘ └─────────────────┘ └─────────────────┘ └──────────────────┘ └─────────────────┘
│ │ │
│ │ │
┌──────────────────┐ ▼ ▼ ▼
│ GDB Remote │ FastMCP 2.x GDB Remote lokkju/dosbox-x-
│ Protocol │ (stdio) Protocol remotedebug fork
│ (TCP :1234) │ (TCP :1234) (--enable-remotedebug)
└──────────────────┘
``` ```
## Development ### Why lokkju/dosbox-x-remotedebug?
```bash Mainline DOSBox-X doesn't include a GDB server. The [lokkju/dosbox-x-remotedebug](https://github.com/lokkju/dosbox-x-remotedebug) fork adds:
# Run tests
make test
# Lint code - `--enable-remotedebug` build flag
make lint - GDB server on port 1234 (configurable)
- QMP server on port 4444 for machine control
# Format code - Config options in `[dosbox]` section: `gdbserver=true`, `gdbserver port=1234`
make format
# View logs
make logs
```
## Project Structure ## Project Structure
``` ```
dosbox-mcp/ dosbox-mcp/
├── src/dosbox_mcp/ ├── src/dosbox_mcp/
│ ├── server.py # FastMCP server (tools) │ ├── server.py # FastMCP server entry point
│ ├── gdb_client.py # GDB protocol client │ ├── gdb_client.py # GDB Remote Serial Protocol client
│ ├── dosbox.py # DOSBox-X management │ ├── dosbox.py # DOSBox-X process/container management
│ ├── types.py # Type definitions │ ├── state.py # Shared global state (manager, client)
│ └── utils.py # Utilities │ ├── types.py # Type definitions (Registers, Breakpoint, etc.)
├── tests/ │ ├── utils.py # Address parsing, format conversion
├── examples/ │ └── tools/ # MCP tool implementations
├── Dockerfile # DOSBox-X with GDB │ ├── execution.py # launch, attach, continue, step, quit
└── docker-compose.yml │ ├── 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 ## 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`) | Command | Description |
- Memory read/write (`m`, `M`) |---------|-------------|
- Software breakpoints (`Z0`, `z0`) | `qSupported` | Capability negotiation (handshake) |
- Execution control (`c`, `s`, `?`) | `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: `$<command>#<2-digit checksum>`
### Real Mode Addressing ### Real Mode Addressing
@ -190,13 +265,43 @@ DOS uses real mode with segment:offset addressing:
Physical Address = (Segment << 4) + Offset 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 ## Related Projects
- [RIPscrip Research](../rpmesh/) - Parent project for RIPscrip graphics protocol - [RIPscrip Research (rpmesh)](https://github.com/ryanmalloy/rpmesh) — Parent project
- [dosbox-x-gdb](https://github.com/hezi/dosbox-x-gdb) - DOSBox-X fork with GDB support - [lokkju/dosbox-x-remotedebug](https://github.com/lokkju/dosbox-x-remotedebug) — DOSBox-X fork with GDB
- [FastMCP](https://gofastmcp.com/) - MCP server framework - [FastMCP](https://gofastmcp.com/) — MCP server framework
- [GDB Remote Protocol](https://sourceware.org/gdb/current/onlinedocs/gdb.html/Remote-Protocol.html)
## License ## License