rowan-mcp 1.0.2__py3-none-any.whl → 2.0.0__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.
- rowan_mcp/__init__.py +1 -1
- rowan_mcp/__main__.py +3 -5
- rowan_mcp/functions_v2/BENCHMARK.md +86 -0
- rowan_mcp/functions_v2/molecule_lookup.py +232 -0
- rowan_mcp/functions_v2/protein_management.py +141 -0
- rowan_mcp/functions_v2/submit_basic_calculation_workflow.py +195 -0
- rowan_mcp/functions_v2/submit_conformer_search_workflow.py +158 -0
- rowan_mcp/functions_v2/submit_descriptors_workflow.py +52 -0
- rowan_mcp/functions_v2/submit_docking_workflow.py +244 -0
- rowan_mcp/functions_v2/submit_fukui_workflow.py +114 -0
- rowan_mcp/functions_v2/submit_irc_workflow.py +58 -0
- rowan_mcp/functions_v2/submit_macropka_workflow.py +99 -0
- rowan_mcp/functions_v2/submit_pka_workflow.py +72 -0
- rowan_mcp/functions_v2/submit_protein_cofolding_workflow.py +88 -0
- rowan_mcp/functions_v2/submit_redox_potential_workflow.py +55 -0
- rowan_mcp/functions_v2/submit_scan_workflow.py +82 -0
- rowan_mcp/functions_v2/submit_solubility_workflow.py +157 -0
- rowan_mcp/functions_v2/submit_tautomer_search_workflow.py +51 -0
- rowan_mcp/functions_v2/workflow_management_v2.py +382 -0
- rowan_mcp/server.py +109 -144
- rowan_mcp/tests/basic_calculation_from_json.py +0 -0
- rowan_mcp/tests/basic_calculation_with_constraint.py +33 -0
- rowan_mcp/tests/basic_calculation_with_solvent.py +0 -0
- rowan_mcp/tests/bde.py +37 -0
- rowan_mcp/tests/benchmark_queries.md +120 -0
- rowan_mcp/tests/cofolding_screen.py +131 -0
- rowan_mcp/tests/conformer_dependent_redox.py +37 -0
- rowan_mcp/tests/conformers.py +31 -0
- rowan_mcp/tests/data.json +189 -0
- rowan_mcp/tests/docking_screen.py +157 -0
- rowan_mcp/tests/irc.py +24 -0
- rowan_mcp/tests/macropka.py +13 -0
- rowan_mcp/tests/multistage_opt.py +13 -0
- rowan_mcp/tests/optimization.py +21 -0
- rowan_mcp/tests/phenol_pka.py +36 -0
- rowan_mcp/tests/pka.py +36 -0
- rowan_mcp/tests/protein_cofolding.py +17 -0
- rowan_mcp/tests/scan.py +28 -0
- {rowan_mcp-1.0.2.dist-info → rowan_mcp-2.0.0.dist-info}/METADATA +41 -33
- rowan_mcp-2.0.0.dist-info/RECORD +42 -0
- rowan_mcp/functions/admet.py +0 -94
- rowan_mcp/functions/bde.py +0 -113
- rowan_mcp/functions/calculation_retrieve.py +0 -89
- rowan_mcp/functions/conformers.py +0 -80
- rowan_mcp/functions/descriptors.py +0 -92
- rowan_mcp/functions/docking.py +0 -340
- rowan_mcp/functions/docking_enhanced.py +0 -174
- rowan_mcp/functions/electronic_properties.py +0 -205
- rowan_mcp/functions/folder_management.py +0 -137
- rowan_mcp/functions/fukui.py +0 -219
- rowan_mcp/functions/hydrogen_bond_basicity.py +0 -94
- rowan_mcp/functions/irc.py +0 -125
- rowan_mcp/functions/macropka.py +0 -120
- rowan_mcp/functions/molecular_converter.py +0 -423
- rowan_mcp/functions/molecular_dynamics.py +0 -191
- rowan_mcp/functions/molecule_lookup.py +0 -57
- rowan_mcp/functions/multistage_opt.py +0 -171
- rowan_mcp/functions/pdb_handler.py +0 -200
- rowan_mcp/functions/pka.py +0 -88
- rowan_mcp/functions/redox_potential.py +0 -352
- rowan_mcp/functions/scan.py +0 -536
- rowan_mcp/functions/scan_analyzer.py +0 -347
- rowan_mcp/functions/solubility.py +0 -277
- rowan_mcp/functions/spin_states.py +0 -747
- rowan_mcp/functions/system_management.py +0 -368
- rowan_mcp/functions/tautomers.py +0 -91
- rowan_mcp/functions/workflow_management.py +0 -422
- rowan_mcp-1.0.2.dist-info/RECORD +0 -34
- {rowan_mcp-1.0.2.dist-info → rowan_mcp-2.0.0.dist-info}/WHEEL +0 -0
- {rowan_mcp-1.0.2.dist-info → rowan_mcp-2.0.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Rowan v2 API: MacropKa Workflow
|
|
3
|
+
Calculate macroscopic pKa values across a pH range.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Annotated
|
|
7
|
+
import rowan
|
|
8
|
+
|
|
9
|
+
def submit_macropka_workflow(
|
|
10
|
+
initial_smiles: Annotated[str, "SMILES string of the molecule for macropKa calculation"],
|
|
11
|
+
min_pH: Annotated[int, "Minimum pH value for the calculation range"] = 0,
|
|
12
|
+
max_pH: Annotated[int, "Maximum pH value for the calculation range"] = 14,
|
|
13
|
+
min_charge: Annotated[int, "Minimum molecular charge to consider"] = -2,
|
|
14
|
+
max_charge: Annotated[int, "Maximum molecular charge to consider"] = 2,
|
|
15
|
+
compute_solvation_energy: Annotated[bool, "Whether to compute solvation energy corrections"] = True,
|
|
16
|
+
name: Annotated[str, "Workflow name for identification and tracking"] = "Macropka 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 MacropKa workflow using Rowan v2 API.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
initial_smiles: SMILES string of the molecule for macropKa calculation
|
|
24
|
+
min_pH: Minimum pH value for the calculation range
|
|
25
|
+
max_pH: Maximum pH value for the calculation range
|
|
26
|
+
min_charge: Minimum molecular charge to consider
|
|
27
|
+
max_charge: Maximum molecular charge to consider
|
|
28
|
+
compute_solvation_energy: Whether to compute solvation energy for each species
|
|
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
|
+
Calculates macroscopic pKa values across a pH range, determining the
|
|
34
|
+
protonation states and their populations at different pH values.
|
|
35
|
+
|
|
36
|
+
The workflow will:
|
|
37
|
+
1. Identify all ionizable sites in the molecule
|
|
38
|
+
2. Calculate microscopic pKa values for each site
|
|
39
|
+
3. Determine macroscopic pKa values and species populations
|
|
40
|
+
4. Optionally compute solvation energies
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Workflow object representing the submitted workflow
|
|
44
|
+
|
|
45
|
+
Examples:
|
|
46
|
+
# Simple molecule macropKa
|
|
47
|
+
result = submit_macropka_workflow(
|
|
48
|
+
initial_smiles="CC(=O)O", # Acetic acid
|
|
49
|
+
min_pH=0,
|
|
50
|
+
max_pH=14
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# Complex molecule with custom charge range
|
|
54
|
+
result = submit_macropka_workflow(
|
|
55
|
+
initial_smiles="CC(C)CC(C(=O)O)N", # Leucine
|
|
56
|
+
min_pH=0,
|
|
57
|
+
max_pH=14,
|
|
58
|
+
min_charge=-3,
|
|
59
|
+
max_charge=3,
|
|
60
|
+
compute_solvation_energy=True
|
|
61
|
+
)
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
# Build workflow_data
|
|
66
|
+
workflow_data = {
|
|
67
|
+
"min_pH": min_pH,
|
|
68
|
+
"max_pH": max_pH,
|
|
69
|
+
"min_charge": min_charge,
|
|
70
|
+
"max_charge": max_charge,
|
|
71
|
+
"compute_solvation_energy": compute_solvation_energy,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# Build the API request
|
|
75
|
+
data = {
|
|
76
|
+
"name": name,
|
|
77
|
+
"folder_uuid": folder_uuid,
|
|
78
|
+
"workflow_type": "macropka",
|
|
79
|
+
"workflow_data": workflow_data,
|
|
80
|
+
"initial_smiles": initial_smiles,
|
|
81
|
+
"max_credits": max_credits,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# Submit to API using rowan module
|
|
85
|
+
return rowan.submit_macropka_workflow(
|
|
86
|
+
initial_smiles=initial_smiles,
|
|
87
|
+
min_pH=min_pH,
|
|
88
|
+
max_pH=max_pH,
|
|
89
|
+
min_charge=min_charge,
|
|
90
|
+
max_charge=max_charge,
|
|
91
|
+
compute_solvation_energy=compute_solvation_energy,
|
|
92
|
+
name=name,
|
|
93
|
+
folder_uuid=folder_uuid if folder_uuid else None,
|
|
94
|
+
max_credits=max_credits if max_credits > 0 else None
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
except Exception as e:
|
|
98
|
+
# Re-raise the exception so MCP can handle it
|
|
99
|
+
raise e
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Rowan v2 API: pKa Workflow
|
|
3
|
+
Predict acid dissociation constants for ionizable groups in molecules.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import List, Dict, Any, Annotated
|
|
7
|
+
import rowan
|
|
8
|
+
import json
|
|
9
|
+
import stjames
|
|
10
|
+
|
|
11
|
+
def submit_pka_workflow(
|
|
12
|
+
initial_molecule: Annotated[str, "SMILES string of the molecule to calculate pKa"],
|
|
13
|
+
pka_range: Annotated[List[float], "pKa range [min, max] to search (e.g., [2, 12])"] = [2, 12],
|
|
14
|
+
deprotonate_elements: Annotated[str, "Comma-separated elements for deprotonation (e.g., 'N,O,S'). Empty for auto-detect"] = "",
|
|
15
|
+
protonate_elements: Annotated[str, "Comma-separated elements for protonation (e.g., 'N,O'). Empty for auto-detect"] = "",
|
|
16
|
+
mode: Annotated[str, "Calculation mode: 'rapid', 'careful', or 'meticulous'"] = "careful",
|
|
17
|
+
name: Annotated[str, "Workflow name for identification and tracking"] = "pKa 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 pKa prediction workflow using Rowan v2 API.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
initial_molecule: The molecule to calculate the pKa of. SMILES string.
|
|
25
|
+
pka_range: pKa range [min, max] to search, e.g., [2, 12]
|
|
26
|
+
deprotonate_elements: Atomic numbers to consider for deprotonation, e.g., "[7, 8, 16]" for N, O, S. Empty string uses defaults.
|
|
27
|
+
protonate_elements: Atomic numbers to consider for protonation, e.g., "[7, 8]" for N, O. Empty string uses defaults.
|
|
28
|
+
mode: Calculation mode: 'rapid' (fast), 'careful' (balanced), or 'meticulous' (thorough)
|
|
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
|
+
Returns:
|
|
34
|
+
Workflow object representing the submitted workflow
|
|
35
|
+
|
|
36
|
+
Examples:
|
|
37
|
+
# Phenol pKa
|
|
38
|
+
result = submit_pka_workflow(
|
|
39
|
+
initial_molecule="Oc1ccccc1",
|
|
40
|
+
name="pKa phenol",
|
|
41
|
+
deprotonate_elements="[8]" # Only consider oxygen
|
|
42
|
+
)
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
# Handle JSON string inputs for element lists
|
|
46
|
+
parsed_deprotonate_elements = None
|
|
47
|
+
if deprotonate_elements:
|
|
48
|
+
try:
|
|
49
|
+
parsed_deprotonate_elements = json.loads(deprotonate_elements)
|
|
50
|
+
except json.JSONDecodeError:
|
|
51
|
+
pass # Keep as None if not valid JSON
|
|
52
|
+
|
|
53
|
+
parsed_protonate_elements = None
|
|
54
|
+
if protonate_elements:
|
|
55
|
+
try:
|
|
56
|
+
parsed_protonate_elements = json.loads(protonate_elements)
|
|
57
|
+
except json.JSONDecodeError:
|
|
58
|
+
pass # Keep as None if not valid JSON
|
|
59
|
+
|
|
60
|
+
# Convert List[float] to Tuple[float, float] for Rowan SDK compatibility
|
|
61
|
+
pka_range_tuple = tuple(pka_range) if len(pka_range) == 2 else (pka_range[0], pka_range[1] if len(pka_range) > 1 else pka_range[0])
|
|
62
|
+
|
|
63
|
+
return rowan.submit_pka_workflow(
|
|
64
|
+
initial_molecule=stjames.Molecule.from_smiles(initial_molecule),
|
|
65
|
+
pka_range=pka_range_tuple,
|
|
66
|
+
deprotonate_elements=parsed_deprotonate_elements,
|
|
67
|
+
protonate_elements=parsed_protonate_elements,
|
|
68
|
+
mode=mode,
|
|
69
|
+
name=name,
|
|
70
|
+
folder_uuid=folder_uuid if folder_uuid else None,
|
|
71
|
+
max_credits=max_credits if max_credits > 0 else None
|
|
72
|
+
)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Rowan v2 API: Protein Cofolding Workflow
|
|
3
|
+
Simulate protein-protein interactions and cofolding.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import List, Annotated
|
|
7
|
+
import rowan
|
|
8
|
+
import stjames
|
|
9
|
+
import json
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def submit_protein_cofolding_workflow(
|
|
13
|
+
initial_protein_sequences: Annotated[str, "JSON string list of protein sequences for cofolding (e.g., '[\"MKLLV...\", \"MAHQR...\"]')"],
|
|
14
|
+
initial_smiles_list: Annotated[str, "JSON string list of SMILES for ligands to include in cofolding (e.g., '[\"CCO\", \"CC(=O)O\"]'). Empty for protein-only"] = "",
|
|
15
|
+
ligand_binding_affinity_index: Annotated[str, "JSON string mapping ligand indices to protein binding sites. Empty for automatic detection"] = "",
|
|
16
|
+
use_msa_server: Annotated[bool, "Whether to use multiple sequence alignment server for better structure prediction"] = True,
|
|
17
|
+
use_potentials: Annotated[bool, "Whether to include additional potentials in the calculation"] = False,
|
|
18
|
+
name: Annotated[str, "Workflow name for identification and tracking"] = "Cofolding Workflow",
|
|
19
|
+
model: Annotated[str, "Structure prediction model to use (e.g., 'boltz_2', 'alphafold3')"] = "boltz_2",
|
|
20
|
+
folder_uuid: Annotated[str, "UUID of folder to organize this workflow. Empty string uses default folder"] = "",
|
|
21
|
+
max_credits: Annotated[int, "Maximum credits to spend on this calculation. 0 for no limit"] = 0
|
|
22
|
+
):
|
|
23
|
+
"""Submits a protein cofolding workflow to the API.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
initial_protein_sequences: JSON string list of protein sequences (amino acid strings) to cofold
|
|
27
|
+
initial_smiles_list: JSON string list of ligand SMILES strings to include in cofolding. Empty string for protein-only
|
|
28
|
+
ligand_binding_affinity_index: Index of ligand in initial_smiles_list for binding affinity calculation (e.g., "0"). Empty string skips affinity
|
|
29
|
+
use_msa_server: Whether to use MSA (Multiple Sequence Alignment) server for improved accuracy
|
|
30
|
+
use_potentials: Whether to use statistical potentials in the calculation
|
|
31
|
+
name: Workflow name for identification and tracking
|
|
32
|
+
model: Cofolding model to use (e.g., 'boltz_2', 'alphafold3')
|
|
33
|
+
folder_uuid: UUID of folder to organize this workflow. Empty string uses default folder.
|
|
34
|
+
max_credits: Maximum credits to spend on this calculation. 0 for no limit.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Workflow object representing the submitted workflow
|
|
38
|
+
|
|
39
|
+
Example:
|
|
40
|
+
# Protein-ligand cofolding with CDK2 kinase (from test)
|
|
41
|
+
result = submit_protein_cofolding_workflow(
|
|
42
|
+
initial_protein_sequences='["MENFQKVEKIGEGTYGVVYKARNKLTGEVVALKKIRLDTETEGVPSTAIREISLLKELNHPNIVKLLDVIHTENKLYLVFEFLHQDLKKFMDASALTGIPLPLIKSYLFQLLQGLAFCHSHRVLHRDLKPQNLLINTEGAIKLADFGLARAFGVPVRTYTHEVVTLWYRAPEILLGCKYYSTAVDIWSLGCIFAEMVTRRALFPGDSEIDQLFRIFRTLGTPDEVVWPGVTSMPDYKPSFPKWARQDFSKVVPPLDEDGRSLLSQMLHYDPNKRISAKAALAHPFFQDVTKPVPHLRL"]',
|
|
43
|
+
initial_smiles_list='["CCC(C)(C)NC1=NCC2(CCC(=O)C2C)N1"]',
|
|
44
|
+
ligand_binding_affinity_index="0",
|
|
45
|
+
name="Cofolding CDK2 with ligand"
|
|
46
|
+
)
|
|
47
|
+
"""
|
|
48
|
+
# Parse initial_protein_sequences (always a string in simplified version)
|
|
49
|
+
try:
|
|
50
|
+
initial_protein_sequences = json.loads(initial_protein_sequences)
|
|
51
|
+
except (json.JSONDecodeError, ValueError):
|
|
52
|
+
# Try to parse as comma-separated
|
|
53
|
+
if ',' in initial_protein_sequences:
|
|
54
|
+
initial_protein_sequences = [s.strip() for s in initial_protein_sequences.split(',') if s.strip()]
|
|
55
|
+
else:
|
|
56
|
+
initial_protein_sequences = [initial_protein_sequences.strip()]
|
|
57
|
+
|
|
58
|
+
# Parse initial_smiles_list if provided
|
|
59
|
+
parsed_initial_smiles_list = None
|
|
60
|
+
if initial_smiles_list:
|
|
61
|
+
try:
|
|
62
|
+
parsed_initial_smiles_list = json.loads(initial_smiles_list)
|
|
63
|
+
except (json.JSONDecodeError, ValueError):
|
|
64
|
+
# Try to parse as comma-separated
|
|
65
|
+
if ',' in initial_smiles_list:
|
|
66
|
+
parsed_initial_smiles_list = [s.strip() for s in initial_smiles_list.split(',') if s.strip()]
|
|
67
|
+
else:
|
|
68
|
+
parsed_initial_smiles_list = [initial_smiles_list.strip()]
|
|
69
|
+
|
|
70
|
+
# Parse ligand_binding_affinity_index if provided
|
|
71
|
+
parsed_ligand_index = None
|
|
72
|
+
if ligand_binding_affinity_index:
|
|
73
|
+
try:
|
|
74
|
+
parsed_ligand_index = int(ligand_binding_affinity_index)
|
|
75
|
+
except (ValueError, TypeError):
|
|
76
|
+
raise ValueError(f"Invalid ligand_binding_affinity_index: '{ligand_binding_affinity_index}' must be an integer")
|
|
77
|
+
|
|
78
|
+
return rowan.submit_protein_cofolding_workflow(
|
|
79
|
+
initial_protein_sequences=initial_protein_sequences,
|
|
80
|
+
initial_smiles_list=parsed_initial_smiles_list,
|
|
81
|
+
ligand_binding_affinity_index=parsed_ligand_index,
|
|
82
|
+
use_msa_server=use_msa_server,
|
|
83
|
+
use_potentials=use_potentials,
|
|
84
|
+
name=name,
|
|
85
|
+
model=model,
|
|
86
|
+
folder_uuid=folder_uuid if folder_uuid else None,
|
|
87
|
+
max_credits=max_credits if max_credits > 0 else None
|
|
88
|
+
)
|
|
@@ -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
|
+
)
|