Use Blender MCP when you want to explore and build one model with an agent watching screenshots. Use headless blender -b --python build scripts once you need a batch of models, parallel agent sessions, re-runnable builds, and automated checks. I used both on the same project, back to back, and the switch happened when the agent pointed out why the first approach would not scale.
Here is the short version of how it went. I built a quick 3D asset pipeline for Wavesworn, a browser pirate game, as a demo project for a client. The first ship took one 20-minute live MCP session, and turning it into the player hull took another round of live work the next morning. The next 17 GLB files, 183 root objects in total, came out of plain Python scripts that never touched my open Blender window. Same agent, same Blender, very different tool.
Full example: Clone the working files at github.com/KingPin/sumguy-examples/blender/blender-mcp-vs-headless-bpy
What “Blender MCP” means here
There are several Blender MCP servers. The one I used is the official Blender Lab blender_mcp, version 1.0.2, not the community ahujasid/blender-mcp server. I ran it against Blender 5.2.2 LTS.
It has two parts. A Blender add-on runs inside your open Blender and listens on localhost port 9876. A separate stdio server, launched by your MCP client, relays tool calls to that socket. The data flow looks like this:
MCP client <-- stdio --> blender-mcp <-- TCP 9876 --> Blender add-onClaude Code registers it as a stdio server:
claude mcp add blender -- uv --directory /path/to/blender_mcp/mcp run blender-mcpOther MCP clients read a JSON config instead. The kit ships this as mcp.json.example:
{ "mcpServers": { "blender": { "command": "uv", "args": ["--directory", "/path/to/blender_mcp/mcp", "run", "blender-mcp"] } }}The server ships about 20 tools. The ones that mattered in my sessions were execute_blender_code (run Python inside the live Blender), get_screenshot_of_window_as_image and get_screenshot_of_area_as_image (the agent looks at your viewport), get_objects_summary, and get_blendfile_summary_path_info. The server also ships _for_cli variants that run code in a background Blender process, which I did not use.
One gotcha is easy to hit. I had registered the server in one Claude Code profile and not in another, so the tools simply did not exist in the second. A server added mid-session also does not load its tools until you restart the session. claude --continue brings the conversation back, so you lose nothing but the pause.
Phase 1: one ship, live, watching screenshots
The goal was a tier-1 sloop for the game. The agent loaded a Kenney Pirate Kit ship into Blender as a scale and style reference and asked the agent to build a sloop in that spirit. Then the loop ran: the agent executes code, grabs a screenshot of my Blender window, looks at it, and adjusts.
Across October 15 and 16, 2026, counting the subagents the main session spawned, that came to 143 MCP tool calls:
| Tool | Calls |
|---|---|
execute_blender_code | 101 |
get_screenshot_of_window_as_image | 21 |
get_screenshot_of_area_as_image | 9 |
get_blendfile_summary_path_info | 9 |
get_objects_summary | 3 |
That is the shape of exploration. About seven in ten calls ran code in the scene, and most of the rest were screenshots. You are in the viewport too, so you can say “the bow is too blunt” and watch it change. For a model nobody has designed yet, that feedback loop is what you are paying for.
The sloop became the player hull, hull20. It was stretched to 20 units, and it exports as a 3376-triangle GLB. The export itself is a headless script, scripts/export-hull20.py, so even the “live” model ended up with a re-runnable export step.
Where live MCP stops working
The next day, at the start of a batch of new models, the agent said something I wrote down: “Headless jobs can run in parallel. The live Blender is a single shared scene, so I will use it only for my own review.”
That sentence names the limits. A live Blender session is one scene, one viewport, one window, and one human’s attention. Four things follow from that.
- No parallelism. Two agent sessions sharing port 9876 are editing the same scene. One agent’s
bpy.ops.wm.read_factory_settingsis the other agent’s disaster. - No re-runs. The result lives in the session. If you want a variation, the agent edits state, and the history of how you got there is a chat transcript.
- No gate. Nothing fails when a model blows its triangle budget. A person has to look.
- It is your session. The agent is clicking around in the Blender window you have open.
The agent then measured something useful: a headless EEVEE preview render takes about one second. A screenshot tool call took 1.2 to 2.4 seconds, and the render needs no window at all. After that the agent made no more live MCP calls, and every review used headless preview PNGs. Two minutes later the agent wrote it into the art brief as a rule: “Never connect to the live Blender on port 9876 (the user’s open session).”
Phase 2: headless batch builds
For the batch, the agent wrote art/BRIEF.md and a shared helper library, then launched 8 parallel subagents. Each read the brief and wrote one build script per category. A top-level art/build.py and a stdlib checker came later that evening. Over October 16 and 17 that produced 17 new GLBs and 183 roots (127 of them without skins): figureheads, flags, lanterns, pets, sea life, combat effects, island props, monsters, part styles, tier-4 parts, and 7 hull skins. 76 commits touched art/ for the batch.
The rules in the brief did the heavy lifting:
- The per-category
build_<cat>.pyis the source of truth. A.blendgets saved for hand tweaks, but nobody edits it as the master. - Flat colors only. No textures, no UVs. Role materials get recolored by the game client.
- Triangle budgets are enforced by the checker as a ratchet. The checker fails a model that grows past its recorded count, and raising the count takes an explicit
OVERRIDESedit in the same commit. - Export validates names, identity roots, and materials, then a re-import check confirms the GLB reads back.
- A mandatory quality loop: build, render, look at your renders (the agent reads the PNG), critique, rebuild, at least 3 rounds.
- Sign-off renders use the game camera, orthographic at roughly 50 degrees down.
That list is the real deliverable. The MCP session produced one ship. The rules made 17 files reproducible, and they made a second agent able to continue the work without me.
I will not paste the client’s helper library. Here is a generic version of the same skeleton, small enough to read in one sitting. It lives in the kit as build.py.
The build script
Failure first. A build script that cannot fail the shell is just a script that prints things:
def fail(msg): print(f"build: FAIL {msg}", file=sys.stderr) sys.exit(1) # exits 1; --python-exit-code 1 also covers uncaught exceptions
def reset(): bpy.ops.wm.read_factory_settings(use_empty=True)reset() is what makes a build re-runnable. Every run starts from an empty factory scene, so there is no hidden state from the last run. Then a flat material and a triangle counter:
def material(name, rgb): m = bpy.data.materials.new(name) m.diffuse_color = (*rgb, 1.0) m.use_nodes = True m.node_tree.nodes["Principled BSDF"].inputs["Base Color"].default_value = (*rgb, 1.0) return m
def tris(obj): return sum(len(p.vertices) - 2 for p in obj.data.polygons)The props and the budget assert:
def prop(name, op, mat, loc, **kw): op(location=loc, **kw) o = bpy.context.active_object o.name = name o.data.name = name o.data.materials.append(mat) return o
def build(): red, wood = material("role_red", (0.8, 0.1, 0.1)), material("role_wood", (0.45, 0.28, 0.12)) crate = prop("crate", bpy.ops.mesh.primitive_cube_add, wood, (0, 0, 0.5)) # 12 tris buoy = prop("buoy", bpy.ops.mesh.primitive_cone_add, red, (3, 0, 1), vertices=16, radius1=0.6, depth=2) for o in (crate, buoy): if tris(o) > BUDGET[o.name]: fail(f"{o.name} has {tris(o)} tris, budget {BUDGET[o.name]}") return [crate, buoy]The BUDGET dict at the top of the file holds the numbers: 24 triangles for the crate, 120 for the buoy. The crate measures 12 and the buoy 30. Lower the buoy budget to 10 and the build exits 1 with build: FAIL buoy has 30 tris, budget 10. I tested that.
Next, export and a re-import check. This is the step the client’s pipeline hand-wrote nine times before it moved into a shared helper (more on that below):
def export(objs, path): bpy.ops.object.select_all(action="DESELECT") for o in objs: o.select_set(True) bpy.ops.export_scene.gltf(filepath=str(path), export_format="GLB", use_selection=True)
def reimport_check(path, names): reset() bpy.ops.import_scene.gltf(filepath=str(path)) got = {o.name for o in bpy.data.objects if o.type == "MESH"} missing = set(names) - got if missing: fail(f"re-import is missing {sorted(missing)}") if not bpy.data.materials: fail("re-import lost the materials")Last, the preview render. This is what replaces the screenshot of a live window:
def preview(path): sc = bpy.context.scene sc.render.engine = "BLENDER_EEVEE" sc.render.resolution_x, sc.render.resolution_y = 640, 400 sc.render.filepath = str(path) bpy.ops.object.camera_add(location=(8, -8, 6), rotation=(1.1, 0, 0.785)) sc.camera = bpy.context.active_object bpy.ops.object.light_add(type="SUN", location=(4, -4, 8), rotation=(0.8, 0.2, 0.6)) bpy.ops.render.render(write_still=True)The agent runs it, then reads out/preview.png with its normal file reader and critiques the image. That is the same visual feedback as the MCP screenshot, but it comes from a job that anyone can launch from a shell.
Running it
blender -b --python-exit-code 1 --python build.py -- --norender-b is background mode. --python-exit-code 1 makes any uncaught Python exception exit 1 instead of 0, which is the difference between a CI gate and a silent pass. Everything after the lone -- goes to your script, so --norender skips the preview. Drop it to also write out/preview.png:
blender -b --python-exit-code 1 --python build.pyOn Blender 5.2.2 the build without a render takes under a second here, and about 1.2 seconds with the preview render.
Checking the GLB without Blender
The build script checks its own work, but a second checker that does not import bpy is worth having. It runs anywhere, including a CI box with no Blender installed. The kit’s check_glb.py reads only the JSON chunk of the GLB with the standard library. The core is counting triangles per primitive:
# glTF primitive modes: 4 = TRIANGLES, 5 = STRIP, 6 = FANdef tri_count(gltf, prim): n = gltf["accessors"][prim["indices"]]["count"] if "indices" in prim else \ gltf["accessors"][prim["attributes"]["POSITION"]]["count"] mode = prim.get("mode", 4) return n // 3 if mode == 4 else max(n - 2, 0) if mode in (5, 6) else 0Run it against the build output:
python3 check_glb.py out/props.glb --budget 120ok buoy 30 tris (budget 120)ok crate 12 tris (budget 120)Pass --budget 20 and it prints FAIL buoy 30 tris (budget 20) and exits 1. That exit code is the useful part: a loop of agents can run it after every build and stop when it goes red.
Parallel sessions are the actual win
Once builds are scripts, parallelism is free. Each category has its own script and its own output file, so 8 subagents worked on sea life, combat effects, island props, and the rest at the same time. Each one runs blender -b in its own process with its own scene. Nothing shares port 9876. The top-level art/build.py takes a category name, all, or a hull skin id, and validates flags up front.
I would not try this with live MCP. You would have one scene and N agents with opinions.
Scripts also give you something a chat transcript never will: a diff. The git history shows what changed between the first figurehead and the fifth, and the agent can read it.
What went wrong anyway
The pipeline was not clean. The audit file from this project (docs/art/audit-2026-10-16/pipeline_review.md) lists real problems:
- The re-import check was hand-written 9 times across category scripts.
- The skip-render flag had four different spellings.
- A contact sheet grew from 259 KB to 2.16 MB because nobody ran the PNG compressor.
- Every rebuild re-saved the
.blendfiles, so git showed them as dirty with byte-level churn. - The GLBs were served uncompressed. Gzip would take 3.9 MB down to about 0.93 MB.
Several of these were fixed in later commits. The re-import check, mount empties, and sign-off renders moved into the shared helper. One flag vocabulary now covers every build script, and build.py rejects unknown flags. The build now stops the .blend churn and compresses renders, and models are served gzipped under content-hash URLs. The helper also gained validation for open edges, material counts, and child scale at export.
I did not re-check every item in that audit against the final tree, so treat the list as findings from a review. The bigger lesson is the pattern. Headless scripts invite copy-paste, because each category script is a standalone file. Budget time to pull the repeated parts into one helper early, before the ninth copy.
So which one do you pick
Pick by what you are doing, not by which one feels more modern.
| Situation | Tool |
|---|---|
| One new model, no design yet | Live MCP |
| You want to say “make the bow sharper” and watch it | Live MCP |
| A batch of 5 or more related models | Headless scripts |
| Two or more agents working at once | Headless scripts |
| You need the same output tomorrow | Headless scripts |
| A triangle budget or naming rule must be enforced | Headless scripts |
| The agent needs to look at a result | Either: screenshot or preview PNG |
My workflow now is this. Explore the first model live with MCP and screenshots until the look is right. Then ask the agent to write the build script that reproduces it, run it headless, and compare the preview PNG against the live result. From that point on, the script is the source of truth and the live session is a review window.
If an agent starts using your open Blender for production work, stop it and write the rule into the brief. In this project the agent wrote the port 9876 line itself.
Common Questions
Can Claude Code control Blender?
Yes. Claude Code can drive a running Blender through an MCP server such as the official Blender Lab blender_mcp, which exposes tools to execute Python and capture screenshots. It can also run Blender headless through the shell with blender -b --python script.py, which needs no MCP server at all.
How do I run a Blender Python script without opening Blender?
Run blender -b --python-exit-code 1 --python build.py. The -b flag starts Blender in background mode with no window, and --python-exit-code 1 makes a Python exception exit with status 1. Put script arguments after a lone --, then read them from sys.argv in the script.
Is Blender MCP safe to leave connected to my open session?
No, not for production work. The add-on executes whatever Python the agent sends in your live scene, and the scene is shared. An agent can overwrite your unsaved edits. Use live MCP for exploration you are watching, and keep batch jobs on headless Blender with their own scenes.
How do I check a GLB file’s triangle count?
Read the GLB’s JSON chunk and sum the index accessor counts for each mesh primitive, divided by 3 for triangle mode. Python’s standard library can do this in about 20 lines with struct and json. The kit’s check_glb.py does it and exits 1 when any mesh goes over budget.
Do I need the Blender add-on for headless builds?
No. Headless builds use Blender’s own bpy module through blender -b, with no add-on, socket, or MCP server. The add-on and port 9876 matter only for live MCP sessions against an open Blender window. The same build.py runs on a CI runner or a headless server with only Blender installed.