rowan-mcp 0.1.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.

Files changed (35) hide show
  1. rowan_mcp/__init__.py +14 -0
  2. rowan_mcp/__main__.py +14 -0
  3. rowan_mcp/functions/admet.py +94 -0
  4. rowan_mcp/functions/bde.py +113 -0
  5. rowan_mcp/functions/calculation_retrieve.py +89 -0
  6. rowan_mcp/functions/conformers.py +135 -0
  7. rowan_mcp/functions/descriptors.py +92 -0
  8. rowan_mcp/functions/docking.py +340 -0
  9. rowan_mcp/functions/docking_enhanced.py +174 -0
  10. rowan_mcp/functions/electronic_properties.py +263 -0
  11. rowan_mcp/functions/folder_management.py +137 -0
  12. rowan_mcp/functions/fukui.py +355 -0
  13. rowan_mcp/functions/hydrogen_bond_basicity.py +94 -0
  14. rowan_mcp/functions/irc.py +125 -0
  15. rowan_mcp/functions/macropka.py +195 -0
  16. rowan_mcp/functions/molecular_converter.py +423 -0
  17. rowan_mcp/functions/molecular_dynamics.py +191 -0
  18. rowan_mcp/functions/molecule_cache.db +0 -0
  19. rowan_mcp/functions/molecule_lookup.py +446 -0
  20. rowan_mcp/functions/multistage_opt.py +171 -0
  21. rowan_mcp/functions/pdb_handler.py +200 -0
  22. rowan_mcp/functions/pka.py +137 -0
  23. rowan_mcp/functions/redox_potential.py +352 -0
  24. rowan_mcp/functions/scan.py +536 -0
  25. rowan_mcp/functions/scan_analyzer.py +347 -0
  26. rowan_mcp/functions/solubility.py +277 -0
  27. rowan_mcp/functions/spin_states.py +747 -0
  28. rowan_mcp/functions/system_management.py +368 -0
  29. rowan_mcp/functions/tautomers.py +91 -0
  30. rowan_mcp/functions/workflow_management.py +422 -0
  31. rowan_mcp/server.py +169 -0
  32. rowan_mcp-0.1.0.dist-info/METADATA +216 -0
  33. rowan_mcp-0.1.0.dist-info/RECORD +35 -0
  34. rowan_mcp-0.1.0.dist-info/WHEEL +4 -0
  35. rowan_mcp-0.1.0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,263 @@
1
+ """
2
+ Electronic Properties Analysis for Rowan MCP Server
3
+
4
+ This module provides electronic structure property calculations including:
5
+ - HOMO/LUMO energies and molecular orbitals
6
+ - Electron density and electrostatic potential
7
+ - Population analysis and bond orders
8
+ - Orbital visualization data
9
+ """
10
+
11
+ import os
12
+ import logging
13
+ import time
14
+ from typing import Any, Dict, List, Optional
15
+
16
+ try:
17
+ import rowan
18
+ except ImportError:
19
+ rowan = None
20
+
21
+ # Configure logging
22
+ logging.basicConfig(level=logging.INFO)
23
+ logger = logging.getLogger(__name__)
24
+
25
+ # Setup API key
26
+ api_key = os.getenv("ROWAN_API_KEY")
27
+ if api_key and rowan:
28
+ rowan.api_key = api_key
29
+
30
+ def log_rowan_api_call(workflow_type: str, **kwargs):
31
+ """Log Rowan API calls with detailed parameters."""
32
+
33
+ try:
34
+ start_time = time.time()
35
+
36
+ if not rowan:
37
+ raise ImportError("Rowan package not available - please install with 'pip install rowan'")
38
+
39
+ logger.info(f"Calling Rowan {workflow_type} workflow")
40
+ for key, value in kwargs.items():
41
+ if key != 'ping_interval':
42
+ logger.info(f" {key}: {value}")
43
+
44
+ result = rowan.compute(workflow_type=workflow_type, **kwargs)
45
+
46
+ end_time = time.time()
47
+ duration = end_time - start_time
48
+ logger.info(f"Rowan {workflow_type} completed in {duration:.2f} seconds")
49
+
50
+ return result
51
+
52
+ except Exception as e:
53
+ logger.error(f"Rowan {workflow_type} failed: {str(e)}")
54
+ raise e
55
+
56
+ def lookup_molecule_smiles(molecule_name: str) -> str:
57
+ """Look up canonical SMILES using the advanced molecule_lookup system.
58
+
59
+ Uses PubChemPy + SQLite caching + RDKit validation for scalable molecule lookup.
60
+ """
61
+ try:
62
+ # Import the advanced molecule lookup system
63
+ from .molecule_lookup import get_lookup_instance
64
+
65
+ lookup = get_lookup_instance()
66
+ smiles, source, metadata = lookup.get_smiles(molecule_name)
67
+
68
+ if smiles:
69
+ logger.info(f"Molecule lookup successful: '{molecule_name}' → '{smiles}' (source: {source})")
70
+ return smiles
71
+ else:
72
+ logger.warning(f"Molecule lookup failed for '{molecule_name}': {metadata.get('error', 'Unknown error')}")
73
+ # Return original input as fallback (might be valid SMILES)
74
+ return molecule_name
75
+
76
+ except ImportError as e:
77
+ logger.error(f"Could not import molecule_lookup: {e}")
78
+ # Fallback: return original input
79
+ return molecule_name
80
+ except Exception as e:
81
+ logger.error(f"Molecule lookup error for '{molecule_name}': {e}")
82
+ # Fallback: return original input
83
+ return molecule_name
84
+
85
+ def rowan_electronic_properties(
86
+ name: str,
87
+ molecule: str,
88
+ # Settings parameters (quantum chemistry calculation settings)
89
+ method: Optional[str] = None,
90
+ basis_set: Optional[str] = None,
91
+ engine: Optional[str] = None,
92
+ charge: int = 0,
93
+ multiplicity: int = 1,
94
+ # Cube computation control parameters
95
+ compute_density_cube: bool = True,
96
+ compute_electrostatic_potential_cube: bool = True,
97
+ compute_num_occupied_orbitals: int = 1,
98
+ compute_num_virtual_orbitals: int = 1,
99
+ # Workflow control parameters
100
+ mode: Optional[str] = None,
101
+ folder_uuid: Optional[str] = None,
102
+ blocking: bool = True,
103
+ ping_interval: int = 5
104
+ ) -> str:
105
+ """Calculate comprehensive electronic structure properties using Rowan's ElectronicPropertiesWorkflow.
106
+
107
+ Implements the ElectronicPropertiesWorkflow class for computing detailed electronic properties including:
108
+ - **Molecular Orbitals**: HOMO/LUMO energies, orbital cubes, occupation numbers
109
+ - **Electron Density**: Total, α/β spin densities, spin density differences
110
+ - **Electrostatic Properties**: Dipole moments, quadrupole moments, electrostatic potential
111
+ - **Population Analysis**: Mulliken charges, Löwdin charges
112
+ - **Bond Analysis**: Wiberg bond orders, Mayer bond orders
113
+ - **Visualization Data**: Cube files for density, ESP, and molecular orbitals
114
+
115
+ **Molecule Lookup**: Uses advanced PubChemPy + SQLite caching + RDKit validation system
116
+ for robust molecule identification and SMILES canonicalization.
117
+
118
+ Args:
119
+ name: Name for the calculation
120
+ molecule: Molecule name (e.g., "aspirin", "taxol") or SMILES string
121
+ method: QM method (default: b3lyp for electronic properties)
122
+ basis_set: Basis set (default: def2-svp for balanced accuracy)
123
+ engine: Computational engine (default: psi4)
124
+ charge: Molecular charge (default: 0)
125
+ multiplicity: Spin multiplicity (default: 1 for singlet)
126
+ compute_density_cube: Generate electron density cube (default: True)
127
+ compute_electrostatic_potential_cube: Generate ESP cube (default: True)
128
+ compute_num_occupied_orbitals: Number of occupied MOs to save (default: 1)
129
+ compute_num_virtual_orbitals: Number of virtual MOs to save (default: 1)
130
+ mode: Calculation mode/precision (optional)
131
+ folder_uuid: Optional folder UUID for organization
132
+ blocking: Whether to wait for completion (default: True)
133
+ ping_interval: Check status interval in seconds (default: 5)
134
+
135
+ Returns:
136
+ Comprehensive electronic properties results following ElectronicPropertiesWorkflow format
137
+ """
138
+ # Look up canonical SMILES using advanced molecule lookup (PubChemPy + caching + RDKit)
139
+ canonical_smiles = lookup_molecule_smiles(molecule)
140
+
141
+ # Apply smart defaults for electronic properties calculations
142
+ if method is None:
143
+ method = "b3lyp" # Good for electronic properties
144
+ if basis_set is None:
145
+ basis_set = "def2-svp" # Balanced accuracy/cost for properties
146
+ if engine is None:
147
+ engine = "psi4" # Robust for electronic properties
148
+
149
+ # Validate orbital count parameters
150
+ if compute_num_occupied_orbitals < 0:
151
+ return f"compute_num_occupied_orbitals must be non-negative (got {compute_num_occupied_orbitals})"
152
+ if compute_num_virtual_orbitals < 0:
153
+ return f"compute_num_virtual_orbitals must be non-negative (got {compute_num_virtual_orbitals})"
154
+
155
+ # Build parameters following ElectronicPropertiesWorkflow specification
156
+ electronic_params = {
157
+ "name": name,
158
+ "molecule": canonical_smiles, # API interface requirement
159
+ "initial_molecule": canonical_smiles, # ElectronicPropertiesWorkflow requirement
160
+ # Settings (quantum chemistry parameters) - exactly as in ElectronicPropertiesWorkflow
161
+ "settings": {
162
+ "method": method.lower(),
163
+ "basis_set": basis_set.lower(),
164
+ "engine": engine.lower(),
165
+ "charge": charge,
166
+ "multiplicity": multiplicity
167
+ },
168
+ # Cube computation control - exactly as in ElectronicPropertiesWorkflow
169
+ "compute_density_cube": compute_density_cube,
170
+ "compute_electrostatic_potential_cube": compute_electrostatic_potential_cube,
171
+ "compute_num_occupied_orbitals": compute_num_occupied_orbitals,
172
+ "compute_num_virtual_orbitals": compute_num_virtual_orbitals,
173
+ # Workflow parameters
174
+ "folder_uuid": folder_uuid,
175
+ "blocking": blocking,
176
+ "ping_interval": ping_interval
177
+ }
178
+
179
+ # Add mode if specified (optional in ElectronicPropertiesWorkflow)
180
+ if mode:
181
+ electronic_params["mode"] = mode
182
+
183
+ try:
184
+ result = log_rowan_api_call(
185
+ workflow_type="electronic_properties",
186
+ **electronic_params
187
+ )
188
+
189
+ # Enhanced result formatting for electronic properties
190
+ if blocking:
191
+ status = result.get('status', result.get('object_status', 'Unknown'))
192
+
193
+ if status == 2: # Completed successfully
194
+ formatted = f"Electronic properties calculation for '{name}' completed successfully!\n\n"
195
+ elif status == 3: # Failed
196
+ formatted = f"Electronic properties calculation for '{name}' failed!\n\n"
197
+ else:
198
+ formatted = f"Electronic properties calculation for '{name}' submitted!\n\n"
199
+
200
+ formatted += f"Molecule: {molecule}\n"
201
+ formatted += f"Canonical SMILES: {canonical_smiles}\n"
202
+ formatted += f"Job UUID: {result.get('uuid', 'N/A')}\n"
203
+ formatted += f"Status: {status}\n\n"
204
+
205
+ formatted += f"Molecule Lookup: Advanced PubChemPy + SQLite + RDKit system\n\n"
206
+ formatted += f"Calculation Settings:\n"
207
+ formatted += f"• Method: {method.upper()}\n"
208
+ formatted += f"• Basis Set: {basis_set}\n"
209
+ formatted += f"• Engine: {engine.upper()}\n"
210
+ formatted += f"• Charge: {charge}, Multiplicity: {multiplicity}\n\n"
211
+
212
+ formatted += f"Property Calculations:\n"
213
+ formatted += f"• Density Cube: {'Enabled' if compute_density_cube else 'Disabled'}\n"
214
+ formatted += f"• ESP Cube: {'Enabled' if compute_electrostatic_potential_cube else 'Disabled'}\n"
215
+ formatted += f"• Occupied MOs: {compute_num_occupied_orbitals}\n"
216
+ formatted += f"• Virtual MOs: {compute_num_virtual_orbitals}\n\n"
217
+
218
+ if status == 2:
219
+ formatted += f"Additional Analysis:\n"
220
+ formatted += f"• Use rowan_calculation_retrieve('{result.get('uuid')}') for full calculation details\n"
221
+ formatted += f"• Use rowan_workflow_management(action='retrieve', workflow_uuid='{result.get('uuid')}') for workflow metadata\n"
222
+
223
+ elif status == 3:
224
+ formatted += f"Troubleshooting:\n"
225
+ formatted += f"• Try simpler method/basis: method='hf', basis_set='sto-3g'\n"
226
+ formatted += f"• Check molecular charge and multiplicity\n"
227
+ formatted += f"• Disable cube generation for faster calculations\n"
228
+ formatted += f"• Use rowan_workflow_management(action='retrieve', workflow_uuid='{result.get('uuid')}') for error details\n"
229
+ else:
230
+ formatted += f"Next Steps:\n"
231
+ formatted += f"• Monitor status with rowan_workflow_management(action='retrieve', workflow_uuid='{result.get('uuid')}')\n"
232
+ formatted += f"• Electronic properties calculations may take several minutes\n"
233
+
234
+ return formatted
235
+ else:
236
+ return str(result)
237
+
238
+ except Exception as e:
239
+ error_msg = f"Electronic properties calculation failed: {str(e)}\n\n"
240
+ error_msg += f"Molecule: {molecule}\n"
241
+ error_msg += f"Canonical SMILES: {canonical_smiles}\n"
242
+ error_msg += f"Settings: {method}/{basis_set}/{engine}\n\n"
243
+ error_msg += f"Molecule Lookup: Advanced PubChemPy + SQLite + RDKit system\n\n"
244
+ error_msg += f"Common Issues:\n"
245
+ error_msg += f"• Invalid method/basis set combination\n"
246
+ error_msg += f"• Incorrect charge/multiplicity for molecule\n"
247
+ error_msg += f"• Engine compatibility issues\n"
248
+ error_msg += f"• Molecule not found in PubChem database\n"
249
+ error_msg += f"• Try with default parameters first\n"
250
+ return error_msg
251
+
252
+ def test_electronic_properties():
253
+ """Test the electronic properties function with advanced molecule lookup."""
254
+ return rowan_electronic_properties(
255
+ name="test_electronic_properties",
256
+ molecule="aspirin", # Test advanced lookup with a pharmaceutical
257
+ method="hf",
258
+ basis_set="sto-3g",
259
+ blocking=True
260
+ )
261
+
262
+ if __name__ == "__main__":
263
+ print(test_electronic_properties())
@@ -0,0 +1,137 @@
1
+ """
2
+ Folder management operations for Rowan API.
3
+ """
4
+
5
+ import os
6
+ import rowan
7
+ from typing import Optional
8
+
9
+ # Set up logging
10
+ import logging
11
+ logger = logging.getLogger(__name__)
12
+
13
+ # Configure rowan API key
14
+ if not hasattr(rowan, 'api_key') or not rowan.api_key:
15
+ api_key = os.getenv("ROWAN_API_KEY")
16
+ if api_key:
17
+ rowan.api_key = api_key
18
+ logger.info("Rowan API key configured")
19
+ else:
20
+ logger.error("No ROWAN_API_KEY found in environment")
21
+
22
+ def rowan_folder_management(
23
+ action: str,
24
+ folder_uuid: Optional[str] = None,
25
+ name: Optional[str] = None,
26
+ parent_uuid: Optional[str] = None,
27
+ notes: Optional[str] = None,
28
+ starred: Optional[bool] = None,
29
+ public: Optional[bool] = None,
30
+ created_at: Optional[str] = None,
31
+ updated_at: Optional[str] = None,
32
+ is_root: Optional[bool] = None,
33
+ uuid: Optional[str] = None,
34
+ name_contains: Optional[str] = None,
35
+ page: int = 0,
36
+ size: int = 50
37
+ ) -> str:
38
+ """Unified folder management tool for all folder operations. Available actions: create, retrieve, update, delete, list.
39
+
40
+ **Available Actions:**
41
+ - **create**: Create a new folder (requires: name, optional: parent_uuid, notes, starred, public)
42
+ - **retrieve**: Get folder details (requires: folder_uuid)
43
+ - **update**: Update folder properties (requires: folder_uuid, optional: name, parent_uuid, notes, starred, public)
44
+ - **delete**: Delete a folder (requires: folder_uuid)
45
+ - **list**: List folders with filters (optional: name_contains, parent_uuid, starred, public, page, size)
46
+
47
+ Args:
48
+ action: Action to perform ('create', 'retrieve', 'update', 'delete', 'list')
49
+ folder_uuid: UUID of the folder (required for retrieve, update, delete)
50
+ name: Folder name (required for create, optional for update)
51
+ parent_uuid: Parent folder UUID (optional for create/update, if not provided creates in root)
52
+ notes: Folder notes (optional for create/update)
53
+ starred: Star the folder (optional for create/update)
54
+ public: Make folder public (optional for create/update)
55
+ created_at: The date and time at which this folder was created
56
+ updated_at: The date and time at which this folder was most recently updated
57
+ is_root: Whether or not this folder is the user's root folder
58
+ uuid: The UUID of this folder
59
+ name_contains: Filter by name containing text (optional for list)
60
+ page: Page number for pagination (default: 1, for list)
61
+ size: Results per page (default: 50, for list)
62
+
63
+ Returns:
64
+ Results of the folder operation
65
+ """
66
+
67
+ action = action.lower()
68
+
69
+ try:
70
+ if action == "create":
71
+ if not name:
72
+ return "Error: 'name' is required for creating a folder"
73
+
74
+ return rowan.Folder.create(
75
+ name=name,
76
+ parent_uuid=parent_uuid,
77
+ notes=notes or "",
78
+ starred=starred or False,
79
+ public=public or False
80
+ )
81
+
82
+ elif action == "retrieve":
83
+ if not folder_uuid:
84
+ return "Error: 'folder_uuid' is required for retrieving a folder"
85
+
86
+ return rowan.Folder.retrieve(uuid=folder_uuid)
87
+
88
+ elif action == "update":
89
+ if not folder_uuid:
90
+ return "Error: 'folder_uuid' is required for updating a folder"
91
+
92
+ return rowan.Folder.update(
93
+ uuid=folder_uuid,
94
+ name=name,
95
+ notes=notes,
96
+ starred=starred,
97
+ public=public,
98
+ parent_uuid=parent_uuid
99
+
100
+ )
101
+
102
+ elif action == "delete":
103
+ if not folder_uuid:
104
+ return "Error: 'folder_uuid' is required for deleting a folder"
105
+
106
+ rowan.Folder.delete(uuid=folder_uuid)
107
+ return "Folder deleted successfully"
108
+
109
+ elif action == "list":
110
+ return rowan.Folder.list(
111
+ name_contains=name_contains,
112
+ parent_uuid=parent_uuid,
113
+ starred=starred,
114
+ public=public,
115
+ page=page,
116
+ size=size
117
+ )
118
+
119
+ except Exception as e:
120
+ return f"Error in folder {action}: {str(e)}"
121
+
122
+
123
+ def test_rowan_folder_management():
124
+ """Test the rowan_folder_management function."""
125
+ try:
126
+ # Test listing folders
127
+ result = rowan_folder_management(action="list", size=5)
128
+ print("Folder management test successful!")
129
+ print(f"Result: {result[:200]}...") # Show first 200 chars
130
+ return True
131
+ except Exception as e:
132
+ print(f"Folder management test failed: {e}")
133
+ return False
134
+
135
+
136
+ if __name__ == "__main__":
137
+ test_rowan_folder_management()