Liking cljdoc? Tell your friends :D

Blender native seams (read-only reference study)

Reference: clones-ref/blender-mcp at 7a0373e (MIT, LICENSE:1-21), and facts only from GPL-2.0 clones-ref/bambu-printer-mcp at cc93603e (LICENSE:1-20). Paths below are relative to their respective clone roots. No Blender runtime was available (command -v blender returned nothing); these are source observations, not a live integration test.

1. Native entry points and ownership

BoundaryActual entry / ownerEvidence
Blender add-onaddon.py:6176-6182 register() installs scene props and UI classes; addon.py:6361-6375 schedules update check and auto-start. BlenderMCPServer(host='localhost', port=9876) holds socket, client sockets, threads and command queue; start() binds/listens; stop() unregisters timer, closes clients and drains queue.addon.py:1175-1193,1251-1368,6361-6375
Host-side connectionBlenderConnection(host,port).connect(), .send_command(type,params), .disconnect(); get_blender_connection() maintains one global persistent socket, with one lock spanning request + reply. server_lifespan disconnects at shutdown.src/blender_mcp/server.py:95-128,184-251,269-317,355-414
MCP façadePython package's mcp-for-blender entry runs blender_mcp.server:main (pyproject.toml:1-22); main() calls context_log.install and mcp.run() (stdio FastMCP), with install/setup/update CLI routing. The add-on is a separate in-Blender process component, not an importable bpy engine on a normal JVM Python interpreter.src/blender_mcp/server.py:337-350,2176-2225; addon.py:1-29,1175-1193
Scene/documentScene is bpy.context.scene; objects/materials/collections live under bpy.data and current selection/view layer under bpy.context. get_scene_info names/counts scene objects; get_object_info(name) resolves bpy.data.objects.get(name) and reports transform, material slots, mesh stats. .blend pathname is bpy.data.filepath.addon.py:1600-1625,1862-1873,2035-2060; src/blender_mcp/server.py:2120-2153
Save/open/closeNo dedicated add-on command for .blend open/save or document handles in the base dispatcher; execute_code can call bpy.ops.wm.open_mainfile/save_as_mainfile (safe-mode policy deliberately permits open/save). To close the bridge use BlenderConnection.disconnect() or BlenderMCPServer.stop(); neither closes Blender's document. INFERRED: live scene is process-owned, not a detachable session.addon.py:1490-1577,2258-2282,1303-1368; src/blender_mcp/safe_mode.py:20-37; src/blender_mcp/server.py:116-128

2. Socket contract and command model

  • The add-on binds TCP IPv4 at localhost:9876 by default, backlog 5, accepting in a daemon thread and spawning a daemon thread per client; stop() shuts down live sockets. Port is a scene property (range 1024–65535); auto-start defaults true, but manual disconnect suppresses automatic restart on subsequent file loads. addon.py:1175-1193,1260-1287,1355-1392,60-134,6177-6200; client CLI host/port override env BLENDER_HOST/BLENDER_PORT and defaults in src/blender_mcp/server.py:45-87.
  • Not JSON-lines: one UTF-8 JSON object {"type":"execute_code","params":{"code":"..."}} sent with no length prefix, newline or request id; client assembles recv(8192) chunks until json.loads succeeds. Add-on similarly accumulates bytes until one JSON parse succeeds, then queues (command,client) and resets buffer. Pipelining / coalesced objects can fail parsing indefinitely; therefore serialize one outstanding call per socket. No explicit inbound size bound in these loops. src/blender_mcp/server.py:130-244; addon.py:1415-1480.
  • Exactly one main-thread bpy.app.timers callback drains the queue at 0.05-second intervals, calls execute_command(command), json.dumps the reply and sendall (no terminator); Python socket threads must not call bpy. 180-second client receive timeout; on timeout or transport/JSON errors the client invalidates the socket. addon.py:1182-1188,1277-1290,1394-1437; src/blender_mcp/server.py:132-181,230-267.
  • Exact call: BlenderMCPServer._drain_command_queue -> execute_command -> _execute_command_internal. Dispatch is a Python dictionary of str -> bound method constructed per call, handler(**params) (no serde/schema registry or 500+ commands). Base commands: ping, scene/snapshot/object/item queries, screenshots/picking, execute_code, RNA lookup/introspection, telemetry controls, export_scene, status checks. Provider handlers are dynamically added only when corresponding scene checkbox is on; Tripo only through Premium. Unknown id returns {"status":"error","message":"Unknown command type: ..."}. Success returns {"status":"success","result": <JSON-compatible value>}; errors return {"status":"error","message":str(e)}. addon.py:1394-1437,1481-1588.
  • Nearest (command-id, params-json) -> result-json is the existing socket, not an importable standalone Python function: BlenderConnection.send_command does JSON encoding/decoding and unwraps result, raising on status:error. In-process inside Blender, _execute_command_internal({"type":id,"params":dict}) provides the same dispatcher but only on the main thread. No authorization or token handshake appears at bind/accept/dispatch (addon.py:1251-1285,1370-1588); INFERRED: local processes can send raw commands without the MCP safe-mode gate.
  • Handshake get_addon_info returns add-on version, ADDON_PROTOCOL_VERSION=13, capabilities, Blender version and Premium generators; host performs one handshake after connecting and handles old add-ons' unknown commands/kwargs as compatibility errors. addon.py:38-40,1589-1613; src/blender_mcp/server.py:358-397,553-624.

3. Data, errors, concurrency and headless limits

  • JSON-compatible snapshots include scene name, frame/fps, selected names, object transforms, geometry AABBs, relations, materials, shader fingerprints and camera/light summaries; lists are capped at 4,000 objects / 1,000 selected names, with truncation flags. Object/mesh/material/scene and gpu.types.GPUOffScreen remain live bpy/GPU objects inside Blender, never JSON values; use names or file paths as handles. addon.py:38-47,1862-2018,2035-2060,2090-2135.
  • get_viewport_screenshot(filepath,format,max_size) writes PNG via GPUOffScreen texture read and image pixels, falling back to window screenshot; reply returns a file path, dimensions, camera matrices, file/scene identity. MCP reads and removes temporary PNG then returns base64 ImageContent; not raw pixel bytes over the add-on socket. Asset previews instead encode image bytes as base64 in JSON. addon.py:2067-2163,3978-4039; src/blender_mcp/server.py:692-752,1821-1839.
  • execute_code(code) executes Python exec(code,{"bpy":bpy}), captures stdout (not expression return), yields {"executed":true,"result": printed_text}; failure raises an Exception whose message is JSON-encoded {exception_type,message,traceback}. Dispatcher catches ordinary Exception, but neither process crash nor BaseException is contained. Some handlers return {"error":...} inside a success envelope (export_scene validation, get_scene_info), so inspect both envelope and result. addon.py:2258-2285,1572-1588,2562-2583,1614-1643; src/blender_mcp/server.py:234-267,786-826.
  • Core scene mutations are main-thread serialized; socket accept/client threads and telemetry workers exist, whereas MCP tools are async and generation polls/waits asynchronously. Keep Blender GUI/event loop, add-on instance, scene and connection alive between calls; do not treat bpy state as Send/Sync. addon.py:1175-1193,1271-1290,1394-1418; src/blender_mcp/server.py:95-107,1680-1705; src/blender_mcp/generation.py:1-42.
  • Add-on explicitly refuses to start in bpy.app.background (blender -b): timer-driven commands would never execute; auto-start exits in background. For a display-less host use a GUI-mode Blender under a virtual display and test 3D viewport/GPU context. Blender is not installed here, so there was no live socket, GUI, GPU or STL-export validation. addon.py:75-82,1251-1259,2067-2118; src/blender_mcp/server.py:248-251. INFERRED: use an official Blender binary compatible with add-on's minimum (3,0,0) or build Blender from source, install/enable addon.py, retain a running GUI session, expose only localhost:9876; source-only Python tests mock bpy (tests/test_export_scene.py:33-111).
  • Build facts: Python >=3.10, mcp>=1.26,<2 and httpx>=0.27; setuptools package script mcp-for-blender, separate addon.py imports Blender-provided bpy, mathutils plus requests. There is no Rust crate/cdylib here. pyproject.toml:1-41; addon.py:1-28. INFERRED: libpython-clj can import the host blender_mcp.server.BlenderConnection via the appropriate Python environment but cannot import Blender's bpy into host Python just by installing the PyPI MCP package.

4. Security, optional services and privacy

  • MCP execute_blender_code runs validate_code only when BLENDER_MCP_SAFE_MODE is truthy; default is unrestricted. The opt-in AST policy limits scripts (200,000 bytes / 20,000 AST nodes / depth 24), imports, dangerous builtins, driver/handler persistence and external .blend datablock loading, yet deliberately permits Blender render/open/save/import/export operators. Crucially the add-on raw socket's execute_code has no validation, so using the direct socket requires our own independent policy; safe mode on the MCP side is not a sandbox. src/blender_mcp/server.py:786-826; src/blender_mcp/safe_mode.py:1-49,74-85,456-461,892-988; addon.py:1514-1528,2258-2285.
  • External asset services are separate opt-in handlers: Poly Haven (hdris/textures/models, CC0), Sketchfab (API key, author/licence varies), Poly Pizza (CC0/CC-BY attribution saved on imported root objects); generation Hyper3D Rodin and Hunyuan (own keys/local API) or Tripo via Premium. Do not silently call them. Client search_assets/import_asset consolidate sources; generate_3d uses resumable provider job handles (paid generation, async). addon.py:1498-1571,286-330,4444-4468,6178-6209,6256-6328; src/blender_mcp/server.py:1796-1950; src/blender_mcp/generation.py:1-79.
  • Telemetry consent checkbox defaults false, but without consent the MCP collector still sends anonymous startup/tool-name/success/duration to Supabase; only disabling via BLENDER_MCP_DISABLE_TELEMETRY=1 (or DISABLE_TELEMETRY/MCP_DISABLE_TELEMETRY) stops it. Consent permits prompts/code/screenshot upload and trajectory snapshots; calls may prompt the user via elicitation. Avoid importing/starting MCP server for our direct socket adapter, and if it is used set the disable env before import; keep consent false and do not call set_telemetry_consent(true). addon.py:5768-5807,1067-1097; src/blender_mcp/telemetry.py:68-112,157-267,272-334; src/blender_mcp/consent_prompt.py:1-17,158-200; src/blender_mcp/server.py:269-299,553-600.
  • Add-on registration schedules a network update check by default after 5 seconds; suppress using BLENDERMCP_NO_UPDATE_CHECK=1 in Blender's environment. Premium key activation calls remote /activate; generation source defaults BYOK, and Premium is opt-in. Avoid activation, update install, Premium and cloud provider calls in the baseline. addon.py:6054-6104,5627-5652,5752-5768,6361-6375.

5. Blender -> Bambu model hand-off

  • Native export_scene(filepath, format='glb', object_names=None, selection_only=False, apply_modifiers=True) supports GLB/FBX only. Named objects include all children; it selects objects and writes using bpy.ops.export_scene.gltf/fbx, returns {path,bytes,selection_only,exported}. It does not provide STL or 3MF and can alter selection/mode. addon.py:2562-2640; tests/test_export_scene.py:112-183.
  • For STL from this add-on, submit trusted execute_code calling Blender's version-dependent STL operator (bpy.ops.wm.stl_export for >=4.0, bpy.ops.export_mesh.stl for older installs), then validate existence/mesh and pass the resulting path to the Bambu slicer/import boundary. The existing GPL bridge uses that exact version fork and a staged output path with request-id receipt, validates regular STL <=256 MiB with finite triangles, then publishes the staged file without overwriting an existing destination. These are observed interoperability facts, not code to copy. clones-ref/bambu-printer-mcp/src/blender-mcp-bridge.ts:82-157,160-230,326-342 (GPL, clones-ref/bambu-printer-mcp/LICENSE:1-20). 3MF is not implemented in the add-on's export_scene; INFERRED: use a Blender-supported 3MF exporter if installed, or hand off STL to BambuStudio for conversion/slicing, never claim native 3MF output (addon.py:2562-2640).
  • The GPL bridge's generic route launches a configured MCP server via stdio, discovers tools, validates input against advertised schema and calls by name; its STL edit route submits execute_blender_code, with decimate/remesh/boolean-union operations, input/output verification, and no replay on timeout after dispatch. Legacy route can call a configured executable and is not our in-process model. Requiring/redistributing that GPL-2.0 npm package from an MIT addon is an open licensing question for Pedro; only its protocol facts are used here. clones-ref/bambu-printer-mcp/src/blender-mcp-bridge.ts:1-25,53-82,120-230,233-344; clones-ref/bambu-printer-mcp/package.json:1-17,33-67.

6. Ranked cut points (proposal, not implementation)

RankAttachAdvantages / constraints on our protocol
1Direct add-on TCP via a bounded client at addon.py:1394-1588, src/blender_mcp/server.py:184-244.Native Blender engine remains in-process where bpy actually lives; reuse {type,params} -> {status,result/message}. Must serialize calls, gate arbitrary execute_code ourselves, handle timeouts as unknown outcome, constrain local socket access; GUI Blender and add-on stay running. INFERRED: a cljw wire client or JVM client can use this socket directly.
2libpython-clj over BlenderConnection at src/blender_mcp/server.py:95-267,390-414.Reuses Python framing/reconnect logic without launching MCP; host Python import does not provide bpy, so still needs the Blender socket and GUI. Use an explicit command-id + map -> result boundary and disable telemetry if importing broader MCP paths (src/blender_mcp/telemetry.py:94-112). INFERRED: importability in libpython-clj requires matching Python environment/dependencies.
3MCP façade/tool calls at src/blender_mcp/server.py:337-350,786-826,1796-1950.Already supplies safe-mode gate and structured tools, but process/stdio MCP transport and optional telemetry/premium/elicitation behavior add complexity; not a direct in-process Python engine. INFERRED: only viable if the host already owns a Python MCP runtime without spawning a separate server.

7. Domain vocabulary and open/closed sets

Noun / vocabularyType or definition and boundary choice
Scene, object, mesh, material, collection, camera, lightbpy.context.scene, scene.objects, bpy.data.objects, obj.data.vertices/polygons, material slots, bpy.data.materials, bpy.data.collections; pass stable names plus explicit scene/file identity as values, not raw bpy pointers (addon.py:1600-1625,1650-1688,1862-2018,2035-2060; src/blender_mcp/server.py:2053-2107).
Transform, AABB, selection, render/viewObject location/rotation/scale/materials and world bounds are JSON values; screenshot has camera/view matrices and normalized pick coordinates with file/scene mismatch checks (addon.py:1888-1944,2035-2059,2146-2229; src/blender_mcp/server.py:2090-2107).
Export formatAdd-on export_scene closed glb|fbx; look mode/angle/shading closed tuples; Poly Haven asset_type closed hdris|textures|models (addon.py:2573-2583,2650-2653; src/blender_mcp/server.py:1534-1549,1859-1889). Blender RNA operators and their enum items are version-dependent: query bpy_api_lookup/describe_node_type, not a global frozen enum (addon.py:2290-2330,2470-2558).
Command id, asset source, generation provider/jobBase command ids plus checkbox-conditional dictionaries are an open capability surface (addon.py:1490-1577); search_assets source is closed in this server version polyhaven|sketchfab|polypizza (src/blender_mcp/server.py:1796-1889); generator provider closed tripo|hunyuan3d|hyper3d, while job ids are opaque strings (src/blender_mcp/generation.py:22-79).

Open questions for Pedro

  1. Should the baseline expose any arbitrary Python at all, or only guarded intent-to-operator commands? Raw socket bypasses upstream safe mode (src/blender_mcp/safe_mode.py:10-16; addon.py:2258-2285).
  2. Is a managed GUI Blender with virtual display/GPU a permissible deployment dependency, and how is the loopback listener isolated from other local users? (addon.py:1251-1287,2067-2118).
  3. Should the hand-off contract standardize on STL with validated units/scale before slicing, or require an installed 3MF exporter? Existing export command is GLB/FBX only (addon.py:2562-2640; clones-ref/bambu-printer-mcp/src/blender-mcp-bridge.ts:189-230).
  4. Is requiring or redistributing GPL-2.0 bambu-printer-mcp from an MIT addon acceptable? This study only extracts facts (clones-ref/bambu-printer-mcp/LICENSE:1-20; clones-ref/bambu-printer-mcp/package.json:33-67).

Can you improve this documentation?Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
←Move to previous article
→Move to next article
Ctrl+/Jump to the search field
× close