Quickstart
End-to-end in five steps. No real Blender required — a stub peer stands in for the addon so you can see the round-trip before installing anything in Blender.
Prerequisites
Section titled “Prerequisites”- Python 3.10+
uvinstalled- A clone of the repo
-
Start the server. Pick one:
Terminal window cd blender-mcpuv syncexport ADMIN_PASSWORD=adminpw # required; server refuses to start without itexport DEMO_PASSWORD=demopw # optional; creates a `demo` useruv run blender-mcpExpected output:
INFO oauth_server /auth/login mountedINFO oauth_server /mcp mounted (Streamable HTTP)INFO uvicorn Uvicorn running on http://0.0.0.0:8000For the next steps, set
MCP=http://localhost:8000.Skip setup entirely — use the reference deploy:
Terminal window export MCP=https://mcp.l.warehack.ingDemo credentials are the same as below. Jump to step 2.
-
Get a JWT.
Terminal window curl -s -X POST "$MCP/auth/login" \-H 'content-type: application/json' \-d '{"username":"demo","password":"demopw"}' | jq -r .access_tokenSave the token as
TOKEN=...for the next two terminals. (Replacedemopwwith whatever you setDEMO_PASSWORDto.) -
Run a fake Blender peer in terminal 2. This registers as a persistent
blenderclient and responds to bothcommand_dispatchandjob_dispatchpayloads.Terminal window uv run python examples/fake_blender_peer.py \--server http://localhost:8000/mcp \--token "$TOKEN" \--uuid blender-demo01Expected output:
[peer] Connected as blender-demo01[peer] set_logging_level=debug[peer] register_client ok[peer] waiting for jobs... -
Call a dispatch tool in terminal 3 — the simplest happy path. No
job_id, no subscription, no notification filtering. The server hides the bus round-trip.Terminal window curl -s -X POST http://localhost:8000/mcp \-H "authorization: Bearer $TOKEN" \-H 'content-type: application/json' \-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"blender_get_scene_info","arguments":{}}}'Expected output (single MCP tool result, JSON-encoded):
{"status":"completed","command":"get_scene_info","target_uuid":"blender-demo01","job_id":"j-7a3f2c1e9b04","result":"{\"name\":\"Scene\",\"object_count\":3,...}","error":""}The server auto-picked
blender-demo01because it’s the only Blender peer on the user’s bus. Passtarget_uuidinargumentsto override. -
Inspect what’s connected. In a fourth terminal:
Terminal window curl -s -X POST http://localhost:8000/mcp \-H "authorization: Bearer $TOKEN" \-H 'content-type: application/json' \-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"blender_list_available_clients","arguments":{}}}'Returns the persistent + ephemeral client lists for the
demouser’s bus.
What just happened
Section titled “What just happened”Terminal 2 registered as a persistent client (is_persistent=true) of type blender. Terminal 3 called blender_get_scene_info — a flat MCP tool. The server’s dispatch component:
- Auto-picked the lone
blenderclient (blender-demo01) as the target. - Registered a Future via
JobWaiterkeyed on(demo, j-...). - Routed a
command_dispatchpayload through the bus. - The forwarding handler pushed an MCP notification to terminal 2’s session.
- Terminal 2’s drainer dispatched
get_scene_infoto its executor (the stub returns a canned scene snapshot), then calledblender_job_update. - The server’s
job_updatehandler calledjob_waiter.deliver(...). The Future resolved. - The dispatch tool’s
await asyncio.wait_for(future)returned, the JSON wire body went back to terminal 3.
The same flow works against a real Blender addon — the stub just shortcuts the actual bpy call.
Advanced — dispatch a free-form script
Section titled “Advanced — dispatch a free-form script”For multi-step Python or anything outside the dispatch tool surface, send a job_dispatch payload via blender_send_message. This is the original path; it still works.
uv run python examples/llm_client_example.py \ --server http://localhost:8000/mcp \ --token "$TOKEN" \ --target blender-demo01 \ --script 'import bpy; print(len(bpy.data.objects))'Expected output:
[llm] send_message -> job_id=job-9c2f...[llm] received job_update status=completed[llm] result: 3The example client maintains its own _message_bus subscription, a JobWaiter, and a notification filter. See Write your own LLM client for both styles in detail.
- Install the real addon and run a job against actual Blender — see Add an addon command for the registry walkthrough.
- Learn when to reach for script-dispatch — see Command dispatch vs script dispatch.
- Build your own LLM client — see Write your own LLM client.
- Understand what’s happening under the hood — see Architecture and Why MCP logging.