Skip to main content
Glama

@gbs-toolkit/mcp-server

MCP server for structured GB Studio 4.x project editing — read/patch scripts, scenes, actors, triggers, variables; build ROMs; sprite generation pipeline.

Designed to be driven by an LLM coding assistant (Claude Code, Cursor, etc.) so the model can mutate .gbsres resources safely instead of dropping bytes into JSON by hand.

Install

npm install -g @gbs-toolkit/mcp-server

Or invoke on demand via npx:

npx -y @gbs-toolkit/mcp-server

Related MCP server: mcp-mgba

Run as an MCP server

The server speaks stdio MCP. Most users wire it up in their LLM client's .mcp.json:

{
  "mcpServers": {
    "gbs": {
      "command": "npx",
      "args": ["-y", "@gbs-toolkit/mcp-server"],
      "env": {
        "GBS_PROJECT_ROOT": "./gbsproj/demo"
      }
    }
  }
}

Environment variables

Variable

Required

Default

Notes

GBS_PROJECT_ROOT

yes

—

Directory containing <name>.gbsproj

GBS_CLI_PATH

no

probes common install locations

Absolute path to gb-studio-cli.js

GBS_MGBA_RUNNER

no

none

Path to a libmgba-linked runner binary used by run_emulator (build it from native/build.sh)

GBS_ROM_OUT

no

<root>/build

Output directory for build_rom

GBS_LOG_PATH

no

<root>/build/compile.log

Captured compile log

GBS_SCREENSHOT_DIR

no

<root>/build/screenshots

Where run_emulator drops PNGs

SPRITE_PROVIDER

no

openai

openai / gemini / replicate / fal

OPENAI_API_KEY

conditional

—

Required if SPRITE_PROVIDER=openai and generate_sprite is used

GEMINI_API_KEY

conditional

—

Required for gemini provider

REPLICATE_API_TOKEN

conditional

—

Required for replicate provider

FAL_KEY

conditional

—

Required for fal provider

Tool inventory

Tool

Kind

list_scenes / read_scene / list_actors / read_script / read_compile_log

read

patch_script

write (insert / replace / delete, applied in order)

set_variable

write (upserts a VARIABLE_SET_TO_VALUE event in the start scene's onInit)

create_scene / create_actor / create_trigger / create_variable / create_custom_event / set_start_scene

write

delete_scene / delete_actor / delete_trigger / delete_custom_event / delete_variable

write (cross-script reference scan; refuses unless force: true)

generate_sprite

write (text-to-image → 4-colour DMG quantise → emits (asset.png, asset.png.gbsres))

convert_image_to_sprite

write (no AI; same pipeline applied to a user-supplied PNG/JPG)

build_rom

subprocess (spawns GB Studio CLI's make:rom)

run_emulator

subprocess (drives a libmgba-linked C runner)

screenshot

read image (returns base64 PNG as multimodal MCP content)

What is NOT exposed

Creation/import of binary assets stays GUI-side: backgrounds, tilesets, emotes, avatars, fonts, music, sounds, plus actorPrefabs / triggerPrefabs / palettes / notes. The MCP layer can read and patch scripts that reference these resources by id, but cannot create their .png / .uge / .wav payloads. Sprites are the one exception (see generate_sprite / convert_image_to_sprite).

Built-in guardrails

The server enforces two checks that previously had to live in prompts:

  1. Dialogue width audit — patch_script validates every EVENT_TEXT / EVENT_MENU / EVENT_CHOICE / EVENT_DIALOGUE / EVENT_MARQUEE string against the GB Studio default-font line budget (18 columns; $var$ placeholders reserve 5 chars to cover signed-16-bit values). Multi-box text: string[] form, CJK width (2 columns / char), and recursive children branches are all walked. A violation rejects the patch with DIALOGUE_WIDTH_EXCEEDED and a per-line breakdown. Pass widthBudget to override (e.g. for a custom narrow font) or force: true to bypass.

  2. Sprite quality check — generate_sprite and convert_image_to_sprite analyse the post-quantisation RGBA buffer (opaque ratio, effective DMG colour count, edge density, single-shade dominance). Clearly degenerate outputs (near-empty frame, ≤1 effective shade) refuse to write the asset; borderline cases (2 shades, low edge density, >85% one shade) save with a quality.level: "warn" field in the response. Pass force: true to skip the check.

Both checks return structured metadata so the LLM can self-correct rather than ploughing through.

Building the emulator runner

run_emulator requires a libmgba-linked C binary. Stock mGBA CLIs do not accept a startup --script flag, so we ship a small dedicated runner. From the cloned repo:

cd native
MGBA_SRC=/path/to/mgba MGBA_BUILD=/path/to/mgba/build ./build.sh

Then point the env var at the result: GBS_MGBA_RUNNER=/path/to/gbs-mgba-runner.

If you don't need run_emulator, ignore this — every other tool works without it.

License

MIT

Available Tools

23 tools
build_romBuild GB Studio project to ROMA

Invoke upstream gb-studio-cli (make:rom) to compile the project. Captures stdout+stderr to the configured compile log (read via read_compile_log). Returns { success, exitCode, romPath?, elapsedMs, stderrTail }. ALWAYS call this after structural edits before declaring work done. Requires GBS_CLI_PATH env var OR an installed GB Studio app in a standard location.

Inputs: target, outputName.

convert_image_to_spriteConvert a local image into a GB Studio spriteA

Read a local PNG/JPG and convert it to a 4-colour DMG-quantised GB Studio sprite. For animationType=fixed the image is treated as one frame. For animationType=multi/multi_movement the input MUST already be a sprite sheet (3 frames wide for multi, 6 frames wide for multi_movement) — single-frame inputs cannot be expanded to multiple directions. After quantisation a heuristic quality check runs: degenerate outputs (near-empty, single-shade, low-detail) refuse to write unless force: true. No API key required.

Inputs: name (required), inputPath (required), animationType (required), force.

create_actorCreate a new actor in a sceneA

Append a new actor file under project/scenes//actors/. Enforces GB Studio hardware caps: up to MAX_ACTORS=20 per normal scene, MAX_ACTORS_SMALL=10 when scene width*height <= 160 tiles. Coordinates default to tiles. Pass spriteSheetId of an existing sprite, or leave empty (actor will not render until set).

Inputs: x (required), y (required), name (required), sceneId (required), +8 more.

create_custom_eventCreate a new custom event (reusable script)A

Create a new script resource file under project/scripts/. Custom events are reusable scripts that can be called from scenes/actors/triggers. The script body starts empty — use patch_script with an ownerType: 'customEvent' locator to populate it.

Inputs: name (required), symbol, description.

create_sceneCreate a new sceneA

Create a new scene folder + scene.gbsres under project/scenes/. Name is slugified for the on-disk folder; collisions get _2/_3 suffixes. Scene type must be uppercase (TOPDOWN, PLATFORM, ADVENTURE, POINTNCLICK, SHMUP, LOGO) — changing type later resets engine configuration, so pick deliberately. Size defaults to 20×18 tiles (one screen); scenes >20×18 scroll. Does not create a background — pass backgroundId of an existing background, or set it empty and add one later.

Inputs: name (required), type (required), width, height, +2 more.

create_triggerCreate a new trigger in a sceneA

Append a new trigger file under project/scenes//triggers/. Triggers are rectangular zones that fire script on player enter and leaveScript on exit. Enforces MAX_TRIGGERS=30 per scene. Coordinates and size are in tiles.

Inputs: x (required), y (required), name (required), sceneId (required), +3 more.

create_variableCreate a new global variableA

Append a new Variable entry to project/variables.gbsres. Id is auto-assigned as the next numeric string. Symbol is auto-derived from name (camelCase → snake_case, e.g. 'curQ' → 'cur_q'). GB Studio variables are 16-bit signed (-32768..32767); set initial value via set_variable.

Inputs: name (required), symbol.

delete_actorDelete an actorA

Delete one actor file from a scene's actors/ directory. Refuses if any script references this actorId (including via property paths like <actorId>:x_pos); pass force: true to override. The actor's own scripts are not counted as external references — they are deleted with the file.

Inputs: actorId (required), sceneId (required), force.

delete_custom_eventDelete a custom eventA

Delete one custom-event (script resource) file under project/scripts/. Refuses if any script references this customEventId (typically via EVENT_CALL_CUSTOM_EVENT). Pass force: true to override. The custom event's own body is deleted with the file.

Inputs: customEventId (required), force.

delete_sceneDelete a sceneA

Recursively delete a scene folder (scene.gbsres + actors/ + triggers/). Refuses if the scene is the start scene, or if any other script references this sceneId (e.g. EVENT_SWITCH_SCENE) — pass force: true to override the reference check (the start-scene guard is unconditional). Use list_scenes to find target ids. There is no undo; commit your work first.

Inputs: sceneId (required), force.

delete_triggerDelete a triggerA

Delete one trigger file from a scene's triggers/ directory. Triggers are rarely referenced by other scripts (they only run on enter/leave), but the cross-reference check still runs for safety. Pass force: true to override. The trigger's own onEnter / onLeave scripts go away with the file.

Inputs: sceneId (required), triggerId (required), force.

delete_variableDelete a global variableA

Remove a Variable entry from project/variables.gbsres. Refuses if any script references the variable (e.g. VARIABLE_SET_TO_VALUE / IF_VARIABLE_VALUE; ScriptValue {type:"variable", value:<id>}). Pass force: true to override — but expect compile errors on the next build_rom unless every reference is repaired.

Inputs: variableId (required), force.

generate_spriteGenerate a sprite from a text promptA

Generate a GB Studio sprite from a text prompt. Calls the configured image-generation provider (SPRITE_PROVIDER env var ∈ {openai, gemini, replicate, fal}; default openai), runs the result through a 4-colour DMG quantiser, and writes (sprite.png + sprite.png.gbsres) under assets/sprites/. Returns a spriteSheetId you can pass to create_actor immediately. Frames are 16×16; for multi the provider is called 3× (one per direction), for multi_movement 6× (idle + walk per direction). After quantisation a heuristic quality check runs; degenerate outputs refuse to save unless force: true.

Inputs: name (required), prompt (required), animationType (required), force, +1 more.

list_actorsList actors in sceneA

List all actors in a scene with id, name, position, sprite sheet, and per-actor script presence flags (hasInteractScript / hasStartScript / hasUpdateScript / hasHit1Script / …). Use read_script to fetch event bodies.

Inputs: sceneId (required).

list_scenesList scenesA

List every scene in the active project. Returns id, name, type, width/height (in tiles), backgroundId, and actor/trigger counts. Use this for orientation before reading a specific scene.

Inputs: no named parameters.

patch_scriptPatch script eventsA

Apply an ordered list of structured operations (insert / replace / delete) to a ScriptEvent[] array. Full-array replacement is intentionally unsupported — use delete + insert. Operations apply sequentially: each op's index refers to the array state AFTER all prior ops in this call. Persists the edited scene/customEvent to disk atomically. Before writing, the resulting tree is checked against the dialogue width budget (default 18 chars/line for EVENT_TEXT/EVENT_MENU/EVENT_CHOICE strings); violations refuse the write unless force: true.

Inputs: locator (required), operations (required), force, widthBudget.

read_compile_logRead last compile logA

Read the full stdout+stderr captured from the last build_rom invocation. Use this INSTEAD of guessing when a build fails. Optionally returns only the last N lines via tailLines.

Inputs: tailLines.

read_sceneRead sceneA

Return the full structured representation of one scene: metadata, actors[], triggers[], and scene-level scripts (script, playerHit1Script, playerHit2Script, playerHit3Script). Does NOT include backgrounds, palettes, or sprites — fetch those separately if needed.

Inputs: sceneId (required).

read_scriptRead script eventsB

Return the ScriptEvent[] for a given owner and key. Each event has shape { id, command, args?, children? } where children is a Record<branchName, ScriptEvent[]> for composite events (if/switch/group). See the gbvm-scripting skill for command semantics.

Inputs: locator (required).

run_emulatorRun ROM in headless emulator (libmgba)A

Launch the ROM via the bundled libmgba-linked gbs-mgba-runner with a scripted input timeline. Captures a PNG at the end (and optionally mid-run). Returns { success, screenshotPath, midScreenshotPath?, exitCode, elapsedMs, stderrTail }. Retrieve the image via the screenshot tool. Build the runner once with mcp-server/native/build.sh (needs a local mGBA checkout); or set GBS_MGBA_RUNNER to an absolute path.

Inputs: romPath (required), inputs, durationMs, screenshotAt.

screenshotRead the latest emulator screenshotA

Return an emulator screenshot as an image content block (base64 PNG). Call AFTER run_emulator. Defaults to the latest.png from run_emulator's final frame; pass filename to read a different file in the screenshot dir.

Inputs: filename.

set_start_sceneSet the project's start sceneA

Patch startSceneId in project/settings.gbsres so the game boots into the given scene. Preserves every other setting field. The sceneId must resolve to an existing scene on disk, or build_rom will fail.

Inputs: sceneId (required).

set_variableSet variable initial valueA

Ensure a variable is initialised to value at game start by inserting/updating a VARIABLE_SET_TO_VALUE event at the top of the START SCENE's onInit script (GB Studio 4.x has no default-value field on Variable). Value must fit signed 16-bit (-32768..32767). The variable must already exist — to add a new variable, call create_variable first. For in-game variable changes during play, use patch_script instead.

Inputs: value (required), variableId (required).

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.2/5.0

    Scored across 23 tools

    Disambiguation5/5

    Each tool targets a distinct resource and action (e.g., delete_custom_event vs delete_scene, create_actor vs create_trigger). Descriptions clearly differentiate overlapping operations like read_script vs patch_script, and list_scenes vs read_scene. No two tools serve the same purpose.

    Naming Consistency5/5

    All tools follow a consistent verb_noun pattern in snake_case (e.g., list_scenes, create_variable, delete_actor). Verbs like list, read, create, delete, set, build, run, generate, convert are used predictably without mixing styles.

    Tool Count5/5

    23 tools cover a comprehensive range of operations for GB Studio development (scenes, actors, triggers, variables, scripts, sprites, build/run). This count is well-scoped for the toolkit's purpose—neither too few to be incomplete nor too many to be unwieldy.

    Completeness4/5

    The toolkit covers CRUD for scenes, actors, triggers, variables, custom events, and scripts, plus building, emulation, and sprite generation. Minor gaps include lack of dedicated list tools for variables and custom events (though scoped via read_scene and inference), and no direct background/palette management despite backgroundId references.

    Maintenance

    ActivityInactive
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers