Tools reference¶
Bonsai MCP exposes eight tools, split into two categories:
| Category | Behaviour | Tools |
|---|---|---|
| QUERY | Read-only. Safe to call without confirmation. | get_scene_info, get_selected_objects, get_psets, get_viewport_screenshot, get_ifc_project_info |
| EDIT | Mutates Blender state, the IFC model, or the filesystem. | execute_ifc_code, execute_blender_code, save_ifc_file |
The category is encoded in two places:
- A
[QUERY]or[EDIT]prefix at the start of every tool description. - The standard MCP
Tool.annotations.readOnlyHintfield, which MCP clients can read to render the distinction natively.
Two code execution tools live in the EDIT category: execute_ifc_code
(preferred, IFC-only, bpy blocked) and execute_blender_code (full bpy
access). See Safety.
get_scene_info (QUERY)¶
Returns a scene-level snapshot. When query is supplied, the response also
includes an objects array filtered by the query.
Inputs (all optional):
{
"query": "walls",
"ifc_class": null,
"name": null,
"global_id": null
}
Supported query values:
| Query | Notes |
|---|---|
| (omitted) | Scene summary only; no objects field. |
all |
All objects in the scene. |
selected |
Currently selected objects. |
walls, doors, windows, spaces, slabs, columns, beams, roofs, stairs |
Filtered by IFC class. |
by_class |
Requires ifc_class, e.g. "IfcCovering". |
by_name |
Requires name (exact Blender object name). |
by_global_id |
Requires global_id (IFC GlobalId). |
Returns:
{
"scene_name": "Scene",
"object_count": 42,
"selected_count": 1,
"selected_objects": ["IfcWall/MyWall"],
"collections": ["IfcSite", "IfcBuilding"],
"object_type_counts": {"MESH": 40, "EMPTY": 2},
"ifc_available": true,
"blender_version": "4.2.0",
"objects": [
{
"name": "IfcWall/MyWall",
"type": "MESH",
"location": [0.0, 0.0, 0.0],
"dimensions": [5.0, 0.2, 3.0],
"ifc_class": "IfcWall",
"global_id": "2O2Fr$t4X7Zf8NOew3FK6X"
}
]
}
The objects field is omitted entirely when no query is supplied.
get_selected_objects (QUERY)¶
Inputs: none.
Returns a list of:
{
"name": "IfcWall/MyWall",
"type": "MESH",
"location": [0.0, 0.0, 0.0],
"dimensions": [5.0, 0.2, 3.0],
"ifc_class": "IfcWall",
"global_id": "2O2Fr$t4X7Zf8NOew3FK6X"
}
ifc_class and global_id are null if no IFC data is associated.
get_psets (QUERY)¶
Returns IFC property sets and quantity sets for one or more objects. Accepts any mix of GlobalIds and Blender object names. Up to 100 targets per call (combined across both lists).
Inputs (at least one entry required between the two lists):
{
"global_ids": ["2O2Fr$t4X7Zf8NOew3FK6X"],
"names": ["IfcWall/MyWall"]
}
Returns an ordered results list that mirrors the input order
(GlobalIds first, then names). Each entry records the original
request, plus either the pset payload or an error field:
{
"results": [
{
"request": {"global_id": "2O2Fr$t4X7Zf8NOew3FK6X"},
"object": {
"name": "IfcWall/MyWall",
"global_id": "2O2Fr$t4X7Zf8NOew3FK6X",
"ifc_class": "IfcWall"
},
"property_sets": {
"Pset_WallCommon": {
"IsExternal": true,
"LoadBearing": false,
"FireRating": "F30"
}
},
"quantity_sets": {
"Qto_WallBaseQuantities": {
"Length": 5.0,
"Height": 3.0,
"NetArea": 14.5
}
}
},
{
"request": {"name": "IfcDoor/Missing"},
"error": "not found"
}
]
}
Each property_sets / quantity_sets map passes through IfcOpenShell's native
ifcopenshell.util.element.get_psets shape, so besides the real property names
each set also carries an integer id key (the STEP id of the underlying
IfcPropertySet / IfcElementQuantity); clients can ignore it.
Returns a clear error if no IFC project is loaded. Per-target lookup
failures (missing GlobalId, missing object name, object without IFC link)
appear as error entries in the results list rather than aborting the
batch.
get_viewport_screenshot (QUERY)¶
Captures the 3D viewport, optionally aiming and framing it first. The
render is downscaled so its longest edge fits max_size and encoded as
JPEG by default, keeping the response safely inside MCP size caps (some
clients enforce 1 MB per tool result). Scene render settings are restored
after the capture; the viewport orientation persists.
Inputs (all optional):
{
"max_size": 800,
"format": "jpeg",
"quality": 85,
"view": "iso",
"fit": "all",
"include_objects": false,
"max_objects": 50
}
| Input | Values | Notes |
|---|---|---|
max_size |
64-2048, default 800 | Longest edge in px. Downscales only, never upscales. |
format |
jpeg (default), png |
JPEG is much smaller; PNG is lossless. |
quality |
1-100, default 85 | JPEG only. |
view |
top, bottom, front, back, left, right, iso, camera |
Aims the viewport first. Axis names give orthographic views, iso a perspective isometric, camera the scene camera. Omit to keep the current orientation. |
fit |
all, selected |
Frames everything or the current selection. Combines with view. |
include_objects |
boolean, default false | Adds screen-space 2D bounding boxes keyed by GlobalId to the text output. |
max_objects |
1-200, default 50 | Cap for include_objects; largest boxes kept, truncation flagged. |
Returns, in order:
- An MCP image content block (
image/jpegorimage/png). The image comes first because some MCP clients mishandle mixed content. - A text block with the saved file path, dimensions and byte size, the
attached base64 length (so a client-side image drop is diagnosable from
text), and structured viewport state: rotation quaternion,
perspective mode (
PERSP/ORTHO/CAMERA),is_orthographic_side_view, view distance, and pivot location.
With include_objects=true, the viewport state also lists in-frame objects:
{
"objects_in_view": [
{
"name": "IfcWall/MyWall",
"ifc_class": "IfcWall",
"global_id": "2O2Fr$t4X7Zf8NOew3FK6X",
"box": [0.329, 0.55, 0.712, 0.563]
}
],
"objects_in_view_total": 1250,
"objects_truncated": true
}
box is [x_min, y_min, x_max, y_max], normalized 0-1 with the origin at
the image's top-left (smaller y is higher on screen). Boxes are
approximate but preserve relative spatial layout, which lets a text-only
agent reason about containment, left/right/above/below relations, and
relative sizes even when its client does not deliver tool-result images.
If Blender is running in --background mode or has no usable 3D viewport,
the tool returns an error message instead (and view/fit require an open
3D viewport).
get_ifc_project_info (QUERY)¶
Inputs: none.
Returns:
{
"schema": "IFC4",
"project_name": "My Project",
"project_global_id": "0YvctVUKr0kugbFTf53O9L",
"entity_counts": {
"IfcSite": 1,
"IfcBuilding": 1,
"IfcBuildingStorey": 3,
"IfcWall": 28,
"IfcDoor": 9,
"IfcWindow": 14,
"IfcSlab": 6,
"IfcSpace": 22,
"IfcColumn": 0,
"IfcBeam": 0,
"IfcRoof": 0,
"IfcStair": 0
},
"materials": {
"count": 14,
"names": ["Concrete", "Glass", "Steel", "Timber"],
"truncated": false
},
"classifications": {
"count": 1,
"systems": [
{"name": "Uniclass 2015", "source": "NBS", "edition": "v1.20"}
],
"truncated": false
}
}
materials.names is sorted and de-duplicated; materials.count is the raw
IfcMaterial count. Both lists are capped at 100 entries; truncated: true
means more entities exist than were returned.
If no IFC project is loaded, returns a clear error.
execute_ifc_code (EDIT)¶
Preferred code execution tool. Runs IfcOpenShell / Bonsai API code with
bpy access blocked. Use this for all IFC/BIM data operations.
Pre-injected namespace (no imports needed):
| Variable | What it is |
|---|---|
ifc |
The currently loaded IFC file (ifcopenshell.file or None) |
ifcopenshell |
The ifcopenshell module |
ifc_api |
ifcopenshell.api (high-level create/edit operations) |
element_util |
ifcopenshell.util.element (psets, qtos, traversal) |
tool |
bonsai.tool module (or None if unavailable) |
get_ifc_file() |
Returns the loaded IFC file, raising a clear error if none is open |
get_default_container() |
Returns the active spatial container (e.g. the active storey) |
save_and_load_ifc(path=None) |
Saves the project (to its own file by default) and reloads it |
Viewport sync: edits made here change the in-memory IFC model but do
not appear in the Blender viewport until the project is reloaded. After a
batch of edits, call save_and_load_ifc() (or use save_ifc_file with
reload=true). Reload once per batch, not per edit; it rebuilds the whole
scene.
Inputs:
{ "code": "walls = ifc.by_type('IfcWall')\nfor w in walls:\n print(w.Name, w.GlobalId)" }
Returns:
{
"success": true,
"stdout": "MyWall 2O2Fr$t4X7Zf8NOew3FK6X\n",
"stderr": "",
"stdout_truncated": false,
"stderr_truncated": false,
"stdout_bytes": 38,
"stderr_bytes": 0,
"error": null,
"traceback": null,
"ifc_available": true,
"namespace_keys": ["ifc", "ifcopenshell", "ifc_api", "element_util", "tool",
"get_ifc_file", "get_default_container", "save_and_load_ifc"]
}
If the code contains bpy imports or references, the tool returns an error
directing you to use execute_blender_code instead.
execute_blender_code (EDIT)¶
Fallback for operations that genuinely require bpy (viewport manipulation,
rendering, object transforms, modifiers). For IFC/BIM data work, always
prefer execute_ifc_code.
The same three helper functions as execute_ifc_code are pre-injected:
get_ifc_file(), get_default_container(), and save_and_load_ifc(path=None).
Inputs:
{ "code": "import bpy; print(len(bpy.context.scene.objects))" }
Returns:
{
"success": true,
"stdout": "42\n",
"stderr": "",
"stdout_truncated": false,
"stderr_truncated": false,
"stdout_bytes": 3,
"stderr_bytes": 0,
"error": null,
"traceback": null
}
Errors are reported with success: false, a stringified error, and the full
traceback.
stdout/stderr are each capped at 256 KB to keep one MCP message bounded;
when truncation occurs, the corresponding _truncated flag is true and
_bytes reports the original size before trimming.
save_ifc_file (EDIT)¶
Inputs (all optional):
{ "output_path": "/abs/path/to/output.ifc", "overwrite": false, "reload": false }
Two modes:
- In-place save (
output_pathomitted): saves the project back to its own file, like Bonsai's File > Save IFC. There is no overwrite guard here, since writing the project's own file is the point. Fails with a clear error if the project has never been saved (no path yet). - Save-as (
output_pathgiven): writes to the new path. Refuses to overwrite an existing file unlessoverwrite=true; the parent directory must already exist. The project keeps pointing at its original file.
With reload=true, the project is reloaded from the saved file afterwards,
clearing and rebuilding the Blender scene so IFC-level edits made via
execute_ifc_code become visible in the viewport.
Returns:
{
"saved": true,
"output_path": "/abs/path/to/output.ifc",
"in_place": false,
"method": "bonsai.bim.export_ifc.IfcExporter",
"reloaded": false
}
method reports which writer ran: "bonsai.bim.export_ifc.IfcExporter"
(preferred; Bonsai's exporter first syncs pending Blender-side edits into
the IFC model) or "ifcopenshell.write" (fallback when Bonsai is not
available).