Blender session

A Blender session (pipeline/gate/blender.js, class BlenderSession) is the only way the pipeline touches Blender: an MCP for Blender server spoken to over stdio JSON-RPC — never hand-rolled sockets, never a hand-managed Blender subprocess. The server bridges into Blender's own addon, which needs a GUI Blender session; glina setup provisions Blender, the server and the addon.

Shape

  • Spawn: uvx mcp-for-blender by default. blender.mcp.command + blender.mcp.args name the program instead; blender.mcp.uvx: false starts a bare mcp-for-blender; blender.mcp.uvxBin names the uvx executable.
  • Tool surface used: exactly get_scene_info and execute_blender_code. Everything else — import, export, decimate, rig — is Blender Python (bpy) sent through execute_blender_code.
  • Environment: the MCP child gets PATH and HOME only, so no credential can leak through environment inheritance.

Lifecycle

  1. BlenderSession.start(options) spawns the server and performs the MCP handshake. A failed start throws: blender MCP server failed to start (<command> <args>): <reason>. Run 'glina setup' to provision Blender, mcp-for-blender and its addon.
  2. execute(code) wraps the code in try/except before sending, so an exception in the block cannot kill the addon's server thread; it comes back as GAC-EXEC-ERROR: <traceback> in the output. The wrapped block ends by printing a fresh completion marker, and an answer without it is refused: Blender execute_blender_code did not complete: <answer>.
  3. importModel(path) resets the scene through the data API only (objects, actions, meshes, armatures, materials) and imports the GLB. bpy.ops.wm.read_factory_settings is never used — it would wipe the addon's scene properties and kill the MCP server thread. Old actions are removed too, so a preview never picks a clip from an earlier import. An import Blender does not confirm is refused: Blender did not confirm import of <path>: <answer>.
  4. exportGlb(path) exports the whole scene and stats the file: Blender did not confirm export to <path>: <answer>, or export produced no file at <path>: <reason> for an absent or empty file.
  5. close() ends the MCP process. A session closed from a finally whose close fails says so on stderr — [blender] MCP session close failed, Blender may still be running: <reason> — without replacing the job's own outcome.

Health

health() answers { healthy, error } from what actually happened, because the MCP server starts fine even when the Blender side of the bridge is dead:

  1. the server answers tools/list,
  2. execute_blender_code is among the tools (the bridge does not expose execute_blender_code),
  3. a print("health-probe") round-trips through Blender and its output comes back (execute_blender_code did not reach Blender: <answer>).

glina doctor runs this probe as part of its report and exits 1 when the report is not healthy; glina setup --check reports the installed prerequisites without changing them.

Invariants

  • One session per job; sessions are always closed in a finally, even on failure.
  • A sculpt job that sees a failed block asks the bridge for its tools and ends when the bridge refuses — see the sculpt job bridge-down rule.