Spec Driven Development
MCP Server that facilitates spec-driven development workflows, not just Vibe Coding.
Beschreibung vom Entwickler (englisches Original)
Structural FEM for AI agents: frames, plates, buckling, seismic and Eurocode checks, solved locally.
Diese drei werden nie zu einer Punktzahl zusammengefasst: Meinungen, Messungen und Beliebtheit sind verschiedene Dinge.
Für diesen Server haben wir noch keine Zusammenfassung in einfacher Sprache geschrieben. Unten stehen die Fakten, die wir aus der Registry gelesen haben.
Wähle die KI-App, die du nutzt, und kopiere die Einstellungen.
FERS_API_KEYAls geheim markierte Werte sind Schlüssel zu deinem Konto. Gib sie nie weiter und füge sie nie in einen Chat ein.
Du brauchst
Welche App nutzt du?
Wähle oben deine App. Wir merken sie uns, damit der Button beim nächsten Mal „In … installieren“ anzeigt.
Funktioniert in Claude im Web, auf dem Desktop und auf dem Handy:
https://ferscloud.com/api/mcpÖffne Claude Desktop → Settings → Developer → Edit Config und füge dies in claude_desktop_config.json ein
{
"mcpServers": {
"fers": {
"command": "npx",
"args": [
"-y",
"@ferscloud/fers-mcp"
],
"env": {
"FERS_API_KEY": "your-value"
}
}
}
}Führe das in deinem Terminal aus
claude mcp add --transport http fers https://ferscloud.com/api/mcpclaude mcp add fers --env FERS_API_KEY=your-value -- npx -y @ferscloud/fers-mcpTrag die leeren Werte ein, bevor du ihn startest.
In .cursor/mcp.json in deinem Projekt oder in ~/.cursor/mcp.json einfügen
{
"mcpServers": {
"fers": {
"url": "https://ferscloud.com/api/mcp"
}
}
}In .cursor/mcp.json in deinem Projekt oder in ~/.cursor/mcp.json einfügen
{
"mcpServers": {
"fers": {
"command": "npx",
"args": [
"-y",
"@ferscloud/fers-mcp"
],
"env": {
"FERS_API_KEY": "your-value"
}
}
}
}Dein Browser fragt, ob er die App öffnen darf, dann zeigt die App die Einstellungen und bittet dich um Bestätigung. Schlüsselwerte bleiben leer, damit du sie selbst einträgst.
In .vscode/mcp.json in deinem Projekt einfügen
{
"servers": {
"fers": {
"type": "http",
"url": "https://ferscloud.com/api/mcp"
}
}
}In .vscode/mcp.json in deinem Projekt einfügen
{
"servers": {
"fers": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"@ferscloud/fers-mcp"
],
"env": {
"FERS_API_KEY": "your-value"
}
}
}
}Dein Browser fragt, ob er die App öffnen darf, dann zeigt die App die Einstellungen und bittet dich um Bestätigung. Schlüsselwerte bleiben leer, damit du sie selbst einträgst.
ChatGPT verbindet sich nur mit Servern im Internet:
https://ferscloud.com/api/mcpMenünamen können sich je nach Tarif und mit der Zeit leicht unterscheiden.
Deine Auswahl bleibt in einem Cookie auf diesem Gerät; kein Konto nötig.
Diese Einstellungen werden aus dem Registry-Eintrag des Entwicklers erzeugt. Wenn in der README etwas anderes steht, folge der README.
Der Server verlangt eine Anmeldung, bevor er seine Tools auflistet.
Wir prüfen Informationen, die jeder sehen kann; den Code haben wir nicht gelesen. Checks, die nicht zutreffen, werden nicht mitgezählt. · Geprüft am 7. Nov. 2026
Der Herausgeber (ferscloud.com) ist nicht der Repo-Inhaber (Jeroen124); vielleicht hat jemand anderes ihn eingetragen
Dieses Repo haben wir noch nicht von GitHub gelesen
Dieses Repo haben wir noch nicht von GitHub gelesen
Antwort in 51 ms (HTTP 401)
Gibt OAuth-Anmeldung an, damit deine KI-App dich zum Einloggen weiterleiten kann
Noch nicht geprüft; wir lesen die Tool-Liste, wenn wir den Endpunkt prüfen
Verglichen mit allen aktiven Servern dieser Kategorie in der Registry. Sterne zählen nur, wenn dem Herausgeber das Repo gehört.
Er hat 2 von 3 zutreffenden Basischecks bestanden. Den Code haben wir nicht gelesen. Bevor du einen Schlüssel oder Token hinzufügst, sieh nach, welche Zugriffe der Server verlangt, und nutze einen Schlüssel, der nur die Berechtigungen hat, die du brauchst.
Das musst du eintragen: FERS_API_KEY
Beides geht: Installiere ihn auf deinem Computer oder verbinde dich mit der Cloud-Version.
Das GitHub-Repo wurde zuletzt – geändert, und die neueste Version (2.83.0) kam vorgestern in die Registry.
Apps, die MCP unterstützen, etwa Claude Desktop, Claude Code, Cursor und VS Code. Er verbindet sich per URL, also funktionieren auch Apps, die Remote-MCP-Server akzeptieren, zum Beispiel ChatGPT im Entwicklermodus.
In derselben Kategorie: Spec Driven Development, Remote Desktop Commander, FactLot
Server derselben Kategorie von anderen Entwicklern, sortiert nach Sternen.
So liest du eine Karte:★ GitHub-SterneiDrei Säulen: Bewertungen · Checks · NutzungiAktualisiert = neueste Version in der Registry veröffentlichti
MCP Server that facilitates spec-driven development workflows, not just Vibe Coding.
Remote Desktop Commander: use files, terminal and processes on your own computers from any AI app.
Cited CRE facts and deterministic calculators for agents: rent, CAM, debt sizing, IRR, parcel IDs.
Unverändert aus dem Repo des Entwicklers übernommen. PopNx MCP hat den Inhalt weder geprüft noch empfiehlt es ihn. HTML, Bilder und Skripte wurden entfernt. Links führen von dieser Website weg.
FERS: structural finite-element analysis (3D frames, trusses, plates, Eurocode 3 checks) for engineers and AI agents. This is its Python SDK and the fers command. The solver is a compiled engine that runs on your machine, with no account and no network call: free for non-commercial use up to 100 members. This SDK is open source (BSD-3-Clause) at github.com/Jeroen124/FERS_core; the engine's licence is under Licence.
It computes linear and second-order (P-Delta) static analysis, natural frequencies, elastic buckling load factors (α_cr), response-spectrum seismic analysis, and EN 1993-1-1 steel member checks, flexural and lateral-torsional buckling included. A model is JSON in and JSON out, written from Python, by the fers command, or by an AI agent through the FERS MCP server, the Claude Code plugin or the agent skill.
pip install FERS
from fers_core import check_beam
model = check_beam(6.0, "IPE200", udl=10_000.0) model.settings.analysis_options.include_member_deflected_shape = True model.run_analysis()
beam = model.resultsbundle.loadcases["Q"].member_results["1"] x_frac, (_, dy, _) = beam.member_displacement_peak ec3 = model.unity_check_results()[0] print(f"M {beam.local_minimums.mz / 1e3:.1f} kNm, sag {dy * 1e3:.1f} mm at x/L {x_frac:.2f}") print(f"EC3 utilization {ec3['max_utilization']:.2f}")
M -45.0 kNm, sag -41.8 mm at x/L 0.50
EC3 utilization 2.85
The sag is 41.3 mm of bending (5wL⁴/384EI) plus 0.4 mm of shear deformation, which the engine includes. The beam fails: the ULS moment of 60.75 kNm exceeds its bending resistance of 51.9 kNm, and with nothing restraining its compression flange, lateral-torsional buckling takes the utilization to 2.85.
fers solve, fers check and fers validate take a model JSON file, print one line (or --json) and say in their exit code whether the numbers can be used; see From the terminal and from coding agents. fers skill --install . gives a coding agent the FERS skill, and MCP clients can use the FERS MCP server. In Claude Code, both come as one plugin: /plugin install fers --marketplace Jeroen124/FERS_core.pip install "FERS[plot]"), section properties meshed from your own dimensions ("FERS[sections]") and DXF import ("FERS[dxf]"); "FERS[all]" installs everything 0.1.x did. Catalogue sections (IPE200, HEB300, UB 457x191x67, W14X30, ...) need no extra.Building a model by hand, create the FERS object first, then its nodes, members and loads. Creating a FERS restarts the id counters, so a node made before it would share an id with one made after it, and the solver refuses node ids that do not run from 1 without gaps.
run_analysis() puts the results on model.resultsbundle. Load cases and load combinations are keyed by name. Within each, nodes and members are keyed by their id as a string, because results round-trip through JSON.
case = model.resultsbundle.loadcases["Load"] # or .loadcombinations["ULS 1"]
case.displacement_nodes["5"].dy # dx dy dz rx ry rz warp case.reaction_nodes["1"].nodal_forces.fy # fx fy fz mx my mz bw beam = case.member_results["4"] beam.local_maximums.mz, beam.local_minimums.mz # envelope along the member beam.local_start_forces.fy # end forces, member axes
start_node_forces, end_node_forces, maximums and minimums are in the global frame; the local_* forces, section_forces and internal_force_series are in the member's own axes, with mz the strong-axis moment. Read a frame's moments from the local ones: outside the XY plane the global mz is not the bending moment, and in a frame in the YZ plane it reads 0.
Since 0.1.98, member_results and displacement_nodes are read-only tables rather than dicts of objects. That is what keeps a large result small in memory. They read like dicts: [], get, in, len, .items(), and iteration in the solver's order. Each lookup builds a fresh object, and nothing in it can be changed:
beam.local_maximums.mz = 0.0 # AttributeError: read-only
editable = case.member_results.copy() # a plain dict of editable objects editable["4"].local_maximums.mz = 0.0 # fine
section_forces, internal_force_series and member_displacements come back as tuples. To test for a table, use isinstance(x, collections.abc.Mapping), not dict. reaction_nodes and plate_results are plain dicts, as before.
from fers_core import FERS
model.save_to_json("beam.json") # the model and its results model = FERS.from_json("beam.json")
FERS.from_json reads any file the solver or the SDK wrote, one entry at a time. A typical result needs less memory than its file to load, and at most about twice the file's size; before 0.1.98 it needed 15 to 20 times. For a large model, let the solver write the file itself. The result then never passes through Python as one string:
model.run_analysis_to_file("beam_results.json")
solved = FERS.from_json("beam_results.json")
fers_core/examples/809_Reading_Results.py runs all of this end to end.
Saving a loaded model writes back everything the solver reads, with the same ids. That includes what the file defines but nothing uses yet, such as a section an editor keeps in its library. Nodes, members and the rest created after loading get ids the loaded model does not use, so saving never lets a new object replace a loaded one.
AnalysisOptions(result_filter=[...]) keeps or drops whole result blocks. result_requests goes further. Each ResultRequest names a block and narrows it to chosen members or nodes, load combinations, components and one member end:
from fers_core import ResultBlock, ResultRequest
from fers_core.builders import check_beam from fers_core.result_requests import nodes_of from fers_core.unity_checks import classification
model = check_beam(5.0, "IPE300", udl=10000.0) # one "Beam", one ULS combination beams = classification("Beam") model.settings.analysis_options.result_requests = [ ResultRequest(ResultBlock.LOCAL_ENVELOPES, members=[beams], limit_state="ULS", components=["fx", "my", "mz"]), ResultRequest(ResultBlock.NODE_DISPLACEMENTS, nodes=nodes_of(beams), limit_state="ULS", components=["dy"]), ResultRequest(ResultBlock.REACTIONS, limit_state="ULS"), ] model.run_analysis()
The values load into the same member_results, displacement_nodes and reaction_nodes, so code that reads them keeps working. Anything that was not requested reads as `None`, not zero. A check that reads a value nobody asked for therefore fails instead of passing on a zero.
members takes the unity checks' selectors: classification(...), members([...]), member_sets([...]) and all_members(). Several selectors form a union.nodes takes nodes([...]), or nodes_of(*selectors) for the end nodes of the selected members.limit_state, as unity checks match them, or by load_combination_ids. To select by limit state, set limit_state on each LoadCombination; the solver does not read situation.limit_state nor load_combination_ids also covers the load cases.end="start" or end="end" keeps one end of the end-force and end-displacement blocks.section_forces, internal_force_series, a component the block does not have, a limit state no combination carries, and result_filter set as well.The values themselves are on model.resultsbundle.selections, one entry per request and group of result sets. Each entry's values is a read-only numpy array shaped (result_sets, ids, fields, components), with each axis listed beside it:
sel = model.resultsbundle.selections[0]
sel["group"] # "loadcombinations" or "loadcases" sel["result_sets"], sel["ids"] # combination names, member or node ids sel["fields"], sel["components"] # e.g. ["local_maximums", "local_minimums"], ["fx", "my", "mz"] my = sel"values".index("my")] # every set, member and field
save_to_json writes the selections back as the solver wrote them, so a saved result reloads unchanged. fers_core/examples/810_Result_Requests.py runs this end to end.
A rail running over beams and resting on each one is continuous along its length, but free to turn on every beam. A MemberHinge releases one member end, so releasing both rail ends at a beam splits the rail into two spans. Put the rail's members in a member set of their own and give the set a ScissorHinge instead. No link member or extra node is needed:
from fers_core import FERS, DistributedLoad, Material, Member, MemberSet, NodalSupport, Node, ScissorHinge, Section
from fers_core.supports.supportcondition import SupportCondition as C
model = FERS() steel = Material(name="S355", e_mod=210e9, g_mod=81e9, density=7850.0, yield_stress=355e6) rail_section = Section(name="rail", material=steel, i_y=1e-6, i_z=1e-6, j=1e-7, area=1e-3) beam_section = Section(name="beam", material=steel, i_y=1e-4, i_z=1e-4, j=1e-4, area=5e-3)
turns = {"X": C.fixed(), "Y": C.free(), "Z": C.free()} a, m, b = Node(0.0, 0.0, 0.0), Node(1.0, 0.0, 0.0), Node(2.0, 0.0, 0.0) a.nodal_support = NodalSupport(rotation_conditions=turns) b.nodal_support = NodalSupport( displacement_conditions={"X": C.free(), "Y": C.fixed(), "Z": C.fixed()}, rotation_conditions=turns ) beam_start, beam_end = Node(1.0, 0.0, -0.5), Node(1.0, 0.0, 0.5) beam_start.nodal_support = beam_end.nodal_support = NodalSupport() # clamped
rail_1 = Member(start_node=a, end_node=m, section=rail_section) rail_2 = Member(start_node=m, end_node=b, section=rail_section) model.add_member_set(MemberSet( members=[rail_1, rail_2], scissor_hinge=ScissorHinge(rotational_release_y=0.0, rotational_release_z=0.0), )) model.add_member_set(MemberSet(members=[ Member(start_node=beam_start, end_node=m, section=beam_section), Member(start_node=m, end_node=beam_end, section=beam_section), ]))
span = model.create_load_case(name="span") DistributedLoad(member=rail_1, load_case=span, magnitude=1000.0, end_magnitude=1000.0, direction=(0, -1, 0)) model.run_analysis() mz = model.resultsbundle.loadcases["span"].member_results[str(rail_1.id)].local_end_forces.mz print(f"{abs(mz):.1f} Nm") # 62.4; a knife edge gives qL²/16 = 62.5, a rigid joint 122.6
MemberSet(scissor_hinge_nodes=[...]) lists where the hinge applies, for a beam that turns on some of the members it meets and is joined to the rest.MemberHinge release doeNur der Anfang wird gezeigt; den Rest findest du im Repo