nhantruong96

bonsai-mcp

Community nhantruong96
Updated

MCP server for IFC: query, view through Blender's viewport, and edit with IFC semantics (IfcOpenShell + Bonsai). No raw mesh tools.

bonsai-mcp

testspythonifcopenshellbonsaimcplicense

MCP server to read, view and edit IFC models with IfcOpenShell and Bonsai (Blender).

Query a model, look at it through Blender's viewport, and edit it with IFCsemantics — profiles, placements, property sets — never raw mesh operations.The read tools run with Blender closed.

Status: Stage 3 — read tools, Blender/Bonsai viewport, and IFC-semantic editing.

Table of contents

  • 1. Requirements
  • 2. Install
  • 3. Install the Blender add-on
  • 4. Selfcheck
  • 5. Claude Desktop config
  • 6. Claude Code config
  • 7. Usage procedure
  • 8. Tool reference
  • 9. Safe editing procedure
  • 10. Unit conventions
  • 11. Environment variables
  • 12. Troubleshooting
  • 13. Development

1. Requirements

Python >= 3.11 (3.13 recommended)
uv >= 0.5
OS Windows 11 (primary), macOS, Linux
Blender >= 4.2, only needed for the bl_* tools
Bonsai the build matching Blender's Python (Blender 5.x → bonsai_py313)

The ifc_* tools run with Blender closed. The bl_* tools need Blender + Bonsai + the add-on.

2. Install

git clone https://github.com/nhantruong96/bonsai-mcp.git
cd bonsai-mcp
uv sync

To pin Python 3.13:

uv sync --python 3.13

Sample model. --selfcheck and pytest use KIT's FZK-Haus model. It is not committed — it is not this project's file — and is downloaded automatically on first run. Nothing to do.

Fetch it manually if the machine is offline at run time:

curl -L -o tests/fixtures/AC20-FZK-Haus.ifc https://www.ifcwiki.org/images/e/e3/AC20-FZK-Haus.ifc

Or point the selfcheck at any IFC file:

uv run python -m bonsai_mcp --selfcheck --file "D:\path\to\your.ifc"

3. Install the Blender add-on

Only needed for the bl_* tools. Skip this if you only use the read tools.

3.1. Install Bonsai (if it is not installed yet)

Blender 5.x runs Python 3.13 → take the bonsai_py313 build. Blender 4.2 runs Python 3.11 → bonsai_py311.

Blender → Edit > Preferences > Get Extensions → the ˅ button top right → Install from Disk... → pick the Bonsai zip.

3.2. Build the add-on zip

Compress-Archive -Path C:\Tools\bonsai-mcp\addon\bonsai_mcp_addon -DestinationPath C:\Tools\bonsai-mcp\bonsai_mcp_addon.zip -Force

3.3. Install the add-on

Blender → Edit > Preferences > Get Extensions → the ˅ button top right → Install from Disk... → pick bonsai_mcp_addon.zip.

3.4. Point the add-on at src

Edit > Preferences > Add-onsBonsai MCP Bridge → expand → fill in bonsai-mcp src:

C:\Tools\bonsai-mcp\src

This is what lets the ifc_* tools read the model Blender has open. Without it the bl_* tools still work but ifc_* cannot read through Blender.

Alternatively set the BONSAI_MCP_SRC environment variable, which takes precedence.

3.5. Run

  1. Open an IFC file: Bonsai > Project > Load Project.
  2. In the 3D viewport press NBonsai MCP tab → Start Server.
  3. The panel shows Running, the address 127.0.0.1:9876, request/error counts and the last 20 log lines.

Change the port under Edit > Preferences > Add-ons > Bonsai MCP Bridge > Port, then set BONSAI_MCP_BLENDER_PORT to the same value on the MCP side.

4. Selfcheck

uv run python -m bonsai_mcp --selfcheck
uv run python -m bonsai_mcp --list-tools
uv run python -m pytest

Selfcheck exits 0 on success. All logging goes to stderr.

The Blender integration tests skip themselves when no bridge is running. To run them, Start Server in Blender then:

uv run python -m pytest tests/test_blender_integration.py -v
uv run python -m pytest tests/test_blender_edit_integration.py -v

5. Claude Desktop config

File: %APPDATA%\Claude\claude_desktop_config.json (Windows) ·~/Library/Application Support/Claude/claude_desktop_config.json (macOS)

{
  "mcpServers": {
    "bonsai-mcp": {
      "command": "uv",
      "args": [
        "--directory",
        "C:\\Tools\\bonsai-mcp",
        "run",
        "python",
        "-m",
        "bonsai_mcp"
      ],
      "env": {
        "BONSAI_MCP_BLENDER_HOST": "127.0.0.1",
        "BONSAI_MCP_BLENDER_PORT": "9876",
        "BONSAI_MCP_LOG_LEVEL": "INFO"
      }
    }
  }
}

The env block is optional; leaving it out uses exactly those defaults.

Restart Claude Desktop after editing the file.

6. Claude Code config

claude mcp add bonsai-mcp -- uv --directory C:\Tools\bonsai-mcp run python -m bonsai_mcp

Verify:

claude mcp list

7. Usage procedure

7.1. Without Blender

  1. ifc_open — open the file; check schema, units and element counts.
  2. ifc_list_property_namesalways run this before writing any property query.
  3. ifc_model_info or ifc_spatial_tree — get the storey structure and per-class counts.
  4. ifc_list_entities / ifc_select — filter to the elements you need; page with limit + offset.
  5. ifc_get_element — full detail for one element.
  6. ifc_geometry_info — geometry, profile and absolute placement of that element.
  7. ifc_representation_stats / ifc_validate — whole-file QC.

7.2. With Blender

  1. Load Project in Bonsai, then Start Server in the Bonsai MCP panel.
  2. bl_status — confirm the file, the versions, and that the session switched to blender mode.
  3. From here every ifc_* tool reads the model Blender holds. Do not call ifc_open — it is refused by design.
  4. bl_set_view for an overview, bl_focus_element(guid) for one element.
  5. For anything internal pass isolate=true, or use bl_isolate, then bl_isolate(restore=true).

8. Tool reference

8.1. IFC read tools

Tool Parameters Returns
ifc_open path schema, exporter, creation dates, units, entity/element totals, ifcopenshell.version
ifc_model_info class_limit=60, storey_limit=200 the above plus a per-class histogram and the storey list
ifc_list_entities ifc_class, limit=50, offset=0 real total plus one page: guid, name, class, storey
ifc_get_element guid | step_id attributes, psets, quantities, material, type, container
ifc_select query, limit=50, offset=0 ifcopenshell.util.selector results
ifc_spatial_tree max_depth=4, class_limit=10 Project → Site → Building → Storey with element counts per node
ifc_list_property_names limit=200, properties_per_set=60 every pset/property name that actually exists, with occurrence counts
ifc_geometry_info guid | step_id, include_metrics=true representations, items, profiles, absolute 4×4 matrix, bbox, volume, area; plus flat profiles[] and extrusions[]
ifc_representation_stats limit=60, shared_map_limit=20, map_scan_limit=2000 breakdown by RepresentationIdentifier/Type and item class, parametric/tessellated share, most-shared maps
ifc_validate limit=50, express_rules=false condensed ifcopenshell.validate report

Query syntax for ifc_select:

IfcWall
IfcWall, IfcSlab
IfcBeam, Name=/B-.*/
IfcSpace, Pset_SpaceCommon.IsExternal=TRUE

8.2. Blender tools

Tool Parameters Returns
bl_status timeout=30 whether Blender is reachable, Bonsai version, the IFC file it has open, ifcopenshell comparison of both sides, session state
bl_screenshot width=1280, timeout=30 viewport PNG plus its real dimensions
bl_focus_element guid, isolate=false, width=1280, timeout=30 selects and zooms to the element, returns the image and what was focused
bl_isolate guids[], restore=false, width=1280, timeout=30 hides everything else, returns the image and hidden/kept counts
bl_set_view direction, frame_all=true, width=1280, timeout=30 sets the viewpoint, returns the image
bl_reload guids[] (empty = all), timeout=60 resyncs the viewport from the in-memory IFC data

direction: front, back, left, right, top, bottom, axo.

width bounds the image's longest side, not literally its width. The viewport aspect ratio is preserved. Clamped to 320–2048.

bl_reload does not reload from disk; it rebuilds the viewport from the in-memory IFC data.

8.3. Edit tools, group A: attributes

No geometry is touched. Each takes guid for one element or query for many.

Tool Parameters
ifc_set_attribute attribute, value, guid | query, max_elements=100
ifc_set_pset_value pset, property, value, guid | query, max_elements=100
ifc_add_pset pset, properties{}, guid | query, max_elements=100
ifc_remove_pset pset, guid | query, max_elements=100

8.4. Edit tools, group B: placement

Tool Parameters
ifc_move_element dx, dy, dz (metres), guid | query, max_elements=100
ifc_rotate_element axis (x/y/z), degrees, guid | query, max_elements=100
ifc_set_placement guid, matrix 4×4 (translation in metres)

ifc_rotate_element spins the element about its own origin, not the project origin.

8.5. Edit tools, group C: parametric geometry

Tool Parameters
ifc_edit_profile guid, params{}, apply_to_all=false
ifc_set_extrusion_depth guid, depth (metres), apply_to_all=false
ifc_patch recipe, args{}, allow_file_replacement=false

Recipes: ResetAbsoluteCoordinates, OffsetObjectPlacements, SetWorldCoordinateSystem, ConvertLengthUnit, Optimise.

args uses the recipe's own parameter names; a wrong name is answered with the accepted list and their defaults.

8.6. Saving

Tool Parameters
ifc_save path, overwrite=false, validate=true
ifc_revert

9. Safe editing procedure

  1. Run ifc_geometry_info before any geometry edit — check the real parameter names and whether the element shares a representation map.
  2. Edit. The session's first edit automatically writes <name>.backup-<timestamp>.ifc beside the original.
  3. Read the before/after values in the result. Every edit tool returns both.
  4. In blender mode, use bl_focus_element or bl_screenshot to look at the result. The viewport is already resynced.
  5. ifc_save to a new path. Overwriting requires an explicit overwrite=true.
  6. If it went wrong, ifc_revert returns to the state before the first edit.

Guardrails

Guardrail Behaviour
Automatic backup Once per session, taken just before the first edit
Never overwrite the original ifc_save requires a new path; overwriting needs overwrite=true
Shared geometry Group C refuses when a map or profile is shared, reporting how many elements would be affected; pass apply_to_all=true to change all of them
No tessellation Editing tessellated geometry is refused, and no tool creates a Tessellation
Bulk ceiling A query matching more than max_elements (default 100) is refused without changing anything
Before and after Every edit tool returns before, after, changed — never {"status": "ok"}
Viewport sync In blender mode, Bonsai rebuilds exactly the affected elements after each edit

Note: Blender's own undo (Ctrl+Z) does not cover MCP edits. Use ifc_revert.

10. Unit conventions

Quantity Unit returned Note
Coordinates, dimensions metres (*_m, origin_m, bounding_box_m) already multiplied by length_unit_scale_to_metre
Profile parameters both parameters_file_units and parameters_metres
Extrusion depth extrusion_depth_file_units and extrusion_depth_m
Volume / area volume_m3, surface_area_m2 from the IfcOpenShell kernel (SI)
Storey elevation elevation_m
Angles degrees
4×4 matrix matrix_4x4_metres (translation column converted to metres) and matrix_4x4_file_units row-major, absolute
Values passed into edit tools metres and degrees dx/dy/dz, depth, matrix, degrees
params of ifc_edit_profile the file's own units use the numbers shown in parameters_file_units

The placement matrix is absolute: the whole nested IfcLocalPlacement chain is multiplied through.

11. Environment variables

Variable Set where Default Effect
BONSAI_MCP_LOG_LEVEL MCP side INFO DEBUG, INFO, WARNING, ERROR
BONSAI_MCP_BLENDER_HOST MCP side 127.0.0.1 add-on address
BONSAI_MCP_BLENDER_PORT MCP side 9876 add-on port; must match the preference set in Blender
BONSAI_MCP_SRC Blender side path to src; takes precedence over the add-on preference

12. Troubleshooting

Symptom Fix
No IFC model is open. Call ifc_open(path) first. Call ifc_open first
IFC file not found: ... Use an absolute path; escape \\ in JSON on Windows
ifc_select returns total: 0 when the data is definitely there Run ifc_list_property_names; real pset names often differ from the standard (BaseQuantitiesQto_WallBaseQuantities)
'IfcXxx' is not usable with by_type Wrong class name; see the suggestions in the error, or ifc_model_info
Result looks truncated Read total, has_more, next_offset and call again with offset
Coordinates are in the wrong place Use origin_m / matrix_4x4_metres, not the raw IfcLocalPlacement
metrics.available: false The element has no geometry the kernel could build; read metrics.error
No profile found under representations Clipping booleans nest; use the flat profiles[] and extrusions[]
profile.parametric: false It is an IfcArbitraryClosedProfileDef — an explicit curve with no parameters to edit
representation_maps.scan_note is not null The map scan was capped; raise map_scan_limit
ModuleNotFoundError: mcp.server.fastmcp mcp moved to 2.x — re-run uv sync (pyproject.toml pins <2)
Could not download the sample model Offline. Fetch it manually with the curl command in section 2, or pass --file
returned N bytes that do not start with 'ISO-10303-21' A captive portal or error page answered instead of the file; nothing was written
sample model sha256 is ..., expected ... The upstream file changed; tests asserting exact counts may now fail
pytest reports many s (skipped) Blender integration tests, or a missing sample model; see the reasons with -rs
Server does not appear in Claude Wrong --directory path, or Claude was not restarted
No log output anywhere Logging goes to stderr, not stdout

Blender

Symptom Fix
Blender is not running, or the Bonsai MCP add-on has not been started In Blender: 3D viewport → N → Bonsai MCP tab → Start Server
The Bonsai MCP panel is not there The add-on is not enabled: Edit > Preferences > Add-ons → tick Bonsai MCP Bridge
Could not bind 127.0.0.1:9876 The port is taken; change Port in the preferences and set BONSAI_MCP_BLENDER_PORT to match
VERSION MISMATCH in bl_status Pin them together: set ifcopenshell==<Bonsai's build> in pyproject.toml and re-run uv sync
query_modules_loaded: false The add-on has no path to src; see section 3.4
No IFC project is loaded in Blender Load one first with Bonsai > Project > Load Project
ifc_open refused with ModelLockedError By design — Blender holds the model; load the other file in Blender instead
bl_focus_element returns an image without the element in it It is behind other geometry; call again with isolate=true
The image is very tall or very narrow The image follows the viewport aspect ratio; make the Blender window landscape
No 3D viewport is available Blender is headless, or the workspace has no VIEW_3D area
main thread is busy Blender has a dialog open or a modal operator running; close it, or raise timeout
Screenshots show the old geometry after an edit Call bl_reload

Editing

Symptom Fix
EditRefused: ... apply to N element(s), not one Shared geometry. Check with ifc_geometry_info; if that is what you want, pass apply_to_all=true
EditRefused: ... will not tessellate The element has no parameters left; re-export it upstream as a swept solid
matches N elements, over the max_elements ceiling Narrow the query, or raise max_elements after checking the count with ifc_select
matched nothing, so there was nothing to edit Run ifc_list_property_names; real pset names often differ from the standard
has no property set named '...' Create it first with ifc_add_pset
has no parameter(s) [...] The error already lists the profile's real parameter names
FileExistsError: Refusing to overwrite Save to a different path, or pass overwrite=true
NoBackupError Nothing has been edited yet, so no backup exists
rebuilds the model into a new file object ConvertLengthUnit / Optimise: standalone needs allow_file_replacement=true; blender mode needs the project closed in Blender first
BackupError: Could not write the safety copy The folder holding the original is not writable; nothing was changed
Ctrl+Z in Blender does not undo an edit Blender's undo does not cover MCP edits; use ifc_revert

13. Development

uv sync
uv run python -m pytest -v
uv run python -m bonsai_mcp --selfcheck

Conventions:

  • uv.lock is committed; .venv/ never is.
  • No print(). stdout is the JSON-RPC channel; log through config.py to stderr.
  • Nothing in src/ may import bpy — the server must run with Blender closed. A test enforces it.
  • addon/bonsai_mcp_addon/protocol.py runs on the socket thread and must never touch bpy; all bpy calls live in handlers.py and run only from the main-thread timer. bpy is not thread safe — a call from the wrong thread crashes Blender dozens of commands later, somewhere unrelated. An AST test enforces it.
  • Failing tools return the full traceback in the traceback field; Blender-side failures carry Blender's own traceback too.
  • Every write goes through ifcopenshell.api, and through Session.write; no tool touches the model directly.
  • There are no raw-mesh tools. Editing a mesh freely and writing it back turns a SweptSolid into a Tessellation — parameters gone, file inflated, recipient unable to edit. A test enforces it.
  • The guardrail fixture is generated by tests/steel_fixture.py: 4 beams sharing one IfcRepresentationMap, 1 column with its own profile, 1 tessellated slab.

License: MIT — see LICENSE.

MCP Server · Populars

MCP Server · New