romdev-mcp 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -8,6 +8,17 @@ Drives the full homebrew ROM dev loop for retro game platforms (NES, Game Boy, S
8
8
 
9
9
  You drive the work. The human is a director — they may want a game, a ROM disassembly, a tool-assisted reverse-engineering session, or anything else this server can do.
10
10
 
11
+ ## The compilers and emulators are bundled — don't install or download those
12
+
13
+ Every **compiler, assembler, linker, and emulator core** you need to build and run ROMs is **already bundled** as WebAssembly inside romdev and runs through these MCP tools. So do **not**:
14
+ - `apt`/`brew`/`npm install` a compiler (cc65, sdcc, gcc, tcc, wla, rgbds, vasm, m68k-gcc, …) — they're built in.
15
+ - fetch a devkit/SDK (devkitPro, SGDK, PVSnesLib, libtonc/libgba, cc65 libs, …) — their source is bundled and the toolchains link against it for you.
16
+ - look for a system emulator — the libretro cores (fceumm, snes9x, gpgx, gambatte, mGBA, …) run in-process.
17
+
18
+ **Build/run/inspect happens through the romdev tools** (`buildSource`, `runSource`, `buildProject`, `loadMedia`, `screenshot`, `inspect*`, …) — not by shelling out to a system `gcc` or a downloaded toolchain. If you catch yourself reaching for a compiler/emulator install, stop and call the equivalent romdev tool. `listPlatforms` / `listToolchains` show what's available.
19
+
20
+ This is scoped to the build/run toolchain — romdev is **not** an everything box. A user may well want **external art and music tools** (Aseprite/LibreSprite for sprites, a tracker like FamiStudio/Deflemask for music, ImageMagick for conversions). Those are legitimately the user's to install, and several romdev asset-loader tools (`loadAsepriteSheet`, `loadSpriteSheet`, `loadGifAnimation`, …) are designed to consume their output. Don't refuse or reinvent those — if the user wants to paint or compose in a real editor, that's expected; romdev imports the result.
21
+
11
22
  ## If a human is watching, open playtest early
12
23
 
13
24
  If a human is sitting next to you during this session — and that's most sessions in practice — open the playtest window as soon as your first build succeeds. `playtest()` opens a native SDL window that runs your ROM live and accepts USB gamepads (hot-plugged controllers are picked up automatically). It returns **immediately** — the render loop runs in the background, so you keep calling other tools while the human plays. Every other MCP tool keeps working against that same running ROM, and **`runSource`/`loadMedia` rebuilds update the window in place** — the window follows your latest build, no relaunch and no crash on rebuild. A human sitting next to you should be **playing the game** while you iterate, not watching screenshots scroll past.
@@ -21,7 +32,7 @@ After that, keep iterating with `runSource` / `buildSource` / readMemory / scree
21
32
 
22
33
  **No gamepad?** `playtest()`'s response includes a `keyboardControls` map and a `tellUser` note when no controller is detected — relay the keys to the human (arrows = D-pad, Z = main action, Enter = START, ESC closes) so they know how to play.
23
34
 
24
- Skip playtest only when there's clearly no human in the loop: CI runs, automated test suites, batch reverse-engineering, or when the user has explicitly said "headless." `playtest()` needs a desktop display; with none it returns `{opened:false, reason:"no-display"}` and tells you how to relaunch the server with the display env — every other tool (build, run, screenshot, inspect) is fully headless and unaffected. When in doubt, ask once, then default to opening it.
35
+ Skip playtest only when there's clearly no human in the loop: CI runs, automated test suites, batch reverse-engineering, or when the user has explicitly said "headless." `playtest()` needs a desktop session to draw into; if it can't open a window it returns `{opened:false, reason:"sdl-error", message}` explaining how to get one (usually: run the server yourself in a terminal inside your desktop session via `npx romdev-mcp`, then connect your agent) — every other tool (build, run, screenshot, inspect) is fully headless and unaffected. When in doubt, ask once, then default to opening it.
25
36
 
26
37
  ## Tool surface: everything is loaded — just call the tool
27
38
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "romdev-mcp",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "MCP server giving coding agents full control of homebrew ROM development across retro platforms (NES, SNES, GB, Genesis, Atari, C64, ...) via WASM libretro cores.",
5
5
  "type": "module",
6
6
  "main": "src/mcp/server.js",
@@ -75,31 +75,12 @@ export function registerPlaytestTools(server, z, sessionKey) {
75
75
  aspect: z.enum(["fb", "tv", "core"]).default("fb").describe("Initial window shape. 'fb' (default) opens at raw framebuffer * scale — square pixels, exact dev-time geometry. 'tv' = 'how a player saw the hardware': 4:3 for consoles (NES/SNES/Genesis/SMS/Atari/C64); native LCD aspect for handhelds (GB/GBC 10:9 = 160×144 NOT stretched; GG ~6:5; Lynx 4:3; GBA 3:2). 'core' honors the core's reported display_aspect_ratio (the framebuffer geometric ratio, often non-4:3 — Genesis H40 reports ~10:7). The user can resize; letterbox preserves the chosen aspect. NOTE: 'tv' looks up the platform from the running host, so always pass the correct `platform` arg to loadMedia (`platform:\"gbc\"` not `\"gb\"` for a CGB game) or 'tv' falls back to the framebuffer aspect."),
76
76
  },
77
77
  safeTool(async ({ scale, title, aspect }) => {
78
- // Preflight #1 (before requiring a host): a native SDL window needs a
79
- // desktop display. If the server was launched from a shell with no
80
- // DISPLAY / WAYLAND_DISPLAY (a tmux/ssh session started before the
81
- // desktop login, or a cron/CI run), createWindow has nowhere to draw —
82
- // it fails or silently shows nothing. Surface an actionable message
83
- // instead of a mystery (and instead of a misleading "no ROM loaded" if
84
- // getHost ran first). The display is what's missing, not the ROM.
85
- if (!process.env.DISPLAY && !process.env.WAYLAND_DISPLAY) {
86
- return jsonContent({
87
- opened: false,
88
- reason: "no-display",
89
- message:
90
- "Can't open a playtest window: the server process has no DISPLAY or " +
91
- "WAYLAND_DISPLAY set, so there's no desktop to draw on. This usually " +
92
- "means the server was started from a shell/tmux/ssh session that " +
93
- "predates the desktop login. Restart the server with the display env, " +
94
- "e.g.: DISPLAY=:0 WAYLAND_DISPLAY=wayland-0 " +
95
- "XAUTHORITY=$XDG_RUNTIME_DIR/.mutter-Xwaylandauth.* node src/mcp/server.js " +
96
- "(discover live values via: tr '\\0' '\\n' < /proc/<desktop-pid>/environ | " +
97
- "grep -E '^(DISPLAY|WAYLAND_DISPLAY|XAUTHORITY)='). Every headless tool " +
98
- "(screenshot, runSource, readMemory, ...) still works — only the live " +
99
- "window needs a display.",
100
- });
101
- }
102
-
78
+ // No preflight display checks. We just attempt to open the SDL window and
79
+ // report whatever SDL says env-var guessing (DISPLAY/WAYLAND_DISPLAY)
80
+ // is Linux-only and wrong on macOS/Windows, where those vars are never
81
+ // set even with a full GUI session. SDL's createWindow already knows
82
+ // whether it can draw on any platform; the try/catch below surfaces the
83
+ // real error.
103
84
  const host = getHost(sessionKey);
104
85
  const loadedMediaPath = host.status?.mediaPath ?? null;
105
86
  if (reconcileSession()) {
@@ -130,17 +111,24 @@ export function registerPlaytestTools(server, z, sessionKey) {
130
111
  aspect,
131
112
  });
132
113
  } catch (e) {
133
- // createWindow / SDL init threw despite a display being set surface
134
- // the real reason rather than a generic tool error.
114
+ // SDL couldn't open a window report what it said. We don't guess at
115
+ // the cause (no display / no GUI session / driver issue differ per OS
116
+ // and SDL already knows); we just relay the error plus the one fix that
117
+ // works everywhere, and remind the agent the headless tools are fine.
135
118
  return jsonContent({
136
119
  opened: false,
137
120
  reason: "sdl-error",
121
+ platform: process.platform,
138
122
  message:
139
- "Failed to open the SDL playtest window: " + (e?.message ?? String(e)) +
140
- ". A display IS set (DISPLAY=" + (process.env.DISPLAY ?? "") +
141
- " WAYLAND_DISPLAY=" + (process.env.WAYLAND_DISPLAY ?? "") +
142
- "), so this is an SDL/driver issue, not a missing-display one. The " +
143
- "ROM stays loaded; screenshot / runSource / other tools still work.",
123
+ "Couldn't open the SDL playtest window: " + (e?.message ?? String(e)) +
124
+ ". This typically happens when the server runs as an MCP subprocess " +
125
+ "(spawned by your agent host) with no access to the logged-in desktop " +
126
+ "session. The reliable fix on any OS: run the server yourself in a " +
127
+ "terminal inside your desktop session (`npx romdev-mcp`), then connect " +
128
+ "your agent to it. Every headless tool (screenshot / runSource / " +
129
+ "readMemory / stepFrames / pressButton) still works against the live " +
130
+ "ROM — only the interactive window needs a desktop. You can also open " +
131
+ "the built ROM in any standalone emulator.",
144
132
  loadedMediaPath,
145
133
  });
146
134
  }