FERS — structural FEM
Server Details
Structural FEM for AI agents: frames, plates, buckling, seismic and Eurocode checks, solved locally.
- Status
- Healthy
- Uptime
- 100.0% over the last 24 hours
- Last Tested
- Transport
- Streamable HTTP · MCP 2025-11-25
- URL
- Repository
- Jeroen124/FERS_core
- GitHub Stars
- 2
- ChatGPT
- –
- Claude
- –
TDQS
Scored across 35 tools
Most tools target a distinct object+operation (add_load vs add_load_case vs add_load_combination, set_member vs set_member_design vs set_support), and the long descriptions consistently flag the subtle boundaries. The main soft spot is a cluster of solve-adjacent tools — solve_model, run_unity_checks, check_beam and validate_model — where an agent must read carefully to pick correctly, and the three plate-authoring tools (surface, grid elements, opening) are related enough to require deliberation.
All 35 names follow a consistent lowercase verb_noun snake_case convention (add_load, create_section, set_support, get_schema, list_templates, run_unity_checks, validate_model). The pattern holds even for multi-word cases and no mixed conventions appear.
35 tools is heavy and exceeds the comfortable band, though the domain (full FEM authoring, loads, sections, supports, plates, code checks, model persistence) genuinely spans that much ground. Some consolidation is plausible — create_beam/create_frame overlap with build_model, get_schema overlaps get_check_schema, list_sections/get_section could merge — so it reads as borderline rather than well-scoped.
The surface covers authoring, analysis configuration, solving, EC3/EN 1990 checks, validation and persistence, which is strong lifecycle breadth. However it is creation-heavy with essentially no deletion or incremental editing of primitives (no remove_load, delete_member, add_node, or update/delete of load cases and combinations), so iterative model surgery often means rebuilding the document.
Publisher details
- Operator
- FERS Cloud · Publisher source
- Operator website
- https://ferscloud.com
- Vendor relationship
- First-party
- Documentation
- https://ferscloud.com/mcp
- Trust center
- Not available
- Restrictions
- Free tier: non-commercial use only, models up to 100 members. Commercial use needs a paid FERS Cloud plan (Pro, up to 10,000 members). The hosted server returns solves as a browser link; for results inside the chat use the local server (npx -y @ferscloud/fers-mcp). · Publisher source
Available Tools
35 toolsadd_imperfection_caseAdd a geometric imperfectionBRead-onlyInspect
Add a geometric imperfection — a global sway (out-of-plumb lean, EN 1993-1-1 §5.3.2) and/or a member bow — bound to specific load combinations. The solver perturbs the geometry before solving those combinations, so it feeds both first-order and P-Delta results. Give the sway as a ratio denominator (200 means a 1/200 lean); it is stored in radians. IMPORTANT: the engine ignores an unknown combination id SILENTLY, so a typo would apply no imperfection at all — this tool rejects unknown ids instead. Free.
Inputs: load_combination_ids (required), model, model_ref, sway_axis_x, +12 more.
add_loadAdd a loadARead-onlyInspect
Add ONE load of any kind to an existing load case. Choose kind: "nodal_force" (force at a node), "nodal_moment" (moment at a node — direction is the moment AXIS, right-hand rule), "distributed" (line load along a member; covers uniform, TRAPEZOIDAL/TRIANGULAR via end_magnitude, and PARTIAL via start_frac/end_frac), "member_point_force" / "member_point_moment" (at a fractional position along a member, in global or local axes), "surface" (pressure over a convex planar polygon, distributed onto the coplanar members inside it), "plate_pressure" (pressure on a meshed plate surface). SIGN: direction_x/y/z is a GLOBAL vector — gravity is direction_y = -1. The direction is normalised for you and the whole load stays in magnitude (the raw engine multiplies component-wise, so an unnormalised [0,-2,0] would silently double the load). Distributed intensity is per unit of MEMBER length, not of horizontal projection. Units are the model's own (default N, m) EXCEPT plate_pressure, which must always be in SI pascals. Free.
Inputs: kind (required), axes, node, model, +18 more.
add_load_caseAdd a load caseARead-onlyInspect
Add an empty load case — a named bucket of UNFACTORED loads of one origin (dead, snow, wind, imposed). Put loads in it with add_load, then combine cases with partial factors using add_load_combination. A load case on its own never reaches a code check: checks envelope over COMBINATIONS only. Returns the model plus the new load_case_id. Free.
Inputs: name (required), model, model_ref.
add_load_combinationAdd a load combinationARead-onlyInspect
Add ONE factored load combination — a weighted sum of load cases at a given limit state. This is what the solver designs against: unity checks envelope over COMBINATIONS, never over raw load cases, so a load case no combination references is invisible to every check. ALWAYS set limit_state: it is the only field of a combination the solver reads, and leaving it null makes every SLS deflection check return an empty result that reads as a pass. Free.
Inputs: name (required), factors (required), check, model, +3 more.
add_plate_grid_elementsAdd an explicit plate meshARead-onlyInspect
Author plate elements EXPLICITLY (model.plate_elements) as a regular quad grid over four corner points, instead of letting the solver mesh a PlateSurface. Creates the nodes, optionally supports the perimeter, and writes one 4-node PlateElement per cell. Verified to give bit-identical results to the equivalent structured surface mesh (16x16: max|dz| 4.31048e-3 m either way).
USE THIS WHEN YOU NEED PLATE UNITY CHECKS. In engine 0.2.52 a unity check targeting plates (AllPlates / PlateSurfaces / PlateElements with a PlateStress variable) returns an EMPTY per_entity for anything meshed from a PlateSurface — status "Green", max_utilization 0, no warning — because the mesh is generated on an internal working copy while the checks run against the original document. Explicit plate elements survive in that document and are checked normally (verified 256/256 entities).
ALSO USE IT when you need stable, known plate element ids: mesh-generated ids start at max(explicit id)+1 and are never echoed back, so an element-targeted pressure on a generated id is a silent no-op.
COST: verbosity. An N x N grid emits (N+1)^2 nodes and N^2 elements inline, and the engine allocates a dense (7 x nodes)^2 block, so keep total model nodes under ~2000. Load the grid with ONE add_load kind:"surface" whose polygon covers it, rather than N^2 individual pressures. Same theory guidance as add_plate_surface: PlateTheory "Auto" resolves to Mindlin, which shear-locks (-55% at 8x8 on a span/thickness 80 slab), so prefer "kirchhoff" at span/thickness >= 20.
Inputs: corner_points (required), model, theory, behavior, +9 more.
add_plate_openingCut an opening in a plateARead-onlyInspect
Cut a hole (stairwell, duct, riser) in an existing PlateSurface: creates the hole's boundary nodes and appends a PlateOpening to that surface's openings. The hole ring must be coplanar with, and lie strictly inside, the outer boundary; elements whose centroid falls in the hole are dropped, so the applied load drops with the removed area (verified: a 1x1 m hole in a 4x4 m slab under 10 kPa took the total reaction from 160000 N to exactly 150000 N).
HARD CONSTRAINT: openings only work with the Delaunay mesher. The structured mesher rejects them outright ("PlateSurface N Structured meshing does not support openings."), so this tool switches the surface to mesh method "Unstructured" with a target_size and tells you it did. That makes the surface's edge-node positions unpredictable, which is why the tool REFUSES to convert a surface whose structured edge grid is carrying a support: those pre-created edge nodes would be stranded off the new mesh and the plate would silently fall back to hanging on its corners (measured 6x too flexible, with the load still balancing exactly). If you need both an opening and a supported edge, author the surface as unstructured from the start and support it by other means.
TARGET SIZE IS A WHOLE-SURFACE SETTING, so a SMALL hole is the expensive case, not the cheap one: left to default (half the shortest hole edge) a 200 mm duct in a 6 m x 6 m slab asks for ~6100 mesh nodes and the engine aborts on that with a raw wasm trap. The default is therefore clamped to what the node budget allows — which means the hole is resolved coarsely and its stress concentration is NOT converged; model the region around the opening as its own smaller surface if you need it sharp. An explicit target_size that busts the budget is refused rather than clamped.
Inputs: model, model_ref, opening_id, surface_id, +3 more.
add_plate_surfaceAdd a plate or slabARead-onlyInspect
Add a meshed plate/shell surface (slab, wall, deck, panel) to a FERS model. You give the corner points; the tool creates the boundary nodes, optionally the full edge-node grid with a support, and appends the PlateSurface to model.plate_surfaces. The SOLVER meshes it at solve time — never author plate_elements for a surface yourself. Load it with add_load kind:"plate_pressure" (surface_id), not with distributed_loads.
MESHING DECIDES WHETHER YOUR MODEL IS CORRECT. The mesher only ever creates NODES and PLATE ELEMENTS: it never splits members and never adds supports, so a plate connects to the rest of the structure ONLY through nodes it shares by exact coordinate. (1) Leave the mesh unset and the surface becomes TWO TRIANGLES spanning the corners — it still solves and reactions still equal q x A, so nothing looks wrong; this tool rejects that instead. (2) A supported or member-connected EDGE only exists where a node sits at an exact mesh-grid coordinate: structured meshing has predictable positions so edge_support can pre-create them, unstructured/Delaunay does not, so edge_support with unstructured meshing is rejected. (3) If a beam runs along a plate edge, split that beam into segments ending on the grid, or the plate hangs off its two end nodes only (measured 4.2x too flexible, beam moment 2.5x too low) with no warning.
THEORY — THE ENGINE DEFAULT IS WRONG FOR THIN PLATES. PlateTheory "Auto" is resolved unconditionally to Mindlin (the OpenAPI text claiming otherwise is stale). Mindlin (DSG3) shear-locks: on a simply-supported square slab at span/thickness 80 the centre-deflection error was -86.1% / -55.2% / -8.1% / -0.4% at 4x4 / 8x8 / 16x16 / 32x32, where Kirchhoff (DKT, rewritten and patch-test-exact in 0.2.44) gave -5.5% / -1.4% / -0.3% / -0.03%. So: span/thickness >= 20 -> "kirchhoff"; span/thickness < 10 -> "mindlin" plus a fine mesh (it correctly adds the ~10% shear deflection Kirchhoff misses). theory:"auto" (the default) makes that choice from the SMALLER IN-PLANE DIMENSION of your boundary (not its shortest edge, which collapses the moment an edge carries intermediate nodes) and tells you which it picked and why.
BOUNDARY POINTS: give the ring OPEN. A closing point that repeats the first is dropped for you (and said so in the notes); any other repeated point is refused, because coincident points collapse onto one node and leave boundary_node_ids with a duplicate id — measured, that solves with no error, balances the load to exactly q x A, and is 513% wrong on deflection.
ALSO TRUE OF EVERY PLATE: plates carry NO self-weight (enable_self_weight loops over members only) — add density x thickness x g as a pressure. Plates contribute NO geometric stiffness, so alpha_cr from a model where plate stability governs is unconservative. behavior "membrane" does not remove bending stiffness, it scales it by 1e-6, so an out-of-plane load on a membrane plate returns absurd deflections rather than an error. Boundary point ORDER sets the element normal and therefore the sign of mx/my/mxy. Keep total model nodes under ~2000: the engine allocates a dense (7 x nodes)^2 block and aborts above that with a raw wasm trap, so both meshers are rejected here once the projected node count crosses it (Delaunay produces ~1.7 nodes per target_size^2 of area, measured, so target_size 0.1 on a 4 m x 4 m slab is already fatal).
Inputs: name, model, offset, theory, +20 more.
add_unity_checkAdd a code checkARead-onlyInspect
Append a unity (code) check to a model's analysis.unity_checks. check is a UnityCheckDefinition JSON (see get_check_schema). Returns the updated model JSON to pass to run_unity_checks. Free.
Inputs: check (required), model, model_ref.
build_modelBuild a model from a node/member graphARead-onlyIdempotentInspect
Build a complete, solver-ready FERS model from an arbitrary node/member graph: 3-D frames, trusses, pitched roofs, bracing, towers — anything create_beam/create_frame cannot express. Returns a full FERS JSON document ready for solve_model.
GEOMETRY (1-BASED). nodes is a JSON array of [X, Y, Z] triples in METRES, Y up (gravity is −Y). The index you use to refer to a node IS its solver node id: nodes[0] is node 1, so results (displacement_nodes, reaction_nodes) come back under exactly the numbers you wrote. Index 0 is rejected. members is a JSON array of [startNode, endNode] / [startNode, endNode, "SectionName"] / [startNode, endNode, "SectionName", {options}]; member ids are 1..N in the order given. Member options: rotation_angle (DEGREES, rolls the section about the member axis), type ("Truss" for a pin-jointed axial-only bar; also Tension/Compression/Cable/Rigid), hinge ("start"|"end"|"both" — releases both bending moments there; at a joint only ONE of the members may be released, because a node whose every member end is released has no rotational stiffness left and the solve then fails outright), classification (free-text group name; one member_set per distinct value, and unity checks can target it).
COLUMN ORIENTATION — READ THIS. FERS's strong bending axis is the LOCAL z axis (i_z > i_y for an I-section), and the member local frame is built from a global-Y reference vector. A HORIZONTAL member therefore bends strong-axis under gravity automatically, but a VERTICAL member gets local y = global Z and local z = global X, so it resists sway along global X with its WEAK axis i_y. Verified: a 6 m cantilever column pushed 20 kN in +X deflects 1.1357 m at rotation_angle 0 (= PL³/3E·i_y) and 0.0820 m at rotation_angle 90 (= PL³/3E·i_z) — a 13.8× difference the solver will never warn about. In plane:"XY" this tool sets rotation_angle 90 on vertical bending members for you (reported in info.auto_rotation), because sway there is along X; in "3D" it only warns, since which axis you want depends on the structure — set rotation_angle explicitly.
SUPPORTS. supports maps a 1-based node number to a preset or an explicit DOF object: "fixed" (all 6 DOFs), "pinned" (X,Y,Z fixed, rotations free), "roller_x"/"roller_y"/"roller_z" (free to slide along that global axis), "free". For anything else pass {"X":"fixed","Y":"fixed","Z":"free","RX":"free","RY":"free","RZ":2.5e6} — a NUMBER is a spring stiffness (N/m for X/Y/Z, N·m/rad for RX/RY/RZ), and that node then settles by exactly force/stiffness. Watch the sign when you read results: a SPRING reaction comes back with the OPPOSITE sign to a rigid support carrying the same force (measured: +30 kN at the pin and −30 kN at the spring of one beam), so do not add the two up. Any DOF you leave out of an explicit object is written as Free (never omitted: in the raw JSON an absent DOF means FIXED, which is the opposite of what everyone expects).
PLANE. plane is "3D" (default) or "XY". "XY" requires every node to have Z = 0 and pins Z, RX and RY at EVERY node, which is what makes a planar model well-posed — essential for a planar TRUSS, where the bars carry exactly zero out-of-plane stiffness. Note that analysis.options.dimensionality is cosmetic: the solver never reads it, so only supports can make a model planar. An under-restrained model whose loads reach the free direction is reported, not solved: that load case's errors_and_warnings carries singular_model and its converged flag is false. Read errors_and_warnings before using any number.
SECTIONS. Give a library name ("IPE300", "HEA200", "CHS 114.3x5", "RHS 100x50x5", "UB 457x191x67", …) in the member row and it is looked up and inserted with all EC3 moduli. For a rectangle or your own numbers, declare it in sections (same object shape as create_section) and reference it by name. A member naming no section gets the first declared section, else default_section (IPE300).
LOADS are in kN, kN/m and kNm and are converted to newtons internally. loads is a list of load cases: {"name":"G","udl":[[member, w]],"nodal_loads":[[node, Fx, Fy, Fz]],"nodal_moments":[[node, Mx, My, Mz]],"point_loads":[[member, position0to1, Fx, Fy, Fz]]}. A 2-entry udl row [member, w] is w kN/m acting DOWN (−Y); a 4-entry row [member, wx, wy, wz] is a full vector. Combine them with load_combinations: [{"name":"ULS","factors":{"G":1.35,"W":1.5}}], referencing load cases BY NAME; omit it for two automatic ones over every case: combination 1 "SLS characteristic (auto)" at factor 1.0 and combination 2 "ULS (auto)" at 1.35. A combination you name gets limit_state "ULS" unless you give one, because a null limit_state makes every later unity check silently evaluate nothing and report a pass.
LIMITS: 100 members on the Free tier (hard "LimitExceeded" above that), so keep element counts modest.
Inputs: nodes (required), members (required), loads, order, +8 more.
check_beamCheck a steel beam (EN 1993-1-1)ARead-onlyInspect
Check a steel beam to EN 1993-1-1 (Eurocode 3). Builds the beam + a ULS load combination + an EC3 member check over one member, solves it, and returns the governing utilization (<= 1.0 = OK) with the per-rule breakdown: bending, shear, combined N+M, and lateral-torsional buckling, plus a short advice string, and deflection: the peak deflection under the unfactored (characteristic) load in mm, where it occurs (at_m) and span/deflection. The deflection includes shear deformation, and is reported, not checked against a limit. Requires a named library section: European (IPE/HEA/HEB/HEM/UPE/RHS/SHS/CHS), UK (UB/UC/PFC) or US (W/HP/HSS/Pipe). Accepts common parameter aliases (span/length for span_m, load_kN_per_m/uniform_load for udl). Set restrained=true if the compression flange is continuously laterally restrained (skips the LTB check). On this hosted server the solve does NOT run here: the tool validates the model, parks it and returns an open_url that solves it in the user's browser, where the results are shown. Tell the user to open the link; for results inside the chat they can install the local FERS MCP server. Units: m, kN, kN/m.
Inputs: section (required), udl, span, length, +10 more.
create_beamBuild a beam modelARead-onlyIdempotentInspect
Generate a solver-ready FERS model JSON for one beam. Use a named library section (IPE300, HEA200, 'RHS 100x50x5', 'UB 457x191x67', 'W10X22', …) or a solid rectangle. Returns model JSON to pass to solve_model. Accepts common parameter aliases (span/length for span_m, load/udl/uniform_load for load_kn; point_load implies load_type 'point'). Units: metres, kN, kN/m. The model carries two load combinations: 1 'SLS characteristic (auto)' at factor 1.0 and 2 'ULS (auto)' at 1.35, so ULS unity checks evaluate factored loads. Shear deformation is included, so a deflection comes out a little above 5wL⁴/384EI (set_analysis turns it off). Free.
Inputs: udl, load, span, length, +13 more.
create_frameBuild a portal frameARead-onlyIdempotentInspect
Generate a solver-ready FERS model JSON for a 2-D rectangular portal frame (columns + beams). Returns model JSON to pass to solve_model. Units: metres, kN, kN/m. Columns are turned 90° so their depth (column_height_mm) resists sway in the frame plane. Load combinations as create_beam: 1 'SLS characteristic (auto)' at 1.0, 2 'ULS (auto)' at 1.35. Free.
Inputs: base, bays, storeys, material, +8 more.
create_sectionAdd a cross-sectionARead-onlyInspect
Add or replace a cross-section on an existing FERS model and optionally assign it to members. Takes the model JSON, mutates model.sections (and model.materials when a new preset is introduced), returns the model.
THREE WAYS TO DEFINE IT:
LIBRARY — shape:"library" with library_name (or just
name) set to a library profile — European IPE/HEA/HEB/HEM/UPE/RHS/SHS/CHS/L or UK UB/UC/PFC, e.g. "IPE300", "HEB240", "CHS 114.3x5", "RHS 100x50x5", "UB 457x191x67". This is the ONLY way to get exact rolled-profile properties (root fillets included) and it carries every EC3 modulus. Prefer it whenever a standard profile fits.PARAMETRIC — shape:"rect" (h_mm, b_mm), "circle" (d_mm), "chs" (d_mm, t_mm), "rhs"/"shs"/"box" (h_mm, b_mm, t_mm) or "i" (h_mm, b_mm, tf_mm, tw_mm), dimensions in MILLIMETRES. rect/circle/chs are exact and rhs area+inertias are exact (its Bredt torsion J is ~2% low), but the "i" generator assumes SHARP fillets: against a real rolled IPE300 it is 3.6% low on area, 4.3% low on i_z and 22% low on J. Use "i" for WELDED plate girders, not for rolled profiles.
CUSTOM — shape:"custom" with your own SI numbers. area, i_y, i_z and j are all MANDATORY and strictly positive; a missing one is a hard parse error and a MISTYPED one is silently ignored (Section has no deny_unknown_fields), giving a wrong answer that still solves.
AXIS CONVENTION: local z is the STRONG axis. For depth h and width b, i_z = b·h³/12 (strong, resists gravity on a horizontal member) and i_y = h·b³/12 (weak). Getting these the wrong way round is the most common authoring error and the solver cannot detect it.
EC3 CHECKS NEED MORE THAN STIFFNESS. An Ec3Steel check on a section lacking wel_y/wpl_y/i_w does not fail — it returns utilization 0.0, status Green and an explanatory message, which reads as a clean pass. So also give wel_y, wel_z, wpl_y, wpl_z and i_w (warping constant, m⁶); the parametric shapes compute these for you. Optionally add ec3 {"section_class":1,"buckling_curve_y":"B","buckling_curve_z":"C","buckling_curve_lt":"B"} — curves A0/A/B/C/D (alpha 0.13/0.21/0.34/0.49/0.76); omitting the block silently uses curve b. Unlike the rest of the section, the ec3 block DOES reject unknown keys, so a typo there is a hard solve-time error.
ASSIGNMENT: apply_to "all" reassigns every member, a JSON array of member ids reassigns those, a classification string reassigns every member in that group. A section whose name already exists is overwritten IN PLACE, keeping its id and all existing member references.
Inputs: name (required), j, ec3, i_w, +21 more.
generate_load_combinations_ec0Generate EN 1990 combinationsARead-onlyInspect
Generate a complete EN 1990 (Eurocode 0) combination set from the model's load cases in one call, instead of adding them one at a time. Classify each load case as an action type — G permanent, Q imposed, S snow, W wind — and this emits ULS expression 6.10 (each variable action taken as leading in turn, with psi_0 on the accompanying ones) and the SLS characteristic combinations, including the favourable-permanent (gamma_G = 1.0) variants that govern wind uplift. Every generated combination carries a limit_state, so unity checks actually evaluate against them. Any load case you do not classify is guessed from its name. Free.
Inputs: actions (required), model, model_ref, include_sls, +3 more.
get_check_schemaCode-check schemaARead-onlyIdempotentInspect
Get the canonical schema for authoring unity (code) checks: the UnityCheckDefinition schema and everything it references, read straight from the solver's OpenAPI, plus a worked EN 1993 example. Call this before add_unity_check. Free.
Inputs: no named parameters.
get_schemaLook up the model schemaARead-onlyIdempotentInspect
Look up the FERS model input contract — the authoritative JSON Schema the solver itself is generated from. Call with NO arguments to get an index of the useful roots (geometry, loads, analysis, checks) with a one-line description of each; then call again with a root to get that schema plus everything it references. Use this instead of guessing field names: the engine silently ignores unknown keys under analysis.*, so a typo becomes a wrong answer rather than an error. Free.
Inputs: root.
get_sectionSection propertiesARead-onlyIdempotentInspect
Get a named section's full properties in SI units (m, m², m⁴): area, second moments i_y/i_z, torsion constant j, warping constant i_w, shear areas, and the elastic/plastic moduli wel_y/wel_z/wpl_y/wpl_z that an EN 1993-1-1 check needs. Free.
Inputs: name (required).
get_templateGet a starter modelARead-onlyIdempotentInspect
Retrieve the full JSON of a template structural model by name. Use list_templates first to see available names.
Inputs: name (required).
get_usageCheck usageARead-onlyIdempotentInspect
Check your FERS tier and how solves run on this server: member limits, licence state and where results come from. With sandbox_token: true, a Pro account also gets a solve token for 15 minutes, for a code sandbox that cannot reach ferscloud.com: set it as FERS_SOLVE_TOKEN there (or run_analysis(solve_token=...), FERS 0.2.0+) to solve beyond 100 members. It sits in the transcript, so ask only when a model needs it.
Inputs: sandbox_token.
list_materialsList materialsARead-onlyIdempotentInspect
List the named material presets the model builders accept, with their E, G, density and yield stress in SI units. The solver itself accepts ANY isotropic material — see get_schema('Material') to author one inline. Free.
Inputs: no named parameters.
list_modelsList saved modelsBRead-onlyIdempotentInspect
List your saved models (id, name, description, last-updated time).
Inputs: no named parameters.
list_sectionsList steel sectionsARead-onlyIdempotentInspect
List the named steel sections FERS knows: European (IPE, HEA, HEB, HEM, UPE, RHS, SHS, CHS, L), UK (UB, UC, PFC) and US (W, HP, HSS, Pipe). check_beam and create_beam require one of these names, so call this rather than guessing. Returns names only — use get_section for a section's properties. Free.
Inputs: family.
list_templatesList starter modelsARead-onlyIdempotentInspect
List all available template structural models. Returns name and description for each template.
Inputs: no named parameters.
load_modelLoad a saved modelARead-onlyIdempotentInspect
Load one of your saved models' full JSON by id (owner only).
Inputs: id (required).
run_unity_checksRun code checksARead-onlyInspect
Solve a model and return its code-check (unity-check) results: per-entity utilizations, status, and the calculation trace behind each rule. The model's analysis.unity_checks must already be defined (see add_unity_check). On this hosted server the solve does NOT run here: the tool validates the model, parks it and returns an open_url that solves it in the user's browser, where the results are shown. Tell the user to open the link; for results inside the chat they can install the local FERS MCP server. Set include_report=true to also get the rendered HTML hand-calc report.
Inputs: model, detail, model_ref, result_filter, +2 more.
save_modelSave a model to your accountAInspect
Save a model to your FersCloud account (requires a Pro subscription; free accounts use local JSON export/import instead). Solver results are never stored. Returns the model id.
Inputs: name (required), model, model_ref, description.
set_analysisConfigure the analysisARead-onlyIdempotentInspect
Configure HOW a model is solved, and switch on the analyses the engine supports beyond plain statics. Writes analysis.options (first- vs second-order, P-Delta, self-weight, gravity, shear deformation, tolerances) and the three optional blocks: analysis.modal (natural frequencies), analysis.buckling (linear eigenvalue alpha_cr) and analysis.seismic (EN 1998-1 response spectrum / lateral force). Pass only what you want to change — each sub-object is merged into the existing block, so load cases, combinations and unity checks are preserved. Returns the updated model for solve_model. KEY FACTS: models from create_beam, create_frame and build_model are LINEAR (the engine itself has no default and requires order; the Python SDK defaults to NONLINEAR), so set order:"NONLINEAR" for second-order/P-Delta. Those models include shear deformation (include_shear_deformation: true, the engine default); options {"include_shear_deformation": false} gives the bending-only answer a textbook formula assumes. Buckling needs exactly one reference form (reference / references / all_combinations) and reports alpha_cr as results.buckling.modes[].critical_load_factor. Seismic directions are GLOBAL AXES and these models are Y-up, so the two horizontal directions are usually X and Z. Unknown field names are REJECTED here on purpose: the solver would accept them, ignore them, and silently give you a default. Call get_schema("BucklingAnalysisSettings") etc. for the exact fields.
Inputs: clear, modal, model, options, +3 more.
set_memberSet member behaviourARead-onlyInspect
Set structural behaviour on one or more existing members: element type, end releases (hinges), rigid end offsets, cross-section rotation/mirroring and cable parameters. Mutates model.members (and model.member_hinges when a release is authored) and returns the model.
member_type: Normal (full 6-DOF beam), Truss (axial only — carries exactly zero moment), Tension (tie that drops out in compression), Compression (strut that drops out in tension), Rigid (near-rigid link; produces NO entry in results.member_results at all) or Cable (analytic elastic catenary; forces the whole solve onto the nonlinear path and only sags when analysis.options.enable_self_weight is true). Tension/Compression drop-out is governed by analysis.options.axial_slack in FORCE units — a member switches off when its axial force passes 0.5*axial_slack on the wrong side — so this tool lowers axial_slack to 1e-3 when it writes one (set keep_axial_slack to skip). With a coarse axial_slack the member never drops out and quietly behaves like a plain Truss. To build a truss use member_type Truss: two double-hinged Normal members meeting at a free node give a singular stiffness matrix.
END RELEASES ARE A TRISTATE and that is the whole point: for each release DOF, null (or simply not naming it) = RIGID, 0.0 = FULLY RELEASED, and a positive number = a spring of that stiffness (force/length for vx/vy/vz, force*length/rad for mx/my/mz/warp). Release DOFs are in the MEMBER LOCAL frame. Verified on an encastre beam under UDL: mz null gives support moment wL^2/12 and midspan wL^2/24; mz 0.0 gives 0 and wL^2/8, i.e. exactly simply supported. Each release object fully defines that end's hinge — DOFs you do not name are written as rigid — and passing "rigid" clears the release. A rotational release is rejected when it would leave a node with NO rotational stiffness at all, because that is a hard "Stiffness matrix is singular or near-singular": either a dead-end node (no other member frames in and the support does not restrain it) or a node where every member meeting there releases the same DOF. Note the asymmetry the check has to respect — an absent RX/RY/RZ on a support is read as FIXED, but an absent warping_condition really is free, so a plain pinned support does NOT hold a warp release. Releasing the same vx, vy, vz or mx at BOTH ends of one member is refused outright: that one is fatal at the element level whatever the model looks like ("apply_end_releases_to_local_beam_k: interface block is singular"), while my/mz/warp at both ends is just an ordinary pin-pin beam.
A release object may also carry rotation_axes: "Global" or {"User":{"x":[...],"y":[...]}} takes mx/my/mz, their curves and their moment-rotation diagrams about those axes instead of the member's own — a beam at an angle in plan, pinned about the axis of the girder it hangs from, needs {"mx":0.0,"rotation_axes":"Global"}. A user frame's first axis runs along x and its second along y made square to it. Translations stay member-local, member results stay in member axes, and the axes stay fixed in space on the corotational path. A model whose hinges release in other axes is written with schema_version 3 (engine 0.2.68), so an older engine refuses it instead of solving the release about the member's axes.
MemberHinge also carries seven capacity fields (max_tension_*, max_moment_*, max_bimoment_warp). They are accepted by the schema and carried through the model but are NEVER ENFORCED by the solver: nothing limits a hinge force by them and they trigger no nonlinearity. Do not author "yielding" hinges with them — this tool does not write them.
Offsets (start_offset/end_offset) are rigid arms between the node and the member work point in the GLOBAL frame, in model length units. rotation_angle_deg rolls the cross-section about the member axis in DEGREES, not radians: on a vertical column FERS builds the local frame from global Z, so sway along global X is resisted by the WEAK axis i_y unless you rotate 90 degrees (verified 13.8x difference on a cantilever column, with no warning from the solver). pretension is valid ONLY on Cable/Tension/Compression (a hard validation error elsewhere) and pins a tie permanently active; unstretched_length only does anything on a Cable and is silently ignored everywhere else. Sign convention for reading results back: local_start_forces.fx is COMPRESSION-POSITIVE.
Inputs: member_ids (required), model, mirror, model_ref, +9 more.
set_member_designSet buckling / design dataARead-onlyInspect
Set the design (code-check) data that lives on a MemberSet — buckling restraints, buckling lengths, LTB length, effective-length factors — plus the per-member chi override.
THESE FIELDS CHANGE NOTHING IN THE STATICS. Displacements, reactions and member forces come back bit-identical; the values only feed the EN 1993-1-1 unity-check evaluator. If the model has no Ec3Steel unity check and a matching load combination you will see no effect at all anywhere in the results, so add one with add_unity_check first.
A MemberSet is one physical beam: the ordered chain of collinear FE members that make it up. Design lengths resolve per axis with this precedence: explicit buckling_length_* > effective_length_factor_* > buckling_restraints spacing > member length. WATCH THE TWO BASELINES — an explicit length and a restraint spacing are measured along the WHOLE chain, but effective_length_factor_y/z multiplies the length of the INDIVIDUAL FE member. Verified on a 6 m column split into 4 x 1.5 m members: buckling_length = 6.0 reproduces the default exactly, buckling_length = 12.0 quarters N_cr, but effective_length_factor = 2.0 gives L_cr = 3.0 m (2 x 1.5 m), not 12 m.
buckling_restraints are named by the member LOCAL axis they hold (restrains_local_y / restrains_local_z / restrains_torsion), not strong/weak — which one is strong depends on the section orientation and rotation_angle. A restraint's node_id must actually lie on one of the set's own members or the whole model fails validation.
chi is a per-MEMBER override of the EN 1993-1-1 flexural-buckling reduction factor. It replaces the section-curve chi for BOTH flexural axes (the slenderness stays physical) and is written onto every member of the targeted sets; pass "clear" to remove it.
Inputs: member_set_ids (required), chi, model, model_ref, +9 more.
set_scissor_hingeLet a continuous beam turn on what it crossesARead-onlyInspect
Let a continuous beam turn or slide on the members it crosses (a scissor hinge, engine 0.2.68), with no link member or extra node: a rail resting on beams, a bracing pair crossing at a bolt. Name the beam by member_ids (or an existing member_set_id) and give the releases. Returns the model.
releases is an object over tx, ty, tz (translations) and rx, ry, rz (rotations): 0.0 FREES that axis, a positive number is a spring between the beam and the node (force/length for t*, forcelength/rad for r), and an axis you do not name is HELD — the same tristate as a member hinge. {"rz":0.0} lets a beam in the XY plane turn on what it crosses; {"tx":0.0} lets it slide along X.
The hinge goes on a MEMBER SET and applies wherever one of the set's members meets a member OUTSIDE the set (or a plate). Inside the set the members stay joined to each other, so the beam stays continuous. A set holding every member meets nothing and the solver refuses it, so when no set holds exactly these members they leave their sets for a new one; info lists the sets they were taken from. at_nodes limits the hinge to those nodes of the beam (a rail that slides over its first post only); omit it for every crossing.
axes: "Global" (the default) or {"User":{"x":[...],"y":[...]}} for a rack turned in plan — the first axis runs along x, the second along y made square to it. "Local" is refused: the set's members need not share one.
Supports and nodal loads at a released node act on the members outside the set, and a node's reported displacement is theirs; the beam's own motion there is in its members' local end displacements. The solver refuses a member point load or moment exactly at a released end of the beam. In second order, a model with scissor hinges takes the corotational correction only when analysis.options.nonlinear_method is COROTATIONAL. The model is written with schema_version 3, so an engine older than 0.2.68 refuses it instead of solving every crossing rigid. scissor_hinge_id reuses (and with releases or axes, rewrites) one definition for several beams; clear:true takes the hinge off the beam's set.
Inputs: axes, clear, model, at_nodes, +5 more.
set_supportSet a support conditionARead-onlyInspect
Write a nodal support (boundary condition) and point one or more nodes at it: creates or overwrites an entry in model.nodal_supports and sets node.nodal_support on every node you name. Returns the updated model — feed it to solve_model.
All 7 DOFs are set independently, in the GLOBAL frame: X/Y/Z (translations), RX/RY/RZ (rotations about the global axes) and warping (the 7th DOF, only live when analysis.options.include_warping is true and the section has i_w). Condition types are case-sensitive: Fixed (rigid), Free (no restraint), Spring (needs the matching _stiffness > 0 — N/m for translations, N*m/rad for rotations), PositiveOnly (one-way: may only push in the +global direction, i.e. lift-off) and NegativeOnly (the mirror image). A one-way condition may also carry a _stiffness, which from engine 0.2.65 is the bedding stiffness it acts through while in contact — a base plate on a slab is not infinitely stiff. Released, it is free regardless. On 0.2.64 and earlier that number was accepted, range-checked and then silently discarded, so the model solved as rigid contact.
THERE IS NO "FREE BY OMISSION". Once a node references a support, any DOF you leave out is treated by the solver as FULLY FIXED — a support of literally {"id":1} is an encastre, and the OpenAPI text claiming an absent field means free is wrong. This tool therefore always writes all six translation/rotation conditions explicitly, and anything you do not name is written as Free. Use preset to fill them in one go: fixed (encastre), pinned (X/Y/Z fixed, rotations free), free, roller_x/roller_y/roller_z (ONLY that translation fixed, everything else free), ground_contact_y (X/Z fixed, Y PositiveOnly, rotations free — a base plate that can lift off). Per-DOF fields are applied on top of the preset. Warping is the one exception: leaving it out really does mean free.
ONE-WAY SUPPORTS have a prerequisite this tool checks for you: analysis.options.rigid_strategy must be RigidMember (LinearMpc is a hard validation error). Tension/Compression members are fine alongside them from engine 0.2.65 -- before that the combination was refused at validation, so if you are reading advice written against an older engine, that restriction is gone. There is also a SILENT prerequisite — the release tolerance is 0.5*axial_slack in force units, so a coarse axial_slack leaves the DOF engaged while still reporting its reaction as 0, and the printed reactions stop summing to the applied load. This tool lowers axial_slack to 1e-3 when it writes a one-way condition (set keep_axial_slack to skip that). Beware that a support which lifts off can leave the structure unrestrained, and that a one-way support can break an eigen solve if the model also has a modal or buckling block.
Reactions use ONE SIGN CONVENTION for every support type from engine 0.2.60: each row is the force the support applies to the structure, so reactions can be summed against the applied load without checking which kind of support produced each one. (Up to 0.2.59 a Spring DOF reported the spring force k*u, the negative of what a Fixed DOF reported for the same physical situation. If you are reading results solved on an older engine, that flip is still in them.) A stiffness curve (stiffness_curves) makes the spring load-dependent, forces the DOF to Spring and requires a NONLINEAR analysis order — and note its driver is the EXTERNAL nodal load at that node, not the reaction, so a base-plate curve with no direct nodal load degenerates to the first point's stiffness.
Inputs: node_ids (required), X, Y, Z, +18 more.
solve_modelSolve a structural modelARead-onlyInspect
Run the FERS FEM solver on a structural model. Input is the full model JSON (as a string). Returns displacements, internal forces and reactions. On this hosted server the solve does NOT run here: the tool validates the model, parks it and returns an open_url that solves it in the user's browser, where the results are shown. Tell the user to open the link; for results inside the chat they can install the local FERS MCP server. Results are returned at detail: standard by default — ask for 'summary' when you only need peak values, and 'full' only when you genuinely need every sample point.
Inputs: model, detail, model_ref, result_filter, +2 more.
stash_modelPark a model, get a handleAInspect
Park a model and get back a short handle (model_ref). Every authoring tool, and solve_model, accepts model_ref instead of model — so a multi-step build stops shipping the whole document through your context on every call, which on a real model is thousands of tokens per step. Tools called with a handle return the same handle, so the chain stays cheap. Handles are private to you and expire after 24 hours; they are NOT saved models and never appear in list_models (use save_model for that). Free.
Inputs: model (required), name.
summarize_plate_resultsSummarize plate resultsARead-onlyIdempotentInspect
Reduce a solved FERS document to the plate numbers an engineer actually reads, and derive extreme-fibre stresses. READ-ONLY: it returns a summary, not a model.
You need this because plate results cannot be read back through the model. The mesh is generated on an internal working copy, so the returned document has model.plate_elements = [] and plate_surfaces[].generated_plate_element_ids = [], and the mesh node ids in displacement_nodes have no coordinates anywhere in model.nodes. The only georeferenced plate output is results.<case|combination>.plate_results, keyed by element id, carrying centroid {X,Y,Z}, centroid_displacement_global/local and the stress resultants.
UNITS AND CONVENTION, from the engine's own denormalisation: nx, ny, nxy, qx, qy are membrane forces and transverse shears per unit width, in force/length (N/m); mx, my, mxy are moments per unit width, in force (N.m/m, numerically N). mx is the moment PRODUCING sigma_x (conjugate to curvature about local y), not "the moment about x" — on a rectangular slab spanning the short way with local_x along the long side, the large resultant is my. The sign of mx/my/mxy follows the element normal, which follows the boundary/connectivity ordering, so reversing it flips all three; von Mises and |principal| are immune. qx and qy are identically ZERO for Kirchhoff/DKT plates by construction, so non-zero transverse shear is itself proof the Mindlin element was used. results..summary.total_plate_forces is a COUNT of plate results, not a force.
Give thickness and the tool derives the extreme-fibre stresses exactly as the engine's own plate unity check does: sigma_x = nx/t +/- 6mx/t^2, sigma_y = ny/t +/- 6my/t^2, tau_xy = nxy/t +/- 6mxy/t^2, evaluated at both fibres with the governing (larger von Mises) one reported, then vonMises = sqrt(sx^2 - sx.sy + sy^2 + 3txy^2) and the in-plane principals. Add yield_stress for a utilisation.
Inputs: results (required), top_n, thickness, quantities, +2 more.
validate_modelValidate a modelARead-onlyIdempotentInspect
Check a FERS model against the solver's own schema WITHOUT solving it — free. Reports field-level errors (missing or wrong-typed fields, invalid enum values), broken id references (a member pointing at a node that does not exist, a load on a deleted member, duplicate ids), and warns about unknown fields under analysis.* — which matter because the engine silently ignores those and gives you a default instead of an error. Always run this before solve_model on a hand-authored model.
Inputs: model, model_ref.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
Related MCP Connectors
Parametric 3D CAD for AI agents: build print-ready parts, check them, export STL, 3MF or STEP.
Real FEA on CAD parts: validated setups, stress, safety factor, natural frequencies
AI-callable calculators and engineering models with real formulas. No hallucinated math.
Deterministic reasoning stack for AI agents: simulate, decide & compute, plus cross-domain tools.
Related MCP Servers
- AlicenseBqualityCmaintenanceAn agentic MCP server for CSI ETABS v22+ that enables AI agents to model, load, analyze, design, and inspect building structures through 60 curated engineering tools and live .NET reflection of the full 1,293-method OAPI. Includes offscreen 3D rendering of deformed shapes and D/C ratios, interactive database table editing, and background analysis execution.7120Apache 2.0
- AlicenseAqualityDmaintenanceEnables AI agents to interact with Bentley STAAD.Pro models for tasks like load case definition, data extraction, and property setting, running locally with multi-instance support and no cloud dependency.5MIT
- AlicenseAqualityBmaintenanceThe official MCP interface for AgentFEM, giving AI agents seven typed tools to create, validate, run, inspect, and verify finite-element simulations while preserving scientific evidence.738 PyPI1Apache 2.0
- FlicenseNot gradedqualityDmaintenanceEnables AI assistants to perform structural engineering analysis using the Pynite finite element library, including model creation, element definition, analysis execution, and result visualization through natural language.-
Glama MCP Gateway
Add one secure layer between your agents and this server.