Skip to main content
Glama

mcp-mgba

npm version npm downloads CI License: MIT

An MCP server that exposes the mGBA Game Boy Advance emulator to any MCP-compatible client (Claude Desktop, Claude Code, etc.).

Lets your model read and write GBA memory, inject button presses, take screenshots, and step the emulator — all through a clean tool interface.

demo

Claude driving an in-development homebrew side-scroller through mgba_press_buttons — Start to begin, A to confirm New Game, then Right to walk and A to jump. Each frame is captured via mgba_screenshot.

How it works

+------------------+    stdio     +------------------+   TCP :8765   +------------------+
|   MCP client     |   JSON-RPC   |     mcp-mgba     |  newline JSON |  mGBA emulator   |
| (Claude / etc.)  | ===========> |     (Node.js)    | ============> |    bridge.lua    |
+------------------+              +------------------+               +------------------+

Two pieces:

  • lua/bridge.lua — runs inside mGBA's scripting engine, opens a loopback TCP server on port 8765

  • dist/index.js — Node.js MCP server, talks to the Lua bridge over TCP, exposes tools over stdio

Requirements

  • mGBA 0.10 or newer (with Lua scripting)

  • Node.js 18+ (for the MCP server)

Install

npm install -g mcp-mgba

Puts mcp-mgba on your PATH. Verify with mcp-mgba --help (it'll print a startup line and wait for stdio — Ctrl+C to exit).

Option B — npx (no install)

npx -y mcp-mgba

Run on demand. Good for trying it out without committing to a global install.

Option C — clone and develop

git clone https://github.com/dmang-dev/mcp-mgba
cd mcp-mgba
npm install        # also runs the build via the `prepare` hook

Then reference the absolute path to dist/index.js when registering, or npm install -g . to symlink the bin globally.

Set up the mGBA bridge

  1. Launch mGBA and load any GBA ROM.

  2. Open Tools > Scripting…

  3. Click File > Load script and select lua/bridge.lua from this repo.

You should see in the scripting console:

[mcp-mgba] bridge listening on 127.0.0.1:8765
[mcp-mgba] frame callback registered — bridge is active

If you see a bind failed error, the previous instance's socket is still held — quit and relaunch mGBA.

Register with your MCP client

Claude Code (CLI)

claude mcp add mgba --scope user mcp-mgba

(if you used Option B without global install, replace mcp-mgba with node /absolute/path/to/dist/index.js)

Verify:

claude mcp list
# mgba: mcp-mgba - ✓ Connected

Claude Desktop

Edit claude_desktop_config.json:

Platform

Path

macOS

~/Library/Application Support/Claude/claude_desktop_config.json

Windows

%APPDATA%\Claude\claude_desktop_config.json

Linux

~/.config/Claude/claude_desktop_config.json

Add (assuming Option A — globally installed):

{
  "mcpServers": {
    "mgba": {
      "command": "mcp-mgba"
    }
  }
}

Or with explicit Node + path (Option B):

{
  "mcpServers": {
    "mgba": {
      "command": "node",
      "args": ["/absolute/path/to/mcp-mgba/dist/index.js"]
    }
  }
}

Restart Claude Desktop after editing.

Other MCP clients

The server speaks standard MCP over stdio. Run mcp-mgba (or node dist/index.js) and connect any MCP client to its stdio.

Configuration

Env var

Default

Purpose

MGBA_HOST

127.0.0.1

Bridge host to dial

MGBA_PORT

8765

Bridge port to dial

Tools

Tool

Description

mgba_ping

Verify bridge connectivity (returns pong)

mgba_get_info

Game title, code, frame count

mgba_read8 / mgba_read16 / mgba_read32

Read memory at an address

mgba_write8 / mgba_write16 / mgba_write32

Write to RAM

mgba_read_range

Read up to 4096 bytes as a byte array

mgba_write_range

Write up to 4096 bytes from a byte array

mgba_press_buttons

Queue a button press (FIFO; consecutive calls produce distinct events)

mgba_advance_frames

Step the emulator N frames

mgba_pause / mgba_unpause

Pause / resume emulation

mgba_reset

Reset the loaded ROM

mgba_screenshot

Save a PNG of the current display

mgba_save_state / mgba_load_state

Save/load emulator state to a slot or path

See docs/RECIPES.md for end-to-end examples (RAM hunting, snapshot-experiment-restore, side-scroller automation, etc.).

GBA button names

A, B, Select, Start, Right, Left, Up, Down, R, L

GBA address space (cheat sheet)

Range

Region

0x02000000

EWRAM (256 KiB, general)

0x03000000

IWRAM (32 KiB, fast)

0x04000000

I/O registers

0x05000000

Palette RAM

0x06000000

VRAM

0x07000000

OAM

0x08000000

ROM (read-only)

Troubleshooting

Symptom

Cause / Fix

Cannot reach mGBA bridge at 127.0.0.1:8765

mGBA isn't running, or bridge.lua isn't loaded — open Tools > Scripting and load it

bind failed — port 8765 may already be in use

A previous mGBA instance still holds the socket; quit and relaunch mGBA

Tool calls hang

The bridge script may have errored out silently after a hot-reload — check the mGBA scripting console

Tools missing in Claude after install

Restart your MCP client; Claude only enumerates servers on startup

Tool calls return data shaped like an old version after editing bridge.lua and choosing Load Script again

mGBA doesn't fully tear down a previous script when you reload. The new script's bind() may succeed but the old frame callback keeps serving requests. Fix: quit mGBA entirely, relaunch, load the ROM, then load bridge.lua once. Check the console for the frame callback registered line — there should be exactly one.

attempt to index a nil value (global 'emu') at script load

mGBA's emu global only exists once a ROM is loaded. Load any ROM first, then load bridge.lua. (Or load the script first; capability detection will defer until a ROM is loaded.)

emu:foo not available on this mGBA build for pause, unpause, frameAdvance, etc.

This particular build of mGBA doesn't expose that method. The bridge feature-detects on the first frame; check mgba_get_info for the full capabilities map. For frameAdvance, the bridge falls back to runFrame then step automatically.

read8/16/32 returns "invoking failed" intermittently

Known mGBA Lua quirk — the typed read methods are flaky via pcall from the frame callback. The bridge already routes read8/16/32 through the more reliable readRange internally; if you still see this on a write, the retry loop usually clears it within a few attempts.

Multiple press_buttons calls don't seem to register as distinct events

Older mgba_press_buttons (≤0.1.0) had this bug; v0.2.0+ uses a FIFO queue. Make sure you've upgraded with npm install -g mcp-mgba and restarted your MCP client.

Development

npm install
npm run dev      # tsc --watch — autobuilds on src/ changes

The Lua side (lua/bridge.lua and lua/json.lua) needs no build step. Edit and reload via mGBA's File > Load script.

License

MIT

Install Server
A
license - permissive license
A
quality
A
maintenance

Maintenance

Maintainers
7dResponse time
1dRelease cycle
3Releases (12mo)

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/dmang-dev/mcp-mgba'

If you have feedback or need assistance with the MCP directory API, please join our Discord server