bonsai-mcp
MCP server for reading, viewing, and editing IFC models using IfcOpenShell and Bonsai. Enables querying model data, viewing via Blender's viewport, and making IFC-semantic edits without raw mesh operations.
README
bonsai-mcp
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 IFC semantics — 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-ons → Bonsai 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
- Open an IFC file:
Bonsai > Project > Load Project. - In the 3D viewport press
N→ Bonsai MCP tab → Start Server. - The panel shows
Running, the address127.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
ifc_open— open the file; check schema, units and element counts.ifc_list_property_names— always run this before writing any property query.ifc_model_infoorifc_spatial_tree— get the storey structure and per-class counts.ifc_list_entities/ifc_select— filter to the elements you need; page withlimit+offset.ifc_get_element— full detail for one element.ifc_geometry_info— geometry, profile and absolute placement of that element.ifc_representation_stats/ifc_validate— whole-file QC.
7.2. With Blender
- Load Project in Bonsai, then Start Server in the Bonsai MCP panel.
bl_status— confirm the file, the versions, and that the session switched toblendermode.- From here every
ifc_*tool reads the model Blender holds. Do not callifc_open— it is refused by design. bl_set_viewfor an overview,bl_focus_element(guid)for one element.- For anything internal pass
isolate=true, or usebl_isolate, thenbl_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
- Run
ifc_geometry_infobefore any geometry edit — check the real parameter names and whether the element shares a representation map. - Edit. The session's first edit automatically writes
<name>.backup-<timestamp>.ifcbeside the original. - Read the
before/aftervalues in the result. Every edit tool returns both. - In blender mode, use
bl_focus_elementorbl_screenshotto look at the result. The viewport is already resynced. ifc_saveto a new path. Overwriting requires an explicitoverwrite=true.- If it went wrong,
ifc_revertreturns 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 (BaseQuantities ≠ Qto_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.lockis committed;.venv/never is.- No
print(). stdout is the JSON-RPC channel; log throughconfig.pyto stderr. - Nothing in
src/may importbpy— the server must run with Blender closed. A test enforces it. addon/bonsai_mcp_addon/protocol.pyruns on the socket thread and must never touchbpy; allbpycalls live inhandlers.pyand run only from the main-thread timer.bpyis 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
tracebackfield; Blender-side failures carry Blender's own traceback too. - Every write goes through
ifcopenshell.api, and throughSession.write; no tool touches the model directly. - There are no raw-mesh tools. Editing a mesh freely and writing it back turns a
SweptSolidinto aTessellation— 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 oneIfcRepresentationMap, 1 column with its own profile, 1 tessellated slab.
License: MIT — see LICENSE.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.