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.
| Boundary | Actual entry / owner | Evidence |
|---|
| Blender add-on | addon.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 connection | BlenderConnection(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çade | Python 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/document | Scene 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/close | No 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 |
- 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.
- 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.
- 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.
- 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.
| Rank | Attach | Advantages / constraints on our protocol |
|---|
| 1 | Direct 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. |
| 2 | libpython-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. |
| 3 | MCP 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. |
| Noun / vocabulary | Type or definition and boundary choice |
|---|
| Scene, object, mesh, material, collection, camera, light | bpy.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/view | Object 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 format | Add-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/job | Base 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). |
- 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). - 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). - 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). - 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).