MCP Core Layer: Runners, Models, and Catalogs
NeqSim includes a framework-agnostic MCP (Model Context Protocol) integration layer in the
neqsim.mcp package. This layer provides stateless runners, typed models, and self-describing
catalogs that any MCP server, REST API, or CLI tool can use to expose NeqSim’s thermodynamic
engine to LLMs and other automated clients.
Architecture Overview
| Package | Purpose |
|---|---|
neqsim.mcp.runners |
Stateless runners that accept JSON, call NeqSim, and return JSON |
neqsim.mcp.model |
Typed Java models for requests, responses, and diagnostics |
neqsim.mcp.catalog |
Self-describing examples and JSON Schema catalogs |
All classes are pure Java 8 and have no framework dependencies — they can be used
from Quarkus, Spring Boot, plain servlets, or a standalone main() method.
neqsim/mcp/
├── runners/
│ ├── FlashRunner.java # Flash calculations (9 types × 6 EOS)
│ ├── ProcessRunner.java # Process simulation via JsonProcessBuilder
│ ├── Validator.java # Pre-flight input validation (12+ checks)
│ └── ComponentQuery.java # Component database search & fuzzy matching
├── model/
│ ├── ApiEnvelope.java # Standard response wrapper
│ ├── FlashRequest.java # Typed flash input (builder pattern)
│ ├── FlashResult.java # Typed flash output
│ ├── ProcessResult.java # Typed process simulation output
│ ├── ValueWithUnit.java # Numeric value + unit string
│ └── DiagnosticIssue.java # Validation issue with fix hint
└── catalog/
├── ExampleCatalog.java # 8 ready-to-use JSON examples
└── SchemaCatalog.java # JSON Schema for 4 tools (input + output)
1. Runners
FlashRunner
Performs thermodynamic flash calculations on a fluid mixture. Accepts JSON, creates the
appropriate SystemInterface, runs the flash, and returns a JSON response with per-phase
properties and compositions.
Entry point:
String resultJson = FlashRunner.run(inputJson);
Typed entry point:
ApiEnvelope<FlashResult> envelope = FlashRunner.runTyped(inputJson);
if (envelope.isSuccess()) {
FlashResult result = envelope.getData();
}
Input JSON Format
{
"model": "SRK",
"temperature": { "value": 25.0, "unit": "C" },
"pressure": { "value": 50.0, "unit": "bara" },
"flashType": "TP",
"components": {
"methane": 0.85,
"ethane": 0.10,
"propane": 0.05
},
"mixingRule": "classic"
}
Input Parameters
| Field | Type | Default | Description |
|---|---|---|---|
model |
string | "SRK" |
Equation of state (see table below) |
temperature |
object | — | {"value": number, "unit": "C"\|"K"\|"F"} |
pressure |
object | — | {"value": number, "unit": "bara"\|"barg"\|"Pa"\|"kPa"\|"MPa"\|"psi"\|"atm"} |
flashType |
string | "TP" |
Flash algorithm |
components |
object | — | Map of component name to mole fraction |
mixingRule |
string | "classic" |
Mixing rule name |
enthalpy |
object | — | Required for PH flash: {"value": number, "unit": "J/mol"} |
entropy |
object | — | Required for PS flash: {"value": number, "unit": "J/molK"} |
volume |
object | — | Required for TV flash: {"value": number, "unit": "m3/mol"} |
Supported Equations of State
| Model | Full Name | Best For |
|---|---|---|
SRK |
Soave-Redlich-Kwong | General hydrocarbon systems (default) |
PR |
Peng-Robinson | General purpose, slightly different liquid densities |
CPA |
CPA-SRK | Systems with water, methanol, MEG, or other associating fluids |
GERG2008 |
GERG-2008 | High-accuracy natural gas (reference-quality) |
PCSAFT |
PC-SAFT | Polymers, associating fluids |
UMRPRU |
UMR-PRU with Mathias-Copeman | Advanced mixing rules |
Supported Flash Types
| Flash Type | Description | Required Extra Fields |
|---|---|---|
TP |
Temperature-Pressure flash (most common) | — |
PH |
Pressure-Enthalpy flash | enthalpy |
PS |
Pressure-Entropy flash | entropy |
TV |
Temperature-Volume flash | volume |
dewPointT |
Dew point temperature at given pressure | — |
dewPointP |
Dew point pressure at given temperature | — |
bubblePointT |
Bubble point temperature at given pressure | — |
bubblePointP |
Bubble point pressure at given temperature | — |
hydrateTP |
Hydrate equilibrium temperature at given pressure | — |
Temperature Unit Conversion
FlashRunner converts all temperature units to Kelvin internally:
| Input Unit | Conversion |
|---|---|
C |
K = C + 273.15 |
K |
No conversion |
F |
K = (F - 32) × 5/9 + 273.15 |
Pressure Unit Conversion
| Input Unit | Conversion to bara |
|---|---|
bara |
No conversion |
barg |
bara = barg + 1.01325 |
Pa |
bara = Pa / 100000 |
kPa |
bara = kPa / 100 |
MPa |
bara = MPa × 10 |
psi |
bara = psi × 0.0689476 |
atm |
bara = atm × 1.01325 |
Output JSON Structure
The response follows the FluidResponse format used by NeqSim’s monitoring layer:
{
"status": "success",
"flash": {
"model": "SRK",
"flashType": "TP",
"numberOfPhases": 1,
"phases": ["gas"]
},
"fluid": {
"conditions": {
"overall": { "temperature": 298.15, "pressure": 50.0 },
"gas": { "temperature": 298.15, "pressure": 50.0, "molarMass": 0.0178 }
},
"properties": {
"gas": {
"density": { "value": 38.9, "unit": "kg/m3" },
"compressibilityFactor": { "value": 0.907, "unit": "" },
"viscosity": { "value": 1.17e-5, "unit": "Pa·s" },
"thermalConductivity": { "value": 0.038, "unit": "W/(m·K)" },
"Cp": { "value": 2350, "unit": "J/(kg·K)" }
}
},
"composition": {
"gas": {
"methane": { "value": 0.85 },
"ethane": { "value": 0.10 },
"propane": { "value": 0.05 }
}
}
}
}
Important for dew/bubble point calculations: The converged temperature or pressure
is found in fluid.conditions.overall.temperature (Kelvin) or fluid.conditions.overall.pressure
(bara), not in a separate field.
ProcessRunner
Runs a process simulation from a JSON definition. Delegates to
ProcessSystem.fromJsonAndRun() and returns structured results.
Entry points:
// JSON string in, JSON string out
String resultJson = ProcessRunner.run(inputJson);
// Pre-validate then run (merges validation warnings into result)
String resultJson = ProcessRunner.validateAndRun(inputJson);
// Typed API
ApiEnvelope<ProcessResult> envelope = ProcessRunner.runTyped(inputJson);
Input JSON Format
{
"fluid": {
"model": "SRK",
"temperature": 298.15,
"pressure": 50.0,
"mixingRule": "classic",
"components": {
"methane": 0.85,
"ethane": 0.10,
"propane": 0.05
}
},
"process": [
{
"type": "Stream",
"name": "feed",
"properties": { "flowRate": [50000.0, "kg/hr"] }
},
{
"type": "Separator",
"name": "HP Sep",
"inlet": "feed"
},
{
"type": "Compressor",
"name": "Comp",
"inlet": "HP Sep.gasOut",
"properties": { "outletPressure": [80.0, "bara"] }
}
]
}
Supported Equipment Types
The Validator recognizes all equipment types supported by JsonProcessBuilder:
| Equipment Type | Description |
|---|---|
Stream |
Feed or intermediate stream |
Separator |
Two-phase gas/liquid separator |
ThreePhaseSeparator |
Three-phase gas/oil/water separator |
Compressor |
Gas compressor |
Expander |
Gas expander/turbine |
Heater |
Heat addition |
Cooler |
Heat removal |
HeatExchanger |
Two-stream heat exchanger |
Mixer |
Stream mixer |
Splitter |
Stream splitter |
Valve / ThrottlingValve |
Throttling valve (isenthalpic) |
Pump |
Liquid pump |
DistillationColumn / Column |
Distillation column |
AdiabaticPipe / Pipe / Pipeline / PipeBeggsAndBrills |
Pipe flow |
Recycle |
Recycle loop |
Adjuster / SetPoint / GORfitter |
Process adjusters |
StreamSaturatorUtil / Saturator |
Stream saturation utility |
CO2Electrolyzer |
CO2 electrolysis reactor |
WindTurbine / SolarPanel / WindFarm |
Renewable energy equipment |
BatteryStorage |
Energy storage |
OffshoreEnergySystem |
Offshore energy system |
AmmoniaSynthesisReactor |
Ammonia synthesis |
SubseaPowerCable |
Subsea power cable |
Outlet Port Selectors
When connecting equipment, use dot-notation to select specific outlet streams:
| Port | Description |
|---|---|
<name>.gasOut |
Gas outlet from separator |
<name>.liquidOut |
Liquid outlet from separator |
<name>.oilOut |
Oil outlet from three-phase separator |
<name>.waterOut |
Water outlet from three-phase separator |
Multiple Fluids
Use "fluids" (plural) for processes with different fluid compositions:
{
"fluids": {
"gas": {
"model": "SRK",
"temperature": 298.15,
"pressure": 50.0,
"components": { "methane": 0.9, "ethane": 0.1 }
},
"oil": {
"model": "PR",
"temperature": 350.0,
"pressure": 100.0,
"components": { "nC10": 1.0 }
}
},
"process": [
{ "type": "Stream", "name": "gasFeed", "fluidRef": "gas",
"properties": { "flowRate": [10000.0, "kg/hr"] } },
{ "type": "Stream", "name": "oilFeed", "fluidRef": "oil",
"properties": { "flowRate": [50000.0, "kg/hr"] } }
]
}
Validator
Validates a flash or process JSON input without running any simulation. Auto-detects
whether the input is a flash definition or a process definition based on the presence
of a "process" array.
Entry point:
String validationJson = Validator.validate(inputJson);
Checks Performed
| Check Code | Severity | Description |
|---|---|---|
INPUT_ERROR |
error | JSON is null or empty |
JSON_PARSE_ERROR |
error | Malformed JSON |
UNKNOWN_MODEL |
error | Unrecognized EOS model |
UNKNOWN_FLASH_TYPE |
error | Unrecognized flash type |
MISSING_SPEC |
error | PH flash without enthalpy, PS without entropy, TV without volume |
NEGATIVE_TEMPERATURE |
error | Temperature below absolute zero (-273.15 °C) |
EXTREME_TEMPERATURE |
warning | Temperature outside -200 to 1000 °C |
NEGATIVE_PRESSURE |
error | Pressure ≤ 0 |
EXTREME_PRESSURE |
warning | Pressure above 1000 bara |
UNKNOWN_COMPONENT |
error | Component not in NeqSim database (suggests correction) |
COMP_SUM_WARNING |
warning | Mole fractions don’t sum to ~1.0 (tolerance: ±0.01) |
UNKNOWN_EQUIPMENT_TYPE |
error | Process equipment type not recognized |
DUPLICATE_EQUIPMENT_NAME |
warning | Two equipment items with the same name |
Output Format
{
"valid": false,
"issues": [
{
"severity": "error",
"code": "UNKNOWN_COMPONENT",
"message": "'metane' is not a known component",
"fix": "Did you mean 'methane'?"
},
{
"severity": "warning",
"code": "COMP_SUM_WARNING",
"message": "Component mole fractions sum to 0.80 (expected ~1.0)",
"fix": "Normalize fractions to sum to 1.0"
}
]
}
ComponentQuery
Searches the NeqSim component database. Uses lazy-loaded caching for fast lookups.
Entry points:
// Search by substring (case-insensitive)
String resultsJson = ComponentQuery.search("meth");
// Check if exact name exists
boolean valid = ComponentQuery.isValid("methane");
// Fuzzy match for misspelled names (Levenshtein distance)
String suggestion = ComponentQuery.closestMatch("metane"); // → "methane"
// Get component properties from database
String infoJson = ComponentQuery.getInfo("methane");
// List all component names
List<String> allNames = ComponentQuery.allNames();
Search Response
{
"status": "success",
"query": "meth",
"matchCount": 4,
"components": ["methane", "methanol", "dimethylether", "methylamine"]
}
Component Info Response
{
"status": "success",
"component": {
"name": "methane",
"formula": "CH4",
"molarMass_g_mol": 16.043,
"criticalTemperature_C": -82.55,
"criticalPressure_bara": 46.0,
"acentricFactor": 0.0115,
"normalBoilingPoint_C": -161.5
}
}
2. Typed Models
ApiEnvelope
Standard response wrapper used by all runners:
public class ApiEnvelope<T> {
private String status; // "success" or "error"
private T data; // The typed result
private String errorCode; // Error code (if error)
private String errorMessage; // Error description (if error)
private String fix; // Remediation hint (if error)
private List<DiagnosticIssue> warnings; // Non-fatal warnings
}
FlashRequest
Typed flash input with builder pattern:
FlashRequest request = FlashRequest.builder()
.model("SRK")
.temperature(25.0, "C")
.pressure(50.0, "bara")
.flashType("TP")
.component("methane", 0.85)
.component("ethane", 0.10)
.component("propane", 0.05)
.build();
FlashResult
Typed flash output containing phase information and the FluidResponse:
FlashResult result = envelope.getData();
String model = result.getModel();
int numPhases = result.getNumberOfPhases();
List<String> phases = result.getPhases();
ProcessResult
Typed process simulation output with equipment-level results.
ValueWithUnit
Simple holder for a numeric value with its unit:
ValueWithUnit density = new ValueWithUnit(38.9, "kg/m3");
double value = density.getValue();
String unit = density.getUnit();
DiagnosticIssue
Validation issue with severity, error code, message, and fix suggestion:
DiagnosticIssue issue = DiagnosticIssue.error(
"UNKNOWN_COMPONENT",
"'metane' is not a known component",
"Did you mean 'methane'?"
);
3. Catalogs
ExampleCatalog
Provides 8 ready-to-use JSON examples that LLMs use to learn the input format:
| Category | Name | Description |
|---|---|---|
flash |
tp-simple-gas |
TP flash of a simple natural gas |
flash |
tp-two-phase |
TP flash producing gas + liquid phases |
flash |
dew-point-t |
Dew point temperature calculation |
flash |
bubble-point-p |
Bubble point pressure calculation |
flash |
cpa-with-water |
CPA EOS flash with water |
process |
simple-separation |
Stream → Separator |
process |
compression-with-cooling |
Stream → Compressor → Cooler |
validation |
error-flash |
Deliberately invalid flash input |
Usage:
String example = ExampleCatalog.getExample("flash", "tp-simple-gas");
String catalogJson = ExampleCatalog.getCatalogJson();
SchemaCatalog
Provides JSON Schema (Draft 2020-12) definitions for each tool’s input and output:
| Tool | Schema Types |
|---|---|
run_flash |
input, output |
run_process |
input, output |
validate_input |
input, output |
search_components |
input, output |
Usage:
String schema = SchemaCatalog.getSchema("run_flash", "input");
String catalogJson = SchemaCatalog.getCatalogJson();
4. Test Coverage
The MCP core layer has 139+ JUnit 5 tests across 12 test classes:
| Test Class | Description |
|---|---|
FlashRunnerTest |
TP flash, multi-phase, all EOS models |
FlashRunnerDewBubbleTest |
Dew point and bubble point calculations |
FlashRunnerSpecialFlashTest |
PH, PS, TV flash types |
FlashRunnerEdgeCasesTest |
Error handling, invalid inputs |
FlashRunnerUnitConversionTest |
Temperature and pressure unit conversion |
ProcessRunnerTest |
Simple and multi-unit process simulations |
ProcessRunnerValidateAndRunTest |
Combined validation + run workflow |
ValidatorTest |
All 12+ validation check codes |
ComponentQueryTest |
Search, isValid, closestMatch, getInfo |
ExampleCatalogTest |
All examples parse and contain required fields |
SchemaCatalogTest |
All schemas are valid JSON Schema |
ModelClassTest |
ApiEnvelope, FlashRequest builder, ValueWithUnit |
Run all MCP tests from the NeqSim root:
# Linux/macOS
./mvnw test -Dtest="neqsim.mcp.**"
# Windows
.\mvnw.cmd test -Dtest="neqsim.mcp.**"
5. Design Principles
-
Stateless — Every call is independent. No session state, no shared mutable fields. All runners use static methods.
-
JSON-in, JSON-out — Input and output are always JSON strings. This makes the layer transport-agnostic. The same runner works with MCP STDIO, HTTP REST, WebSocket, or CLI.
-
Framework-agnostic — Zero dependencies on Quarkus, Spring, or any web framework. The layer depends only on NeqSim core and Gson.
-
Self-describing — The
ExampleCatalogandSchemaCatalogmean any LLM can discover the correct JSON format without external documentation. -
Defensive validation — The
Validatorcatches common mistakes (misspelled components, wrong units, missing specs) before expensive simulation runs, with actionable fix hints. -
Standard envelope — All responses use the same
ApiEnvelopestructure withstatus,data,warnings, and error details, making response parsing predictable.
Related Documentation
- MCP Server Guide — Quarkus-based MCP server that wraps this layer
- Web API / JSON Process Builder — JSON process definition format
- AI Validation Framework — NeqSim’s broader validation system
- AI Agents Reference — Full catalog of NeqSim AI agents