rowan-mcp 1.0.2__py3-none-any.whl → 2.0.1__py3-none-any.whl

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.

Potentially problematic release.


This version of rowan-mcp might be problematic. Click here for more details.

Files changed (57) hide show
  1. rowan_mcp/__init__.py +1 -1
  2. rowan_mcp/__main__.py +3 -5
  3. rowan_mcp/functions/admet.py +0 -5
  4. rowan_mcp/functions/bde.py +1 -8
  5. rowan_mcp/functions/conformers.py +1 -4
  6. rowan_mcp/functions/descriptors.py +1 -4
  7. rowan_mcp/functions/docking.py +6 -56
  8. rowan_mcp/functions/electronic_properties.py +1 -4
  9. rowan_mcp/functions/folder_management.py +1 -8
  10. rowan_mcp/functions/fukui.py +1 -4
  11. rowan_mcp/functions/hydrogen_bond_basicity.py +1 -8
  12. rowan_mcp/functions/multistage_opt.py +1 -4
  13. rowan_mcp/functions/pka.py +1 -8
  14. rowan_mcp/functions/redox_potential.py +2 -5
  15. rowan_mcp/functions/system_management.py +1 -8
  16. rowan_mcp/functions/tautomers.py +1 -4
  17. rowan_mcp/functions_v2/BENCHMARK.md +86 -0
  18. rowan_mcp/functions_v2/molecule_lookup.py +232 -0
  19. rowan_mcp/functions_v2/protein_management.py +141 -0
  20. rowan_mcp/functions_v2/submit_basic_calculation_workflow.py +195 -0
  21. rowan_mcp/functions_v2/submit_conformer_search_workflow.py +158 -0
  22. rowan_mcp/functions_v2/submit_descriptors_workflow.py +52 -0
  23. rowan_mcp/functions_v2/submit_docking_workflow.py +244 -0
  24. rowan_mcp/functions_v2/submit_fukui_workflow.py +114 -0
  25. rowan_mcp/functions_v2/submit_irc_workflow.py +58 -0
  26. rowan_mcp/functions_v2/submit_macropka_workflow.py +99 -0
  27. rowan_mcp/functions_v2/submit_pka_workflow.py +72 -0
  28. rowan_mcp/functions_v2/submit_protein_cofolding_workflow.py +88 -0
  29. rowan_mcp/functions_v2/submit_redox_potential_workflow.py +55 -0
  30. rowan_mcp/functions_v2/submit_scan_workflow.py +82 -0
  31. rowan_mcp/functions_v2/submit_solubility_workflow.py +157 -0
  32. rowan_mcp/functions_v2/submit_tautomer_search_workflow.py +51 -0
  33. rowan_mcp/functions_v2/workflow_management_v2.py +382 -0
  34. rowan_mcp/server.py +109 -144
  35. rowan_mcp/tests/basic_calculation_from_json.py +0 -0
  36. rowan_mcp/tests/basic_calculation_with_constraint.py +33 -0
  37. rowan_mcp/tests/basic_calculation_with_solvent.py +0 -0
  38. rowan_mcp/tests/bde.py +37 -0
  39. rowan_mcp/tests/benchmark_queries.md +120 -0
  40. rowan_mcp/tests/cofolding_screen.py +131 -0
  41. rowan_mcp/tests/conformer_dependent_redox.py +37 -0
  42. rowan_mcp/tests/conformers.py +31 -0
  43. rowan_mcp/tests/data.json +189 -0
  44. rowan_mcp/tests/docking_screen.py +157 -0
  45. rowan_mcp/tests/irc.py +24 -0
  46. rowan_mcp/tests/macropka.py +13 -0
  47. rowan_mcp/tests/multistage_opt.py +13 -0
  48. rowan_mcp/tests/optimization.py +21 -0
  49. rowan_mcp/tests/phenol_pka.py +36 -0
  50. rowan_mcp/tests/pka.py +36 -0
  51. rowan_mcp/tests/protein_cofolding.py +17 -0
  52. rowan_mcp/tests/scan.py +28 -0
  53. {rowan_mcp-1.0.2.dist-info → rowan_mcp-2.0.1.dist-info}/METADATA +38 -45
  54. rowan_mcp-2.0.1.dist-info/RECORD +69 -0
  55. rowan_mcp-1.0.2.dist-info/RECORD +0 -34
  56. {rowan_mcp-1.0.2.dist-info → rowan_mcp-2.0.1.dist-info}/WHEEL +0 -0
  57. {rowan_mcp-1.0.2.dist-info → rowan_mcp-2.0.1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,55 @@
1
+ """
2
+ Rowan v2 API: Redox Potential Workflow
3
+ Calculate reduction and oxidation potentials for molecules.
4
+ """
5
+
6
+ from typing import Annotated
7
+ import rowan
8
+ import stjames
9
+
10
+
11
+ def submit_redox_potential_workflow(
12
+ initial_molecule: Annotated[str, "SMILES string for redox potential calculation"],
13
+ reduction: Annotated[bool, "Whether to calculate reduction potential (gaining electron)"] = False,
14
+ oxidization: Annotated[bool, "Whether to calculate oxidation potential (losing electron)"] = True,
15
+ mode: Annotated[str, "Calculation mode: 'rapid' (fast), 'careful' (balanced), or 'meticulous' (thorough)"] = "rapid",
16
+ name: Annotated[str, "Workflow name for identification and tracking"] = "Redox Potential Workflow",
17
+ folder_uuid: Annotated[str, "UUID of folder to organize this workflow. Empty string uses default folder"] = "",
18
+ max_credits: Annotated[int, "Maximum credits to spend on this calculation. 0 for no limit"] = 0
19
+ ):
20
+ """Submit a redox potential calculation workflow using Rowan v2 API.
21
+
22
+ Args:
23
+ initial_molecule: SMILES string for redox potential calculation
24
+ reduction: Whether to calculate reduction potential (gaining electron)
25
+ oxidization: Whether to calculate oxidation potential (losing electron)
26
+ mode: Calculation mode: 'rapid' (fast), 'careful' (balanced), or 'meticulous' (thorough)
27
+ name: Workflow name for identification and tracking
28
+ folder_uuid: UUID of folder to organize this workflow. Empty string uses default folder.
29
+ max_credits: Maximum credits to spend on this calculation. 0 for no limit.
30
+
31
+ Calculates reduction and/or oxidation potentials for a molecule using
32
+ quantum chemistry methods.
33
+
34
+ Returns:
35
+ Workflow object representing the submitted workflow
36
+
37
+ Example:
38
+ # Simple redox potential from SMILES
39
+ result = submit_redox_potential_workflow(
40
+ initial_molecule="Cc1ccccc1", # Toluene
41
+ reduction=True,
42
+ oxidization=True,
43
+ name="Toluene Redox Potential"
44
+ )
45
+ """
46
+
47
+ return rowan.submit_redox_potential_workflow(
48
+ initial_molecule=stjames.Molecule.from_smiles(initial_molecule),
49
+ reduction=reduction,
50
+ oxidization=oxidization,
51
+ mode=mode,
52
+ name=name,
53
+ folder_uuid=folder_uuid if folder_uuid else None,
54
+ max_credits=max_credits if max_credits > 0 else None
55
+ )
@@ -0,0 +1,82 @@
1
+ """
2
+ Rowan v2 API: Scan Workflow
3
+ Perform potential energy surface scans along molecular coordinates.
4
+ """
5
+
6
+ from typing import Dict, Any, Annotated
7
+ import rowan
8
+ import stjames
9
+ import json
10
+
11
+ def submit_scan_workflow(
12
+ initial_molecule: Annotated[str, "SMILES string to scan"],
13
+ scan_settings: Annotated[str, "JSON string of scan parameters: '{\"type\": \"dihedral\"/\"bond\"/\"angle\", \"atoms\": [1-indexed], \"start\": value, \"stop\": value, \"num\": points}'"] = "",
14
+ calculation_engine: Annotated[str, "Computational engine: 'omol25', 'xtb', 'psi4'"] = "omol25",
15
+ calculation_method: Annotated[str, "Computational method (e.g., 'uma_m_omol', 'gfn2-xtb', 'b3lyp-d3bj')"] = "uma_m_omol",
16
+ wavefront_propagation: Annotated[bool, "Whether to use wavefront propagation for scan"] = True,
17
+ name: Annotated[str, "Workflow name for identification and tracking"] = "Scan Workflow",
18
+ folder_uuid: Annotated[str, "UUID of folder to organize this workflow. Empty string uses default folder"] = "",
19
+ max_credits: Annotated[int, "Maximum credits to spend on this calculation. 0 for no limit"] = 0
20
+ ):
21
+ """Submit a potential energy surface scan workflow using Rowan v2 API.
22
+
23
+ Args:
24
+ initial_molecule: SMILES string to scan
25
+ scan_settings: JSON string of scan parameters: '{"type": "dihedral"/"bond"/"angle", "atoms": [1-indexed], "start": value, "stop": value, "num": points}'
26
+ calculation_engine: Computational engine: 'omol25', 'xtb', or 'psi4'
27
+ calculation_method: Calculation method (depends on engine): 'uma_m_omol', 'gfn2_xtb', 'r2scan_3c'
28
+ wavefront_propagation: Use previous scan point geometries as starting points for faster convergence
29
+ name: Workflow name for identification and tracking
30
+ folder_uuid: UUID of folder to organize this workflow. Empty string uses default folder.
31
+ max_credits: Maximum credits to spend on this calculation. 0 for no limit.
32
+
33
+ Performs systematic scans along specified molecular coordinates (bonds, angles,
34
+ or dihedrals) to map the potential energy surface.
35
+
36
+ Returns:
37
+ Workflow object representing the submitted workflow
38
+
39
+ Example:
40
+ # Water angle scan
41
+ result = submit_scan_workflow(
42
+ initial_molecule="O", # Water SMILES
43
+ name="Water Angle scan",
44
+ scan_settings='{"type": "angle", "atoms": [2, 1, 3], "start": 100, "stop": 110, "num": 5}',
45
+ calculation_method="GFN2-xTB",
46
+ calculation_engine="xtb"
47
+ )
48
+ """
49
+ # Parse scan_settings if provided
50
+ parsed_scan_settings = None
51
+ if scan_settings:
52
+ try:
53
+ parsed_scan_settings = json.loads(scan_settings)
54
+ except (json.JSONDecodeError, ValueError) as e:
55
+ raise ValueError(f"Invalid scan_settings format: {e}")
56
+
57
+ # Validate and convert scan_settings
58
+ if parsed_scan_settings is not None:
59
+ # Convert step to num if step is provided instead of num
60
+ if 'step' in parsed_scan_settings and 'num' not in parsed_scan_settings:
61
+ start = parsed_scan_settings.get('start', 0)
62
+ stop = parsed_scan_settings.get('stop', 360)
63
+ step = parsed_scan_settings['step']
64
+ parsed_scan_settings['num'] = int((stop - start) / step) + 1
65
+ del parsed_scan_settings['step'] # Remove step as API doesn't accept it
66
+
67
+ # Validate required fields
68
+ required_fields = ['type', 'atoms', 'start', 'stop', 'num']
69
+ missing_fields = [field for field in required_fields if field not in parsed_scan_settings]
70
+ if missing_fields:
71
+ raise ValueError(f"Missing required fields in scan_settings: {missing_fields}")
72
+
73
+ return rowan.submit_scan_workflow(
74
+ initial_molecule=stjames.Molecule.from_smiles(initial_molecule),
75
+ scan_settings=parsed_scan_settings,
76
+ calculation_engine=calculation_engine,
77
+ calculation_method=calculation_method,
78
+ wavefront_propagation=wavefront_propagation,
79
+ name=name,
80
+ folder_uuid=folder_uuid if folder_uuid else None,
81
+ max_credits=max_credits if max_credits > 0 else None
82
+ )
@@ -0,0 +1,157 @@
1
+ """
2
+ Rowan v2 API: Solubility Workflow
3
+ Predict molecular solubility in various solvents at different temperatures.
4
+ """
5
+
6
+ from typing import List, Annotated
7
+ import rowan
8
+ import json
9
+
10
+
11
+ def submit_solubility_workflow(
12
+ initial_smiles: Annotated[str, "SMILES string of the molecule for solubility prediction"],
13
+ solvents: Annotated[str, "JSON string list of solvents as SMILES or names (e.g., '[\"water\", \"ethanol\", \"CCO\"]'). Empty string uses defaults"] = "",
14
+ temperatures: Annotated[str, "JSON string list of temperatures in Kelvin (e.g., '[298.15, 310.15]'). Empty string uses default range"] = "",
15
+ name: Annotated[str, "Workflow name for identification and tracking"] = "Solubility Workflow",
16
+ folder_uuid: Annotated[str, "UUID of folder to organize this workflow. Empty string uses default folder"] = "",
17
+ max_credits: Annotated[int, "Maximum credits to spend on this calculation. 0 for no limit"] = 0
18
+ ):
19
+ """Submit a solubility prediction workflow using Rowan v2 API.
20
+
21
+ Args:
22
+ initial_smiles: SMILES string of the molecule for solubility prediction
23
+ solvents: JSON string list of solvents as SMILES or names (e.g., '["water", "ethanol", "CCO"]'). Empty string uses defaults
24
+ temperatures: JSON string list of temperatures in Kelvin (e.g., '[298.15, 310.15]'). Empty string uses default range
25
+ name: Workflow name for identification and tracking
26
+ folder_uuid: UUID of folder to organize this workflow. Empty string uses default folder.
27
+ max_credits: Maximum credits to spend on this calculation. 0 for no limit.
28
+
29
+ Predicts solubility (log S) of a molecule in multiple solvents at various temperatures
30
+ using machine learning models.
31
+
32
+ Returns:
33
+ Workflow object representing the submitted workflow
34
+
35
+ Example:
36
+ # Basic solubility prediction
37
+ result = submit_solubility_workflow(
38
+ initial_smiles="CC(=O)Nc1ccc(O)cc1",
39
+ solvents='["water", "ethanol"]',
40
+ temperatures='[298.15, 310.15]'
41
+ )
42
+
43
+ # With SMILES solvents
44
+ result = submit_solubility_workflow(
45
+ initial_smiles="CC(=O)O",
46
+ solvents='["O", "CCO", "CCCCCC"]',
47
+ temperatures='[273.15, 298.15, 323.15]'
48
+ )
49
+ """
50
+
51
+ # Parse solvents parameter - handle string input
52
+ parsed_solvents = None
53
+ if solvents:
54
+ # Handle various string formats
55
+ solvents = solvents.strip()
56
+ if solvents.startswith('[') and solvents.endswith(']'):
57
+ # JSON array format like '["water", "ethanol"]'
58
+ try:
59
+ parsed_solvents = json.loads(solvents)
60
+ except (json.JSONDecodeError, ValueError):
61
+ # Failed to parse as JSON, try as comma-separated
62
+ solvents = solvents.strip('[]').replace('"', '').replace("'", "")
63
+ parsed_solvents = [s.strip() for s in solvents.split(',') if s.strip()]
64
+ elif ',' in solvents:
65
+ # Comma-separated format like 'water, ethanol'
66
+ parsed_solvents = [s.strip() for s in solvents.split(',') if s.strip()]
67
+ else:
68
+ # Single solvent as string like 'water'
69
+ parsed_solvents = [solvents]
70
+
71
+ # Convert solvent names to SMILES if needed
72
+ solvent_name_to_smiles = {
73
+ 'water': 'O',
74
+ 'ethanol': 'CCO',
75
+ 'dmso': 'CS(=O)C',
76
+ 'acetone': 'CC(=O)C',
77
+ 'methanol': 'CO',
78
+ 'chloroform': 'C(Cl)(Cl)Cl',
79
+ 'dichloromethane': 'C(Cl)Cl',
80
+ 'toluene': 'Cc1ccccc1',
81
+ 'benzene': 'c1ccccc1',
82
+ 'hexane': 'CCCCCC',
83
+ 'ether': 'CCOCC',
84
+ 'diethyl ether': 'CCOCC',
85
+ 'thf': 'C1CCOC1',
86
+ 'tetrahydrofuran': 'C1CCOC1',
87
+ 'dioxane': 'C1COCCO1',
88
+ 'acetonitrile': 'CC#N',
89
+ 'pyridine': 'c1ccncc1'
90
+ }
91
+
92
+ converted_solvents = []
93
+ for solvent in parsed_solvents:
94
+ solvent_lower = solvent.lower().strip()
95
+ if solvent_lower in solvent_name_to_smiles:
96
+ converted_solvents.append(solvent_name_to_smiles[solvent_lower])
97
+ else:
98
+ # Assume it's already a SMILES string or use as-is
99
+ converted_solvents.append(solvent)
100
+ parsed_solvents = converted_solvents
101
+
102
+ # Validate the final solvent SMILES to catch issues early
103
+ try:
104
+ from rdkit import Chem
105
+ for i, solvent_smiles in enumerate(parsed_solvents):
106
+ mol = Chem.MolFromSmiles(solvent_smiles)
107
+ if mol is None:
108
+ raise ValueError(f"Invalid solvent SMILES: '{solvent_smiles}' at position {i}")
109
+ except ImportError:
110
+ # RDKit not available for validation, proceed anyway
111
+ pass
112
+
113
+ # Parse temperatures parameter - handle string input
114
+ parsed_temperatures = None
115
+ if temperatures:
116
+ # Handle various string formats
117
+ temperatures = temperatures.strip()
118
+ if temperatures.startswith('[') and temperatures.endswith(']'):
119
+ # JSON array format like '[298.15, 310.15]'
120
+ try:
121
+ parsed_temperatures = json.loads(temperatures)
122
+ except (json.JSONDecodeError, ValueError):
123
+ # Failed to parse as JSON, try as comma-separated
124
+ temperatures = temperatures.strip('[]').replace('"', '').replace("'", "")
125
+ parsed_temperatures = [float(t.strip()) for t in temperatures.split(',') if t.strip()]
126
+ elif ',' in temperatures:
127
+ # Comma-separated format like '298.15, 310.15'
128
+ parsed_temperatures = [float(t.strip()) for t in temperatures.split(',') if t.strip()]
129
+ else:
130
+ # Single temperature as string like '298.15'
131
+ parsed_temperatures = [float(temperatures)]
132
+
133
+ # Validate the main SMILES string early to catch issues
134
+ try:
135
+ from rdkit import Chem
136
+ mol = Chem.MolFromSmiles(initial_smiles)
137
+ if mol is None:
138
+ raise ValueError(f"Invalid initial SMILES: '{initial_smiles}'")
139
+ except ImportError:
140
+ # RDKit not available for validation, proceed anyway
141
+ pass
142
+
143
+ try:
144
+ result = rowan.submit_solubility_workflow(
145
+ initial_smiles=initial_smiles,
146
+ solvents=parsed_solvents,
147
+ temperatures=parsed_temperatures,
148
+ name=name,
149
+ folder_uuid=folder_uuid if folder_uuid else None,
150
+ max_credits=max_credits if max_credits > 0 else None
151
+ )
152
+
153
+ return result
154
+
155
+ except Exception as e:
156
+ # Re-raise the exception so MCP can handle it
157
+ raise
@@ -0,0 +1,51 @@
1
+ """
2
+ Rowan v2 API: Tautomer Search Workflow
3
+ Search for tautomeric forms of molecules.
4
+ """
5
+
6
+ from typing import Annotated
7
+ import rowan
8
+ import stjames
9
+
10
+ def submit_tautomer_search_workflow(
11
+ initial_molecule: Annotated[str, "SMILES string to search for tautomers"],
12
+ mode: Annotated[str, "Search mode: 'rapid' (fast), 'careful' (balanced), or 'meticulous' (thorough)"] = "careful",
13
+ name: Annotated[str, "Workflow name for identification and tracking"] = "Tautomer Search Workflow",
14
+ folder_uuid: Annotated[str, "UUID of folder to organize this workflow. Empty string uses default folder"] = "",
15
+ max_credits: Annotated[int, "Maximum credits to spend on this calculation. 0 for no limit"] = 0
16
+ ):
17
+ """Submit a tautomer search workflow using Rowan v2 API.
18
+
19
+ Args:
20
+ initial_molecule: SMILES string to search for tautomers
21
+ mode: Search mode: 'rapid' (fast), 'careful' (balanced), or 'meticulous' (thorough)
22
+ name: Workflow name for identification and tracking
23
+ folder_uuid: UUID of folder to organize this workflow. Empty string uses default folder.
24
+ max_credits: Maximum credits to spend on this calculation. 0 for no limit.
25
+
26
+ Searches for different tautomeric forms of a molecule and evaluates their
27
+ relative stabilities. Tautomers are structural isomers that readily interconvert.
28
+
29
+ Returns:
30
+ Workflow object representing the submitted workflow
31
+
32
+ Example:
33
+ # Basic tautomer search
34
+ result = submit_tautomer_search_workflow(
35
+ initial_molecule="CC(=O)CC(=O)C"
36
+ )
37
+
38
+ # Meticulous search for complex molecule
39
+ result = submit_tautomer_search_workflow(
40
+ initial_molecule="c1ccc2c(c1)ncc(=O)[nH]2",
41
+ mode="meticulous"
42
+ )
43
+ """
44
+
45
+ return rowan.submit_tautomer_search_workflow(
46
+ initial_molecule=stjames.Molecule.from_smiles(initial_molecule),
47
+ mode=mode,
48
+ name=name,
49
+ folder_uuid=folder_uuid if folder_uuid else None,
50
+ max_credits=max_credits if max_credits > 0 else None
51
+ )