Skip to main content
Glama
hx-multianim-mcp

hx-multianim-mcp

by bh213

hx-multianim-mcp

An MCP server that connects Claude (or any MCP client) to a running hx-multianim application via its DevBridge.

It reaches a game on HashLink over the DevBridge's HTTP server, and a game in a browser page over a WebSocket relay the page dials into (--listen). Same tools, same answers.

Which game. With exactly one game connected, every tool goes to it; connect is not needed. With none, the first call looks for a HashLink game on the default port (the port in HX_DEV_READY_FILE, else HX_DEV_PORT, else 9001). With several, pass target (an id from list_instances, e.g. "http:9001" or "web-1", or a bare port) or choose one with connect. A HashLink game prints its port on startup: [DevBridge] Listening on port N.

Tools

Connection & health

Tool

Description

connect

Choose the game calls go to: {port, host?} for a HashLink game (pinged first; nothing changes if it fails) or {instance} for a known one

list_instances

Browser games on the relay (app, title, URL, session), HashLink games connected, and DevBridges answering on the ten default ports

ping

Lightweight health check — uptime and port

performance

FPS, draw calls, triangle count, object count, scene dimensions

Scene inspection

Tool

Description

list_screens

Registered screens with active/failed status

list_builders

Loaded .manim builders and their parameter definitions

scene_graph

Recursive scene tree dump (depth)

inspect_element

Position, size, visibility, text of a named element

inspect_programmable

Deep inspection of a live programmable (params, slots, refs, elements)

find_element_at

Hit-test scene coords, front-to-back list of objects

get_screen_state

Screen manager state: mode, transitions, pause, counts

get_tween_state

All active tweens with target, duration, progress

list_interactives

Interactive hit-test regions with IDs and bounds

list_slots

Swappable container slots of a programmable

list_active_programmables

Live incremental-mode programmables with current state

list_resources

Loaded sprites, fonts, .manim, .anim files

list_fonts

Registered font names

list_atlases

Loaded sprite atlases and tile/sprite names

list_cursors

Cursor names an interactive can use: OS ones and tile cursors (size, hot point)

coordinate_transform

Convert between scene and element-local coordinates

check_overlaps

Detect overlapping interactives/visuals to find layout bugs

Screenshots

Tool

Description

screenshot

Capture current frame as PNG (optional width/height scale-down)

State manipulation

Tool

Description

set_parameter

Modify a programmable parameter at runtime (incremental mode)

get_parameters

Current parameter values and definitions for a programmable

set_visibility

Toggle element visibility

reload

Hot-reload .manim/.anim files (specific file or all changed)

eval_manim

Parse and validate .manim snippets

Game control

Tool

Description

pause

Pause/resume the game loop

step

Advance N frames while paused (max 100)

wait_for_idle

Check if no tweens/transitions are active

quit

Cleanly shut down the game

Input injection

Tool

Description

send_event

Inject a single mouse/keyboard/wheel event

send_events

Sequence of events with frame steps (drag, scrub, multi-step gestures)

click_button

Click an interactive by ID, bypassing hit testing

Diagnostics

Tool

Description

get_traces

Recent trace() output (ring buffer)

get_errors

Accumulated runtime errors/exceptions

get_debugger_hits

Poll DevBridge.debugger(data, pause?) breakpoint hits (cursor-based)

events

Everything the connected games pushed (traces, errors, reloads, screen changes, breakpoints, game events), from a buffer this server keeps (last 1000), with a cursor (since_id) and kinds filter

Game ops

Tool

Description

list_game_ops

The queries, commands and events the game registered, and the library's own queries (builtIn)

game_op

Call one of them by name

get_game_events

Poll the events the game emitted (cursor-based)

Game data

Tables, picks and trees: what the game loaded from .manim data blocks, and the tables its own code registers with DataRegistry.registerTable (hx-multianim's bh.multianim.data).

Tool

Description

list_data

Every table, pick and tree: its kind, rows, and where it is (.manim file, block and line, or the code that builds it)

get_data

One in full: a table's columns and rows (a tree's edges too), a pick's exact odds

roll_pick

Draw from a pick with the game's own picker and a seed: the rows the game draws from that seed

A game built on an hx-multianim without data tables answers these with not_supported.

Sound

The game's sound (hx-multianim's ScreenManager.audio, the sounds, mix and music blocks of its .manim files): what it has, what it did, and a hand on it. Every line of its history also streams as the audio event.

Tool

Description

audio_state

The banks and sounds, the mix as it stands (each bus, the VCAs, the snapshots on), the parameters, the tracks and the one playing, the plays alive, the backend

audio_log

The last 200 lines of what the sound did: plays and drops with their reason, stops, snapshots, VCAs, parameters, music; since an entry id for only the new ones

audio_set

A VCA to 0..1, a snapshot on or off, a parameter to a label, a sound played or stopped, music crossfaded to or stopped; several at once

A game built on an hx-multianim without sound answers these with not_supported.

Every tool that talks to a game takes an optional target.

Related MCP server: mcp-defold

Breakpoints from game code

Call DevBridge.debugger(data, pause) anywhere in your game code to capture a data snapshot (with auto-captured file/line/method):

screenManager.devBridge.debugger({hp: unit.hp, target: unit.target?.name});     // pauses by default
screenManager.devBridge.debugger({fps: hxd.Timer.fps()}, false);                 // push-only, no pause

Hits are delivered three ways:

  • events — kind debugger, with every other event, from this server's buffer (the reliable one: clients do not always put log notifications in the model's context).

  • Poll — get_debugger_hits tool with since_id cursor.

  • Push — warning-level MCP log notifications.

If pause=true, resume with pause({paused:false}).

Usage

Claude Code

// .mcp.json
{
  "mcpServers": {
    "hx-multianim": {
      "command": "npx",
      "args": ["-y", "@bh213/hx-multianim-mcp"]
    }
  }
}

A game in a browser: relay mode

A page cannot listen on a port, so the roles swap: this server listens and the game dials in.

{
  "mcpServers": {
    "hx-multianim": {
      "command": "node",
      "args": ["../hx-multianim-mcp/dist/index.js", "--listen"]
    }
  }
}

--listen (or --listen=9011, or HX_DEV_WS_PORT) listens on ws://127.0.0.1:9010, trying the next nine ports when busy. Open the game built with -D MULTIANIM_DEV with ?devbridge=ws://127.0.0.1:9010 (add &token=... when a token is set). It appears in list_instances as web-1 (the id stays while the page keeps its session, so a page that dials back after a restart is the same game). HashLink games work alongside it. In a browser game reload needs the file's text (content, or source_path for this server to read) and quit answers not_supported; its tab must be visible (a hidden tab stops its frames).

Wire protocol (one JSON object per message): the game sends {"kind":"hello","protocol":1,"session",...}, this server answers {"kind":"welcome","instance":"web-1"}, then sends {"kind":"call","id","method","params"} and gets {"kind":"result","id","ok",...}; the game pushes {"kind":"event","seq","event","data"}. See hx-multianim's docs/devbridge.md.

Environment variables

Variable

Default

Description

HX_DEV_PORT

9001

HashLink DevBridge port tried when nothing is connected

HX_DEV_HOST

localhost

HashLink DevBridge host

HX_DEV_READY_FILE

—

A game writes {port} here once listening; read in preference to HX_DEV_PORT

HX_DEV_TOKEN

—

Sent to every game (X-HX-Dev-Token), and required in every browser game's hello (refused with close code 4401 otherwise). Set the same token for the game

HX_DEV_TIMEOUT_MS

10000

A call with no answer by then fails with code timeout

HX_DEV_WS_PORT

—

Relay mode on this port (same as --listen)

HX_DEV_WS_HOST

127.0.0.1

Relay bind address

Error codes

Tool errors come back as {error, code} with isError: not_connected, ambiguous_target, unknown_target, connection_failed, timeout, bad_reply, unauthorized, and the DevBridge's own (not_found, invalid_params, invalid_state, not_supported, unknown_method, internal).

Security

What this server is: a bridge from an MCP client to a game the developer is running, built with -D MULTIANIM_DEV. It does what the game's DevBridge does and nothing else.

  • Transport. The MCP side is stdio only: no HTTP or WebSocket endpoint for clients. The game side is the DevBridge of a HashLink game (localhost unless HX_DEV_HOST names another host) or, in relay mode, a WebSocket server bound to 127.0.0.1 (HX_DEV_WS_HOST) that a game page dials into. Nothing is sent anywhere else.

  • Authentication. Set HX_DEV_TOKEN in both the game and this server: it is sent with every call to a HashLink game and required in every browser game's hello. Without it, anything that can reach the DevBridge port can call it, which is the DevBridge's own default (HX_DEV_BIND on the game side chooses its bind address).

  • No code execution. No tool runs a shell command or evaluates code. eval_manim parses and builds a .manim snippet with the game's own parser to report its errors; nothing it builds is shown, kept or run. game_op calls a handler the game registered in its own code, and does whatever that handler does.

  • Files. This server reads two kinds of file: HX_DEV_READY_FILE, for the port a game wrote there, and reload's source_path, only ever a .manim or .anim file, sent to the game as the new text of a resource. It writes no files.

  • What the game gives back is the game's own state: scene graph, parameters, traces, errors, screenshots, the data tables it registered, and the events its code emits with DevBridge.emitEvent. All of it comes from the developer's own build, not from players or the network. Treat it as you would the game's logs.

  • Annotations. Every tool declares MCP tool annotations: the readers (scene_graph, screenshot, eval_manim, list_*, get_data, ...) are readOnlyHint; the setters and input tools (set_parameter, reload, send_event, pause, ...) write but are not destructive; get_traces, get_errors, get_debugger_hits and get_game_events can clear the buffer they read; quit and game_op (a command is whatever the game made it) are destructiveHint. Nothing is openWorldHint.

Development

npm run build
npm test                    # node:test against fake games: HTTP + SSE and browser games over the relay
node scripts/smoke.mjs      # end to end: runs dist/ over stdio in relay mode and drives whatever
                            # games it finds (open a browser game with ?devbridge=ws://127.0.0.1:9010)

License

BSD-3-Clause

Available Tools

38 tools
check_overlapsA

Detect overlapping elements to find layout bugs and broken click targets.

  • Interactive overlaps (severity: high): two clickable regions overlap, causing unreliable clicks

  • Visual overlaps (severity: low): sibling elements with overlapping bounds (parent-child overlap is normal and ignored) Returns overlap pairs with their bounds, overlap rectangle, and overlap area in pixels.

Inputs: mode, screen, include_hidden, min_overlap_area.

click_buttonA

Directly click an interactive button by its ID, bypassing coordinate-based hit testing. Works even if the button is scrolled off-screen or obscured by other elements. Use list_interactives to discover available button IDs.

Inputs: id (required), screen.

connectA

Connect to a game instance on a specific port (and optionally host). Sets up both DevBridge and SSE connections. Must be called before using any other tools. Default port is 9001, but the actual port should be obtained from the game's stdout output, e.g.: [DevBridge] Listening on port 9002

Inputs: port (required), host.

coordinate_transformA

Transform coordinates between local and global space relative to a named element. Use to_local to convert scene coords to element-local, to_global to convert element-local to scene coords.

Inputs: x (required), y (required), element (required), direction (required), +1 more.

eval_manimA

Parse and validate a .manim source snippet. Returns parsed node names and per-node buildErrors[]. Each build error has 'node' (programmable name or '') and 'error' (message); runtime builder failures additionally include 'file', 'line', 'col', and optional 'code' ('not_a_number', 'missing_ref', etc.) for clickable diagnostics.

Inputs: source (required).

find_element_atA

Hit-test a screen position to find all scene graph objects at the given coordinates, sorted front-to-back by depth

Inputs: x (required), y (required), relative_to.

game_opA

Invoke a game-specific custom query or command registered by the running game. Use list_game_ops to discover available ops and their param shapes. Returns {kind: "query"|"command", op, result}. Errors with code "not_found" for unknown ops, "internal" if the handler throws.

Inputs: op (required), params.

get_debugger_hitsA

Poll recent hits from DevBridge.debugger(data, pause?) calls placed in the game (JS-debugger-style breakpoint). Each hit has {id, data, paused, file, line, method, timestamp}. Use since_id from a previous call as a cursor to get only new hits. Hits also arrive in real time as SSE 'debugger' events (shown as warning-level log notifications). If paused=true, the game is paused at the hit — resume with pause({paused:false}).

Inputs: clear, limit, since_id.

get_errorsA

Get accumulated runtime errors/exceptions since last query

Inputs: clear.

get_game_eventsA

Poll custom game events emitted via DevBridge.emitEvent(name, data) on the Haxe side. Events also arrive in real time as SSE "game_event" notifications. Mirrors the get_debugger_hits cursor pattern: use since_id from the previous response to fetch only new events.

Inputs: clear, limit, types, since_id.

get_parametersC

Get current parameter values and definitions for a live programmable instance

Inputs: programmable (required).

get_screen_stateA

Get detailed screen manager state: mode, active screens, transition status, pause state, element/interactive counts

Inputs: no named parameters.

get_tracesA

Get recent trace() output from the running application (ring buffer of last 200 lines)

Inputs: clear, limit.

get_tween_stateA

Get all active tweens/animations with their targets, duration, elapsed time, and progress

Inputs: no named parameters.

inspect_elementA

Get detailed info about a named element on a screen (position, size, visibility, text content)

Inputs: screen (required), element (required).

inspect_programmableA

Deep inspection of a live programmable: current parameter values, slots, dynamic refs, named elements, interactives, and settings

Inputs: programmable (required).

list_active_programmablesA

List all live incremental-mode programmables currently in the scene. Returns current parameter values, parameter definitions (types), named elements, slots, interactive counts, position, and visibility for each. Only programmables built with incremental:true are tracked.

Inputs: depth, sceneGraph, programmable.

list_atlasesA

List all loaded sprite atlases with their tile/sprite names

Inputs: no named parameters.

list_buildersA

List all loaded .manim builders with their programmable names and parameter definitions

Inputs: no named parameters.

list_fontsA

List all registered font names available for use in .manim files

Inputs: no named parameters.

list_game_opsA

List game-specific custom operations registered by the running game. Returns {queries, commands, events}, each entry has {op|name, description, params|payload} where params/payload is a schema-lite hint (e.g. {lane: "int", count: "int?"}). Call this first to discover what the current game exposes, then use game_op to invoke a query/command, or get_game_events to poll events.

Inputs: no named parameters.

list_interactivesB

List all registered interactive hit-test regions on a screen with their IDs, positions, and metadata

Inputs: screen.

list_resourcesA

List all loaded resources: sprite sheets, fonts, .manim files, .anim files

Inputs: no named parameters.

list_screensB

List all registered screens with their active/failed status

Inputs: no named parameters.

list_slotsB

List all slots (swappable containers) on a programmable with their occupied/empty status

Inputs: programmable (required).

pauseA

Pause or resume the game loop. When paused, all game logic, animations, and rendering stop but the DevBridge remains responsive for inspection. Use step() to advance frame-by-frame while paused.

Inputs: paused.

performanceB

Get FPS, draw calls, triangle count, object count, and scene dimensions

Inputs: no named parameters.

pingA

Health check - returns uptime and port. Lightweight alternative to performance for connection testing.

Inputs: no named parameters.

quitB

Cleanly shut down the running game application

Inputs: no named parameters.

reloadB

Hot-reload a .manim file (or all files if no file specified). On failure, errors[] entries include 'message', 'file', 'line', 'col', 'errorType' ('parse' | 'build' | 'signatureIncompatible'), and 'context' — builder errors now carry accurate file/line/col instead of 0 placeholders.

Inputs: file.

scene_graphB

Dump the scene graph tree showing object types, positions, visibility, and names

Inputs: depth.

screenshotB

Capture the current frame as a PNG image. Provide width and/or height to scale down (aspect ratio is preserved when only one is given; error if both are given with wrong aspect ratio).

Inputs: width, height.

send_eventB

Inject an input event into the running application. Event types:

  • click: mouse click (push + release) at x,y with button (0=left, 1=middle, 2=right)

  • mouse_down / mouse_up: separate push/release at x,y

  • move: mouse move to x,y

  • key_down / key_up: keyboard key press/release with keyCode (hxd.Key constants)

  • key_press: key_down + key_up combined

  • text: text input with charCode

  • wheel: mouse wheel with delta at x,y

Common key codes: SPACE=32, ENTER=13, ESCAPE=27, TAB=9, A=65, 0=48, UP=38, DOWN=40, LEFT=37, RIGHT=39, F1=112

Inputs: type (required), x, y, delta, +3 more.

send_eventsA

Send a sequence of input events with game frame steps between them. Enables multi-step interactions (drag-and-drop, slider scrub, card hand drag) in a single call.

Each entry in the events array is either:

  • An event: {type, x, y, button, ...} (same params as send_event)

  • A frame step: {step: N} — advance N game frames (processes animations, state machines, zone detection)

The game must be paused for frame steps to work. Use auto_pause:true to auto-pause before and resume after.

Example drag: [ {type:"mouse_down", x:100, y:200}, {step:2}, {type:"move", x:200, y:150}, {step:1}, {type:"move", x:300, y:100}, {step:1}, {type:"mouse_up", x:300, y:100} ]

Inputs: events (required), auto_pause.

set_parameterC

Set a parameter on a live programmable BuilderResult (uses incremental mode)

Inputs: param (required), value (required), programmable (required).

set_visibilityC

Toggle visibility of a named element on a screen

Inputs: screen (required), element (required), visible (required).

stepA

Advance the game by N frames while paused, then re-pause. Game must be paused first.

Inputs: frames.

wait_for_idleA

Check if the system is idle (no active tweens, no screen transitions). Returns current state without blocking.

Inputs: no named parameters.

Tool Schema Changelog

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

Loading tool schema changelog…

    TDQS

    A3.6/5.0

    Scored across 38 tools

    Disambiguation4/5

    Most tools target a clearly distinct subsystem (scene graph, interactives, traces, events, tweens), but a few clusters overlap: get_parameters vs inspect_programmable vs list_active_programmables all surface parameter values, and list_resources vs list_fonts vs list_atlases are superset/subset. send_event vs send_events vs click_button also need the descriptions to disambiguate. Boundaries are mostly clear thanks to detailed descriptions.

    Naming Consistency4/5

    Names overwhelmingly follow snake_case verb_noun or verb patterns (list_*, get_*, set_*, inspect_*, send_event(s)), with no camelCase mixing. Minor deviations like single-word verbs (connect, ping, quit, pause, step, reload, screenshot) and coordinate_transform are still readable and consistent in spirit.

    Tool Count3/5

    38 tools is heavy, well past the 3-15 sweet spot. The domain is genuinely broad (input, inspection, resources, screens, programmables, diagnostics, events), so most tools map to a distinct subsystem rather than being redundant, but the surface is still on the bulky side.

    Completeness5/5

    Covers the full debug-bridge lifecycle: connect/quit, pause/step, input injection, hot-reload, eval, screenshot, scene/element/programmable inspection, resource and screen listing, plus diagnostics (traces, errors, debugger hits, performance) and game-specific ops/events. No obvious dead ends for the stated purpose.

    Maintenance

    ActivityMaintained
    ResponsivenessOutdated data

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      A
      quality
      B
      maintenance
      Connects MCP clients directly to a live Godot editor, enabling AI assistants to build scenes, edit nodes, and control the editor through over 120 operations.
      47
      30,790 PyPI
      3,482
      MIT
    • A
      license
      A
      quality
      B
      maintenance
      This MCP server drives Defold games through an in-game debug bridge, allowing MCP clients to build, launch, playtest, and interact with real game builds via tools for input, screen reading, state, and logs.
      9
      1
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables an MCP client to connect to a running Blockland client to execute TorqueScript, inspect player state, control character movement and actions, and capture screenshots.
      MIT
    • A
      license
      A
      quality
      C
      maintenance
      Connects an MCP client to a running Serious Editor 2017 / Serious Sam Fusion instance through a local Lua bridge, enabling package and entry inspection, log reading, editor diagnostics, and previewed/undoable entity property edits on a captured selection.
      14
      MIT