Run heavy work in a background worker
A job on the user’s Blender holds its main thread, so a multi-minute rebuild freezes the viewport for as long as it runs. A background worker avoids that. It’s a headless blender -b that the user’s Blender starts on a saved copy of its scene. The worker joins the bus as its own client, heavy jobs go to it, and the user keeps working.
The sequence
Section titled “The sequence”-
Spawn the worker.
blender_spawn_worker()→ {"status": "ok", "worker_uuid": "blender-worker-3f9a1c…", "pid": 48213,"worker_dir": "/tmp/blender_mcp_workers/blender-worker-3f9a1c…","snapshot_path": ".../snapshot.blend", "expires_at": 1790461234}The user’s Blender saves a copy of the current scene, unsaved changes included, into a private directory and starts the worker on it. The call returns once the worker has joined the bus. Pass
target_uuidwhen more than one Blender is connected. -
Send the heavy work to the worker. Target it by uuid:
blender_submit(command="execute_code",params={"code": "..."},target_uuid="blender-worker-3f9a1c…")Inside the code, call
report_progress(fraction, message)as the work moves along:for i, part in enumerate(parts):rebuild(part)report_progress((i + 1) / len(parts), f"boolean {i + 1} of {len(parts)}")Save the result inside the worker’s directory, for example with
bpy.ops.wm.save_as_mainfile(filepath=..., copy=True), and print its path. -
Follow the job.
blender_job_status(job_id, wait_seconds=50)returns when the job reports progress or finishes, with the latest report underprogress:{"status": "running", "progress": {"fraction": 0.4, "message": "boolean 3 of 7"}}Read the output with
blender_job_result(job_id)whendoneis true. -
Offer the result to the user.
blender_offer_reload(path=".../attic.blend", message="attic rebuild finished")The user’s sidebar shows a Background result ready banner with Reload and Dismiss. Nothing is loaded until they click Reload. If they have unsaved changes, the banner warns that reloading discards them.
If the user has kept working while the worker ran, offer only the collections the job changed instead, so their other edits survive (see Merging collections).
-
Stop the worker with
blender_stop_worker(worker_uuid)once you’re done with it.
Merging collections instead of reloading
Section titled “Merging collections instead of reloading”A reload replaces the whole scene with the worker’s file, including anything the user changed after the snapshot was taken. When the job only rebuilt part of the scene, merge just those collections:
blender_offer_merge(path=".../attic.blend", collections=["Attic", "Roof"], message="attic rebuild finished")The banner reads Background result ready, names the collections, and offers Merge and Dismiss. Nothing changes until the user clicks Merge. Then, for each named collection:
- The collection is appended from the result file and linked in the same place as the live collection of the same name (the same parent collection), then the old one is deleted along with objects that lived only in it. Objects the old collection shared with other collections are kept, only unlinked. The new collection takes the old name back, and names Blender suffixed during the append (
Beam.001) are restored. - A collection that doesn’t exist in the live scene is added under the scene’s top-level collection.
- A collection missing from the result file is skipped and reported; nothing is deleted for it.
- Meshes and materials that only the deleted objects used are removed. Other unused data the user already had is left alone.
- If the append itself fails, everything it brought in is removed again and the scene is unchanged.
mode="add" brings the collections in alongside the existing ones without deleting anything; Blender’s .001 suffix stays on any clashing name.
Check what happened with blender_get_merge_result(). pending is set while the banner waits for an answer; last_result lists what was replaced, added or skipped with object counts, or dismissed: true.
Edits the user made inside a replaced collection are lost, since that collection is swapped for the worker’s version. Everything outside the named collections is kept.
How workers behave
Section titled “How workers behave”- Never picked by accident. Workers are reached only by uuid or
pid:. Calls without a target, andtarget_uuid="latest", still go to the user’s Blender.blender_list_available_clientsshows workers withrole: "worker"and theparent_uuidthat spawned them. - Cancel works. A worker is a separate process, so
blender_job_cancelon a running worker job stops the worker. A running job on the user’s own Blender still can’t be interrupted. - They clean up after themselves. A worker exits when its parent Blender goes away, when it’s stopped, or when its time runs out. Files in its directory (snapshot, results, log) stay on disk.
- One at a time by default. The addon preference Max workers allows up to four.
Limits
Section titled “Limits”- Memory roughly doubles while a worker runs, since it holds a full copy of the scene. GPU renders on a worker compete with the user’s viewport.
- At most two hours. A worker uses the user’s current access token and can’t refresh it, so it also stops five minutes before that token expires. Spawning refreshes the token first when it has less than 30 minutes left.
- The snapshot is a point in time. Edits the user makes after the spawn aren’t in the worker’s copy, which is why reloading is always the user’s choice.
- Paths. Relative texture paths are remapped when the copy is saved; absolute paths and packed data carry over. External caches with unusual paths may need checking.
A Supported Systems Joint
BlenderMCP is built and maintained by Supported Systems, a small studio that writes software for people who have to keep it running afterwards.
Visit supported.systems