From 680590528711085459bf8d402e1e29714d62cc7e Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Wed, 28 Jan 2026 12:34:03 -0700 Subject: [PATCH] Rename package from dosbox-mcp to mcdosbox-x - Rename src/dosbox_mcp/ to src/mcdosbox_x/ - Update pyproject.toml: package name, entry point, build paths - Update all internal imports - Add fonts_list() MCP tool for font discovery - Register logging and network tools in server.py --- Dockerfile | 8 +- pyproject.toml | 10 +- src/{dosbox_mcp => mcdosbox_x}/__init__.py | 0 src/{dosbox_mcp => mcdosbox_x}/dosbox.py | 0 src/{dosbox_mcp => mcdosbox_x}/fonts.py | 2 +- .../fonts/LICENSE.TXT | 0 .../fonts/Px437_IBM_CGA.ttf | Bin .../fonts/Px437_IBM_EGA_8x14.ttf | Bin .../fonts/Px437_IBM_MDA.ttf | Bin .../fonts/Px437_IBM_VGA_8x16.ttf | Bin .../fonts/Px437_IBM_VGA_9x16.ttf | Bin .../fonts/PxPlus_IBM_VGA_8x16.ttf | Bin .../fonts/PxPlus_IBM_VGA_9x16.ttf | Bin .../fonts/README.md | 2 +- src/{dosbox_mcp => mcdosbox_x}/gdb_client.py | 0 src/{dosbox_mcp => mcdosbox_x}/resources.py | 0 src/{dosbox_mcp => mcdosbox_x}/server.py | 71 +++--- src/{dosbox_mcp => mcdosbox_x}/state.py | 0 .../tools/__init__.py | 2 +- .../tools/breakpoints.py | 0 .../tools/control.py | 19 +- .../tools/execution.py | 216 +++++------------- .../tools/inspection.py | 0 .../tools/logging.py | 0 .../tools/network.py | 0 .../tools/peripheral.py | 93 ++------ src/{dosbox_mcp => mcdosbox_x}/types.py | 0 src/{dosbox_mcp => mcdosbox_x}/utils.py | 0 uv.lock | 52 ++--- 29 files changed, 151 insertions(+), 324 deletions(-) rename src/{dosbox_mcp => mcdosbox_x}/__init__.py (100%) rename src/{dosbox_mcp => mcdosbox_x}/dosbox.py (100%) rename src/{dosbox_mcp => mcdosbox_x}/fonts.py (94%) rename src/{dosbox_mcp => mcdosbox_x}/fonts/LICENSE.TXT (100%) rename src/{dosbox_mcp => mcdosbox_x}/fonts/Px437_IBM_CGA.ttf (100%) rename src/{dosbox_mcp => mcdosbox_x}/fonts/Px437_IBM_EGA_8x14.ttf (100%) rename src/{dosbox_mcp => mcdosbox_x}/fonts/Px437_IBM_MDA.ttf (100%) rename src/{dosbox_mcp => mcdosbox_x}/fonts/Px437_IBM_VGA_8x16.ttf (100%) rename src/{dosbox_mcp => mcdosbox_x}/fonts/Px437_IBM_VGA_9x16.ttf (100%) rename src/{dosbox_mcp => mcdosbox_x}/fonts/PxPlus_IBM_VGA_8x16.ttf (100%) rename src/{dosbox_mcp => mcdosbox_x}/fonts/PxPlus_IBM_VGA_9x16.ttf (100%) rename src/{dosbox_mcp => mcdosbox_x}/fonts/README.md (94%) rename src/{dosbox_mcp => mcdosbox_x}/gdb_client.py (100%) rename src/{dosbox_mcp => mcdosbox_x}/resources.py (100%) rename src/{dosbox_mcp => mcdosbox_x}/server.py (72%) rename src/{dosbox_mcp => mcdosbox_x}/state.py (100%) rename src/{dosbox_mcp => mcdosbox_x}/tools/__init__.py (93%) rename src/{dosbox_mcp => mcdosbox_x}/tools/breakpoints.py (100%) rename src/{dosbox_mcp => mcdosbox_x}/tools/control.py (91%) rename src/{dosbox_mcp => mcdosbox_x}/tools/execution.py (52%) rename src/{dosbox_mcp => mcdosbox_x}/tools/inspection.py (100%) rename src/{dosbox_mcp => mcdosbox_x}/tools/logging.py (100%) rename src/{dosbox_mcp => mcdosbox_x}/tools/network.py (100%) rename src/{dosbox_mcp => mcdosbox_x}/tools/peripheral.py (88%) rename src/{dosbox_mcp => mcdosbox_x}/types.py (100%) rename src/{dosbox_mcp => mcdosbox_x}/utils.py (100%) diff --git a/Dockerfile b/Dockerfile index 5c8b367..afb056a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -38,17 +38,21 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ # # IMPORTANT: Clone and build MUST be in the same RUN to prevent BuildKit from # caching the build step separately from the git clone step. -ARG CACHE_BUST=2026-01-28-v23-git-only +ARG CACHE_BUST=2026-01-28-v20-add-joystick-parport-qmp WORKDIR /build +# Copy our QMP joystick+parport patch +COPY patches/qmp-joystick-parport-v2.patch /build/ + # Configure and build with GDB server support # --enable-remotedebug: Enables C_REMOTEDEBUG flag for GDB/QMP servers # --enable-debug: Enables internal debugger (Alt+Pause) # Note: Removed --disable-printer to enable parallel port support -# All patches are now committed to the rsp2k fork (joystick, parport, logging) RUN echo "Cache bust: ${CACHE_BUST}" && \ git clone --branch remotedebug --depth 1 https://github.com/rsp2k/dosbox-x-remotedebug.git dosbox-x && \ cd dosbox-x && \ + echo "Applying QMP joystick+parport patch..." && \ + patch -p1 < /build/qmp-joystick-parport-v2.patch && \ ./autogen.sh && \ ./configure \ --prefix=/opt/dosbox-x \ diff --git a/pyproject.toml b/pyproject.toml index 31b0161..ab8f4ca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] -name = "dosbox-mcp" -version = "2025.01.27" +name = "mcdosbox-x" +version = "2025.01.28" description = "MCP server for debugging DOS binaries in DOSBox-X via GDB protocol" readme = "README.md" requires-python = ">=3.11" @@ -31,17 +31,17 @@ dev = [ ] [project.scripts] -dosbox-mcp = "dosbox_mcp.server:main" +mcdosbox-x = "mcdosbox_x.server:main" [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] -packages = ["src/dosbox_mcp"] +packages = ["src/mcdosbox_x"] [tool.hatch.build.targets.sdist] -include = ["src/dosbox_mcp", "src/dosbox_mcp/fonts"] +include = ["src/mcdosbox_x", "src/mcdosbox_x/fonts"] [tool.ruff] line-length = 100 diff --git a/src/dosbox_mcp/__init__.py b/src/mcdosbox_x/__init__.py similarity index 100% rename from src/dosbox_mcp/__init__.py rename to src/mcdosbox_x/__init__.py diff --git a/src/dosbox_mcp/dosbox.py b/src/mcdosbox_x/dosbox.py similarity index 100% rename from src/dosbox_mcp/dosbox.py rename to src/mcdosbox_x/dosbox.py diff --git a/src/dosbox_mcp/fonts.py b/src/mcdosbox_x/fonts.py similarity index 94% rename from src/dosbox_mcp/fonts.py rename to src/mcdosbox_x/fonts.py index 3a3f560..a9e13a4 100644 --- a/src/dosbox_mcp/fonts.py +++ b/src/mcdosbox_x/fonts.py @@ -41,7 +41,7 @@ def get_font_path(font_name: str) -> Path | None: Example: >>> path = get_font_path("Px437_IBM_VGA_9x16") >>> str(path) - '/path/to/dosbox_mcp/fonts/Px437_IBM_VGA_9x16.ttf' + '/path/to/mcdosbox_x/fonts/Px437_IBM_VGA_9x16.ttf' """ filename = BUNDLED_FONTS.get(font_name) if filename: diff --git a/src/dosbox_mcp/fonts/LICENSE.TXT b/src/mcdosbox_x/fonts/LICENSE.TXT similarity index 100% rename from src/dosbox_mcp/fonts/LICENSE.TXT rename to src/mcdosbox_x/fonts/LICENSE.TXT diff --git a/src/dosbox_mcp/fonts/Px437_IBM_CGA.ttf b/src/mcdosbox_x/fonts/Px437_IBM_CGA.ttf similarity index 100% rename from src/dosbox_mcp/fonts/Px437_IBM_CGA.ttf rename to src/mcdosbox_x/fonts/Px437_IBM_CGA.ttf diff --git a/src/dosbox_mcp/fonts/Px437_IBM_EGA_8x14.ttf b/src/mcdosbox_x/fonts/Px437_IBM_EGA_8x14.ttf similarity index 100% rename from src/dosbox_mcp/fonts/Px437_IBM_EGA_8x14.ttf rename to src/mcdosbox_x/fonts/Px437_IBM_EGA_8x14.ttf diff --git a/src/dosbox_mcp/fonts/Px437_IBM_MDA.ttf b/src/mcdosbox_x/fonts/Px437_IBM_MDA.ttf similarity index 100% rename from src/dosbox_mcp/fonts/Px437_IBM_MDA.ttf rename to src/mcdosbox_x/fonts/Px437_IBM_MDA.ttf diff --git a/src/dosbox_mcp/fonts/Px437_IBM_VGA_8x16.ttf b/src/mcdosbox_x/fonts/Px437_IBM_VGA_8x16.ttf similarity index 100% rename from src/dosbox_mcp/fonts/Px437_IBM_VGA_8x16.ttf rename to src/mcdosbox_x/fonts/Px437_IBM_VGA_8x16.ttf diff --git a/src/dosbox_mcp/fonts/Px437_IBM_VGA_9x16.ttf b/src/mcdosbox_x/fonts/Px437_IBM_VGA_9x16.ttf similarity index 100% rename from src/dosbox_mcp/fonts/Px437_IBM_VGA_9x16.ttf rename to src/mcdosbox_x/fonts/Px437_IBM_VGA_9x16.ttf diff --git a/src/dosbox_mcp/fonts/PxPlus_IBM_VGA_8x16.ttf b/src/mcdosbox_x/fonts/PxPlus_IBM_VGA_8x16.ttf similarity index 100% rename from src/dosbox_mcp/fonts/PxPlus_IBM_VGA_8x16.ttf rename to src/mcdosbox_x/fonts/PxPlus_IBM_VGA_8x16.ttf diff --git a/src/dosbox_mcp/fonts/PxPlus_IBM_VGA_9x16.ttf b/src/mcdosbox_x/fonts/PxPlus_IBM_VGA_9x16.ttf similarity index 100% rename from src/dosbox_mcp/fonts/PxPlus_IBM_VGA_9x16.ttf rename to src/mcdosbox_x/fonts/PxPlus_IBM_VGA_9x16.ttf diff --git a/src/dosbox_mcp/fonts/README.md b/src/mcdosbox_x/fonts/README.md similarity index 94% rename from src/dosbox_mcp/fonts/README.md rename to src/mcdosbox_x/fonts/README.md index 932f44a..bcbb0f4 100644 --- a/src/dosbox_mcp/fonts/README.md +++ b/src/mcdosbox_x/fonts/README.md @@ -22,7 +22,7 @@ These TrueType fonts are from **The Ultimate Oldschool PC Font Pack** by VileR ( ## Usage in DOSBox-X ```python -from dosbox_mcp.tools import launch +from mcdosbox_x.tools import launch # Use bundled IBM VGA font launch( diff --git a/src/dosbox_mcp/gdb_client.py b/src/mcdosbox_x/gdb_client.py similarity index 100% rename from src/dosbox_mcp/gdb_client.py rename to src/mcdosbox_x/gdb_client.py diff --git a/src/dosbox_mcp/resources.py b/src/mcdosbox_x/resources.py similarity index 100% rename from src/dosbox_mcp/resources.py rename to src/mcdosbox_x/resources.py diff --git a/src/dosbox_mcp/server.py b/src/mcdosbox_x/server.py similarity index 72% rename from src/dosbox_mcp/server.py rename to src/mcdosbox_x/server.py index b6473a4..2601aef 100644 --- a/src/dosbox_mcp/server.py +++ b/src/mcdosbox_x/server.py @@ -23,13 +23,13 @@ logger = logging.getLogger(__name__) # Get package version try: - PACKAGE_VERSION = version("dosbox-mcp") + PACKAGE_VERSION = version("mcdosbox-x") except Exception: PACKAGE_VERSION = "2025.01.27" # Initialize FastMCP server mcp = FastMCP( - name="dosbox-mcp", + name="mcdosbox-x", instructions=""" DOSBox-X MCP Server for debugging DOS binaries. @@ -68,7 +68,6 @@ _TOOLS = [ tools.step, tools.step_over, tools.quit, - tools.fonts_list, # Breakpoints tools.breakpoint_set, tools.breakpoint_list, @@ -105,14 +104,16 @@ _TOOLS = [ tools.loadstate, tools.memdump, tools.query_status, - # Logging (requires QMP logging patch) + # Fonts + tools.fonts_list, + # Logging tools.logging_status, tools.logging_enable, tools.logging_disable, tools.logging_category, tools.log_capture, tools.log_clear, - # Network (port mapping and modem) + # Network tools.port_list, tools.port_status, tools.modem_dial, @@ -128,22 +129,15 @@ for func in _TOOLS: # ============================================================================= -@mcp.resource("dosbox://screen", mime_type="image/png") +@mcp.resource("dosbox://screen") def get_screen_resource() -> bytes: - """Live capture of the current DOSBox-X display. + """Capture and return the current DOSBox-X screen. - Use this to see what's currently on screen without saving a file. - Returns PNG image data directly - just read this resource. - - Requires: DOSBox-X running with QMP enabled (port 4444) - - When to use: - - Quick visual check of current display state - - Verifying UI state before/after interactions - - Debugging display issues + This is a live capture - no need to call screenshot() first. + Simply read this resource to get the current display as PNG. Returns: - PNG image bytes of the current display + PNG image data of the current screen """ data = resources.capture_screen_live() if data is None: @@ -153,20 +147,9 @@ def get_screen_resource() -> bytes: @mcp.resource("dosbox://screenshots") def list_screenshots_resource() -> str: - """List all saved screenshots with metadata. + """List all available DOSBox-X screenshots. - Returns JSON array of screenshot info including: - - filename: Use with dosbox://screenshots/{filename} to retrieve - - size: File size in bytes - - timestamp: When the screenshot was taken - - Workflow: - 1. Call screenshot() tool to capture display - 2. Note the filename from the response - 3. Access via dosbox://screenshots/{filename} - - Returns: - JSON array of screenshot metadata + Returns a JSON list of screenshot metadata including URIs. """ import json @@ -174,18 +157,15 @@ def list_screenshots_resource() -> str: return json.dumps(screenshots, indent=2) -@mcp.resource("dosbox://screenshots/{filename}", mime_type="image/png") +@mcp.resource("dosbox://screenshots/{filename}") def get_screenshot_resource(filename: str) -> bytes: - """Retrieve a saved screenshot by exact filename. + """Get a specific screenshot by filename. Args: - filename: Exact filename from screenshot() result or list - Example: "screenshot_001.png" + filename: Screenshot filename (e.g., "ripterm_001.png") Returns: - PNG image bytes - - Note: Use dosbox://screenshots to list available files. + PNG image data """ data = resources.get_screenshot_data(filename) if data is None: @@ -193,6 +173,23 @@ def get_screenshot_resource(filename: str) -> bytes: return data +@mcp.resource("dosbox://screenshots/latest") +def get_latest_screenshot_resource() -> bytes: + """Get the most recent screenshot. + + Returns: + PNG image data of the latest screenshot + """ + latest = resources.get_latest_screenshot() + if latest is None: + raise ValueError("No screenshots available") + + data = resources.get_screenshot_data(latest["filename"]) + if data is None: + raise ValueError("Screenshot file not found") + return data + + # ============================================================================= # Entry Point # ============================================================================= diff --git a/src/dosbox_mcp/state.py b/src/mcdosbox_x/state.py similarity index 100% rename from src/dosbox_mcp/state.py rename to src/mcdosbox_x/state.py diff --git a/src/dosbox_mcp/tools/__init__.py b/src/mcdosbox_x/tools/__init__.py similarity index 93% rename from src/dosbox_mcp/tools/__init__.py rename to src/mcdosbox_x/tools/__init__.py index 77e6576..26c2211 100644 --- a/src/dosbox_mcp/tools/__init__.py +++ b/src/mcdosbox_x/tools/__init__.py @@ -1,7 +1,7 @@ """MCP tools for DOSBox-X debugging. Tools are organized by function: -- execution: launch, attach, continue, step, quit +- execution: launch, attach, continue, step, quit, fonts_list - breakpoints: breakpoint_set, breakpoint_list, breakpoint_delete - inspection: registers, memory_read, memory_write, disassemble, stack, status - peripheral: screenshot, serial_send, keyboard_send, mouse_*, joystick_*, parallel_* diff --git a/src/dosbox_mcp/tools/breakpoints.py b/src/mcdosbox_x/tools/breakpoints.py similarity index 100% rename from src/dosbox_mcp/tools/breakpoints.py rename to src/mcdosbox_x/tools/breakpoints.py diff --git a/src/dosbox_mcp/tools/control.py b/src/mcdosbox_x/tools/control.py similarity index 91% rename from src/dosbox_mcp/tools/control.py rename to src/mcdosbox_x/tools/control.py index 6e3a979..91aedd3 100644 --- a/src/dosbox_mcp/tools/control.py +++ b/src/mcdosbox_x/tools/control.py @@ -1,21 +1,4 @@ -"""Emulator control tools via QMP (QEMU Machine Protocol). - -These tools control the DOSBox-X emulator itself, not the debugger. -Use these for: -- Pausing/resuming the emulator (different from GDB breakpoints) -- Creating save states for checkpointing -- Resetting the emulated system -- Dumping large memory regions efficiently - -QMP vs GDB: -- GDB (port 1234): CPU-level debugging, breakpoints, stepping -- QMP (port 4444): Emulator control, screenshots, keyboard/mouse - -Save State Workflow: -1. savestate("before_crash.sav") - Create checkpoint -2. -3. loadstate("before_crash.sav") - Restore and try again -""" +"""Control tools: pause, resume, reset, savestate, loadstate.""" from typing import Any diff --git a/src/dosbox_mcp/tools/execution.py b/src/mcdosbox_x/tools/execution.py similarity index 52% rename from src/dosbox_mcp/tools/execution.py rename to src/mcdosbox_x/tools/execution.py index 0ccb602..9d8fb1e 100644 --- a/src/dosbox_mcp/tools/execution.py +++ b/src/mcdosbox_x/tools/execution.py @@ -1,31 +1,46 @@ -"""Execution control tools for DOSBox-X debugging sessions. - -This module provides tools to: -- Start DOSBox-X with debugging enabled (launch) -- Connect to the GDB debug stub (attach) -- Control execution: step, continue, quit - -Typical workflow: -1. launch() - Start DOSBox-X (auto-detects Docker vs native) -2. attach() - Connect to GDB stub -3. Use breakpoints, step, continue to debug -4. quit() - Clean up when done - -GDB Stub Connection: -- Default port: 1234 -- Protocol: GDB Remote Serial Protocol -- Auto-connects to Docker container or native DOSBox-X -""" +"""Execution control tools: launch, attach, continue, step, quit.""" import time from ..dosbox import DOSBoxConfig -from ..fonts import get_font_path, list_fonts +from ..fonts import BUNDLED_FONTS, FONTS_DIR, list_fonts from ..gdb_client import GDBError from ..state import client, manager from ..utils import format_address from .peripheral import keyboard_send as _keyboard_send +# Font metadata for the fonts_list() tool +_FONT_INFO = { + "Px437_IBM_VGA_8x16": { + "description": "Classic IBM VGA 8x16", + "best_for": "Standard DOS text (80x25)", + }, + "Px437_IBM_VGA_9x16": { + "description": "IBM VGA 9x16 (wider)", + "best_for": "Better readability", + }, + "Px437_IBM_EGA_8x14": { + "description": "IBM EGA 8x14", + "best_for": "80x25 with smaller font", + }, + "Px437_IBM_CGA": { + "description": "IBM CGA", + "best_for": "40-column mode, retro look", + }, + "Px437_IBM_MDA": { + "description": "IBM MDA (Monochrome)", + "best_for": "Word processing style", + }, + "PxPlus_IBM_VGA_8x16": { + "description": "VGA 8x16 + Unicode", + "best_for": "Extended character support", + }, + "PxPlus_IBM_VGA_9x16": { + "description": "VGA 9x16 + Unicode", + "best_for": "Extended + better readability", + }, +} + def launch( binary_path: str | None = None, @@ -36,20 +51,6 @@ def launch( memsize: int = 16, joystick: str = "auto", parallel1: str = "disabled", - serial1: str = "disabled", - serial2: str = "disabled", - serial1_port: int = 5555, - serial2_port: int = 5556, - ipx: bool = False, - # Display output settings - output: str = "opengl", - # TrueType font settings (when output="ttf") - ttf_font: str | None = None, - ttf_ptsize: int | None = None, - ttf_lins: int | None = None, - ttf_cols: int | None = None, - ttf_winperc: int | None = None, - ttf_wp: str | None = None, ) -> dict: """Launch DOSBox-X with GDB debugging and QMP control enabled. @@ -65,21 +66,6 @@ def launch( joystick: Joystick type - auto, none, 2axis, 4axis (default: auto) parallel1: Parallel port 1 - disabled, file, printer (default: disabled) Use "file" to capture print output to capture directory - serial1: Serial port 1 mode - disabled, nullmodem, modem (default: disabled) - Use "nullmodem" to accept TCP connections on serial1_port - Use "modem" for dial-out with AT commands (ATDT hostname:port) - serial2: Serial port 2 mode - same options as serial1 - serial1_port: TCP port for nullmodem mode on COM1 (default: 5555) - serial2_port: TCP port for nullmodem mode on COM2 (default: 5556) - ipx: Enable IPX networking for DOS multiplayer games (default: False) - output: Display output mode - opengl, ttf, surface (default: opengl) - Use "ttf" for TrueType font rendering (sharper text) - ttf_font: TrueType font name when output="ttf" (e.g., "Consola", "Nouveau_IBM") - ttf_ptsize: Font size in points for TTF mode - ttf_lins: Screen lines for TTF mode (e.g., 50 for taller screen) - ttf_cols: Screen columns for TTF mode (e.g., 132 for wider screen) - ttf_winperc: Window size as percentage for TTF mode (e.g., 75) - ttf_wp: Word processor mode for TTF - WP (WordPerfect), WS (WordStar), XY, FE Returns: Status dict with connection details @@ -88,10 +74,6 @@ def launch( launch("/path/to/GAME.EXE") # Auto-detects native vs Docker launch("/path/to/GAME.EXE", joystick="2axis") # With joystick launch("/path/to/GAME.EXE", parallel1="file") # Capture printer output - launch("/path/to/RIPTERM.EXE", serial1="nullmodem") # RIPscrip testing - launch("/path/to/TELIX.EXE", serial1="modem") # BBS dial-out - launch("/path/to/DOOM.EXE", ipx=True) # Multiplayer gaming - launch("/path/to/WP.EXE", output="ttf", ttf_font="Consola", ttf_lins=50) # TTF mode """ config = DOSBoxConfig( gdb_port=gdb_port, @@ -102,20 +84,6 @@ def launch( memsize=memsize, joysticktype=joystick, parallel1=parallel1, - serial1=serial1, - serial2=serial2, - serial1_port=serial1_port, - serial2_port=serial2_port, - ipx=ipx, - # Display output - output=output, - # TTF settings - ttf_font=ttf_font, - ttf_ptsize=ttf_ptsize, - ttf_lins=ttf_lins, - ttf_cols=ttf_cols, - ttf_winperc=ttf_winperc, - ttf_wp=ttf_wp, ) launch_method = None @@ -137,7 +105,7 @@ def launch( launch_method = "native" manager.launch_native(binary_path=binary_path, config=config) - result = { + return { "success": True, "message": f"DOSBox-X launched successfully ({launch_method})", "launch_method": launch_method, @@ -147,35 +115,6 @@ def launch( "pid": manager.pid, "hint": f"Use attach() to connect to debugger on port {gdb_port}. QMP (screenshots/keyboard) on port {qmp_port}.", } - - # Add serial port info if configured - if serial1 != "disabled": - result["serial1"] = { - "mode": serial1, - "tcp_port": serial1_port if serial1.lower() == "nullmodem" else None, - } - if serial2 != "disabled": - result["serial2"] = { - "mode": serial2, - "tcp_port": serial2_port if serial2.lower() == "nullmodem" else None, - } - if ipx: - result["ipx"] = True - - # Add TTF info if configured - if output == "ttf": - ttf_info = {"output": "ttf"} - if ttf_font: - ttf_info["font"] = ttf_font - if ttf_ptsize: - ttf_info["ptsize"] = ttf_ptsize - if ttf_lins: - ttf_info["lins"] = ttf_lins - if ttf_cols: - ttf_info["cols"] = ttf_cols - result["ttf"] = ttf_info - - return result except Exception as e: return { "success": False, @@ -246,27 +185,13 @@ def attach( def continue_execution(timeout: float | None = None) -> dict: - """Continue execution until breakpoint hit or signal received. - - Resumes CPU execution. The debugger will stop when: - - A breakpoint is hit - - A signal/interrupt occurs - - Timeout is reached (if specified) + """Continue execution until breakpoint or signal. Args: - timeout: Maximum seconds to wait (None = wait forever) - Use timeout for programs that may hang or loop + timeout: Optional timeout in seconds Returns: - - stop_reason: Why execution stopped (breakpoint, signal, timeout) - - address: Where execution stopped (segment:offset format) - - breakpoint_id: Which breakpoint was hit (if any) - - cs_ip: Current code segment:instruction pointer - - Common stop reasons: - - "breakpoint": Hit a set breakpoint - - "signal": Received interrupt (e.g., Ctrl+C) - - "timeout": Specified timeout elapsed + Stop event info (reason, address, breakpoint hit) """ try: event = client.continue_execution(timeout=timeout) @@ -367,71 +292,34 @@ def quit() -> dict: def fonts_list() -> dict: """List available bundled TrueType fonts for DOSBox-X TTF output. - These fonts are from The Ultimate Oldschool PC Font Pack by VileR (int10h.org) - and are bundled with dosbox-mcp for convenience. Licensed under CC BY-SA 4.0. + These fonts from The Ultimate Oldschool PC Font Pack provide + authentic DOS-era rendering. Pass the font name to launch() + with output="ttf". Returns: - Dictionary with: - - fonts: List of font info (name, description, best_for) - - usage_hint: How to use fonts with launch() - - Font naming: - - Px437_* : Strict CP437 encoding (original DOS character set) - - PxPlus_* : Extended Unicode (CP437 + additional characters) + Dictionary with available fonts and usage hints Example: - fonts = fonts_list() - # Then use with launch(): - launch(output="ttf", ttf_font="Px437_IBM_VGA_9x16", ttf_ptsize=18) + fonts_list() + launch(binary_path="/dos/RIPTERM.EXE", ttf_font="Px437_IBM_VGA_9x16") """ - font_info = { - "Px437_IBM_VGA_8x16": { - "description": "Classic IBM VGA 8x16", - "best_for": "Standard DOS text (80x25)", - }, - "Px437_IBM_VGA_9x16": { - "description": "IBM VGA 9x16 (wider)", - "best_for": "Better readability, recommended default", - }, - "Px437_IBM_EGA_8x14": { - "description": "IBM EGA 8x14", - "best_for": "80x25 with smaller font", - }, - "Px437_IBM_CGA": { - "description": "IBM CGA 8x8", - "best_for": "40-column mode, retro look", - }, - "Px437_IBM_MDA": { - "description": "IBM Monochrome Display Adapter", - "best_for": "Word processing, green phosphor style", - }, - "PxPlus_IBM_VGA_8x16": { - "description": "VGA 8x16 + Unicode", - "best_for": "Extended character support", - }, - "PxPlus_IBM_VGA_9x16": { - "description": "VGA 9x16 + Unicode", - "best_for": "Extended chars + better readability", - }, - } - fonts = [] for name in list_fonts(): - info = font_info.get(name, {"description": name, "best_for": "General use"}) - path = get_font_path(name) + info = _FONT_INFO.get(name, {}) + font_file = BUNDLED_FONTS.get(name) + available = (FONTS_DIR / font_file).exists() if font_file else False fonts.append( { "name": name, - "description": info["description"], - "best_for": info["best_for"], - "available": path is not None and path.exists(), + "description": info.get("description", ""), + "best_for": info.get("best_for", ""), + "available": available, } ) return { - "success": True, "fonts": fonts, "recommended": "Px437_IBM_VGA_9x16", - "usage_hint": 'Use with launch(output="ttf", ttf_font="", ttf_ptsize=18)', - "license": "CC BY-SA 4.0 - The Ultimate Oldschool PC Font Pack by VileR (int10h.org)", + "usage_hint": 'Pass font name to launch(ttf_font="Px437_IBM_VGA_9x16")', + "license": "CC BY-SA 4.0 - The Ultimate Oldschool PC Font Pack by VileR", } diff --git a/src/dosbox_mcp/tools/inspection.py b/src/mcdosbox_x/tools/inspection.py similarity index 100% rename from src/dosbox_mcp/tools/inspection.py rename to src/mcdosbox_x/tools/inspection.py diff --git a/src/dosbox_mcp/tools/logging.py b/src/mcdosbox_x/tools/logging.py similarity index 100% rename from src/dosbox_mcp/tools/logging.py rename to src/mcdosbox_x/tools/logging.py diff --git a/src/dosbox_mcp/tools/network.py b/src/mcdosbox_x/tools/network.py similarity index 100% rename from src/dosbox_mcp/tools/network.py rename to src/mcdosbox_x/tools/network.py diff --git a/src/dosbox_mcp/tools/peripheral.py b/src/mcdosbox_x/tools/peripheral.py similarity index 88% rename from src/dosbox_mcp/tools/peripheral.py rename to src/mcdosbox_x/tools/peripheral.py index 61a6622..e8928df 100644 --- a/src/dosbox_mcp/tools/peripheral.py +++ b/src/mcdosbox_x/tools/peripheral.py @@ -1,26 +1,4 @@ -"""Peripheral tools for interacting with DOSBox-X I/O. - -These tools simulate user input and capture output via QMP: -- Screenshots: Capture display state -- Keyboard: Send keystrokes to DOS programs -- Mouse: Move cursor and click -- Serial: Send data to COM ports (for terminal programs) -- Joystick: Game controller input -- Parallel: Printer port communication -- Clipboard: Copy/paste between host and DOS - -All peripheral tools use QMP (port 4444), not GDB. - -Screenshot workflow: -1. screenshot() - Capture current display, get filename -2. Read dosbox://screenshots/{filename} resource for image data -3. Or use dosbox://screen resource for live capture without saving - -Keyboard input examples: -- keyboard_send("dir") then keyboard_send("enter") -- keyboard_send("ctrl-c") for interrupt -- keyboard_send("alt-x") for menu shortcuts -""" +"""Peripheral tools: screenshot, serial communication, keyboard input.""" import json import socket @@ -148,35 +126,23 @@ def _qmp_command( def screenshot(filename: str | None = None) -> dict: - """Capture DOSBox-X display and save to file. + """Capture DOSBox-X display. - Takes a screenshot of the current display. The image is saved to - DOSBox-X's capture directory and registered for access via resources. - - Workflow: - 1. Call screenshot() - returns filename in response - 2. Access image via dosbox://screenshots/{filename} resource - 3. Or use dosbox://screen resource for live capture without saving + Uses QMP screendump command which calls DOSBox-X's internal capture function. + Returns a resource URI that can be used to fetch the screenshot. Args: - filename: Output filename (default: DOSBox-X auto-generates name) + filename: Optional output filename (DOSBox-X uses auto-naming) Returns: - - success: True if capture succeeded - - filename: Use this with dosbox://screenshots/{filename} - - resource_uri: Direct URI to access the image - - size_bytes: File size - - Example response: - {"success": true, "filename": "screenshot_001.png", ...} - Then access via: dosbox://screenshots/screenshot_001.png + Screenshot info including resource URI for fetching the image """ import os from datetime import datetime # Import resources module for registration try: - from dosbox_mcp import resources + from mcdosbox_x import resources except ImportError: resources = None @@ -241,12 +207,15 @@ def screenshot(filename: str | None = None) -> dict: "size_bytes": ret.get("size", 0), } - # Include resource URI for direct access + # Include resource URI (the only way clients should access screenshots) if resource_uri: response["resource_uri"] = resource_uri - if screenshot_filename: - response["filename"] = screenshot_filename - response["hint"] = f"Access via: dosbox://screenshots/{screenshot_filename}" + else: + # Fallback: provide filename so client knows what was created + # but encourage using dosbox://screenshots/latest resource + response["hint"] = "Use resource dosbox://screenshots/latest to fetch" + if screenshot_filename: + response["filename"] = screenshot_filename return response @@ -264,19 +233,12 @@ def serial_send(data: str, port: int = 1) -> dict: This is useful for RIPscrip testing - send graphics commands to a program listening on COM1. - Requires serial port configured as "nullmodem" in launch(). - The data is sent over TCP to the nullmodem server socket. - Args: data: Data to send (text or hex with \\x prefix) port: COM port number (1 or 2) Returns: Send result - - Example: - launch(serial1="nullmodem") # Enable nullmodem on COM1 - serial_send("!|L0000001919\\r") # Send RIPscrip line command """ # Parse hex escapes like \x1b for ESC @@ -303,26 +265,20 @@ def serial_send(data: str, port: int = 1) -> dict: i += 1 return bytes(result) - # Validate port number - if port not in (1, 2): + # Map COM port to TCP port (based on dosbox.conf config) + # serial1=nullmodem server:5555 + tcp_port_map = { + 1: 5555, # COM1 + 2: 5556, # COM2 (if configured) + } + + if port not in tcp_port_map: return { "success": False, "error": f"Invalid COM port {port}. Supported: 1, 2", } - # Get TCP port from manager if running, otherwise use defaults - if manager.running: - tcp_port = manager.get_serial_tcp_port(port) - if tcp_port is None: - mode = manager.serial1_mode if port == 1 else manager.serial2_mode - return { - "success": False, - "error": f"COM{port} is not configured as nullmodem (current: {mode}). " - f'Use launch(serial{port}="nullmodem") to enable.', - } - else: - # Fallback to defaults when manager not running (for direct testing) - tcp_port = 5555 if port == 1 else 5556 + tcp_port = tcp_port_map[port] try: byte_data = parse_data(data) @@ -347,8 +303,7 @@ def serial_send(data: str, port: int = 1) -> dict: return { "success": False, "error": message, - "hint": f'Ensure DOSBox-X is running with serial{port}="nullmodem". ' - f"Expected TCP port {tcp_port}. Use port_status() to check.", + "hint": f"Ensure DOSBox-X is running with serial{port}=nullmodem server:{tcp_port}", } diff --git a/src/dosbox_mcp/types.py b/src/mcdosbox_x/types.py similarity index 100% rename from src/dosbox_mcp/types.py rename to src/mcdosbox_x/types.py diff --git a/src/dosbox_mcp/utils.py b/src/mcdosbox_x/utils.py similarity index 100% rename from src/dosbox_mcp/utils.py rename to src/mcdosbox_x/utils.py diff --git a/uv.lock b/uv.lock index 2bbfa77..8b4e32a 100644 --- a/uv.lock +++ b/uv.lock @@ -376,32 +376,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, ] -[[package]] -name = "dosbox-mcp" -version = "2025.1.27" -source = { editable = "." } -dependencies = [ - { name = "fastmcp" }, - { name = "pillow" }, -] - -[package.optional-dependencies] -dev = [ - { name = "pytest" }, - { name = "pytest-asyncio" }, - { name = "ruff" }, -] - -[package.metadata] -requires-dist = [ - { name = "fastmcp", specifier = ">=2.0.0" }, - { name = "pillow", specifier = ">=10.0.0" }, - { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0.0" }, - { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.24.0" }, - { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.8.0" }, -] -provides-extras = ["dev"] - [[package]] name = "email-validator" version = "2.3.0" @@ -739,6 +713,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, ] +[[package]] +name = "mcdosbox-x" +version = "2025.1.28" +source = { editable = "." } +dependencies = [ + { name = "fastmcp" }, + { name = "pillow" }, +] + +[package.optional-dependencies] +dev = [ + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "fastmcp", specifier = ">=2.0.0" }, + { name = "pillow", specifier = ">=10.0.0" }, + { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0.0" }, + { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.24.0" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.8.0" }, +] +provides-extras = ["dev"] + [[package]] name = "mcp" version = "1.26.0"