gnnepcsaft-mcp-server 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,59 @@
1
+ Metadata-Version: 2.4
2
+ Name: gnnepcsaft-mcp-server
3
+ Version: 0.3.1
4
+ Summary: Model Context Protocol server for GNNePCSAFT tools
5
+ License: GNU General Public License v3.0
6
+ Author: wildsonbbl
7
+ Author-email: wil_bbl@hotmail.com
8
+ Requires-Python: >=3.10
9
+ Classifier: License :: Other/Proprietary License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Requires-Dist: feos (>=0.8.0,<0.9.0)
17
+ Requires-Dist: gnnepcsaft (>=0.2.4)
18
+ Requires-Dist: mcp (>=1.6.0,<2.0.0)
19
+ Requires-Dist: numpy (>=2.2.4,<3.0.0)
20
+ Requires-Dist: onnxruntime (>=1.21.0,<2.0.0)
21
+ Requires-Dist: rdkit (>=2024.9.6,<2025.0.0)
22
+ Requires-Dist: si-units (>=0.11.0,<0.12.0)
23
+ Description-Content-Type: text/markdown
24
+
25
+ # GNNEPCSAFT MCP Server
26
+
27
+ GNNEPCSAFT MCP Server is an implementation of the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) for [GNNePCSAFT](https://github.com/wildsonbbl/gnnepcsaft) tools. GNNePCSAFT leverages Graph Neural Networks (GNNs) to estimate [PC-SAFT](https://en.wikipedia.org/wiki/PC-SAFT) pure-component parameters, allowing property predictions such as density and vapor pressure for any molecule or mixture. [FeOs](https://github.com/feos-org/feos) is used for PC-SAFT calculations.
28
+
29
+ ## How to Use
30
+
31
+ ### Installation
32
+
33
+ You need [uvx](https://docs.astral.sh/uv/) installed.
34
+
35
+ ### Starting the Server
36
+
37
+ ```bash
38
+ uvx --from gnnepcsaft-mcp-server gnnepcsaftmcp
39
+ ```
40
+
41
+ ### Example: Claude Desktop Configuration
42
+
43
+ ```json
44
+ {
45
+ "mcpServers": {
46
+ "gnnepcsaft": {
47
+ "command": "uvx",
48
+ "args": ["--from", "gnnepcsaft-mcp-server", "gnnepcsaftmcp"]
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ ---
55
+
56
+ ## License
57
+
58
+ GNU General Public License v3.0
59
+
@@ -0,0 +1,34 @@
1
+ # GNNEPCSAFT MCP Server
2
+
3
+ GNNEPCSAFT MCP Server is an implementation of the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) for [GNNePCSAFT](https://github.com/wildsonbbl/gnnepcsaft) tools. GNNePCSAFT leverages Graph Neural Networks (GNNs) to estimate [PC-SAFT](https://en.wikipedia.org/wiki/PC-SAFT) pure-component parameters, allowing property predictions such as density and vapor pressure for any molecule or mixture. [FeOs](https://github.com/feos-org/feos) is used for PC-SAFT calculations.
4
+
5
+ ## How to Use
6
+
7
+ ### Installation
8
+
9
+ You need [uvx](https://docs.astral.sh/uv/) installed.
10
+
11
+ ### Starting the Server
12
+
13
+ ```bash
14
+ uvx --from gnnepcsaft-mcp-server gnnepcsaftmcp
15
+ ```
16
+
17
+ ### Example: Claude Desktop Configuration
18
+
19
+ ```json
20
+ {
21
+ "mcpServers": {
22
+ "gnnepcsaft": {
23
+ "command": "uvx",
24
+ "args": ["--from", "gnnepcsaft-mcp-server", "gnnepcsaftmcp"]
25
+ }
26
+ }
27
+ }
28
+ ```
29
+
30
+ ---
31
+
32
+ ## License
33
+
34
+ GNU General Public License v3.0
@@ -0,0 +1 @@
1
+ "GNNePCSAFT MCP Server"
@@ -0,0 +1,51 @@
1
+ "GNNePCSAFT MCP Server"
2
+
3
+ from typing import Any, Callable, List
4
+
5
+ from mcp.server.fastmcp import FastMCP
6
+
7
+ from .plot_utils import v3000_mol_block
8
+ from .utils import (
9
+ batch_convert_pure_density_to_kg_per_m3,
10
+ batch_critical_points,
11
+ batch_inchi_to_smiles,
12
+ batch_molecular_weights,
13
+ batch_pa_to_bar,
14
+ batch_predict_epcsaft_parameters,
15
+ batch_pure_density,
16
+ batch_pure_h_lv,
17
+ batch_pure_vapor_pressure,
18
+ batch_smiles_to_inchi,
19
+ mixture_density,
20
+ mixture_phase,
21
+ mixture_vapor_pressure,
22
+ pubchem_description,
23
+ pure_phase,
24
+ )
25
+
26
+ mcp = FastMCP("gnnepcsaft")
27
+ fn_list: List[Callable[..., Any]] = [
28
+ batch_convert_pure_density_to_kg_per_m3,
29
+ batch_critical_points,
30
+ batch_inchi_to_smiles,
31
+ batch_molecular_weights,
32
+ batch_pa_to_bar,
33
+ batch_predict_epcsaft_parameters,
34
+ batch_pure_density,
35
+ batch_pure_h_lv,
36
+ batch_pure_vapor_pressure,
37
+ batch_smiles_to_inchi,
38
+ mixture_density,
39
+ mixture_phase,
40
+ mixture_vapor_pressure,
41
+ pubchem_description,
42
+ pure_phase,
43
+ v3000_mol_block,
44
+ ]
45
+ for fn in fn_list:
46
+ mcp.add_tool(fn)
47
+
48
+
49
+ def run():
50
+ "run stdio"
51
+ mcp.run("stdio")
@@ -0,0 +1,28 @@
1
+ "plot utils"
2
+
3
+ from rdkit.Chem import AllChem as Chem
4
+
5
+ from .utils import smilestoinchi
6
+
7
+
8
+ def v3000_mol_block(smiles: str) -> str:
9
+ """Returns a V3000 Mol block for a molecule.
10
+
11
+ Args:
12
+ smiles (str): SMILES of the molecule.
13
+
14
+ """
15
+ inchi = smilestoinchi(smiles)
16
+
17
+ mol = Chem.MolFromInchi(inchi)
18
+ mol = Chem.AddHs(mol) # type: ignore
19
+ params = Chem.ETKDGv3() # type: ignore
20
+ params.randomSeed = 0xF00D
21
+ result = Chem.EmbedMolecule(mol, params) # type: ignore
22
+ if result == 0:
23
+ Chem.MMFFOptimizeMolecule( # type: ignore
24
+ mol, maxIters=1000, nonBondedThresh=100, ignoreInterfragInteractions=False
25
+ )
26
+ # mol = Chem.RemoveHs(mol, implicitOnly=False)
27
+ imgmol = Chem.MolToV3KMolBlock(mol) # type: ignore
28
+ return imgmol
@@ -0,0 +1,330 @@
1
+ "Utils for MCP Server"
2
+
3
+ from json import loads
4
+ from pathlib import Path
5
+ from typing import List, Literal, Tuple
6
+ from urllib.parse import quote
7
+ from urllib.request import HTTPError, urlopen
8
+
9
+ import numpy as np
10
+ import onnxruntime as ort
11
+ from gnnepcsaft.data.ogb_utils import smiles2graph
12
+ from gnnepcsaft.data.rdkit_util import assoc_number, inchitosmiles, mw, smilestoinchi
13
+ from gnnepcsaft.epcsaft.epcsaft_feos import (
14
+ critical_points_feos,
15
+ mix_den_feos,
16
+ mix_vp_feos,
17
+ pure_den_feos,
18
+ pure_h_lv_feos,
19
+ pure_vp_feos,
20
+ )
21
+
22
+ file_dir = Path(__file__).parent
23
+ model_dir = file_dir / "models"
24
+ ort.set_default_logger_severity(3)
25
+
26
+ msigmae_onnx = ort.InferenceSession(model_dir / "msigmae_7.onnx")
27
+ assoc_onnx = ort.InferenceSession(model_dir / "assoc_8.onnx")
28
+
29
+
30
+ def predict_epcsaft_parameters(
31
+ smiles: str,
32
+ ) -> List[float]:
33
+ """Predict PC-SAFT parameters
34
+ `[m, sigma, epsilon/kB, kappa_ab, epsilon_ab/kB, dipole moment, na, nb, MW]` with
35
+ the GNNePCSAFT model.
36
+
37
+ Args:
38
+ smiles (str): SMILES of the molecule.
39
+ """
40
+ lower_bounds = np.asarray([1.0, 1.9, 50.0, 0.0, 0.0, 0, 0, 0, 0])
41
+ upper_bounds = np.asarray(
42
+ [25.0, 4.5, 550.0, 0.9, 5000.0, np.inf, np.inf, np.inf, np.inf]
43
+ )
44
+
45
+ inchi = smilestoinchi(smiles)
46
+
47
+ graph = smiles2graph(smiles)
48
+ na, nb = assoc_number(inchi)
49
+ x, edge_index, edge_attr = (
50
+ graph["node_feat"],
51
+ graph["edge_index"],
52
+ graph["edge_feat"],
53
+ )
54
+
55
+ assoc = 10 ** (
56
+ assoc_onnx.run(
57
+ None,
58
+ {
59
+ "x": x,
60
+ "edge_index": edge_index,
61
+ "edge_attr": edge_attr,
62
+ },
63
+ )[0][0]
64
+ * np.asarray([-1.0, 1.0])
65
+ )
66
+ if na == 0 and nb == 0:
67
+ assoc *= 0
68
+ msigmae = msigmae_onnx.run(
69
+ None,
70
+ {
71
+ "x": x,
72
+ "edge_index": edge_index,
73
+ "edge_attr": edge_attr,
74
+ },
75
+ )[0][0]
76
+ munanbmw = np.asarray([0.0, na, nb, mw(inchi)])
77
+ pred = np.hstack([msigmae, assoc, munanbmw], dtype=np.float64)
78
+ np.clip(pred, lower_bounds, upper_bounds, out=pred)
79
+
80
+ return pred.tolist() # type: ignore
81
+
82
+
83
+ def pure_phase(
84
+ vapor_pressure: float, system_pressure: float
85
+ ) -> Literal["liquid", "vapor"]:
86
+ """
87
+ Given the vapor pressure and system pressure, return the phase of the molecule.
88
+ Both pressures must be in the same unit.
89
+
90
+ Args:
91
+ vapor_pressure (float): The calculated vapor pressure of the pure component.
92
+ system_pressure (float): The actual system pressure.
93
+
94
+ """
95
+ assert isinstance(vapor_pressure, (int, float)), "vapor_pressure must be a number"
96
+ assert isinstance(system_pressure, (int, float)), "system_pressure must be a number"
97
+ assert vapor_pressure > 0, "vapor_pressure must be positive"
98
+ assert system_pressure > 0, "system_pressure must be positive"
99
+
100
+ return "liquid" if vapor_pressure < system_pressure else "vapor"
101
+
102
+
103
+ def mixture_phase(
104
+ bubble_point: float,
105
+ dew_point: float,
106
+ system_pressure: float,
107
+ ) -> Literal["liquid", "vapor", "two-phase"]:
108
+ """
109
+ Given the bubble/dew point of the mixture and the system pressure,
110
+ return the phase of the mixture.
111
+ All pressures must be in the same unit.
112
+
113
+ Args:
114
+ bubble_point (float): The calculated bubble point of the mixture.
115
+ dew_point (float): The calculated dew point of the mixture.
116
+ system_pressure (float): The actual system pressure.
117
+ """
118
+ assert isinstance(bubble_point, (int, float)), "bubble_point must be a number"
119
+ assert isinstance(dew_point, (int, float)), "dew_point must be a number"
120
+ assert isinstance(system_pressure, (int, float)), "system_pressure must be a number"
121
+ assert bubble_point > 0, "bubble_point must be positive"
122
+ assert dew_point > 0, "dew_point must be positive"
123
+ assert system_pressure > 0, "system_pressure must be positive"
124
+ return (
125
+ "liquid"
126
+ if bubble_point < system_pressure
127
+ else ("two-phase" if dew_point <= system_pressure else "vapor")
128
+ )
129
+
130
+
131
+ def pubchem_description(smiles: str) -> str:
132
+ """
133
+ Look for information on PubChem for the SMILES.
134
+
135
+ Args:
136
+ smiles (str): The SMILES of the molecule.
137
+ """
138
+
139
+ try:
140
+ inchi = smilestoinchi(smiles)
141
+ url = (
142
+ "https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/inchi/description/json?inchi="
143
+ + quote(inchi, safe="")
144
+ )
145
+ with urlopen(url) as ans:
146
+ ans = loads(ans.read().decode("utf8").strip())
147
+ except (TypeError, HTTPError, ValueError):
148
+ ans = "no data available on this molecule in PubChem."
149
+ return ans
150
+
151
+
152
+ def mixture_density(
153
+ parameters: List[List[float]],
154
+ state: List[float],
155
+ kij_matrix: List[List[float]],
156
+ ) -> float:
157
+ """Calculates mixture liquid density (mol/m³) with PC-SAFT.
158
+
159
+ Args:
160
+ parameters: A list of
161
+ `[m, sigma, epsilon/kB, kappa_ab, epsilon_ab/kB, dipole moment, na, nb, MW]`
162
+ for each component of the mixture
163
+ state: A list with the state of the mixture
164
+ `[Temperature (K), Pressure (Pa), mole_fractions_1, mole_fractions_2, ...]`
165
+ kij_matrix: A matrix of binary interaction parameters
166
+ """
167
+
168
+ return mix_den_feos(parameters, state, kij_matrix)
169
+
170
+
171
+ def mixture_vapor_pressure(
172
+ parameters: List[List[float]],
173
+ state: List[float],
174
+ kij_matrix: List[List[float]],
175
+ ) -> Tuple[float, float]:
176
+ """Calculates mixture `(Bubble point (Pa), Dew point (Pa))` with PC-SAFT.
177
+
178
+ Args:
179
+ parameters: A list of
180
+ `[m, sigma, epsilon/kB, kappa_ab, epsilon_ab/kB, dipole moment, na, nb, MW]`
181
+ for each component of the mixture
182
+ state: A list with the state of the mixture
183
+ `[Temperature (K), Pressure (Pa), mole_fractions_1, molefractions_2, ...]`.
184
+ The pressure should be any `float` value since it's not used in the calculation.
185
+ kij_matrix: A matrix of binary interaction parameters.
186
+ """
187
+
188
+ return mix_vp_feos(parameters, state, kij_matrix)
189
+
190
+
191
+ def batch_predict_epcsaft_parameters(
192
+ smiles: List[str],
193
+ ) -> List[List[float]]:
194
+ """Predict PC-SAFT parameters
195
+ `[m, sigma, epsilon/kB, kappa_ab, epsilon_ab/kB, dipole moment, na, nb, MW]`
196
+ for a list of SMILES with the GNNePCSAFT model.
197
+
198
+ Args:
199
+ smiles (List[str]): SMILES of the molecules.
200
+ """
201
+ return [predict_epcsaft_parameters(smi) for smi in smiles]
202
+
203
+
204
+ def batch_molecular_weights(
205
+ smiles: List[str],
206
+ ) -> List[float]:
207
+ """Calcultes molecular weight in `g/mol` for a list of SMILES
208
+
209
+ Args:
210
+ smiles (List[str]): SMILES of the molecules.
211
+ """
212
+ inchi_list = [smilestoinchi(smi) for smi in smiles]
213
+ return [mw(inchi) for inchi in inchi_list]
214
+
215
+
216
+ def batch_inchi_to_smiles(
217
+ inchi_list: List[str],
218
+ ) -> List[str]:
219
+ """Transform a list of InChI to SMILES.
220
+
221
+ Args:
222
+ inchi_list (List[str]): List of InChI
223
+ """
224
+ return [inchitosmiles(inchi) for inchi in inchi_list]
225
+
226
+
227
+ def batch_smiles_to_inchi(
228
+ smiles_list: List[str],
229
+ ) -> List[str]:
230
+ """Transform a list of SMILES to InChI.
231
+
232
+ Args:
233
+ smiles_list (List[str]): List of SMILES
234
+ """
235
+ return [smilestoinchi(smi) for smi in smiles_list]
236
+
237
+
238
+ def batch_pure_density(
239
+ smiles_list: List[str],
240
+ state: List[float],
241
+ ) -> List[float]:
242
+ """Calculates pure liquid density in `mol/m³` with PC-SAFT for a list of SMILES.
243
+ The state is the same for all molecules. The GNNePCSAFT model is used to predict
244
+ PCSAFT parameters.
245
+
246
+ Args:
247
+ smiles_list (List[str]): List of SMILES
248
+ state: A list with
249
+ `[Temperature (K), Pressure (Pa)]`
250
+ """
251
+ return [
252
+ pure_den_feos(predict_epcsaft_parameters(smi), state) for smi in smiles_list
253
+ ]
254
+
255
+
256
+ def batch_pure_vapor_pressure(
257
+ smiles_list: List[str],
258
+ temperature: float,
259
+ ) -> List[float]:
260
+ """Calculates pure vapor pressure in `Pa` with PC-SAFT for a list of SMILES.
261
+ The temperature is the same for all molecules. The GNNePCSAFT model is used to predict
262
+ PCSAFT parameters.
263
+
264
+ Args:
265
+ smiles_list (List[str]): List of SMILES
266
+ temperature: `Temperature (K)`
267
+ """
268
+ return [
269
+ pure_vp_feos(predict_epcsaft_parameters(smi), [temperature])
270
+ for smi in smiles_list
271
+ ]
272
+
273
+
274
+ def batch_pure_h_lv(
275
+ smiles_list: List[str],
276
+ temperature: float,
277
+ ) -> List[float]:
278
+ """Calculates pure liquid enthalpy of vaporization in `kJ/mol`
279
+ with PC-SAFT for a list of SMILES.
280
+ The temperature is the same for all molecules.
281
+ The GNNePCSAFT model is used to predict
282
+ PCSAFT parameters.
283
+
284
+ Args:
285
+ smiles_list (List[str]): List of SMILES
286
+ temperature: `Temperature (K)`
287
+ """
288
+ return [
289
+ pure_h_lv_feos(predict_epcsaft_parameters(smi), [temperature])
290
+ for smi in smiles_list
291
+ ]
292
+
293
+
294
+ def batch_critical_points(
295
+ smiles_list: List[str],
296
+ ) -> List[List[float]]:
297
+ """
298
+ Calculates critical points `[Temperature (K), Pressure (Pa), Density (mol/m³)]` with PC-SAFT
299
+ for a list of SMILES. The GNNePCSAFT model is used to predict PCSAFT parameters.
300
+
301
+ Args:
302
+ smiles_list (List[str]): List of SMILES
303
+ """
304
+ return [
305
+ critical_points_feos(predict_epcsaft_parameters(smi)) for smi in smiles_list
306
+ ]
307
+
308
+
309
+ def batch_pa_to_bar(
310
+ pressure_in_pa_list: List[float],
311
+ ) -> List[float]:
312
+ """Convert a list of pressure from `Pa` to `bar`.
313
+
314
+ Args:
315
+ pressure_in_pa_list (List[float]): List of pressure in `Pa`
316
+ """
317
+ return [pa / 100_000.0 for pa in pressure_in_pa_list]
318
+
319
+
320
+ def batch_convert_pure_density_to_kg_per_m3(
321
+ density_list: List[float],
322
+ molecular_weight_list: List[float],
323
+ ) -> List[float]:
324
+ """Convert a list of density from `mol/m³` to `kg/m³`
325
+
326
+ Args:
327
+ density_list (List[float]): List of density in `mol/m³`
328
+ molecular_weight_list (List[float]): List of molecular weight in `g/mol`
329
+ """
330
+ return [den * molw / 1000 for den, molw in zip(density_list, molecular_weight_list)]
@@ -0,0 +1,34 @@
1
+ [project]
2
+ name = "gnnepcsaft-mcp-server"
3
+ version = "0.3.1"
4
+ description = "Model Context Protocol server for GNNePCSAFT tools"
5
+ authors = [
6
+ {name = "wildsonbbl",email = "wil_bbl@hotmail.com"}
7
+ ]
8
+ license = {text = "GNU General Public License v3.0"}
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ dependencies = [
12
+ "numpy (>=2.2.4,<3.0.0)",
13
+ "onnxruntime (>=1.21.0,<2.0.0)",
14
+ "gnnepcsaft (>=0.2.4)",
15
+ "mcp (>=1.6.0,<2.0.0)",
16
+ "rdkit (>=2024.9.6,<2025.0.0)",
17
+ "feos (>=0.8.0,<0.9.0)",
18
+ "si-units (>=0.11.0,<0.12.0)",
19
+ ]
20
+
21
+ [project.scripts]
22
+ gnnepcsaftmcp = 'gnnepcsaft_mcp_server.mcp_server:run'
23
+
24
+ [tool.poetry]
25
+ include = ["gnnepcsaft_mcp_server/models/assoc_8.onnx", "gnnepcsaft_mcp_server/models/msigmae_7.onnx"]
26
+
27
+
28
+
29
+ [tool.poetry.group.dev.dependencies]
30
+ pytest = "^8.3.5"
31
+
32
+ [build-system]
33
+ requires = ["poetry-core>=2.0.0,<3.0.0"]
34
+ build-backend = "poetry.core.masonry.api"