Simulation MCP is an engineering-tool workspace with two independent Model Context Protocol (MCP) servers:
- A Python stdio server for validated OpenFOAM case management and CFD operations.
- A TypeScript NitroStack server for FreeCAD, Avogadro/Open Babel, and three interactive result-preview widgets.
They can run independently. An MCP host can use both servers in one workflow, but the TypeScript server does not start or proxy the Python OpenFOAM server automatically.
MCP host
|-- stdio --> Python Simulation MCP
| |-- FastMCP tool adapters
| |-- OpenFOAMService
| |-- OpenFOAMExecutor --> bash -lc --> OpenFOAM CLI
| `-- SIMULATION_ROOT/<case>/
|
`-- stdio / NitroStack transport --> NitroStack MCP server
|-- FreeCAD module --> XML-RPC --> FreeCAD MCP addon
|-- Avogadro module --> Open Babel CLI / local bridge
`-- widget tools --> Next.js widgets
Optional cloud path
NitroStack deployment --> authenticated HTTPS tunnel --> loopback local bridge
|-- /freecad --> FreeCAD XML-RPC
`-- /avogadro --> local Open Babel
| Path | Responsibility |
|---|---|
simulation_mcp/server.py |
Python FastMCP composition root and stdio entry point. |
simulation_mcp/openfoam/ |
OpenFOAM input models, tool adapters, case service, executor, parsers, and safe domain errors. |
src/app.module.ts |
NitroStack composition root for the dashboard, FreeCAD, Avogadro, and health modules. |
src/modules/freecad/ |
Typed FreeCAD tools, sequential XML-RPC client, service layer, and workflow prompt. |
src/modules/avogadro/ |
Zod schemas and local or bridged Open Babel operations. |
src/modules/simulation/ |
display_simulation_results widget bridge. |
src/local-bridge.ts |
Loopback-only HTTP bridge for a tunnelled local FreeCAD and Avogadro installation. |
src/widgets/ |
Next.js implementations of the simulation dashboard and 3D previews. |
examples/mcp_client.py |
Minimal Python stdio MCP client. |
tests/ |
Python service tests. |
Install only the components required for the capabilities you plan to use.
| Capability | Requirements |
|---|---|
| OpenFOAM | Python 3.12+, an Ubuntu OpenFOAM installation, and its etc/bashrc. |
| NitroStack | A supported Node.js runtime and npm. |
| FreeCAD | FreeCAD with its MCP addon installed and its XML-RPC server started. The addon is an external dependency and is not included in this repository. |
| Avogadro | Open Babel CLI (obabel and obprop), its force-field data directory, and optionally Avogadro or the Open Babel GUI for opening files. |
| Cloud access to local engineering apps | An authenticated HTTPS tunnel such as ngrok, plus a locally running bridge. |
python3.12 -m venv .venv
source .venv/bin/activate
pip install -e '.[test]'
cp .env.example .env
# Set OPENFOAM_BASHRC in .env if discovery cannot find the installation.
simulation-mcpThe Python server communicates through stdio. Do not write ordinary output to its stdout; MCP protocol messages use that stream. Use python -m simulation_mcp as an equivalent entry point.
By default, cases are stored in ./simulation-data. Set SIMULATION_ROOT to use a different root, for example SIMULATION_ROOT=/data/simulation-cases.
npm install
cp .env.example .env
npm run devnpm run build compiles the TypeScript application and bundles the widgets. npm start builds and starts the production server; npm run start:prod starts an existing production build. The NitroStack CLI installs widget dependencies while bundling, and npm run widget runs the widget-package command in src/widgets.
Start the FreeCAD addon RPC server before calling FreeCAD tools. Configure executable paths and data roots before calling Avogadro tools, then use avogadro_status to verify the installation.
Copy .env.example to .env. Python reads its OpenFOAM settings with Pydantic settings; NitroStack and the bridge load the same file with dotenv.
| Variable | Default | Purpose |
|---|---|---|
SIMULATION_ROOT |
./simulation-data |
Root directory containing server-owned cases. |
OPENFOAM_BASHRC |
auto-discovered | Explicit path to the OpenFOAM etc/bashrc. |
OPENFOAM_COMMAND_TIMEOUT_SECONDS |
3600 |
Per-command timeout, from 1 to 86,400 seconds. |
LOG_LEVEL |
INFO |
Python log level. |
When OPENFOAM_BASHRC is absent, the executor checks /opt/openfoam/etc/bashrc, then recent openfoam*/etc/bashrc installations under /opt and /usr/lib/openfoam.
| Variable | Default | Purpose |
|---|---|---|
FREECAD_RPC_HOST / FREECAD_RPC_PORT |
127.0.0.1 / 9875 |
Local FreeCAD addon XML-RPC endpoint. |
FREECAD_RPC_URL |
unset | Complete HTTP(S) RPC endpoint. Use a /freecad tunnel route when applicable. |
FREECAD_RPC_TIMEOUT_MS |
150000 |
XML-RPC timeout, from 1,000 to 3,600,000 ms. |
FREECAD_FEM_MAX_TIMEOUT_SECONDS |
1800 |
Maximum accepted FEM tool timeout. |
FREECAD_ALLOW_REMOTE_RPC |
false |
Required before a non-loopback RPC endpoint is accepted. |
FREECAD_ALLOW_CODE_EXECUTION |
false |
Enables the arbitrary Python execution tools. Use only for trusted clients. |
FREECAD_RPC_BASIC_AUTH_USERNAME / FREECAD_RPC_BASIC_AUTH_PASSWORD |
unset | Paired basic-auth credentials for a remote RPC endpoint. |
FreeCAD operations are serialized in the NitroStack service so requests do not concurrently mutate the FreeCAD process. Inputs use 1-based FreeCAD subelement IDs: for example, face_id: 1 refers to Face1.
| Variable | Default | Purpose |
|---|---|---|
AVOGADRO_EXECUTABLE |
Windows Avogadro2 path | Avogadro application used by avogadro_open_file. |
OPENBABEL_GUI_EXECUTABLE |
Windows Open Babel GUI path | GUI application used by avogadro_open_file. |
OPENBABEL_EXECUTABLE / OPENBABEL_OBPROP_EXECUTABLE |
Derived from GUI path | Open Babel CLI executables. |
OPENBABEL_DATA_DIRECTORY |
Avogadro data directory |
Force-field data, passed as BABEL_DATADIR. |
AVOGADRO_DATA_ROOT |
./avogadro-data |
Root for molecule inputs and outputs. |
OPENBABEL_COMMAND_TIMEOUT_MS |
120000 |
Open Babel command and bridge-request timeout. |
AVOGADRO_BRIDGE_URL |
unset | Local or remote /avogadro bridge endpoint. |
AVOGADRO_ALLOW_REMOTE_BRIDGE |
false |
Required before a non-loopback bridge URL is accepted. |
AVOGADRO_BRIDGE_BASIC_AUTH_USERNAME / AVOGADRO_BRIDGE_BASIC_AUTH_PASSWORD |
unset | Paired basic-auth credentials for the bridge. |
Supported molecule formats are cjson, cml, mol, mol2, pdb, sdf, smi, and xyz.
| Variable | Default | Purpose |
|---|---|---|
LOCAL_BRIDGE_PORT |
9876 |
Loopback HTTP port used by the local bridge. |
FREECAD_RPC_LOCAL_PORT |
9875 |
Local FreeCAD RPC port to proxy from /freecad. |
All Python tools return a JSON envelope:
{"ok": true, "message": "Case created.", "data": {}}Expected failures return ok: false with an error_code in data. The adapters return safe validation, case, installation, and command errors rather than Python tracebacks.
| Tool | Purpose |
|---|---|
list_available_solvers |
Report availability of the supported solver catalog. |
create_case, list_cases, clean_case, delete_case |
Create, inspect, clean, and permanently delete isolated cases. |
import_geometry |
Copy an existing .stl into constant/triSurface. |
generate_mesh, check_mesh |
Run blockMesh and checkMesh -allTopology -allGeometry. |
set_boundary_conditions |
Write initial conditions for U, p, k, omega, epsilon, or nut. |
set_simulation_parameters |
Update supported controlDict values. |
run_solver, monitor_simulation |
Run an allowlisted solver synchronously or retain an in-process background status record. |
calculate_forces, extract_results, export_vtk |
Run force post-processing, inspect result times and fields, and run foamToVTK. |
Supported solver names are simpleFoam, pisoFoam, icoFoam, potentialFoam, rhoSimpleFoam, and buoyantSimpleFoam. list_available_solvers determines which of those are installed.
create_case creates a basic incompressible-flow skeleton with editable OpenFOAM dictionaries and a rectangular blockMeshDict. import_geometry owns an STL within the case but does not make the initial blockMesh conform to that geometry. For geometry-conformal production meshes, edit the generated dictionaries and add an appropriate meshing workflow such as snappyHexMesh.
Background solver state is held in memory. After the Python server restarts, provide no run_id to inspect the most recently modified case log instead.
The MCP host supplies the input object expected by the Python tool signatures:
{"tool":"create_case","arguments":{"input":{"case_name":"naca2412"}}}
{"tool":"import_geometry","arguments":{"input":{"case_name":"naca2412","stl_path":"/data/naca2412.stl"}}}
{"tool":"generate_mesh","arguments":{"input":{"case_name":"naca2412"}}}
{"tool":"check_mesh","arguments":{"input":{"case_name":"naca2412"}}}
{"tool":"set_boundary_conditions","arguments":{"input":{"case_name":"naca2412","conditions":[{"patch":"inlet","field":"U","condition_type":"fixedValue","value":[30,0,0]}]}}}
{"tool":"run_solver","arguments":{"input":{"case_name":"naca2412","solver":"simpleFoam"}}}
{"tool":"calculate_forces","arguments":{"input":{"case_name":"naca2412","patches":["walls"]}}}
{"tool":"extract_results","arguments":{"input":{"case_name":"naca2412","fields":["U","p"]}}}
{"tool":"export_vtk","arguments":{"input":{"case_name":"naca2412","latest_time_only":true}}}Run python examples/mcp_client.py after installing the Python package for a minimal official MCP-client example.
Configure a stdio-capable MCP host with the installed entry point:
{
"mcpServers": {
"simulation-mcp": {
"command": "/absolute/path/to/.venv/bin/simulation-mcp",
"args": [],
"env": {
"SIMULATION_ROOT": "/absolute/path/to/simulation-data",
"OPENFOAM_BASHRC": "/opt/openfoam12/etc/bashrc"
}
}
}
}The freecad_ tools control the external FreeCAD MCP addon over XML-RPC.
| Area | Tools |
|---|---|
| Connectivity and documents | freecad_status, freecad_create_document, freecad_list_documents, freecad_reload_document |
| Object operations | freecad_create_object, freecad_edit_object, freecad_delete_object, freecad_get_objects, freecad_get_object, freecad_list_features |
| Measurement and queries | freecad_get_face_normal, freecad_get_edge_direction, freecad_measure_distance, freecad_measure_angle, freecad_bounding_box, freecad_center_of_mass, freecad_intersects, freecad_closest_face, freecad_raycast, freecad_validate_constraints |
| Placement | freecad_align_face_to_face, freecad_align_axis, freecad_rotate_about_edge, freecad_look_at, freecad_snap_to_vertex, freecad_snap_to_edge, freecad_snap_to_face |
| Mates | freecad_mate_coincident, freecad_mate_parallel, freecad_mate_perpendicular, freecad_mate_tangent |
| Visualization | freecad_get_view, freecad_preview_model |
| Parts library | freecad_get_parts_list, freecad_list_parts_library_categories, freecad_search_parts_library, freecad_insert_part_from_library |
| Advanced operations | freecad_execute_code, freecad_execute_code_async, freecad_run_fem_analysis |
freecad_asset_creation_strategy is an MCP prompt that directs clients to inspect current documents, search the installed parts library before creating geometry, verify changes, and validate FEM setup before solving. The code-execution tools remain disabled unless explicitly enabled.
| Tool | Purpose |
|---|---|
avogadro_status |
Verify the configured applications, CLI tools, data directory, and molecule root. |
avogadro_create_molecule |
Create a 2D or 3D structure from SMILES, optionally optimizing it. |
avogadro_convert_molecule |
Convert formats and preserve, add, or remove hydrogens. |
avogadro_optimize_molecule |
Optimize geometry with MMFF94, MMFF94s, UFF, GAFF, or Ghemical. |
avogadro_get_properties |
Obtain Open Babel molecular properties. |
avogadro_preview_molecule |
Render a bounded interactive ball-and-stick preview. |
avogadro_list_files |
List supported files under AVOGADRO_DATA_ROOT. |
avogadro_open_file |
Launch a stored molecule in Avogadro or the Open Babel GUI. |
Molecule file paths must be relative to AVOGADRO_DATA_ROOT; absolute paths and traversal outside that root are rejected. Preview source files are limited to 25 MB and output is limited to 999 atoms and 999 bonds.
| Tool | Widget | Behavior |
|---|---|---|
display_simulation_results |
simulation-dashboard |
Displays mapped OpenFOAM status, mesh data, residuals, forces, and VTK artifact names. |
freecad_preview_model |
freecad-model-preview |
Converts bounded FreeCAD tessellation buffers to an in-browser GLB preview. |
avogadro_preview_molecule |
avogadro-molecule-preview |
Builds an in-browser ball-and-stick GLB preview from Open Babel geometry. |
The dashboard does not load VTK files. It identifies exported artifacts for use in a compatible viewer such as ParaView. VTK rendering in a browser would require a separately hosted file endpoint and a WebGL viewer.
For a cloud-hosted NitroStack server to access local FreeCAD and Open Babel, run the loopback bridge and expose only its port through an authenticated HTTPS tunnel:
npm run bridge
ngrok http 9876 --basic-auth="engineering:YOUR_LONG_RANDOM_PASSWORD"The bridge binds to 127.0.0.1, provides GET /health, proxies all /freecad traffic to local XML-RPC, and accepts validated POST /avogadro actions. Its JSON request body limit is 1 MiB. Authentication is supplied by the HTTPS tunnel or another reverse proxy; the bridge itself does not implement authentication.
Configure the NitroStack deployment with the tunnel's HTTPS URL and matching credentials:
FREECAD_RPC_URL=https://your-tunnel.ngrok-free.app/freecad
FREECAD_RPC_BASIC_AUTH_USERNAME=engineering
FREECAD_RPC_BASIC_AUTH_PASSWORD=YOUR_LONG_RANDOM_PASSWORD
FREECAD_ALLOW_REMOTE_RPC=true
FREECAD_ALLOW_CODE_EXECUTION=false
AVOGADRO_BRIDGE_URL=https://your-tunnel.ngrok-free.app/avogadro
AVOGADRO_BRIDGE_BASIC_AUTH_USERNAME=engineering
AVOGADRO_BRIDGE_BASIC_AUTH_PASSWORD=YOUR_LONG_RANDOM_PASSWORD
AVOGADRO_ALLOW_REMOTE_BRIDGE=trueKeep the FreeCAD addon, bridge, and tunnel running. A new ephemeral tunnel URL must be applied to both remote endpoint variables after a tunnel restart.
- OpenFOAM case paths are confined to
SIMULATION_ROOT; case names are allowlisted and STL imports require an existing.stlfile. - The Python executor uses
asyncio.create_subprocess_exec. It invokesbash -lconly to source the OpenFOAM environment, then runs commands assembled from validated values. - Avogadro operations use direct
execFilearguments and confine molecule paths toAVOGADRO_DATA_ROOT. - FreeCAD rejects non-loopback endpoints unless
FREECAD_ALLOW_REMOTE_RPC=true; Avogadro applies the same opt-in rule to bridge URLs. freecad_execute_codeandfreecad_execute_code_asyncexecute arbitrary Python in FreeCAD. Keep them disabled for untrusted clients and remote deployments.- The NitroStack system health check runs every 30 seconds and reports
degradedwhen Node.js heap use reaches 90%.
# Python tests
pip install -e '.[test]'
pytest
# TypeScript application and widgets
npm install
npm run buildThe Python test suite covers case creation/listing, case-name traversal rejection, and STL import validation. The repository currently has no TypeScript test suite; npm run build is the TypeScript and widget verification command.