MCP — technology preview
MCP is not part of the stable Core 0.3 contract. ifc-mcp contains
transport-independent semantic read-only queries; ifc-mcp-server is a local
JSON-RPC/MCP stdio adapter. Neither contains an LLM client or credentials. MCP transport is stdio; an
optional loopback HTTP listener serves read-only Viewer sessions.
Available server tools
Semantic Stages 1–2 use ifc-mcp-server.dto.v3:
open_model— open an IFC file, identify its pinned official schema, and build reusable indexes once;close_model— idempotently release source bytes, indexes, and diagnostics;model_info— IFC schema, revision, byte/entity counts, bounded spatial and unit-assignment summaries, and diagnostic counts;model_diagnostics— a bounded diagnostics page filtered by severity/code;search_entities— search by IFC type/subtypes, name, GlobalId, and spatial container;get_spatial_tree— a page of spatial nodes preserving parent relationships;get_elements_in_container— direct or recursive container contents;get_entity— a semantic entity card;get_entity_raw— the separate original STEP record.
The previous get_properties and get_relations tools remain until later
stages, together with the IDS tools. model_summary and find_entities are no
longer advertised; the previous raw get_entity behavior moved to
get_entity_raw.
Diagnostics are bounded while they are collected: completed passes retain exact
counts, while exhausted bounded validation work marks the summary incomplete;
materialized details never exceed the server limit. The
diagnostics_truncated marker survives severity/code filters. Name is
resolved from EXPRESS metadata, and GlobalId is restricted to IfcRoot
descendants. Before allocating an owned string, semantic projections enforce the
official 22-byte IfcGloballyUniqueId bound; an oversized malformed value remains
available in raw STEP. casefold_exact/contains use full Unicode case folding.
get_entity decodes only Name, Description, and PredefinedType under its
query budget/deadline. Unknown IFC types and malformed Name values return typed
errors. Raw STEP source ranges and encoded lossy/JSON size are checked before
copying the full string; non-UTF-8 bytes remain available through a lossy UTF-8
projection with replacement characters. model_info summary lists stop after
one probe item beyond the limit and share the request entity budget/deadline; a
schema without IfcFacility skips that model scan. Scanner DiagnosticsLimit
always marks the diagnostic total as a lower bound and preserves the truncation
marker. close_model checks its response budget before releasing the model.
Successful close responses and open_model/close_model errors are explicit root
JSON Schema variants; lifecycle errors carry stable diagnostic codes without a
fabricated model revision. Full validation without query indexes builds one
compact type lookup instead of repeating a linear entity scan for every IFC
reference. Stage 1 retains a bounded Express ID/type/GlobalId/reference/Pset-Qto
profile so the preserved get_properties and get_relations tools remain
correct. max_index_bytes bounds indexes; the aggregate registry budget also
charges source capacity, scanner/type metadata, the complete EXPRESS AST, model
caches, diagnostics, and the session allocation. If any mandatory index or the
complete retained session does not fit, open fails rather than silently dropping
semantics; rollback and close release the full charge. A malformed
IFC type is bounded
before ownership and projected as <invalid_ifc_type> while raw STEP stays
inspectable. Model-bound string prechecks use the DTO v3 error envelope.
max_response_entities caps the combined model_info references and requested
search page.
Request contract
open_model accepts schema + path + context; close_model accepts
schema + model_id + context. Other model-bound calls accept:
{
"schema": "ifc-mcp-server.dto.v3",
"model_ref": {
"model_id": 1,
"expected_revision": 0
},
"arguments": {},
"context": {
"request_id": "request-1",
"budget": {
"max_entities_scanned": 100000,
"max_values_decoded": 100000,
"max_relation_expansions": 100000,
"max_response_bytes": 1048576,
"soft_timeout_ms": 5000
}
}
}
Search returns at most 1,000 items per page. Its cursor is bound to the tool,
model, revision, and complete normalized request. Spatial container, storey_id, and space_id filters are supported.
Material, classification, and property filters fail closed until Stage 3
instead of being silently ignored.
Defaults remain 512 MiB per model, 8 open models, Express ID up to 10,000,000, 4 KiB per string argument, 10,000 retained diagnostics/IDs, and a 1 MiB input frame. Each response is also constrained by the request budget.
Run
cargo build --release -p ifc-mcp-server
./target/release/ifc-mcp-server
Client configuration:
{
"mcpServers": {
"ifccore": {
"command": "/absolute/path/target/release/ifc-mcp-server",
"args": []
}
}
}
The process can read any file allowed by its OS account. Apply OS-level permissions and sandboxing. See the preview specification.
Semantic Stage 3
Adds get_entity_relations, get_materials, get_quantities, and v3 forms of
get_property/get_properties. Legacy forms and the IDS family are preserved.
Values retain IFC datatype, units, independent origin/ownership, and exact
association paths; same-name instance/type values are listed separately.
Search supports material/classification/property predicates before pagination,
with cumulative budgets and filter_coverage (material → classification → properties).
get_quantities never substitutes geometry for Qto.
The preview supports scalar attributes, IfcPropertySingleValue, complex
containers, and simple Qto. Other property/aggregate kinds, unknown relation kinds,
and unsupported units fail with typed errors. Request contracts, limitations,
and acceptance tests: MCP technology preview.
Viewer, IFC writing, and deployment are unchanged.
Stage 6: semantic adapters
The first sequential slice adds get_entity_location, get_related_entities,
find_by_property, find_by_material, material_summary, quantity_coverage,
and summarize_entities over the shared Stage 1–4 engines. All use model/revision-
aware v3 DTOs and remain read-only. These adapters do not establish completion
of geometry, temporary Viewer sessions, or the Stage 5 preview release. See the
canonical plan for current limitations and
acceptance evidence.
Stage 6: geometry queries
get_bounding_box, get_geometry_metrics and query_elements_in_box use
ifc-geometry, never Qto fallback. computed_geometry is explicit;
ifc_model_world includes placements but is not a georeferenced CRS. Length
units come from project UnitsInContext; missing/ambiguous/unsupported units fail.
The preview caps are 50,000 records, 4 MiB source range, 1,000 products,
200,000 triangles, 64 MiB geometry and 128 expanded mesh objects. Callers may
only lower them. Full-model preflight conservatively reserves source bytes ×
product count against decode/relation work; large models can be rejected even
for one selected product. Synchronous extraction is not cooperatively
cancellable; soft deadlines are checked around it, with engine hard limits.
Box queries intersect AABBs, not exact solids. Metrics are tessellated values;
unverified topology yields an explicit unavailable reason, and multiple meshes
are not reported as a union volume. Existing internal workspace dependency
ifc-geometry is reused; no new external package or stable facade API change.
Acceptance uses three official schemas and a real stdio two-box fixture.
Local Viewer sessions are implemented as a separate opt-in capability below.
Stage 6: local Viewer sessions — implemented
create_viewer_session, update_viewer_selection, and close_viewer_session
use the v3 model/revision envelope. The backend is disabled by default
(viewer_disabled); only the host can enable it with --enable-local-viewer.
MCP remains stdio. A separate read-only HTTP listener binds to 127.0.0.1 on
an ephemeral port. Browser and MCP server must run on the same machine; a
remote VM loopback URL is not a public link. No CDN, hosted upload, source IFC,
filesystem path, or write operation is exposed.
Create accepts either entity_ids (at most 16) or the shared filter, plus
selection, ttl_seconds (1–900, default 300), and geometry limits. Larger
selections and unsupported geometry fail rather than truncate. It returns
viewer_session_id, url, model_id, model_revision, ttl_seconds.
Update takes the handle and selection; close takes the handle. Revision is
checked before semantic argument decoding.
The URL carries a random 256-bit bearer capability in its fragment; the browser removes it from the address bar and uses Authorization. Host/Origin/Fetch-Site checks, CSP, no-store and no-referrer protect the browser boundary. Any local client possessing the token can read that scene: this is not OS-user identity or multi-tenant sharing. The owning actor boundary is the stdio server instance.
Limits: 4 sessions, 8 MiB serialized scene, 16 MiB total JSON payloads. Typed meshes and bounded temporary serialization/HTTP copies are additional; JSON quotas are not total RSS limits. No session IFC copies or temp files are created. Timer expiry works without requests. Close and close_model revoke future requests and release retained payload; an in-flight response may finish. A lifecycle lock prevents create-vs-close races. Browser expiry/revocation/ network failure clears canvas and GPU buffers.
Acceptance uses security/lifecycle tests, real stdio, and
scripts/test-mcp-viewer-session.mjs against the actual Rust server and IFC:
2 meshes/24 triangles, selection 2 → 3, orbit, close, independent TTL, no JS errors.
Server-only getrandom 0.3.4 (MIT OR Apache-2.0, already in Cargo.lock) supplies
portable OS cryptographic entropy absent from std. It runs only at session
creation, not in transport-free MCP or the stable WASM facade. Size/build impact
is not quantified. Hosted service, write tools and Stage 5 preview release are
not claimed complete by Stage 6.