Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 

Repository files navigation

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 bpy access — create meshes, apply modifiers, render, import/export, anything
  • stdout captureprint() 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)

  1. Download blender_mcp_addon.py
  2. In Blender: Edit → Preferences → Add-ons → Install…
  3. Select the downloaded .py file and click Install Add-on
  4. Enable the checkbox next to "MCP Server"
  5. 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_start toggle 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_url and tracker_url in bl_info

v1.2.0

  • Stale-socket cleanup via gc.get_objects() (fixes Windows SO_REUSEADDR issue)
  • N-Panel with Start / Stop / Restart buttons
  • _auto_start deferred 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.timers main-thread execution
  • stdout/stderr capture per request
  • JSON default=str fallback for non-serialisable values

License

GPL-3.0-or-later — the same license as Blender itself.


Author

Olivier HoarauTarraw974@gmail.com

Contributions, issues and pull requests are welcome!

About

Blender addon that starts a local TCP server (port 9876) so Claude / Cowork can execute Python code inside Blender via MCP.

Topics

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages