The MCP server (mcp-for-blender 2.1.1, frozen requirements) runs inside the Blender container. Its add-on is vendored at upstream 41a18432 (MIT) and started by a startup hook. scripts/blender-mcp carries the stdio over ssh + docker exec, so the add-on socket, which runs arbitrary Python with no auth, stays on the container's localhost with no published port. It also runs there because viewport screenshots need a filesystem shared by server and Blender. Telemetry is off and safe mode is on. The hook also defaults Cycles to OptiX on GPU 3, because safe mode forbids agents from touching preferences. Verified end to end from nh3-dev: 36 tools; a GPU render of an agent-built scene; a viewport screenshot; and safe mode refusing 'import os'. Blender left down (on demand).
blender
Blender 5.2.2 LTS on fv-ml1 GPU 3, on demand. Agents drive it; there is also a browser desktop to watch it or take over. Prime, 2026-09-27: "go ahead with gpu 3, both". He does not use Blender himself, so the agent side (MCP) is the primary interface.
| Desktop | https://10.251.50.54:3001 (self-signed cert). Basic auth: user blender, password secret get fv-ml1/blender-web-password. |
| Image | lscr.io/linuxserver/blender:5.2.2-ls241@sha256:9216c77a… (Selkies 2.0 Wayland desktop, NVENC stream). |
| GPU | GPU 3 only (NVIDIA_VISIBLE_DEVICES=3). About 270 MiB is held while the desktop runs. |
| Files | /work → /tank/blender (projects, assets, renders; not backed up). /config → /opt/docker/data/blender (prefs, add-ons; restic). Files are owned by infra-ops (uid 1002), so agents can scp in and out. |
⚠ On demand: GPU 3 is borrowed
GPU 3 is the fleet's reserve card for a full-size vLLM seat (servers/fv-ml1/README.md).
Blender uses it only while in use:
ssh infra-ops@10.251.50.54 'cd /opt/docker/compose/blender && docker compose up -d' # start
ssh infra-ops@10.251.50.54 'cd /opt/docker/compose/blender && docker compose down' # stop, card back to 0
restart: "no", so a reboot never brings it back. When a big seat moves onto GPU 3, Blender
stays down.
Headless rendering (no desktop needed)
ssh infra-ops@10.251.50.54 'docker exec -u abc blender blender -b /work/<file>.blend -E CYCLES -o /work/out/frame_#### -a -- --cycles-device OPTIX'
Run as -u abc, the image's user, mapped to uid 1002, so outputs land owned by infra-ops.
Acceptance (2026-09-27, 1356)
- Cycles sees the card on both OptiX and CUDA: "NVIDIA RTX PRO 6000 Blackwell Max-Q Workstation
Edition". The build ships
kernel_sm_120.cubinplus OptiX PTX. - Self-test (
/tank/blender/render_test.py): a subdivided glass monkey, 1920×1080, 1024 samples, 32 bounces, no denoise. OptiX 3.54 s against CPU 22.71 s (96 threads). That is n=1 per device: a functional check that the GPU is really used, not a benchmark. A trivial default-cube scene could not separate them (0.59 s against 0.65 s), which is why the self-test scene is heavy. - Web auth: no credentials gives 401, a wrong password 401, the right one 200.
- Selkies: "Render node 1 encodes H264, AV1, H265 on nvenc"; the Wayland renderer runs GL on GPU 3.
Agent control (MCP): scripts/blender-mcp
The chosen server is mcp-for-blender (MIT, one
maintainer, ~29k stars; researched by dvalin-smithy-dev 2026-09-27, thread
01M3JA61FTFW2ZPSD20MHF7RPJ, full note in dvalin-smithy
research/blender-agent-drive-2026-09-27.md). Two halves:
- The add-on is inside the running GUI Blender and serves a socket that executes arbitrary
Python with no authentication. It is vendored at upstream commit
41a18432(conf/scripts/addons/blender_mcp.py, MIT licence alongside) and started byconf/scripts/startup/fleet_mcp.py. The add-on only serves from a GUI Blender, never fromblender -b, which is one reason the desktop exists. - The MCP server:
mcp-for-blender==2.1.1, frozen inconf/mcp-requirements.txtand installed in/work/.mcp-venvinside the container (scripts/blender-mcp setup). Agents reach it as stdio overssh infra-ops@fv-ml1 docker exec -i. It runs in the container for two reasons:- No port is published. The socket stays on the container's localhost, so access means ssh + docker on fv-ml1.
- Viewport screenshots need a shared filesystem. Blender writes the image and the server reads it back. With the server on nh3-dev it failed ("Screenshot file was not created").
Settings: DISABLE_TELEMETRY=true and BLENDER_MCP_SAFE_MODE=1. Safe mode puts upstream's AST
allowlist in front of execute_blender_code: bpy, bmesh, mathutils and pure stdlib only, and no
os, open, eval or network. It guards against prompt injection from third-party asset text; it is
not a sandbox.
For an agent
scripts/blender-mcp up # GPU 3 is borrowed: start only when needed
scripts/blender-mcp status # wait for "mcp add-on: answering" (~10-40 s)
claude mcp add blender -- /home/lkraven/development/eshpfi-management/scripts/blender-mcp # once per session/scope
scripts/blender-mcp down # when finished: the card goes back to 0
- Always pass
user_prompt. The tools require it; it is a short statement of the user's request. - Render on the GPU: set
scene.cycles.device = 'GPU'on any scene you create. Safe mode forbids touchingbpy.context.preferences, so the startup hook has already pointed Cycles at OptiX on GPU 3. The startup scene is already set to GPU. - Save everything under
/work/…(=fv-ml1:/tank/blender), thenscpit out. - Look before you report:
get_viewport_screenshotreturns an image, and a still render to/workis the real check. - The asset tools (Poly Haven etc.) reach the internet from Blender. The paid ones (Hyper3D, Hunyuan, Tripo, Sketchfab) need keys we do not have; leave them off.
Acceptance (2026-09-27, 1433)
An MCP client on nh3-dev → scripts/blender-mcp → the in-container server → the add-on:
initializeOK; 36 tools listed.execute_blender_codebuilt a gold metallic torus and rendered it with Cycles on the GPU to/work/_selftest/mcp-torus.png. The file landed, and I checked it by eye.get_viewport_screenshotreturned an image of the scene.- Negative control:
import oswas rejected by safe mode. statuspings the add-on itself. A TCP probe was useless because the port accepted while Blender was still loading.
⚠ Two traps found on the way, both fixed and commented where they live:
- Enabling the add-on from a startup script gets undone when user prefs load. The hook enables it in a timer instead.
- A pre-flight
sshwithout-nswallowed the MCP client'sinitialize, and the session hung at init.