Skip to content

Commit 2ffb426

Browse files
authored
Add CoolProp MCP tool (#1420)
Signed-off-by: tamohannes <hovhannes.tamoyan@gmail.com> Signed-off-by: Hovhannes Tamoyan <hovhannes.tamoyan@gmail.com>
1 parent 0ca0257 commit 2ffb426

3 files changed

Lines changed: 159 additions & 0 deletions

File tree

docs/agentic_inference/tool_calling.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,4 +372,5 @@ For vLLM, you may need to specify tool calling arguments:
372372
- [`nemo_skills.mcp.servers.python_tool.PythonTool`](https://github.com/NVIDIA-NeMo/Skills/tree/main/nemo_skills/mcp/servers/python_tool.py) - Python code execution
373373
- [`nemo_skills.mcp.servers.arxiv_tool.ArxivSearchTool`](https://github.com/NVIDIA-NeMo/Skills/tree/main/nemo_skills/mcp/servers/arxiv_tool.py) - ArXiv paper search and retrieval (no API key required)
374374
- [`nemo_skills.mcp.servers.exa_tool.ExaTool`](https://github.com/NVIDIA-NeMo/Skills/tree/main/nemo_skills/mcp/servers/exa_tool.py) - Web search via Exa API
375+
- [`nemo_skills.mcp.servers.coolprop_tool.CoolPropTool`](https://github.com/NVIDIA-NeMo/Skills/tree/main/nemo_skills/mcp/servers/coolprop_tool.py) - Direct thermophysical fluid property lookup via CoolProp (requires `CoolProp`)
375376
- [`nemo_skills.mcp.servers.wikipedia_tool.WikipediaSearchTool`](https://github.com/NVIDIA-NeMo/Skills/tree/main/nemo_skills/mcp/servers/wikipedia_tool.py) - Direct Wikipedia article search and retrieval (no API key required)
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""CoolProp MCP tool for thermophysical fluid properties.
16+
17+
Wraps the ``CoolProp`` library to look up density, viscosity, conductivity,
18+
specific heat, and other properties for 124 fluids. No API key required.
19+
20+
Prerequisites:
21+
pip install CoolProp
22+
23+
Usage:
24+
++tool_modules=[nemo_skills.mcp.servers.coolprop_tool::CoolPropTool]
25+
"""
26+
27+
import logging
28+
from typing import Annotated, Any
29+
30+
from pydantic import Field
31+
32+
from nemo_skills.mcp.tool_manager import Tool
33+
34+
logger = logging.getLogger(__name__)
35+
36+
PROPERTY_DESCRIPTIONS = {
37+
"D": "Density [kg/m^3]",
38+
"H": "Specific enthalpy [J/kg]",
39+
"S": "Specific entropy [J/(kg*K)]",
40+
"C": "Specific heat at constant pressure Cp [J/(kg*K)]",
41+
"CVMASS": "Specific heat at constant volume Cv [J/(kg*K)]",
42+
"V": "Dynamic viscosity [Pa*s]",
43+
"L": "Thermal conductivity [W/(m*K)]",
44+
"P": "Pressure [Pa]",
45+
"T": "Temperature [K]",
46+
"Q": "Vapor quality [-]",
47+
"SPEED_OF_SOUND": "Speed of sound [m/s]",
48+
"SURFACE_TENSION": "Surface tension [N/m]",
49+
"PRANDTL": "Prandtl number [-]",
50+
"ISENTROPIC_EXPANSION_COEFFICIENT": "Isentropic expansion coefficient [-]",
51+
}
52+
53+
54+
def fluid_property(
55+
fluid: Annotated[str, Field(description="Fluid name (e.g. 'Water', 'Nitrogen', 'R134a', 'CO2').")],
56+
output_property: Annotated[
57+
str,
58+
Field(
59+
description=(
60+
"Property to calculate. Common codes: "
61+
"D (density), C (Cp), CVMASS (Cv), H (enthalpy), S (entropy), "
62+
"V (viscosity), L (conductivity), SPEED_OF_SOUND, PRANDTL."
63+
)
64+
),
65+
],
66+
temperature: Annotated[float, Field(description="Temperature in Kelvin.")],
67+
pressure: Annotated[float, Field(description="Pressure in Pascals.")],
68+
) -> str:
69+
"""Calculate a thermophysical property of a fluid at given temperature and pressure (SI units)."""
70+
import CoolProp.CoolProp as CP
71+
72+
if temperature <= 0:
73+
return "Temperature must be positive (in Kelvin)."
74+
if pressure <= 0:
75+
return "Pressure must be positive (in Pascals)."
76+
77+
try:
78+
value = CP.PropsSI(output_property, "T", temperature, "P", pressure, fluid)
79+
except ValueError as e:
80+
return f"CoolProp error for {fluid}: {e}"
81+
82+
desc = PROPERTY_DESCRIPTIONS.get(output_property, output_property)
83+
return f"**{fluid}** at T={temperature} K, P={pressure} Pa\n{desc}: {value:.6g}"
84+
85+
86+
def fluid_list() -> str:
87+
"""List all fluids available in CoolProp."""
88+
import CoolProp.CoolProp as CP
89+
90+
fluids = sorted(CP.FluidsList())
91+
return f"**{len(fluids)} fluids available:**\n" + ", ".join(fluids)
92+
93+
94+
class CoolPropTool(Tool):
95+
def __init__(self) -> None:
96+
self._config: dict[str, Any] = {}
97+
98+
def default_config(self) -> dict[str, Any]:
99+
return dict(self._config)
100+
101+
def configure(self, overrides: dict[str, Any] | None = None, context: dict[str, Any] | None = None) -> None:
102+
if overrides:
103+
self._config.update(overrides)
104+
105+
async def list_tools(self) -> list[dict[str, Any]]:
106+
return [
107+
{
108+
"name": "fluid-property",
109+
"description": "Calculate a thermophysical property of a fluid at temperature and pressure in SI units.",
110+
"input_schema": {
111+
"type": "object",
112+
"properties": {
113+
"fluid": {"type": "string", "description": "Fluid name, e.g. Water, Nitrogen, R134a, CO2."},
114+
"output_property": {
115+
"type": "string",
116+
"description": "CoolProp output property code, e.g. D, C, H, S, V, L.",
117+
},
118+
"temperature": {"type": "number", "description": "Temperature in Kelvin."},
119+
"pressure": {"type": "number", "description": "Pressure in Pascals."},
120+
},
121+
"required": ["fluid", "output_property", "temperature", "pressure"],
122+
},
123+
},
124+
{
125+
"name": "fluid-list",
126+
"description": "List fluids available in CoolProp.",
127+
"input_schema": {"type": "object", "properties": {}},
128+
},
129+
]
130+
131+
async def execute(self, tool_name: str, arguments: dict[str, Any], extra_args: dict[str, Any] | None = None):
132+
arguments = dict(arguments or {})
133+
if tool_name == "fluid-property":
134+
return fluid_property(**arguments)
135+
if tool_name == "fluid-list":
136+
return fluid_list()
137+
return f"Error: unknown tool '{tool_name}'"

tests/test_mcp_clients.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,6 +1004,27 @@ async def failing_delete(session_id):
10041004
assert "req-x" not in tool.requests_to_sessions
10051005

10061006

1007+
# -- CoolProp direct tool tests ---------------------------------------------
1008+
1009+
1010+
class TestCoolPropTool:
1011+
def test_coolprop_tool_config(self):
1012+
from nemo_skills.mcp.servers.coolprop_tool import CoolPropTool
1013+
1014+
tool = CoolPropTool()
1015+
assert tool.default_config() == {}
1016+
1017+
@pytest.mark.asyncio
1018+
async def test_coolprop_direct_list_tools(self):
1019+
from nemo_skills.mcp.servers.coolprop_tool import CoolPropTool
1020+
1021+
tool = CoolPropTool()
1022+
tool.configure()
1023+
tool_names = {t["name"] for t in await tool.list_tools()}
1024+
assert "fluid-property" in tool_names
1025+
assert "fluid-list" in tool_names
1026+
1027+
10071028
# -- Wikipedia direct tool tests --------------------------------------------
10081029

10091030

0 commit comments

Comments
 (0)