Blender MCP Server
by djeada
README.md
# Blender MCP Server
Control Blender from any AI assistant using the [Model Context Protocol (MCP)](https://modelcontextprotocol.io).
27 tools across 7 namespaces — create objects, assign materials, render images, export scenes, execute Python scripts, manage async jobs, and more.

## How It Works
```
┌─────────────┐ stdio ┌──────────────────┐ JSON/TCP ┌─────────────────┐
│ MCP Client │ ◄──────────────► │ MCP Server │ ◄─────────────► │ Blender Add-on │
│ (any host) │ │ (Python) │ localhost:9876 │ (runs in bpy) │
└─────────────┘ └──────────────────┘ └─────────────────┘
```
1. The **Blender add-on** runs inside Blender and listens on `localhost:9876`.
2. The **MCP server** connects to your AI client via stdio and forwards tool calls to Blender over TCP.
3. You ask the AI → it calls MCP tools → Blender executes commands → results flow back.
## Quick Start
### 1. Install the MCP Server
```bash
git clone https://github.com/djeada/blender-mcp-server.git
cd blender-mcp-server
python3 -m venv .venv
source .venv/bin/activate
pip install -e .
```
This creates the executable `.venv/bin/blender-mcp-server`.
### 2. Install the Blender Add-on
Build the add-on zip:
```bash
./scripts/build_addon_zip.sh
```
Then in Blender:
1. Go to **Edit → Preferences → Add-ons → Install**.
2. Select `dist/blender_mcp_bridge.zip` and enable **Blender MCP Bridge**.
3. In the 3D Viewport, press **N** → open the **MCP** tab.
4. Confirm it shows **Listening on 127.0.0.1:9876**.
### 3. Connect Your MCP Client
<details>
<summary><strong>Claude Desktop</strong></summary>
Add to your config file:
| OS | Path |
|---|---|
| macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` |
| Windows | `%APPDATA%\Claude\claude_desktop_config.json` |
| Linux | `~/.config/Claude/claude_desktop_config.json` |
```json
{
"mcpServers": {
"blender": {
"command": "/absolute/path/to/blender-mcp-server/.venv/bin/blender-mcp-server"
}
}
}
```
Replace the path with the actual location of your clone.
</details>
<details>
<summary><strong>Codex CLI</strong></summary>
Register the server once:
```bash
codex mcp add blender -- /absolute/path/to/blender-mcp-server/.venv/bin/blender-mcp-server
```
Verify with `codex mcp list`. Then start Codex from any directory — it launches the server automatically.
</details>
<details>
<summary><strong>Other MCP clients</strong></summary>
Point any MCP-compatible client at the server executable:
```
/absolute/path/to/blender-mcp-server/.venv/bin/blender-mcp-server
```
The server uses **stdio** transport. No additional flags are needed.
</details>
### 4. Start Using It
Make sure Blender is open with the add-on listening, then ask your AI assistant:
- *"What objects are in my Blender scene?"*
- *"Create a cube named TestCube at [0, 0, 1]"*
- *"Render the scene to /tmp/render.png"*
The AI calls MCP tools like `blender_scene_list_objects` and `blender_object_create`, which the server forwards to Blender.
## Example Prompts
<details>
<summary><strong>🔍 Inspecting your scene</strong></summary>
- *"What objects are in my scene?"*
- *"Show me the transform of the Camera object"*
- *"List all materials in the file"*
</details>
<details>
<summary><strong>🔨 Creating objects</strong></summary>
- *"Create a sphere named 'Earth' at position [0, 0, 2] with size 3"*
- *"Add a cylinder at the origin, then scale it to [0.5, 0.5, 4] to make a tall pillar"*
- *"Create 5 cubes in a row spaced 3 units apart"*
</details>
<details>
<summary><strong>🎨 Materials & colors</strong></summary>
- *"Create a red material and assign it to the Cube"*
- *"Make a material called 'Ocean' with color [0.0, 0.3, 0.8] and assign it to the Sphere"*
- *"Change the color of 'RedMaterial' to orange"*
</details>
<details>
<summary><strong>📐 Transforming objects</strong></summary>
- *"Move the Cube up 2 units on the Z axis"*
- *"Rotate the Cylinder 45 degrees on the Z axis"*
- *"Scale the Sphere to [2, 2, 2]"*
</details>
<details>
<summary><strong>📸 Rendering & exporting</strong></summary>
- *"Render the scene at 1920×1080 and save it to /tmp/render.png"*
- *"Export the scene as a GLB file to /tmp/scene.glb"*
</details>
<details>
<summary><strong>🐍 Python execution</strong></summary>
- *"Run this Blender Python: `bpy.ops.mesh.primitive_monkey_add(location=(0,0,2))`"*
- *"Execute the fluid_domain.py script from the library with resolution 128"*
- *"Start an async bake job for the fluid simulation and tell me the job ID"*
</details>
<details>
<summary><strong>⏪ Undo / Redo</strong></summary>
- *"Undo the last change"*
- *"Redo what was just undone"*
</details>
## Tool Reference
### Scene Inspection
| Tool | Description |
|---|---|
| `blender_scene_get_info` | Scene metadata — name, frame range, render engine, resolution, object count |
| `blender_scene_list_objects` | List all objects, optionally filter by type (`MESH`, `CAMERA`, `LIGHT`, …) |
| `blender_object_get_transform` | Get position, rotation, and scale of an object by name |
| `blender_object_get_hierarchy` | Parent/child hierarchy tree (full scene or subtree) |
### Object Manipulation
| Tool | Description |
|---|---|
| `blender_object_create` | Create primitives: `cube`, `sphere`, `cylinder`, `plane`, `cone`, `torus` |
| `blender_object_delete` | Delete an object by name |
| `blender_object_translate` | Move — absolute `location` or relative `offset` |
| `blender_object_rotate` | Set rotation `[x, y, z]` in degrees (default) or radians |
| `blender_object_scale` | Set scale `[x, y, z]` |
| `blender_object_duplicate` | Duplicate with optional new name |
### Materials
| Tool | Description |
|---|---|
| `blender_material_list` | List all materials in the file |
| `blender_material_create` | Create a material with optional base color `[r, g, b]` (0–1) |
| `blender_material_assign` | Assign a material to an object |
| `blender_material_set_color` | Set the Principled BSDF base color |
| `blender_material_set_texture` | Set an image texture as base color |
### Rendering & Export
| Tool | Description |
|---|---|
| `blender_render_still` | Render still image — output path, resolution, engine |
| `blender_render_animation` | Render animation — frame range, output path, engine |
| `blender_export_gltf` | Export as glTF/GLB |
| `blender_export_obj` | Export as OBJ |
| `blender_export_fbx` | Export as FBX |
### History
| Tool | Description |
|---|---|
| `blender_history_undo` | Undo the last operation |
| `blender_history_redo` | Redo the last undone operation |
### Python Execution
| Tool | Description |
|---|---|
| `blender_python_exec` | Run a Python script synchronously. Accepts `code` or `script_path`, optional `args`, `timeout_seconds`, and `transport` (`bridge` or `headless`). |
| `blender_python_exec_async` | Start a long-running script asynchronously. Returns a `job_id`. |
| `blender_job_status` | Poll an async job's status, result, stdout, stderr, and error. |
| `blender_job_cancel` | Cancel a running or queued async job. |
| `blender_job_list` | List known async jobs with IDs, status, and creation time. |
## Script Library
Pre-built scripts in `scripts/library/` for use with `blender_python_exec` via `script_path`:
| Script | Description |
|---|---|
| `create_mesh.py` | Create primitive meshes through the data API (no `bpy.ops`) |
| `fluid_domain.py` | Create a Mantaflow fluid domain |
| `fluid_inflow.py` | Create an inflow source |
| `effector.py` | Set objects as collision effectors |
| `rigid_body.py` | Add rigid body physics |
| `frame_range.py` | Set scene frame range |
| `camera.py` | Create and configure a camera |
| `keyframes.py` | Insert transform keyframes |
| `collections.py` | Organize objects into collections |
| `apply_transforms.py` | Apply transforms to objects |
| `save_blend.py` | Save the `.blend` file |
See [`scripts/library/README.md`](scripts/library/README.md) for full argument docs and a dam-break walkthrough.
> **Tips for physics workflows:**
> - Prefer `create_mesh.py` (data API) over `bpy.ops.mesh.primitive_*_add` in live sessions — the operator path can destabilize view-layer updates around fluid setup.
> - Keep Mantaflow liquid modifiers hidden in the viewport to avoid crashes in Blender 4.x.
> - Use `transport="headless"` for heavy physics bakes — this runs scripts in a separate `blender -b` process.
## Safety & Security
| Feature | Description |
|---|---|
| **Automatic undo push** | Mutation tools push an undo step before executing (Python exec excluded for stability). |
| **Safe Mode** | Restricts file I/O to the project directory only. |
| **Tool whitelist** | Limits which commands the bridge accepts. |
| **Script path restrictions** | `script_path` must be under configured approved roots. |
| **Inline code toggle** | Disable inline code execution via add-on preferences. |
| **Module blocklist** | `subprocess`, `shutil`, `socket`, `webbrowser`, `ctypes`, `multiprocessing` are blocked by default. |
## Add-on Preferences
In Blender → **Edit → Preferences → Add-ons → Blender MCP Bridge**:
| Setting | Default | Description |
|---|---|---|
| Safe Mode | Off | Restrict file I/O to the project directory |
| Port | 9876 | TCP port for the MCP bridge |
| Allow Inline Code | On | Allow `python.execute` to run inline code strings |
| Approved Script Roots | *(blend file dir)* | Semicolon-separated directories for script file access |
## Advanced Usage
<details>
<summary><strong>Direct bridge testing (no MCP client)</strong></summary>
The helper scripts in `scripts/` connect directly to the Blender add-on on `127.0.0.1:9876`, bypassing the MCP server entirely. Useful for verifying the add-on works:
```bash
python3 scripts/blender_scene_info.py
python3 scripts/blender_create_test_cube.py --name TestCube --x 0 --y 0 --z 1 --size 2
python3 scripts/blender_bridge_request.py scene.get_info
python3 scripts/blender_bridge_request.py object.translate --params '{"name":"TestCube","offset":[0,0,2]}'
```
All scripts accept `--host`, `--port`, and `--timeout` flags.
</details>
<details>
<summary><strong>Headless / background mode</strong></summary>
Run Blender without a GUI for automation:
```bash
blender -b --python your_script.py
```
Where `your_script.py` starts the MCP bridge:
```python
import sys
sys.path.insert(0, "/path/to/blender-mcp-server")
from addon import CommandHandler, BlenderMCPServer
server = BlenderMCPServer()
server.start()
import socket
s = socket.socket()
s.bind(("127.0.0.1", 9877))
s.listen(1)
s.accept() # Blocks until shutdown signal
```
</details>
<details>
<summary><strong>Programmatic tool call examples</strong></summary>
**Inline code — create a fluid domain:**
```json
{
"tool": "blender_python_exec",
"args": {
"code": "import bpy\nbpy.ops.mesh.primitive_cube_add(size=4, location=(0,0,2))\ndomain = bpy.context.active_object\ndomain.name = 'FluidDomain'\nbpy.ops.object.modifier_add(type='FLUID')\ndomain.modifiers['Fluid'].fluid_type = 'DOMAIN'\nsettings = domain.modifiers['Fluid'].domain_settings\nsettings.domain_type = 'LIQUID'\nsettings.resolution_max = 64\n__result__ = {'domain': domain.name, 'resolution': 64}",
"args": {"resolution": 64}
}
}
```
**Script file — set up colliders:**
```json
{
"tool": "blender_python_exec",
"args": {
"script_path": "scripts/library/effector.py",
"args": {
"objects": ["Ground", "Building_01", "Building_02"],
"effector_type": "COLLISION"
}
}
}
```
**Async bake and poll:**
```json
{"tool": "blender_python_exec_async", "args": {"code": "import bpy\nbpy.ops.fluid.bake_all()\n__result__ = {'baked': True}", "timeout_seconds": 1800}}
```
→ `{"job_id": "job-f8e2a1b3"}`
```json
{"tool": "blender_job_status", "args": {"job_id": "job-f8e2a1b3"}}
```
</details>
## Development
```bash
git clone https://github.com/djeada/blender-mcp-server.git
cd blender-mcp-server
pip install -e ".[dev]"
pytest tests/ -v
```
### Project Structure
```
blender-mcp-server/
├── addon/ # Blender add-on (TCP server + command handlers + job manager)
├── src/blender_mcp_server/ # MCP server (stdio transport + tool definitions)
├── scripts/
│ ├── library/ # Reusable Blender scripts for common tasks
│ ├── demos/ # End-to-end demo scenes
│ └── blender_bridge_request.py # Direct bridge test helpers
├── tests/ # Unit tests (mocked bpy, no Blender required)
├── docs/ # Architecture & design docs
├── pyproject.toml
└── README.md
```
## Contributing
1. Fork the repository.
2. Create a feature branch.
3. Add tests for your changes.
4. Run `pytest tests/ -v` to verify all tests pass.
5. Submit a pull request.
## License
MIT
This server cannot be deployed
Maintenance
ActivityNo data
ResponsivenessSlow