ifcviewx
The pip package behind IFCViewX Local Studio. One install serves the full
viewer at http://127.0.0.1:8765 and adds everything a browser tab alone
cannot do:
- IFC to
.ifcxconversion with IfcOpenShell, with live progress and cancel - native IfcOpenShell Python, so the browser never downloads a runtime
- model checks and element schedules that need no generated code
- the viewer exposed to MCP clients (Claude Desktop, Claude Code) as tools
- an optional assistant proxy so the provider key never reaches the browser
Everything stays on your machine.
Install and run
Until the package is on PyPI, install the wheel from the latest release by URL. IfcOpenShell comes with it, so there is nothing else to add (~100 MB):
pip install https://github.com/nbharathik/ifc-viewx/releases/latest/download/ifcviewx-0.1.0-py3-none-any.whl
ifcviewx # serves the viewer and opens the browser
ifcviewx model.ifc # same, with the model staged and opened
ifcviewx model.ifc --convert # convert to .ifcx first, then open
ifcviewx convert model.ifc # terminal conversion (also: ifcx-convert)
ifcviewx mcp # MCP over stdio, for AI clients
IfcOpenShell is a plain dependency, so conversion, native Python and model
checks are there by default. On a Python it has no wheel for, the install still
succeeds and those three report themselves as not configured rather than
breaking the viewer. A second ifcviewx model.ifc while one instance runs
reuses it: the file is staged into the shared store and the browser opens on
the running service.
Local Studio is a self-contained app, not an add-on to the hosted viewer. It
carries its own copy of the viewer, serves it from 127.0.0.1, and hands that
page its session token, so it opens with everything on and nothing to type. The
hosted copy is a separate app that never talks to this service: there is no
pairing step and no token to paste anywhere.
Working from a checkout: cd local-bridge && pip install -e .. The service
then serves the repo's dist/ from npm run build at the root.
CLI flags
| Flag | Meaning |
|---|---|
--port N |
serve on this port (default 8765) |
--token T |
fix the session token instead of a random one |
--convert |
convert the given model to .ifcx before opening |
--readonly |
refuse uploads, conversions and edits |
--no-python |
disable code execution entirely |
--no-browser |
do not open a browser |
Configuration
Environment variables use the IFCVIEWX_ prefix (the pre-rename
IFC_BRIDGE_ names are still read as a fallback).
| Variable | Default | Purpose |
|---|---|---|
IFCVIEWX_TOKEN |
random 128-bit | fixes the session token across runs |
IFCVIEWX_PORT |
8765 |
HTTP/WebSocket port |
IFCVIEWX_APP |
packaged app / repo dist/ |
a built viewer to serve |
IFCVIEWX_MODELS |
~/.cache/ifcviewx/models |
model store |
IFCVIEWX_STATE |
store parent | audit log location |
IFCVIEWX_ORIGINS |
(unset) | extra browser origins to trust besides localhost; only for hosting the viewer yourself |
IFCVIEWX_ROOTS |
(unset) | restrict convert_model to these directories |
IFCVIEWX_ALLOW_PYTHON |
1 |
set 0 to disable code execution entirely |
IFCVIEWX_READONLY |
0 |
set 1 to refuse uploads, conversions and edits |
IFCVIEWX_STORE_GB |
20 |
store quota; oldest models are evicted past it |
IFCVIEWX_MAX_UPLOAD_MB |
2048 |
per-upload ceiling |
IFCVIEWX_PYTHON_TIMEOUT |
120 |
seconds before generated code is killed |
IFCVIEWX_CONVERT_TIMEOUT |
900 |
seconds before a conversion is killed |
IFCVIEWX_MEMORY_GB |
4 |
address-space cap for child processes (POSIX) |
IFCVIEWX_RESULT_TTL_S |
3600 |
how long an unapplied edit result is kept |
IFCVIEWX_LLM_PROVIDER |
(unset) | openai-compatible or anthropic to enable the proxy |
IFCVIEWX_LLM_BASE_URL / _API_KEY / _MODEL |
(unset) | proxy target |
HTTP API
| Route | Purpose |
|---|---|
GET /health |
version and capabilities; store and posture with a token |
POST /model |
upload an IFC, stored by SHA-256 |
POST /convert |
start a conversion job |
GET /jobs/{id} |
job status with percent |
POST /jobs/{id}/cancel |
kill a running conversion |
GET /models/{sha}.ifcx |
the converted model (.ifc serves the source) |
POST /python |
run guarded code, query or edit |
GET /python/result/{id} |
download an edit result |
POST /guard |
check code without running it |
POST /validate |
structural QA, no code execution |
POST /schedule |
element/property table, no code execution |
GET /store, POST /store/prune |
model cache stats and cleanup |
POST /llm/chat |
assistant proxy (when configured) |
GET /audit |
recent activity |
WS /ws?token= |
MCP bridge to the browser |
Everything except /health and /models/{sha} requires the X-IFC-Token
header.
Security
Transport. Bound to 127.0.0.1 only. The Host header must be localhost,
which blocks DNS rebinding that a token check alone would not. Browser origins
must be localhost. The only page this service answers is the copy of the viewer
it served itself, so no site on the internet is trusted, including the hosted
copy of this same viewer. IFCVIEWX_ORIGINS names extra origins for someone
hosting the viewer themselves; it is empty by default, and pointing it at a
page you do not control hands that page everything the token protects.
Authentication. A 128-bit per-run token gates every route that writes, executes or describes this machine. Comparisons are constant time, and repeated failures from one client are throttled.
Generated code passes through three layers, because any one of them can be wrong:
- an AST guard, not a regex scan, so
getattr(x, "__cla" + "ss__")is caught like any other dunder access. Imports are allowlisted, andeval,exec,compile,open,globalsand friends are rejected outright. - a throwaway subprocess with curated
__builtins__(the dangerous names are simply absent), an import hook that re-checks the allowlist at runtime, a scrubbed environment, a temporary working directory, and address-space, CPU and file-descriptor limits where the platform provides them. - the edit contract: code never touches the stored source. It runs against a fresh handle, writes a separate result file, and the change only reaches the model after the user clicks Apply in the viewer.
The reply carries a measured diff (added / removed / modified GlobalIds)
computed by comparing the model before and after, not the summary the code
claims. IFCVIEWX_ALLOW_PYTHON=0 removes the capability altogether.
Data. Uploads are content-addressed, sniffed for a STEP header before they are kept, capped per file and in total, and evicted oldest-first past the quota. Unapplied edit results expire. No filename ever comes from a client.
Auditing. Every guarded action appends one line to
~/.cache/ifcviewx/audit.jsonl: what ran, when, from where, and for code
its hash and first line, never the source itself.
What this is not. The service executes IfcOpenShell code on your machine by
design. The layers above make accidental and casual-hostile code fail closed;
they are not a substitute for an OS sandbox. Run with
IFCVIEWX_ALLOW_PYTHON=0 if you only want viewing, conversion and checks.
MCP client configuration (Claude Desktop example)
{
"mcpServers": {
"ifcviewx": {
"command": "ifcviewx",
"args": ["mcp"],
"env": { "IFCVIEWX_TOKEN": "choose-a-token" }
}
}
}
ifcviewx mcp serves the viewer and the HTTP API too, so the browser side of
the bridge is the same app at http://127.0.0.1:8765.
Tools
Viewer: get_status, get_model_info, get_spatial_tree, get_selection,
select_element, get_properties, set_visibility, show_all, fit_view.
Analysis without generated code: validate_model, element_schedule.
Files and housekeeping: convert_model, list_converted_models,
service_status.
No execution tool. There is no run_python and no other way for an MCP
client to execute code. A client reads the model, drives the viewport and
stages typed edits, and every edit waits for the user to click Apply. Running
IfcOpenShell is the user's, in the viewer's Python Console, which only a human
click starts. The service still executes Python for that console over HTTP,
authenticated with the session token.
Tests and packaging
python -m pytest tests -q
The suite covers guard bypasses, the route authorisation matrix, store quotas and path safety, and sandbox behaviour end to end against a real IFC file.
Building the wheel bundles the viewer: npm run build at the repo root, then
python -m build local-bridge. The hatch hook copies dist/ into the package
and refuses to pack without it. Tagging v* publishes to PyPI via
.github/workflows/publish-pypi.yml (trusted publishing).
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ifcviewx-0.1.1.tar.gz.
File metadata
- Download URL: ifcviewx-0.1.1.tar.gz
- Upload date:
- Size: 1.6 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7f4a93545cebec87bad59c8b670ee784acb3fcba410952f594e7ea3fca7b69d4
|
|
| MD5 |
4279517a80c3141257bdb3c8152ea546
|
|
| BLAKE2b-256 |
04688f50805934c2dbaa3b525bf57352dbc92bca3aa8f9d1d7406c173cf2f3b2
|
Provenance
The following attestation bundles were made for ifcviewx-0.1.1.tar.gz:
Publisher:
publish-pypi.yml on nbharathik/ifc-viewx
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ifcviewx-0.1.1.tar.gz -
Subject digest:
7f4a93545cebec87bad59c8b670ee784acb3fcba410952f594e7ea3fca7b69d4 - Sigstore transparency entry: 2359292609
- Sigstore integration time:
-
Permalink:
nbharathik/ifc-viewx@9e79b7d54a0297b22add161222a8517507cccbbf -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/nbharathik
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@9e79b7d54a0297b22add161222a8517507cccbbf -
Trigger Event:
push
-
Statement type:
File details
Details for the file ifcviewx-0.1.1-py3-none-any.whl.
File metadata
- Download URL: ifcviewx-0.1.1-py3-none-any.whl
- Upload date:
- Size: 1.6 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
79c5bac4502da7ac73d06857ca93f449af07a09f891f689c03341ede508aa7f0
|
|
| MD5 |
91b79c86def7e9ebf4318aad99c51dd4
|
|
| BLAKE2b-256 |
cd7aaf5941535a0d0ca85243cd88a8520530a8a47fa9fc257ab1fdc37f463505
|
Provenance
The following attestation bundles were made for ifcviewx-0.1.1-py3-none-any.whl:
Publisher:
publish-pypi.yml on nbharathik/ifc-viewx
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ifcviewx-0.1.1-py3-none-any.whl -
Subject digest:
79c5bac4502da7ac73d06857ca93f449af07a09f891f689c03341ede508aa7f0 - Sigstore transparency entry: 2359292712
- Sigstore integration time:
-
Permalink:
nbharathik/ifc-viewx@9e79b7d54a0297b22add161222a8517507cccbbf -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/nbharathik
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@9e79b7d54a0297b22add161222a8517507cccbbf -
Trigger Event:
push
-
Statement type: