mcdosbox-x/CLAUDE.md
Ryan Malloy 79c646cf87 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
2026-01-27 15:26:22 -07:00

372 lines
11 KiB
Markdown

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