Skip to content

Use capabilities

capabilities is a free-form list of strings attached to a client at registration. The server stores it on ClientInfo and returns it in list_available_clients. Nothing about the bus enforces capabilities — they’re advisory metadata for the sending side to filter on.

A typical addon registration:

await client.call_tool("blender_register_client", {
"client_uuid": "blender-abc",
"client_type": "blender",
"is_persistent": True,
"capabilities": [
"python_execution",
"modeling",
"rendering",
"scene_management",
"asset_processing",
],
})

An LLM client might declare narrower or different capabilities:

await client.call_tool("blender_register_client", {
"client_uuid": "llm-claude-vision",
"client_type": "llm",
"is_persistent": False,
"capabilities": ["chat", "vision", "code_review"],
})

These are conventions, not enforced names. Pick what your fleet agrees on.

CapabilityWhat it means
python_executionWill execute job_dispatch payloads with arbitrary Python
modelingCan build/edit geometry
renderingHas a render engine available (Cycles/EEVEE)
scene_managementWill accept scene-level commands (object add/remove, etc.)
asset_processingWill handle GLB/FBX/HDRI import
gpuHas a GPU available for compute
headlessRunning without UI — viewport screenshots won’t work
visionLLM-side: can interpret images
chatLLM-side: can hold a conversation

The sender pulls the client list, filters in Python, and addresses by target_uuid.

import json
async def pick_renderer(client) -> str | None:
raw = await client.call_tool("blender_list_available_clients", {})
listing = json.loads(raw.content[0].text)
for c in listing.get("persistent", []):
if c["client_type"] == "blender" and "rendering" in c["capabilities"]:
return c["uuid"]
return None
target = await pick_renderer(client)
if target is None:
raise RuntimeError("no renderer-capable Blender peer available")
await client.call_tool("blender_send_message", {
"target_uuid": target,
"from_uuid": "llm-mine",
"priority": "info",
"payload": {
"message_type": "job_dispatch",
"job_id": "job-render01",
"script": render_script,
},
})

Combine group_id with a capability check to address “all GPU peers in the render-cluster group”:

listing = json.loads((await client.call_tool("blender_list_available_clients", {})).content[0].text)
gpu_renderers = [
c["uuid"] for c in listing["persistent"]
if c["group_id"] == "render-cluster" and "gpu" in c["capabilities"]
]
# Fan out one job per target.
for uuid in gpu_renderers:
await client.call_tool("blender_send_message", {
"target_uuid": uuid,
"from_uuid": "llm-mine",
"priority": "info",
"payload": {"message_type": "job_dispatch", "job_id": f"job-{uuid}", "script": script},
})

Calling register_client again with the same client_uuid updates the existing record in place. Useful when a client gains a capability mid-session (e.g., a Blender peer that just enabled the Cycles GPU device):

await client.call_tool("blender_register_client", {
"client_uuid": my_uuid, # same UUID
"client_type": "blender",
"is_persistent": True,
"capabilities": [..., "gpu"], # new entry
})

The bucket (persistent vs ephemeral) is set on first registration and stays put.

  • ClientInfo — every field, including how capabilities is stored
  • Routing modes — direct/group/type_filter/broadcast
  • Bus toolsregister_client and list_available_clients signatures