Spec Driven Development
MCP Server that facilitates spec-driven development workflows, not just Vibe Coding.
作者による説明(英語の原文)
Structural FEM for AI agents: frames, plates, buckling, seismic and Eurocode checks, solved locally.
この3つを1つのスコアにまとめることはしません。意見、測定、人気はそれぞれ別のものだからです。
このサーバーのやさしい解説はまだ書いていません。以下はレジストリから読み取った事実です。
使っているAIアプリを選んで、設定をコピーしてください。
FERS_API_KEY「秘密」と表示された値は、あなたのアカウントの鍵です。絶対に人に教えたり、チャットに貼り付けたりしないでください。
必要なもの
どのアプリを使っていますか?
上でアプリを選んでください。選んだアプリを覚えておくので、次からはボタンに「…にインストール」と表示されます。
ウェブ、デスクトップ、モバイルのClaudeで使えます:
https://ferscloud.com/api/mcpClaude Desktop → Settings → Developer → Edit Config を開き、claude_desktop_config.json にこれを貼り付けます
{
"mcpServers": {
"fers": {
"command": "npx",
"args": [
"-y",
"@ferscloud/fers-mcp"
],
"env": {
"FERS_API_KEY": "your-value"
}
}
}
}ターミナルでこれを実行します
claude mcp add --transport http fers https://ferscloud.com/api/mcpclaude mcp add fers --env FERS_API_KEY=your-value -- npx -y @ferscloud/fers-mcp動かす前に、空欄の値を入力してください。
プロジェクトの .cursor/mcp.json、または ~/.cursor/mcp.json に貼り付けます
{
"mcpServers": {
"fers": {
"url": "https://ferscloud.com/api/mcp"
}
}
}プロジェクトの .cursor/mcp.json、または ~/.cursor/mcp.json に貼り付けます
{
"mcpServers": {
"fers": {
"command": "npx",
"args": [
"-y",
"@ferscloud/fers-mcp"
],
"env": {
"FERS_API_KEY": "your-value"
}
}
}
}ブラウザがアプリを開いてよいか確認し、そのあとアプリが設定を表示して確認を求めます。キーの値は空欄のままなので、自分で入力してください。
プロジェクトの .vscode/mcp.json に貼り付けます
{
"servers": {
"fers": {
"type": "http",
"url": "https://ferscloud.com/api/mcp"
}
}
}プロジェクトの .vscode/mcp.json に貼り付けます
{
"servers": {
"fers": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"@ferscloud/fers-mcp"
],
"env": {
"FERS_API_KEY": "your-value"
}
}
}
}ブラウザがアプリを開いてよいか確認し、そのあとアプリが設定を表示して確認を求めます。キーの値は空欄のままなので、自分で入力してください。
ChatGPTがつなげるのは、インターネット上のサーバーだけです:
https://ferscloud.com/api/mcpメニューの名前は、プランや時期によって少し違うことがあります。
選んだアプリは、この端末のCookieに保存されます。アカウントは不要です。
この設定は、作者のレジストリ登録情報から作成しています。READMEと違う場合は、READMEに従ってください。
このサーバーは、ツール一覧を表示する前にサインインを求めます。
誰でも見られる情報をチェックしており、コードは読んでいません。当てはまらない項目は数に入れていません。 · 2026年11月7日にチェック
公開者(ferscloud.com)がリポジトリの所有者(Jeroen124)ではありません。別の人が登録した可能性があります
このリポジトリはまだGitHubから読み取っていません
このリポジトリはまだGitHubから読み取っていません
51ミリ秒で応答(HTTP 401)
OAuthでのサインインに対応しているため、AIアプリがログイン画面へ案内してくれます
まだチェックしていません。エンドポイントをチェックするときにツール一覧を読み取ります
レジストリでこのカテゴリにある有効なすべてのサーバーと比較しています。スターは、公開者がリポジトリの所有者である場合だけ数えます。
当てはまる基本チェック3項目のうち2項目に合格しました。コードは読んでいません。キーやトークンを追加する前に、サーバーがどんなアクセスを求めているかを確認し、必要な権限だけを持つキーを使ってください。
入力が必要なもの:FERS_API_KEY
どちらでも使えます。パソコンにインストールしても、クラウド版につないでもかまいません。
GitHubリポジトリの最終更新は–、最新バージョン(2.83.0)がレジストリに届いたのは一昨日です。
Claude Desktop、Claude Code、Cursor、VS CodeなどのMCP対応アプリで使えます。URLでつながるので、リモートMCPサーバーに対応したアプリ(開発者モードのChatGPTなど)でも使えます。
同じカテゴリには:Spec Driven Development, Remote Desktop Commander, FlyDocs
同じカテゴリで、ほかの作者が作ったサーバー。スターの多い順。
カードの見方:★ GitHubスターi3つの柱:レビュー · チェック · 利用状況i更新=最新バージョンをレジストリに公開した日i
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.
Spec-driven development workflow: your agent works issues, sessions and the status lifecycle.
作者のリポジトリから書かれたとおりにコピーしたものです。PopNx MCPは内容を確認・推奨していません。HTML、画像、スクリプトは取り除いています。リンクはこのサイトの外へ移動します。
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 doe冒頭だけを表示しています。続きはリポジトリで読めます