Blender addon that starts a local TCP server (port 9876) so Claude / Cowork can execute Python code inside Blender via MCP.
Blender MCP Server Addon
Execute Python code inside a running Blender instance from any MCP client — including Claude Cowork, Claude Code, or your own tooling.
What it does
This Blender addon starts a lightweight TCP server on localhost:9876 when Blender launches. Any MCP client can then send Python code and receive the result in real-time — with full access to bpy, all scene data, and Blender's Python API.
Claude / Cowork ──TCP JSON──► Blender MCP Addon ──exec()──► bpy
◄──JSON result── ◄──result────
Key features
- Zero configuration — installs as a standard Blender addon, auto-starts on launch
- Full
bpyaccess — create meshes, apply modifiers, render, import/export, anything - stdout capture —
print()output is returned alongside the result - Robust error handling — Python exceptions are caught and returned as structured JSON; the server never crashes
- Stale-socket cleanup — automatically closes leftover sockets from previous sessions (critical on Windows)
- Configurable port — change the port from Blender's addon preferences
- N-Panel UI — Start / Stop / Restart buttons in the 3D Viewport sidebar (N key → MCP tab)
- Compatible with Blender 4.0 → 5.x (tested on 4.2 LTS and 5.1.1)
Installation
Option A — Install from file (recommended)
- Download
blender_mcp_addon.py - In Blender: Edit → Preferences → Add-ons → Install…
- Select the downloaded
.pyfile and click Install Add-on - Enable the checkbox next to "MCP Server"
- Click Save Preferences (hamburger icon → Save Preferences)
The server starts automatically the next time Blender opens. You can also start it immediately from 3D Viewport → N-Panel → MCP → Start Server.
Option B — Copy manually
# Linux / macOS
cp blender_mcp_addon.py ~/.config/blender/4.x/scripts/addons/
# Windows
copy blender_mcp_addon.py "%APPDATA%\Blender Foundation\Blender\4.x\scripts\addons\"
Then enable it in Edit → Preferences → Add-ons (search for "MCP Server").
Usage
With Claude Cowork / Claude Code
Install the official Blender MCP connector from the Claude extensions marketplace. Once both the connector and this addon are active, Claude can directly control Blender — no further setup needed.
Example prompt:
"Create a low-poly house with a red roof and two trees on a green ground plane."
With any MCP client
Send a JSON request over TCP to localhost:9876:
{"type": "execute", "code": "result = {'objects': len(bpy.data.objects)}", "strict_json": false}
Terminate the frame with a null byte (\0). The response is also null-byte delimited:
{"status": "ok", "result": {"objects": 3}, "stdout": ""}
Direct Python test
import socket, json
def run_in_blender(code: str) -> dict:
req = json.dumps({"type": "execute", "code": code, "strict_json": False}) + "\0"
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect(("localhost", 9876))
s.sendall(req.encode())
buf = bytearray()
while b"\0" not in buf:
buf.extend(s.recv(65536))
return json.loads(buf.split(b"\0")[0])
# Example
print(run_in_blender("result = {'version': bpy.app.version_string}"))
# → {'status': 'ok', 'result': {'version': '5.1.1'}, 'stdout': ''}
Returning data
Assign a JSON-serialisable dict to a variable named result:
code = """
import bpy
result = {
"objects": [obj.name for obj in bpy.data.objects],
"scene": bpy.context.scene.name,
}
"""
Non-serialisable values (Blender objects, vectors, etc.) are automatically converted via repr() rather than raising an error.
Protocol reference
| Field | Type | Description |
|-------|------|-------------|
| type | "execute" | Always "execute" (reserved for future commands) |
| code | str | Python source to execute inside Blender |
| strict_json | bool | Ignored by this addon (kept for compatibility) |
Response
| Field | Type | Description |
|-------|------|-------------|
| status | "ok" | "error" | Execution outcome |
| result | any | Value assigned to result in user code (on success) |
| stdout | str | Text printed to stdout/stderr during execution |
| message | str | Full Python traceback (on error) |
Architecture
Blender main thread
│
├── register()
│ ├── bpy.utils.register_class(…) ← UI operators & panel
│ └── bpy.app.timers.register(_auto_start, first_interval=0.5)
│
└── _tick() [every 50 ms, persistent timer]
└── exec(code, {"bpy": bpy, "result": None})
└── writes result to sys.modules["_blender_mcp_srv"].st.rm
Background threads
├── _accept_loop(srv_sock) ← waits for TCP connections
└── _handle(conn) [one per client]
├── recv request
├── push (tid, code) onto task queue
├── poll result map until result appears
└── send response
Why sys.modules?
Blender's timer callbacks run inside exec(), which has its own scope. Storing state in a custom entry in sys.modules ensures it persists across calls, restarts, and the addon's own exec() execution context.
Why stale-socket cleanup?
On Windows, SO_REUSEADDR allows multiple sockets to bind the same port simultaneously. Without cleanup, old server instances (e.g. from a previous exec(open(...).read()) in the Blender console) keep intercepting connections. The cleanup function uses gc.get_objects() to find and close any socket bound to the same port other than the current server socket.
Troubleshooting
"Connection refused" / "Cannot connect to Blender"
- Make sure the addon is enabled in Blender preferences
- Check the N-Panel (3D Viewport → N → MCP) — the status should show ● Running
- If the status shows ○ Stopped, click Start Server
- Verify no firewall is blocking
localhost:9876
"Empty response from Blender" / timeout
Click Restart in the N-Panel. This purges any stale state and re-binds the port cleanly.
Port already in use
Change the port in Edit → Preferences → Add-ons → MCP Server → Port, then restart the server.
Blender 5.x: KeyError: 'Principled BSDF'
In Blender 5.x the Principled BSDF node is no longer auto-created. Always create it explicitly:
nodes.clear()
bsdf = nodes.new('ShaderNodeBsdfPrincipled')
out = nodes.new('ShaderNodeOutputMaterial')
links.new(bsdf.outputs['BSDF'], out.inputs['Surface'])
bpy.ops.*.poll() failed, context is incorrect
bpy.ops operators require a valid context. When possible, use the bpy.data / bmesh API directly (no context needed). For operators that truly require a VIEW_3D context:
for window in bpy.context.window_manager.windows:
for area in window.screen.areas:
if area.type == 'VIEW_3D':
for region in area.regions:
if region.type == 'WINDOW':
with bpy.context.temp_override(window=window, area=area, region=region):
bpy.ops.object.shade_flat()
Changelog
v1.3.0 (2026-05-04)
- Configurable port via addon preferences
auto_starttoggle in preferences- Improved protocol error handling (JSON / UTF-8 decode errors)
server_restart()public function_close_stale_sockets()returns count of sockets closed- English UI and docstrings
doc_urlandtracker_urlinbl_info
v1.2.0
- Stale-socket cleanup via
gc.get_objects()(fixes Windows SO_REUSEADDR issue) - N-Panel with Start / Stop / Restart buttons
_auto_startdeferred timer (0.5 s) to let Blender initialise first- Persistent state in
sys.modules[_MOD]to survive exec() scope issues
v1.1.0
- Initial working version with
bpy.app.timersmain-thread execution - stdout/stderr capture per request
- JSON
default=strfallback for non-serialisable values
License
GPL-3.0-or-later — the same license as Blender itself.
Author
Olivier Hoarau — Tarraw974@gmail.com
Contributions, issues and pull requests are welcome!