# dmang-dev/mcp-bizhawk [Health: Active]

**Category:** 🎮 Gaming  
**Repository:** https://github.com/dmang-dev/mcp-bizhawk  
**GitHub Stars:** 6  
**npm Downloads (last month):** 97  
**Views:** 11  
**Installs:** 0  
**Upvotes:** 0  
**Directory Page:** https://allmcps.com/mcp/dmang-dev-mcp-bizhawk

## Description
Drive the BizHawk multi-system emulator from any MCP client. Memory r/w across named domains, joypad input, frame-advance, screenshot, save/load state. One bridge unlocks NES, SNES, GB/GBC/GBA, Genesis, N64, PSX, Saturn, and more.

## Tools
Capabilities this server exposes over MCP:

- **bizhawk_ping** — PURPOSE: Verify that the BizHawk Lua bridge is connected and responding to RPC over the TCP socket. USAGE: Call this once at start-of-session before issuing other tool calls; if it succeeds, every other tool will work. BEHAVIOR: No side effects — pure liveness probe. Times out after ~10 seconds with a clear error if BizHawk isn't running, isn't pointed at the right host:port, or hasn't loaded lua/bridge.lua via Tools → Lua Console. RETURNS: The literal string 'pong' on success.
- **bizhawk_get_info** — PURPOSE: Get the loaded ROM's name and hash, current frame count, the list of available memory domains, the active default domain (the one used when 'domain' is omitted on read/write tool calls), and the bridge's capability map (which optional emu/client/savestate/joypad/memory methods this BizHawk build exposes). USAGE: Call after bizhawk_ping to learn what system is loaded and which optional features are available; before any memory tool call to confirm the active domain and avoid silent reads from the wrong address space; before pause / unpause / reset / screenshot / save_state to check the corresponding `capabilities.*` flag. BEHAVIOR: No side effects — pure read of emulator metadata. Returns 'unavailable' for fields the loaded core doesn't expose (rom_name when no ROM is loaded, framecount on cores without emu.framecount, etc.). RETURNS: Multi-line text with ROM, ROM hash, framecount, memory_domains list, active domain, and a list of any missing capabilities for this build.
- **bizhawk_list_memory_domains** — PURPOSE: List the memory domains available on the loaded core (e.g. 'WRAM', 'CARTRAM', 'VRAM', 'System Bus' on SNES; 'RAM', 'PPU', 'OAM' on NES). USAGE: Call before any memory r/w tool when you don't know the domain layout for the loaded system. The returned names are exactly what to pass as the `domain` parameter on bizhawk_read*/write* tools (case-sensitive). BEHAVIOR: No side effects — pure read. Returns an error if the loaded BizHawk core doesn't implement memory.getmemorydomainlist (extremely rare). RETURNS: Newline-formatted list of domain names, one per line.
- **bizhawk_read8** — PURPOSE: Read an unsigned 8-bit byte from emulator memory at the given address. USAGE: Use for single-byte status flags, counters, and 8-bit fields. For 16- or 32-bit values use bizhawk_read16/read32 (one call instead of multi-byte assembly); for spans of more than ~4 bytes use bizhawk_read_range (one round-trip instead of N frame-latency hops). BEHAVIOR: No side effects — pure read. Reads work the same way whether emulation is paused or running. Returns an error if the named domain doesn't exist, the address is out of range for the domain, or the loaded core doesn't expose memory.read_u8. RETURNS: Single line 'ADDR_HEX: VAL_DEC (0xVAL_HEX)', e.g. '0x09C6: 99 (0x63)'.
- **bizhawk_read16** — PURPOSE: Read an unsigned 16-bit little-endian value from emulator memory at the given address. USAGE: Use for 16-bit fields (most game-state values: HP, score, coordinates). For single bytes use bizhawk_read8; for 32-bit values use bizhawk_read32; for non-aligned spans or big-endian fields use bizhawk_read_range and decode the bytes yourself (this tool always interprets bytes as little-endian regardless of the target system's native endianness). BEHAVIOR: No side effects — pure read. Reads two consecutive bytes (low byte at `address`, high byte at `address+1`) and combines them as little-endian. Returns an error if the named domain doesn't exist, address+2 exceeds domain size, or the core doesn't expose memory.read_u16_le. RETURNS: Single line 'ADDR_HEX: VAL_DEC (0xVAL_HEX)'.
- **bizhawk_read32** — PURPOSE: Read an unsigned 32-bit little-endian value from emulator memory at the given address. USAGE: Use for 32-bit fields (timestamps, large counters, pointers on 32-bit systems, RGBA colors). For 8/16-bit reads use bizhawk_read8/read16; for big-endian or unaligned multi-word reads use bizhawk_read_range and decode yourself. BEHAVIOR: No side effects — pure read. Reads four consecutive bytes starting at `address` and combines them as little-endian (LSB at `address`, MSB at `address+3`). Returns an error if the domain doesn't exist, address+4 exceeds the domain, or the core lacks memory.read_u32_le. RETURNS: Single line 'ADDR_HEX: VAL_DEC (0xVAL_HEX)'.
- **bizhawk_read_range** — PURPOSE: Read a contiguous range of bytes from emulator memory as a hex dump. USAGE: Use for >4 bytes (one round-trip vs N frame-latency hops). Max 4096 bytes/call (BizHawk serialization limit); chunk larger reads in 4 KiB. Powers the two-snapshot RAM-hunt workflow (snapshot before/after a known change, diff for matching deltas). BEHAVIOR: No side effects — pure read. Returns an error if domain is unknown, length is out of 1-4096, or address+length exceeds the domain. RETURNS: 'ADDR_HEX [N bytes, DOMAIN]:' header + space-separated 2-digit uppercase hex bytes.
- **bizhawk_write8** — PURPOSE: Write a single unsigned byte (0-255) to emulator memory at the given address. USAGE: Use for single-byte cheats, debug pokes, and game-state mutations (give a player N lives, unlock a flag, set a counter). For 16/32-bit values prefer bizhawk_write16/write32 (single call instead of byte-at-a-time); for spans use bizhawk_write_range. To seed cart save RAM realistically (with proper MBC behavior), prefer bizhawk_load_state with a pre-prepared .State file rather than poking SRAM bytes here. BEHAVIOR: DESTRUCTIVE: overwrites whatever was at `address` with no undo (snapshot via bizhawk_save_state first if you need rollback). The write is direct memory access — bypasses MBC bank switches, cartridge mapper side-effects, and DMA semantics — so it cannot be used to emulate cartridge hardware. Returns an error if the domain is unknown, address is out of range, value < 0 or > 255, or the core lacks memory.write_u8. Works whether emulation is paused or running. RETURNS: Single line 'Wrote VAL_DEC (0xVAL_HEX) → ADDR_HEX (DOMAIN)'.
- **bizhawk_write16** — PURPOSE: Write an unsigned 16-bit little-endian value to emulator memory at the given address. USAGE: Use for 16-bit cheats and pokes (HP, score, coordinates). For single bytes use bizhawk_write8; for 32-bit use bizhawk_write32; for big-endian fields, byteswap and use bizhawk_write_range; for cart save RAM seeding, use bizhawk_load_state. BEHAVIOR: DESTRUCTIVE: overwrites two bytes (low byte at `address`, high byte at `address+1`) with no undo. Direct memory write — no MBC/mapper/DMA mediation, see bizhawk_write8 notes. Returns an error if the domain is unknown, address+2 exceeds the domain, value < 0 or > 65535, or the core lacks memory.write_u16_le. RETURNS: Single line 'Wrote VAL_DEC (0xVAL_HEX) → ADDR_HEX (DOMAIN)'.
- **bizhawk_write32** — PURPOSE: Write an unsigned 32-bit little-endian value to emulator memory at the given address. USAGE: Use for 32-bit cheats and pokes (timestamps, large counters, pointers on 32-bit systems). For 8/16-bit values use bizhawk_write8/write16; for big-endian layouts byteswap and use bizhawk_write_range. BEHAVIOR: DESTRUCTIVE: overwrites four bytes starting at `address` with no undo (snapshot via bizhawk_save_state first if you need rollback). Direct memory write — bypasses MBC/mapper/DMA, see bizhawk_write8 notes. Returns an error if the domain is unknown, address+4 exceeds the domain, value < 0 or > 4294967295, or the core lacks memory.write_u32_le. RETURNS: Single line 'Wrote VAL_DEC (0xVAL_HEX) → ADDR_HEX (DOMAIN)'.
- **bizhawk_write_range** — PURPOSE: Write a contiguous byte sequence to emulator memory starting at the given address. USAGE: Use whenever you're seeding more than ~4 bytes — one round-trip vs N frame-latency hops compared to looping bizhawk_write8. Maximum 4096 bytes per call (BizHawk serialization limit); for larger writes, batch in 4 KiB chunks. Useful for installing cheat tables, patching code blocks, restoring a captured byte window after experiments, and writing big-endian multi-byte values (byteswap them yourself first). For cart save RAM seeding with proper MBC semantics, use bizhawk_load_state instead. BEHAVIOR: DESTRUCTIVE: overwrites N bytes starting at `address` with no undo. Direct memory write — bypasses MBC/mapper/DMA, see bizhawk_write8 notes. Bytes are written sequentially address, address+1, ..., address+N-1. Returns an error if the domain is unknown, address+N exceeds the domain, the array contains a value outside 0-255, or the array length is < 1 or > 4096. RETURNS: Single line 'Wrote N bytes → ADDR_HEX (DOMAIN)'.
- **bizhawk_search_memory** — PURPOSE: Find every address in a memory domain whose value equals a target — a Cheat-Engine-style value scan with iterative narrowing. USAGE: Two modes. FIRST scan — omit `addresses`: sweeps the whole domain (or the [start, start+length) window you give) and returns matching offsets. NEXT scan — pass `addresses` (the offsets a prior scan returned): keeps only those that STILL equal `value`, the classic 'the value changed to X — which of my candidates match now?' narrowing. Typical RAM hunt: search a known value (lives=3) → play until it changes → search the new value passing the prior addresses → repeat until a handful remain, then poke them with bizhawk_write*. Pick `width` to match the field size; leave `aligned` true (steps by the width — fast and standard) unless you suspect an unaligned value. BEHAVIOR: No side effects — pure read. A FIRST scan runs synchronously inside one bridge tick and briefly stalls the emulator: it bulk-reads the window in one engine call where the build supports it (cap 16 MiB per scan) and falls back to a byte-by-byte read otherwise (cap 256 KiB per scan — chunk larger windows with start+length). Results are capped at `max_results` (default 200); when more matched, `count` reports the true total and the result is flagged truncated — narrow with a more specific value or range. A NEXT scan is exact over whatever addresses you pass. Returns an error on unknown domain, a value out of range for the width, or a scan window over the cap. RETURNS: A summary line (match count, value, width, bytes scanned) followed by the matching offsets as per-domain hex (0-based — the same offsets the read/write tools take).
- **bizhawk_press_buttons** — PURPOSE: Set the joypad button state for one player for EXACTLY the next emulated frame. USAGE: Drive games with input. Each call sets joypad state for ONE frame only — the very next frame BizHawk processes. After that frame, BizHawk's input goes back to whatever the human user is holding (typically nothing). To HOLD a button across N consecutive frames, INTERLEAVE: call bizhawk_press_buttons + bizhawk_frame_advance(count=1) N times in a loop. DO NOT call bizhawk_press_buttons once and then bizhawk_frame_advance(count=N) — only the first of those N frames sees the button; the rest are no-input. Verified empirically against SNES Super Metroid in May 2026: a 60-frame advance after a single press_buttons(Right) moved Samus the same +1 pixel as a 10-frame advance, because frames 2-60 had no input. To release a button mid-hold, just stop calling press_buttons for it; the next frame_advance will see it released. BEHAVIOR: Modifies emulator input state for the next frame poll only — no other side effects. Returns an error if the loaded core doesn't expose joypad.set. Button names that aren't valid for the active core are silently ignored by BizHawk (no error). RETURNS: Single line 'Set joypad N: BUTTON+BUTTON+...' or '... (all released)' if nothing was pressed. 

Button names vary per system. Common names across cores: A, B, X, Y, Up, Down, Left, Right, Start, Select, L, R, L1, R1, L2, R2, L3, R3, C, Z, C-Up, C-Down, C-Left, C-Right, Mode. Use whatever names the active core understands — if unsure, try a name and check BizHawk's input display, or use bizhawk_get_info to confirm joypad_set is available.
- **bizhawk_press_buttons_multi** — PURPOSE: Set joypad input for MULTIPLE controllers at once, all applied to the SAME next emulated frame. USAGE: Use for 2+ player games where controllers must act on the same frame — fighting, racing, co-op (e.g. P1 presses A while P2 presses Right on the very same frame). This is the ONLY way to get simultaneous multi-controller input: calling bizhawk_press_buttons twice in a row lands the two presses on DIFFERENT frames, because each bridge command is followed by exactly one frame advance. For a single controller use bizhawk_press_buttons; for long scripted single-controller runs use bizhawk_play_input_sequence. Like bizhawk_press_buttons this is ONE-FRAME input — to HOLD across N frames, interleave bizhawk_press_buttons_multi + bizhawk_frame_advance(count=1) N times (do NOT call it once then frame_advance(count=N); only the first frame would see the input). BEHAVIOR: Calls joypad.set once per listed controller before the next frame advance, so they all take effect together. For each listed player, anything not in its `buttons` map is released; players you omit entirely are left to whatever the human is holding (usually nothing). Returns an error if the loaded core doesn't expose joypad.set. Button names not valid for the active core are silently ignored by BizHawk. RETURNS: Single line e.g. 'Set 2 controller(s) for next frame — P1: A+Right; P2: B'. 

Button names vary per system. Common names across cores: A, B, X, Y, Up, Down, Left, Right, Start, Select, L, R, L1, R1, L2, R2, L3, R3, C, Z, C-Up, C-Down, C-Left, C-Right, Mode.
- **bizhawk_play_input_sequence** — PURPOSE: Play a pre-built sequence of per-frame joypad inputs back-to-back, advancing one frame per element, ENTIRELY SERVER-SIDE in a single bridge round-trip. Optionally captures screenshots AND labeled memory reads at fixed frame intervals during the play, and optionally aborts play early when a specified memory address changes. All observations come back inline so the agent sees the full trajectory + game state in one tool response. USAGE: Use whenever you have ≥10 frames of inputs to play in order — TAS movie playback, scripted multi-frame sequences, AI-search of input patterns, agent-driven gameplay. ONE bridge round-trip ships N frames instead of the 2N round-trips you'd pay looping bizhawk_press_buttons + bizhawk_frame_advance(1). For sequences over ~200 frames, CHUNK. FOR AGENT-DRIVEN PLAY: combine `screenshot_every`, `observe_memory`, and `stop_on_memory_change` for the killer pattern — 'walk right for up to 200 frames, observing screenshot+x+y+hp every second, but STOP the moment the room ID changes'. The agent sees: where Samus was at each second, AND whether the goal (room transition) was reached, AND screenshots for visual confirmation — all in one tool response. BEHAVIOR: For each `frames` element, calls joypad.set with that frame's buttons then emu.frameadvance. The bridge's main poll loop is BLOCKED for the duration of the call (no other RPCs, no heartbeat) until the sequence finishes or fails. With `screenshot_every`, each captured screenshot adds ~1 frame of wall-clock. With `observe_memory`, each observation also reads the listed memory addresses at the same frame the screenshot was taken — values come back labeled by `name` in each observation's `memory` field. With `stop_on_memory_change`, the bridge records the listed address's value before the first frame, re-reads it after each frame, and aborts the sequence the moment it changes (a final observation is captured at the stop frame regardless of cadence). Returns an error if joypad.set / emu.frameadvance / client.screenshot is missing when needed, if any `observe_memory` entry references an unknown domain, if any `width` isn't 'u8' / 'u16' / 'u32', or if any address is out of range. RETURNS: A text summary ('Played N frames. Final framecount: M. Stopped early: yes/no [reason]. Captured K observations with their memory values') followed by K inline image content blocks (one per observation, in frame order). Each observation in the text summary includes its frame_offset and labeled memory values so the agent can correlate the visible screenshot with the game state at that exact frame.
- **bizhawk_pause** — PURPOSE: Pause emulation — freeze game-logic clocks and hold the current frame on screen. USAGE: Use before a sequence of memory-inspect / write / screenshot calls when you need a stable game state across calls (so the game doesn't advance between your reads). Use bizhawk_unpause to resume; use bizhawk_frame_advance to step single frames without leaving pause. BEHAVIOR: Modifies emulator run state. The Lua bridge keeps polling the socket while paused, so all other tool calls (memory r/w, screenshot, save_state, etc.) still work. Returns an error if the loaded core doesn't expose emu.pause — check `capabilities.pause` in bizhawk_get_info first to handle that case gracefully. Calling pause when already paused is a no-op. RETURNS: Single line 'Emulation paused'.
- **bizhawk_unpause** — PURPOSE: Resume emulation after a pause, returning to normal real-time playback. USAGE: Counterpart to bizhawk_pause. Use after a paused inspection sequence is complete. To advance only a few frames without resuming full speed, use bizhawk_frame_advance instead. BEHAVIOR: Modifies emulator run state. Returns an error if the loaded core doesn't expose emu.unpause — check `capabilities.unpause` in bizhawk_get_info first. Calling unpause when not paused is a no-op. RETURNS: Single line 'Emulation resumed'.
- **bizhawk_frame_advance** — PURPOSE: Step emulation by exactly N frames (default 1) and return the new framecount. USAGE: Use for frame-precise input automation (combine with bizhawk_press_buttons), animation inspection, or letting the system initialize after a hard reset (RAM is mostly zero in the first ~30 frames after bizhawk_reset). For long jumps (thousands of frames) prefer bizhawk_save_state / bizhawk_load_state of a pre-prepared state — frame_advance scales linearly. Works whether emulation is currently paused or running and does NOT change the pause state. BEHAVIOR: Advances the game-logic clock by N frames. Each step costs roughly one real frame (~16ms at 60Hz) plus one bridge round-trip — so advancing 600 frames takes ~10 seconds wall-clock. Returns an error if the loaded core doesn't expose emu.frameadvance. RETURNS: Single line 'Advanced N frame(s). Framecount: NEW_COUNT'.
- **bizhawk_reset** — PURPOSE: Reset the loaded core — equivalent to a hard reset (power cycle) of the emulated console. USAGE: Use to start fresh from boot. To return to a specific known-good point instead of boot, use bizhawk_load_state with a previously saved state file. BEHAVIOR: DESTRUCTIVE: RAM contents become indeterminate (typically zeroed), CPU returns to the reset vector, framecount resets to 0, joypad state clears, and any in-progress audio/video state is discarded. The loaded ROM stays loaded — only volatile state is cleared. Unsaved game progress is lost. Returns an error if the loaded core doesn't expose client.reboot_core — check `capabilities.reboot_core` in bizhawk_get_info first. RETURNS: Single line 'Core reset'.
- **bizhawk_screenshot** — PURPOSE: Save a PNG screenshot of the current emulator display to the given file path. USAGE: Use to capture visible game state for inspection, comparison across savestates, or sequence documentation. The image captures whatever the emulator is currently rendering — to capture a specific game state, pause / advance frames / load state first to get the frame you want, then call this. BizHawk's underlying client.screenshot requires an explicit path (no temp-file fallback). BEHAVIOR: DESTRUCTIVE TO TARGET FILE: overwrites the file at `path` if it exists, with no prompt or backup. Returns an error if the parent directory doesn't exist, the path isn't writable, or the loaded core doesn't expose client.screenshot — check `capabilities.screenshot` in bizhawk_get_info first. RETURNS: Single line 'Screenshot saved: PATH'.
- **bizhawk_save_state** — PURPOSE: Save the entire emulator state (RAM, CPU/PPU/APU registers, mapper state, sound chip state, timing) to a file at the given path. USAGE: Use as a rollback point before risky writes, to bookmark interesting game states, or to share repro states. The companion bizhawk_load_state can perfectly restore from this file. BizHawk's savestate API is path-based (NOT slot-based like mGBA's). BEHAVIOR: DESTRUCTIVE TO TARGET FILE: overwrites the file at `path` if it exists, with no prompt or backup. The state file is bound to the EXACT ROM and BizHawk core version that produced it — loading it on a different ROM or core version usually crashes the core. Returns an error if the parent directory doesn't exist, the path isn't writable, or the core doesn't expose savestate.save. RETURNS: Single line 'Saved state to PATH'.
- **bizhawk_load_state** — PURPOSE: Restore the emulator from a previously saved .State file at the given path. USAGE: Counterpart to bizhawk_save_state. Use to undo a sequence of writes/inputs (the snapshot/experiment/restore workflow), to jump to a bookmarked game state, or to start each tool-call sequence from a known baseline. To start fresh from console boot instead, use bizhawk_reset. BEHAVIOR: DESTRUCTIVE TO LIVE STATE: replaces ALL current emulator state (RAM, registers, mapper, audio, framecount) with the file's contents. Anything not previously snapshotted is lost. The state file MUST come from the same ROM and same BizHawk core version that produced it — loading an incompatible state typically crashes the core. Returns an error if the file doesn't exist, isn't a valid BizHawk state, or the core doesn't expose savestate.load. RETURNS: Single line 'Loaded state from PATH'.

## Claude Desktop Quick Installation
Install path detected from listing signals. Uses `npx` (confidence: high):

```json
"mcpServers": {
  "mcp-bizhawk": {
    "command": "npx",
    "args": ["-y","mcp-bizhawk"],
    "env": {
      "BIZHAWK_HOST": "",
      "BIZHAWK_PORT": ""
    }
  }
}
```

**Requires environment variables:** `BIZHAWK_HOST`, `BIZHAWK_PORT` — the values above are empty placeholders; fill in real credentials before running (see the repository for what each one is for).

## Documentation

## What dmang-dev/mcp-bizhawk MCP server does

The dmang-dev/mcp-bizhawk MCP server lets an MCP-compatible client operate a running BizHawk instance. It supports systems handled by BizHawk, including NES, SNES, Game Boy, Game Boy Color, Game Boy Advance, Genesis, Saturn, N64, PlayStation 1, Atari systems, Lynx, ColecoVision, and Intellivision. The exact available memory domains depend on the loaded core and ROM.

An agent can inspect the loaded game and emulator state, discover memory domains, read or modify memory, send joypad input, advance or pause emulation, reset the core, capture the display as a PNG, and save or restore emulator state. Memory operations can target a named domain or use the active current domain. Range operations support up to 4096 bytes.

## How it works

The setup has two cooperating components. A Node.js MCP process provides standard MCP over stdio and listens for BizHawk connections on a local TCP port. The `lua/bridge.lua` script runs inside BizHawk’s Lua Console and polls that listener once per frame, forwarding commands and responses using newline-delimited JSON.

This architecture is useful when the client needs frame-by-frame inspection or controlled experimentation. The connection model adds approximately one frame of delay per request, estimated at about 16 milliseconds at 60 Hz. It is therefore less suitable for scripts that require a high rate of calls.

## Setup and configuration

The dmang-dev/mcp-bizhawk MCP server requires BizHawk 2.6.2 or newer and Node.js 22 or newer. Install the package from npm with `npm install -g mcp-bizhawk`, or run it without a global installation using `npx -y mcp-bizhawk`.

Start the MCP process, then configure BizHawk to connect to the listener at `127.0.0.1:8766` by using its socket options or the External Tools settings. Open BizHawk’s Lua Console and load `lua/bridge.lua` from the repository. The MCP client must then be configured to launch the `mcp-bizhawk` command over stdio. The README gives registration steps for Claude Code and Claude Desktop, and states that other standard MCP clients can use the same stdio transport.

The listener address is configurable with `BIZHAWK_HOST`, which defaults to `127.0.0.1`, and `BIZHAWK_PORT`, which defaults to `8766`.

## Tools and capabilities

The dmang-dev/mcp-bizhawk MCP server exposes tools for:

- Checking connectivity and retrieving ROM name, ROM hash, frame count, domains, and capabilities.
- Listing memory domains and reading or writing 8-, 16-, and 32-bit values.
- Reading or writing byte ranges up to 4096 bytes.
- Setting one player’s button state with core-specific button names.
- Advancing a chosen number of frames, pausing or resuming, and resetting the loaded core.
- Saving screenshots to PNG paths.
- Saving and loading emulator states from file paths.

## Limitations and notes

The Lua bridge must be loaded inside BizHawk; installing the npm package alone does not attach it to an emulator. Memory-domain names and controller button names vary by system, so clients should query the loaded core before issuing system-specific operations. BizHawk’s version must meet the stated minimum because earlier releases use a different socket-server wire format.

_Full upstream README: https://allmcps.com/mcp/dmang-dev-mcp-bizhawk/readme_

