MCP server
Use Integraph's Model Context Protocol server to build and automate structural engineering workflows with AI agents — create projects, analyse sections, and run fire design calculations programmatically.
Overview
Integraph exposes a Model Context Protocol (MCP) server that lets AI agents (Claude Code, Claude Desktop, or any MCP client) interact with the platform programmatically. The same operations available in the web UI — creating projects, managing tool instances, running analyses, and reading results — are available as typed, discoverable tools over the MCP connection.
Connection
{
"mcpServers": {
"integraph": {
"type": "http",
"url": "https://app.integraph.com.au/mcp"
}
}
}
For local development, point the URL at http://localhost:5001/mcp instead.
API costs
Most MCP tools consume API credits from your Pro allowance. When your client connects and calls tools/list, each tool’s description ends with a (Cost: N credits per call) suffix — the final price after any platform markup, as a whole number of credits (1 credit = 1 AUD micro-dollar). An agent can read this suffix to make budget-aware tool choices without a separate API call, and can call the GetApiCosts tool (or GET /api/v1/billing/api-costs) for the AUD reference rate. Project, design, design-option, catalogue, preference, organisation, billing and admin tools cost 5 credits per call; material reads and light writes cost 20; analysis tools cost more depending on the computation involved. A tool whose description carries no cost suffix is free — since every REST endpoint is metered, that is now only the operator observability tools, which wrap no REST endpoint.
Every successful call also carries the charge back to you out of band: the tool result’s _meta object gains an integraph.com.au/api-usage entry with costCredits, balanceCredits and periodEndAt. That is the MCP counterpart of the X-Api-* response headers on the REST API — the MCP transport cannot carry per-call response headers, because the HTTP response has already begun streaming before your tool runs.
If the balance cannot fund the call, the tool returns an error result carrying code: api_balance_exhausted along with costCredits and balanceCredits, and the tool never runs. It is the same code the REST API returns with 402 Payment Required, so one branch in your agent handles both.
Hierarchy
MCP tools follow the same four-level data hierarchy as the web UI:
Project → Tool Instance → Design Option → Element
When building an automated workflow, the typical sequence is:
CreateProject— establish the project containerListToolDefinitions→CreateToolInstance— add the engineering tool you needAddElement— build up the geometry or load inputs inside the default design option returned byCreateToolInstance- Call the relevant analysis tool (e.g.,
GetConcreteAnalysisByDesignOption,GetFireCapacityByDesignOption)
Managing tool instances
CreateToolInstance
Creates a tool instance and automatically adds a first design option named “Option 1”, so you can start adding elements immediately. Refused with tool_not_in_plan when the project’s organization does not hold the tool — an entitlement granted by another organization does not unlock it here (#5209).
An authenticated caller who belongs to no organization is refused as well (#5412). The gate used to skip whenever the caller’s org list was empty, which was meant to preserve dev-mode behaviour where no auth is configured — but it could not tell that caller apart from an authenticated one holding no membership, and waived the entitlement check for both. It now skips only for a genuinely unauthenticated caller or a superadmin, matching what row-level security already enforced on the same request.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
projectId | Guid | Yes | The project to place the tool instance in |
name | string | Yes | Name for the tool instance (e.g., “Beam B1 — Level 2”) |
toolDefinitionId | int | Yes | Integer ID from ListToolDefinitions |
description | string | No | Optional description |
Returns
{
"status": "created",
"id": "01966d3e-...",
"projectId": "01966d3e-...",
"toolDefinitionId": 3,
"name": "Beam B1 — Level 2",
"description": null,
"createdAt": "2026-07-05T03:14:00.000Z",
"toolDefinition": { "slug": "concrete-designer", "name": "Advanced Concrete Section" },
"defaultDesignOptionId": "01966d3f-...",
"defaultDesignOptionName": "Option 1"
}
Use defaultDesignOptionId as the designOptionId for subsequent AddElement calls without needing a separate ListDesignOptions lookup.
UpdateToolInstance
Renames or re-describes an existing tool instance. Every parameter except toolInstanceId is optional — pass null to leave a field unchanged.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
toolInstanceId | Guid | Yes | The tool instance to update |
name | string | No | New name — must be non-empty if supplied; omit (null) to leave unchanged |
description | string | No | New description; omit (null) to leave unchanged |
Returns
{
"status": "updated",
"id": "01966d3e-...",
"projectId": "01966d3e-...",
"toolDefinitionId": 3,
"name": "Beam B1 — Level 2 (revised)",
"description": "Updated for RFI-047",
"createdAt": "2026-07-01T10:00:00.000Z",
"updatedAt": "2026-07-05T03:15:00.000Z"
}
Updating a tool instance also bumps the parent project’s updatedAt, so the project appears at the top of the “recently updated” order in the dashboard.
Validation
Supplying a name that is an empty string or only whitespace returns an error — the tool never silently stores a blank name:
{ "error": "Name cannot be empty", "code": "validation_failed" }
ListToolInstances
Returns all tool instances in a project, ordered by most recently updated. Each entry includes the tool definition slug and name, plus the number of design options. The response is the {items, total, page, pageSize} list envelope — the tool instances are under items, not the response body itself.
Fire design tools
The fire tools require a Pro — Advanced Concrete Section subscription. Each call costs 750 credits — about AUD 0.00075, though the exact AUD-per-credit rate varies slightly by which pack a credit was bought from.
Both fire tools are id-addressed: they take a designOptionId and read the geometry, reinforcement, tendons, cover, fire exposure and load combinations from the saved design option. See Fire id-addressed tools below.
:::note[The payload fire tools were retired — August 2026 (#5040)]
CalculateHeatTransfer and CalculateFireCapacity accepted a hand-authored JSON blob carrying the outline, voids, bar coordinates and fire exposure on every call. Both are removed, along with their REST peers POST /api/v1/fire/heat-transfer and POST /api/v1/fire/capacity.
Use GetFireHeatTransferByDesignOption and GetFireCapacityByDesignOption instead. They run the same analyses — the same thermal solve, the same EN 1992-1-2 / AS 3600 reduction pipeline — against a design option you have saved, so an agent no longer has to reproduce the resolved bar coordinates, which was the single hardest part of the old payload to get right.
Two inputs the payload tools accepted have no equivalent on the id-addressed pair, because they have no persisted source:
maxElementArea— the mesher auto-sizes. The engineer-facing discretisation control that does survive is the design option’s own arc discretisation tolerance, which both fire reads honour.exposedVoidEdges— a void may be declaredsealedorventilatedin the Fire panel, but individual void edges can no longer be declared fire-exposed.
Both tools still cost 750 credits per call (about AUD 0.00075). :::
Id-addressed concrete and fire tools
Concrete section analyses are id-addressed. Their former blob tools have been withdrawn; only the
reference-data tools CalculateTendonMaterial and CalculateStressStrainCurve still accept a
calculation body. Migrate any agent calling a removed section-analysis tool to the peer below.
Why id-addressed tools exist
The removed blob calls required callers to supply resolved bar coordinates that come out of the
platform’s BarResolver over a persisted layout. An agent cannot reliably reproduce its corner,
cover and fitment decisions. The id-addressed tools take a designOptionId and read every physics
input off the saved section.
Concrete id-addressed tools (18)
Eighteen tools mirror the GET /api/v1/concrete/design-options/{designOptionId}/{quantity} derived reads. All carry the ApiToolAccess:concrete-designer entitlement gate. For API-key callers, the organization that owns the key must hold the concrete-designer add-on at API tier — an entitlement granted by another organization does not unlock it. Bearer-token callers are checked at user level pending #5308.
| Tool | Quantity | Cost (µ$) |
|---|---|---|
GetConcretePropertiesByDesignOption | Section properties (gross, transformed, cracked) | 40 |
GetConcreteAnalysisByDesignOption | Full analysis runner over every checked combination — a combination unchecked on the Actions tab is excluded from the run and from the governing envelope, exactly as on the REST peer (#5957) | 110 |
GetConcreteFlexureByDesignOption | per bending axis, with its utilisation, axial bounds, minimum-moment floor, Cl. 8.1.5 gate and the pure-bending (axis param: x, y, or omit for both) | 80 |
GetConcreteInteractionByDesignOption | N-M interaction diagram with its radial utilisation and any minimum-moment floor, plus designActions — one demand marker per active ULS combination and the addressed one, at most one solved — and the biaxial N-Mx-My surface for every reinforced ULS combination, INCLUDING one whose design is zero — the surface is a property of the section, not of the demand, and the cost of 110 buys it | 110 |
GetConcreteShearByDesignOption | 40 | |
GetConcreteDurabilityCoverByDesignOption | AS 3600 Cl. 4.10.1 code cover | 20 |
GetConcreteMomentCurvatureByDesignOption | M-κ response on one bending plane (plane param: major, minor or demand — required) | 110 |
GetConcreteCreepCoefficientByDesignOption | and (h0 derived from geometry) | 40 |
GetConcreteMkInteractionByDesignOption | Fibre-based P-M-M surface, with designActions carrying the addressed combination as a checked point (its floored design moments, utilisation, resultant-plane curve and capacityBasis). The combination must be a ULS one — the surface is an ultimate-strength envelope, so a serviceability or fire combination is refused with not_applicable_for_combination rather than answered under the wrong heading | 110 |
GetConcreteStressDistributionByDesignOption | ULS fibre stress field | 40 |
GetConcreteSlsStressFieldByDesignOption | SLS fibre stress field | 40 |
GetConcreteTimeDependentByDesignOption | AEMM creep + shrinkage | 110 |
GetConcreteStressCheckByDesignOption | Service stress check | 40 |
GetConcreteCrackWidthByDesignOption | Crack width | 40 |
GetConcreteDeflectionParamsByDesignOption | and | 40 |
GetConcretePtLossesByDesignOption | AS 3600 Cl. 3.4.2 prestress losses | 40 |
GetConcretePtStressesByDesignOption | Transfer and service prestress stresses | 40 |
GetConcreteBatchDesignCheckByDesignOption | All-combinations ULS/SLS design-check roll-up with governing envelope | 380 |
Most tools take a designOptionId and a combinationId (the UUID of a persisted load combination). Six take only a designOptionId, for two different reasons. properties, durability-cover and pt-losses are load-independent: their quantities are properties of the section and the tendon layout as built, so pt-losses answers even for a design option with no load combinations saved. analyze, time-dependent and batch-design-check instead run the analysis over every persisted combination — there is no one combination to name. GetConcreteFlexureByDesignOption fans out over axis: omit for both x and y (labelled), or supply "x" or "y" — priced flat at 80 either way. GetConcreteMomentCurvatureByDesignOption takes a third required parameter, plane ("major", "minor" or "demand"), and returns exactly one plane per call — omitting it is refused rather than defaulted. It does not fan out like axis does, because each plane is an independent fibre solve rather than another field of one result, and a tool’s cost is fixed before the call is handled — so one plane per call keeps the 110 µ$ exactly right instead of under-charging a three-plane answer.
Concrete section persistence (2)
The eighteen tools above answer what a section does. These two read and write what it is — the
whole persisted document: outline and voids, reinforcement, tendons, stirrups, materials, cover,
load combinations and every design parameter. Both carry the ApiToolAccess:concrete-designer
gate, and both wrap the same handlers the section editor’s own load and save call, so an agent’s
edit is the section the engineer opens, not a parallel copy.
| Tool | Quantity | Cost (µ$) |
|---|---|---|
GetConcreteData | The saved document, plus the rowVersion a later write must carry | 5 |
ReplaceConcreteData | Full-document replace of the saved document | 5 |
They are a read-modify-write, and only useful as a pair. To change anything — one bar diameter,
the cover, a load combination — call GetConcreteData, apply your edit to the concreteData
object it returns, and send that whole object back through ReplaceConcreteData together with the
rowVersion from the same read:
1. GetConcreteData (designOptionId)
→ { concreteData: { ...the whole section... }, rowVersion: "AAAAAAAAF6E=" }
2. edit concreteData.reinforcement in place
3. ReplaceConcreteData (designOptionId, dataJson: <the edited object>, rowVersion: "AAAAAAAAF6E=")
→ the saved document, with a new rowVersion for your next edit
Every field you omit is cleared, not preserved. There is no partial update, on this surface or on REST. Sending a document you assembled yourself rather than one you just read will silently drop whatever you left out.
rowVersion is required once a section has been saved: a write that cannot name the version it
replaces is refused with rowversion_required rather than applied blind, and one naming a
superseded version is refused with concurrency_conflict — someone saved between your read and
your write, so re-read and re-apply your edit. Pass null only for a design option that has never
had an ACS (Advanced Concrete Section) document saved, which GetConcreteData reports as
concreteData: null.
Refusals are the section editor’s own, on the same code: physically meaningless cover or
dimensions (validation_failed, with the offending fields named), a self-intersecting outline
(self_intersection), overlapping voids (overlapping_regions), a tendon outside the section
(tendon_outside_section), a bar size or per-bar material the catalogue cannot honour, and
invalid arc geometry (invalid_arc_geometry).
Fire id-addressed tools (2)
Two tools mirror the fire derived reads. Both carry the ApiToolAccess:concrete-designer gate.
| Tool | Quantity | Cost (µ$) |
|---|---|---|
GetFireCapacityByDesignOption | Fire capacity check (reads geometry + bars from saved section) | 750 |
GetFireHeatTransferByDesignOption | Heat-transfer analysis (reads geometry from saved section) | 750 |
Refusal behaviour
Every RC-4 refusal the REST derived reads established is inherited by the MCP tools by construction — the tools wrap the same helpers. A persisted null refuses by name (design_code_not_set, stress_model_not_set, member_type_not_set, k_phi_not_set, exposure_class_not_set, max_aggregate_size_not_set, cover_method_incomplete, steel_class_required, aggregate_type_required). A supplied-but-unrecognised selector token refuses too (unknown_axis, steel_class_invalid, aggregate_type_invalid). Refusal text is rewritten at the MCP boundary from HTTP language into tool-parameter language.
A combination-addressed quantity that produces no result distinguishes why, on the same four codes the REST peer uses: not_applicable_for_combination when the quantity does not apply to this combination’s limit state, analysis_not_evaluated_for_inputs when the limit state matched and the design option as saved does not meet one of the check’s prerequisites (no shear action or no fitments, no bars or tendons, a rectangular stress block where a fibre field was asked for — the error text names which), analysis_failed_for_combination when it ran and failed, and analysis_did_not_converge when it ran on the right limit state and the section cannot sustain the load. The last two carry the analysis engine’s own explanation in the error text. See analysis_not_evaluated_for_inputs, analysis_failed_for_combination and analysis_did_not_converge.
A discretisation parameter is refused on its range rather than clamped: numAngles, numAxialLevels and numFibres each state their bounds in the tool’s own parameter description, and a value outside them returns sweep_resolution_out_of_range — the identical check the REST peer runs, from the same helper, because a bound enforced on one surface and not the other is the duplicate-path class. Clamping is not an option: a clamped resolution integrates a different surface from the one requested and returns it under the resolution you asked for.
A refusal fires only where the missing value can change a number, so a null does not refuse everywhere the field exists. k_phi_not_set is the one with a non-obvious scope: it is raised for AS 3600 sections only — ACI 318 and Eurocode never read — and on GetConcreteFlexureByDesignOption it additionally requires the combination to carry axial compression (). GetConcreteAnalysisByDesignOption, GetConcreteBatchDesignCheckByDesignOption, GetConcreteInteractionByDesignOption and GetConcreteMkInteractionByDesignOption refuse for any AS 3600 ULS combination whatever its , because an interaction envelope is a section-level surface whose compression half is capped at . The last of those joined in the 2026-09 release, when the fibre M– surface started applying Table 2.2.2 item (d) above ; before that it took the pure-bending item (b) at every axial level and read no , so requiring the class would have refused a request the tool answered identically either way. See k_phi_not_set.
cover_method_incomplete is scoped on three counts together: the section must be in Code-Based cover mode (a Manual cover is taken verbatim and is never re-derived), it must be an AS 3600 section (the derivation is an AS 3600 Section 4 calculation, so a mode left set after switching to ACI 318 or Eurocode is inert — a section with no design code saved at all is answered with design_code_not_set before the cover is examined, on GenerateConcreteReport as well as on the id-addressed tools), and the exposure class must be missing. GetConcreteDurabilityCoverByDesignOption does not raise it — it computes the cover rather than consuming one, and reports the missing exposure class as exposure_class_not_set. GenerateConcreteReport does raise it, so this state cannot be answered with a PDF where the tools above refuse it. The report additionally states its cover basis on every run, naming the exposure classification a code-based cover is recorded against. See cover_method_incomplete.
That parity is now general, not per code. When cover_method_incomplete was first added to the report it was the only one of the id-addressed refusals the report declared, so a section the tools above declined for design_code_not_set, member_type_not_set or k_phi_not_set still rendered a PDF — the last of those meaning every ULS capacity in a signed document was computed at a substituted . All three now refuse on GenerateConcreteReport too. k_phi_not_set takes the unconditional form there, as on GetConcreteAnalysisByDesignOption and unlike GetConcreteFlexureByDesignOption, because the report renders the interaction envelope; and because the ULS combination derived from a section’s applied actions cannot carry a class, a section holding applied actions and no load combinations is refused under AS 3600 until real combinations are saved.
The payload tools that took kPhi as a parameter — CalculateConcreteFlexure, CalculateInteractionDiagram, CalculateBiaxialInteraction and ConcreteBatchDesignCheck (which took it per design action on the body) — were removed in September 2026 with their REST routes. They applied the same per-tool rule (#5003): design code AS 3600 throughout, plus on the flexure tool alone. That rule now lives entirely on the id-addressed peers, read off the class saved on the combination.
A saved section that cannot be materialised at all refuses before any quantity is computed, and every id-addressed tool above — concrete and fire alike — reports it with the same code. These are the materialisation refusals:
| Code | Meaning |
|---|---|
concrete_grade_not_set | The section records no . It drives the constitutive curve, the stress block, , the interaction surface, shear, the cracking moment, deflection and the Cl. 4.10.1 cover, so no quantity is computed without one. |
rebar_grade_not_set | Either the section records no or — both reach every bar and fitment that declares no material of its own, and sets the modular ratio behind the serviceability checks — or one named bar or fitment cannot be resolved to a material, because the design holds no record of that material’s properties (Recalculate, or any save, fixes it) or because the material records neither value (re-pick it). The detail names the element and the cause. |
tendon_jacking_stress_not_set | A persisted tendon’s jacking stress is absent. |
tendon_material_not_resolved | A persisted tendon resolves to no strand material — no area, or at either the tendon or the section level. |
cover_not_set | The section records no cover at all, so the pattern-placed bars cannot be positioned. Cover moves those bar coordinates and with them the section properties, it is the dominant crack-spacing term, it feeds the Cl. 3.4.3 shrinkage-restraint term and so the effective prestress, and it sets the axis distance the Section 5 fire check is graded against. A declared zero is a real cover and is analysed normally; only an absent one refuses. |
rebar_grade_not_qualified_above_500 | A reinforcement material used by this section declares MPa without the evidence AS 3600 Cl. 1.1.2(d) requires before such a grade is admissible — ductility class N or E, and , declared on the grade and never inferred from its label. A yield strength raised above the value the grade itself declares carries no evidence for the higher number and is refused on that ground alone. |
rebar_yield_outside_as3600_domain | A reinforcement material used by this section puts above 800 MPa, which Cl. 1.1.2(d) does not reach. Refused rather than designed at the 600 MPa ultimate ceiling: clipping would design a grade the Standard does not cover on a number nobody chose. |
design_code_not_recognised | The section’s saved design code is not one of AS_3600, ACI_318, EN_1992_1_1, EN_1992_1_1_2023. Only missing and empty values were ever normalised, so before the 2026-09 release a token such as BS_8110 or AS3600 reached the per-code branches and each took its AS 3600 arm — the section designed to a Standard nobody selected. SaveConcreteData now rejects the token on the way in; this covers rows written before it did. |
The two tendon refusals name the offending tendons in the refusal text by their editor labels ("T1") so the remedy is unambiguous. The per-element arm of rebar_grade_not_set and both Cl. 1.1.2(d) refusals name the element or the material for the same reason: since per-element grades, “the section’s reinforcement” is no longer one thing.
cover_not_set and cover_method_incomplete describe different states and are never interchangeable. The row above is no cover was ever specified, and it reads the saved cover alone. cover_method_incomplete is a cover the code-based derivation should have replaced was not replaced, leaving a stale but present number, and it reads the cover mode, the design code and the exposure class without examining the cover. The scope differs too: GetConcreteDurabilityCoverByDesignOption is excused from cover_method_incomplete but does raise cover_not_set, because it reads the saved cover to place the bars whose seat gives the Cl. 5.4 fire axis distance it reports. See cover_not_set.
GetConcreteTimeDependentByDesignOption is not in that table, and distinguishes two states of the saved configuration itself: time_dependent_config_not_set — the Time-Dependent panel was never configured — and time_dependent_config_incomplete — it was, but leaves an AEMM input undeclared (the sustained load, an age, the relative humidity, the cement type, the ageing coefficient, or the snapshot schedule, whose absence changed how many snapshots the response carried). The refusal names the fields. notionalSize and the override fields are exempt: 0 on them is a documented sentinel, not a magnitude. No other tool refuses for those inputs. GetConcreteAnalysisByDesignOption and GetConcreteBatchDesignCheckByDesignOption bundle a time-dependent result and return timeDependent: null rather than substituted numbers, and GenerateConcreteReport degrades — the time-dependent section is dropped with a warning rather than the PDF withheld. See time_dependent_config_incomplete. Anything else the materialisation step rejects (no saved concrete data, an outline it cannot resolve) carries the generic concrete_data_incomplete.
The two fire tools refuse on the persisted fire exposure the same way: fire_exposure_not_configured when no exposure is saved, fire_duration_not_positive when its duration is unset, zero or negative, and — on GetFireCapacityByDesignOption alone — fire_method_required when no method is saved (the method selects between alternative routes and is never resolved to isotherm-500), fire_design_points_missing when the section has no Fire-limit-state combination, and prestress_losses_unavailable when the effective prestress cannot be resolved to one disclosed value. fire_request_invalid carries the fire engine’s own validation message when the assembled request fails it. Under the 500 °C isotherm method the capacity tool also forwards the three carve refusals — isotherm500_outside_table_b1, isotherm_carve_not_representable and isotherm500_no_effective_concrete — each naming the advanced fibre method as the remedy. GetFireCapacityByDesignOption additionally refuses an AS 3600 section whose reinforcement declares MPa with fire_reinforcement_production_route_undeclared: the EN 1992-1-2 Table 3.2a factors AS 3600 Cl. 5.3.1(b) Note adopts are selected by the bar’s production route rather than by its grade, nothing recorded for such a grade says which route it is, and the default — hot-rolled — is the more favourable column (23 % against 12 % of ambient yield at 700 °C). Unlike the three carve refusals it is method-independent: the advanced fibre tier reads the same column, so it is not a remedy. use_upper_conductivity_required has no MCP counterpart: GetFireHeatTransferByDesignOption declares useUpperConductivity as a required boolean, so an omitted value is rejected by the tool’s parameter schema before the tool runs.
The concrete tools forward the two geometry refusals the section-properties step makes — degenerate_outline and reinforcement_in_void — and edition_not_implemented for an EN 1992-1-1:2023 section. Two request-shape refusals share a name with the LGS by-id set on MCP tools but are the concrete usages: combination_required when a combination-addressed tool is called without combinationId, and fan_out_cap_exceeded on GetConcreteFlexureByDesignOption when an omitted axis would fan out over more than four values. Each is explained on API error codes.
:::important[A refusal is charged; a platform fault is not] A tool that returns a refusal — anything in the error tables above, whether it was decidable before the database read or fired only after the section was materialised and the analysis had already run — is charged the tool’s full cost, with no refund. The platform ran the request on your inputs. The REST derived reads these tools wrap apply the same rule to a 4xx; see Charge disclosure on refusal.
Two things are refunded instead: a call rejected by the tool’s own parameter schema before it runs at all (an omitted required argument, a value of the wrong type), and a call that faults inside the platform. Neither reaches the analysis, and neither is your error to pay for. On REST the first of those two is a 400 and is charged — the surfaces differ here because MCP rejects a malformed call at the transport’s marshalling step, before anything of ours is entered. :::
GetDesignOption — material snapshot fidelity
GetDesignOption returns the full geometry and material data for a design option including all its elements. As of the #3033 correctness fix, the tool now serves frozen material snapshots for the engineering values (elastic modulus E, Poisson’s ratio ν, density) rather than live catalog values.
Why this matters
When an element is created — by AddElement or through the web UI — the platform freezes a snapshot of the material’s engineering properties at that moment. If the catalog entry for that material is edited later (for example, the elastic modulus is corrected), elements that were already created retain the properties they were saved with.
Prior to #3033, GetDesignOption returned the live catalog values for E and ν, not the saved snapshot values. This meant an agent reading a design option’s material data could see values that differed from those used in calculations — exactly the class of inconsistency the snapshot system was designed to prevent.
The tool now matches the REST GET /api/v1/design-options/{id} behaviour: E, ν, and density come from the frozen snapshot, with a catalog fallback only for legacy pre-snapshot rows. Material name and colour stay live (presentation values, not engineering inputs).
Concrete elastic modulus is derived — September 2026 (#5527)
For a concrete element whose material carries a characteristic strength , elasticModulus is not read from the snapshot or the catalogue at all: it is derived from the frozen under the concrete design standard registered for the design option’s project jurisdiction (AS 3600 Cl 3.1.2, ACI 318-19 Cl 19.2.2, or EN 1992-1-1 Cl 3.1.3). The freeze is intact — is the frozen input, the value computed from it — and the tool agrees with REST, the material picker and every analysis, all of which derive through the same code path.
If the project’s jurisdiction registers no single concrete design standard, there is no modulus to serve and the tool refuses rather than falling back to the stored catalogue figure, which nothing reads:
{ "error": "concrete_design_code_unresolved", "detail": "This design option's project jurisdiction registers no concrete design standard, so the elastic modulus of a concrete material cannot be derived." }
The refusal fires only for a design that actually contains such an element; the REST peer returns the same code as a 422. A design built entirely from steel, timber or a bespoke concrete mix with no is unaffected — those declare their modulus directly, and it is what both surfaces serve.
Response shape
{
"id": "01966d40-...",
"name": "Option 1",
"toolInstanceId": "01966d3e-...",
"elements": [
{
"id": 42,
"materialId": 7,
"role": "outline",
"displayIndex": 0,
"material": {
"id": 7,
"name": "Normal weight concrete 32 MPa",
"elasticModulus": 30100,
"poissonRatio": 0.2,
"color": "#b0bec5",
"density": 2400
}
}
],
"rowVersion": "AQID..."
}
role is one of "outline", "void" or "inclusion" — the same camelCase name
GET /api/v1/design-options/{designOptionId} returns. Every field on this response now
matches its REST equivalent in both key and value.
:::caution[Breaking change — role is now a name, not a number — August 2026 (#4946)]
role previously came back as the numeric enum value (0 = outline, 1 = void,
2 = inclusion) on GetDesignOption, AddElement and UpdateElement, while REST already
returned the name. An agent or script matching on the number must switch to the name. Reading
role is unaffected on the way in: UpdateElement has always accepted the name
("Outline", "Void", "Inclusion", case-insensitive).
:::
The rowVersion field enables optimistic concurrency: pass it back in a ReplaceElements or PUT call to detect concurrent edits.
Complete agent workflow example
The following sequence shows how to automate a fire design check end-to-end using MCP tools:
1. ListToolDefinitions
→ find toolDefinitionId for "concrete-designer"
2. CreateProject (name: "Office Tower — Level 3 Columns")
→ projectId
3. CreateToolInstance (projectId, name: "Column C7", toolDefinitionId)
→ toolInstanceId, defaultDesignOptionId
4. AddElement (defaultDesignOptionId, materialId=..., pointsJson=rectangle 400×400)
→ elementId (role: Outline)
5. ReplaceConcreteData (defaultDesignOptionId, fireExposure: 90 min ISO834 + a Fire combination)
→ the fire inputs are saved on the design option
6. GetFireCapacityByDesignOption (defaultDesignOptionId, steelClass: "hot_rolled")
→ utilisation per design point, pass/fail
7. UpdateToolInstance (toolInstanceId, name: "Column C7 — FRL 90/90/90 ✓")
→ records the verified result in the tool instance name
Related pages
- Getting started — create a project and add a tool instance in the web UI
- Platform glossary — engineering vocabulary used across tools
- Advanced Concrete Section — the ACS tool that the fire design MCP tools extend
- Fire design — fire exposure configuration, heat transfer analysis, and capacity checks in the web UI