rowan-mcp 2.0.0__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 (31) hide show
  1. rowan_mcp/functions/admet.py +89 -0
  2. rowan_mcp/functions/bde.py +106 -0
  3. rowan_mcp/functions/calculation_retrieve.py +89 -0
  4. rowan_mcp/functions/conformers.py +77 -0
  5. rowan_mcp/functions/descriptors.py +89 -0
  6. rowan_mcp/functions/docking.py +290 -0
  7. rowan_mcp/functions/docking_enhanced.py +174 -0
  8. rowan_mcp/functions/electronic_properties.py +202 -0
  9. rowan_mcp/functions/folder_management.py +130 -0
  10. rowan_mcp/functions/fukui.py +216 -0
  11. rowan_mcp/functions/hydrogen_bond_basicity.py +87 -0
  12. rowan_mcp/functions/irc.py +125 -0
  13. rowan_mcp/functions/macropka.py +120 -0
  14. rowan_mcp/functions/molecular_converter.py +423 -0
  15. rowan_mcp/functions/molecular_dynamics.py +191 -0
  16. rowan_mcp/functions/molecule_lookup.py +57 -0
  17. rowan_mcp/functions/multistage_opt.py +168 -0
  18. rowan_mcp/functions/pdb_handler.py +200 -0
  19. rowan_mcp/functions/pka.py +81 -0
  20. rowan_mcp/functions/redox_potential.py +349 -0
  21. rowan_mcp/functions/scan.py +536 -0
  22. rowan_mcp/functions/scan_analyzer.py +347 -0
  23. rowan_mcp/functions/solubility.py +277 -0
  24. rowan_mcp/functions/spin_states.py +747 -0
  25. rowan_mcp/functions/system_management.py +361 -0
  26. rowan_mcp/functions/tautomers.py +88 -0
  27. rowan_mcp/functions/workflow_management.py +422 -0
  28. {rowan_mcp-2.0.0.dist-info → rowan_mcp-2.0.1.dist-info}/METADATA +3 -18
  29. {rowan_mcp-2.0.0.dist-info → rowan_mcp-2.0.1.dist-info}/RECORD +31 -4
  30. {rowan_mcp-2.0.0.dist-info → rowan_mcp-2.0.1.dist-info}/WHEEL +0 -0
  31. {rowan_mcp-2.0.0.dist-info → rowan_mcp-2.0.1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,361 @@
1
+ """
2
+ System management operations for Rowan API.
3
+ """
4
+
5
+ import os
6
+ import sys
7
+ import rowan
8
+ import logging
9
+ from typing import Optional
10
+
11
+ # Set up logging
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+
16
+ def rowan_system_management(
17
+ action: str,
18
+ job_uuid: Optional[str] = None,
19
+ log_level: Optional[str] = None
20
+ ) -> str:
21
+ """Unified system management tool for server utilities and information.
22
+
23
+ **Available Actions:**
24
+ - **help**: Get list of all available Rowan MCP tools with descriptions
25
+ - **server_info**: Get server status and configuration information
26
+ - **server_status**: Check server health and connectivity
27
+ - **set_log_level**: Set logging level for debugging (requires: log_level)
28
+ - **job_redirect**: Redirect legacy job queries to workflow management (requires: job_uuid)
29
+
30
+ Args:
31
+ action: Action to perform ('help', 'server_info', 'server_status', 'set_log_level', 'job_redirect')
32
+ job_uuid: UUID of the job (required for job_redirect)
33
+ log_level: Logging level - DEBUG, INFO, WARNING, ERROR (required for set_log_level)
34
+
35
+ Returns:
36
+ Results of the system operation
37
+ """
38
+
39
+ action = action.lower()
40
+
41
+ try:
42
+ if action == "help":
43
+ result = "**Available Rowan MCP Tools** \n\n"
44
+
45
+ result += "**Now with unified management tools!**\n"
46
+ result += "Each tool has tailored documentation and parameters.\n\n"
47
+
48
+ # Group by common use cases
49
+ result += "**Quantum Chemistry & Basic Calculations:**\n"
50
+ result += "• `rowan_electronic_properties` - HOMO/LUMO, orbitals\n"
51
+ result += "• `rowan_multistage_opt` - Multi-level optimization (for geometry)\n\n"
52
+
53
+ result += "**Molecular Analysis:**\n"
54
+ result += "• `rowan_conformers` - Find molecular conformations\n"
55
+ result += "• `rowan_tautomers` - Tautomer enumeration\n"
56
+ result += "• `rowan_descriptors` - Molecular descriptors for ML\n\n"
57
+
58
+ result += "**Chemical Properties:**\n"
59
+ result += "• `rowan_pka` - pKa prediction\n"
60
+ result += "• `rowan_redox_potential` - Redox potentials vs SCE\n"
61
+
62
+ result += "• `rowan_solubility` - Solubility prediction\n\n"
63
+
64
+ result += "**Drug Discovery:**\n"
65
+ result += "• `rowan_admet` - ADME-Tox properties\n"
66
+ result += "• `rowan_docking` - Protein-ligand docking\n\n"
67
+
68
+ result += "**Advanced Analysis:**\n"
69
+ result += "• `rowan_scan` - Potential energy surface scans (bond/angle/dihedral)\n"
70
+ result += "• `rowan_fukui` - Reactivity analysis\n"
71
+ result += "• `rowan_spin_states` - Spin state preferences\n"
72
+ result += "• `rowan_irc` - Reaction coordinate following\n"
73
+ result += "• `rowan_molecular_dynamics` - MD simulations\n"
74
+ result += "\n"
75
+
76
+ result += "**Usage Guidelines:**\n"
77
+ result += "• For geometry optimization: use `rowan_multistage_opt`\n"
78
+ result += "• For conformer search: use `rowan_conformers`\n"
79
+ result += "• For pKa prediction: use `rowan_pka`\n"
80
+ result += "• For electronic structure: use `rowan_electronic_properties`\n"
81
+ result += "• For drug properties: use `rowan_admet`\n"
82
+ result += "• For reaction mechanisms: use `rowan_scan` then `rowan_irc`\n"
83
+ result += "• For potential energy scans: use `rowan_scan` with coordinate specification\n\n"
84
+
85
+ result += "**Management Tools:**\n"
86
+ result += "• `rowan_folder_management` - Unified folder operations (create, retrieve, update, delete, list)\n"
87
+ result += "• `rowan_workflow_management` - Unified workflow operations (create, retrieve, update, stop, status, delete, list)\n"
88
+ result += "• `rowan_system_management` - System utilities (help, set_log_level, job_redirect)\n"
89
+ result += "• `rowan_calculation_retrieve` - Get calculation results\n"
90
+ result += "• `rowan_molecule_lookup` - SMILES lookup for common molecules\n\n"
91
+
92
+ result += "**Total Available:** 20+ specialized tools + management tools\n"
93
+ result += "**Each tool has specific documentation - check individual tool descriptions**\n"
94
+ result += "**Management tools use 'action' parameter to specify operation**\n"
95
+
96
+ return result
97
+
98
+ elif action == "server_info":
99
+ result = "**Rowan MCP Server Information**\n\n"
100
+
101
+ # Server configuration
102
+ result += "**Configuration:**\n"
103
+ result += f"• API Key: {'Configured' if os.getenv('ROWAN_API_KEY') else 'Missing'}\n"
104
+ result += f"• Rowan Package: {'Available' if rowan else 'Not Found'}\n"
105
+ result += f"• Log Level: {logger.level}\n"
106
+ result += f"• Python Version: {sys.version.split()[0]}\n\n"
107
+
108
+ # Available tools count
109
+ result += f"**Available Tools:** 20+ specialized computational chemistry tools\n"
110
+ result += f"• Core Calculations: 4 tools (electronic_properties, multistage_opt, etc.)\n"
111
+ result += f"• Molecular Analysis: 3 tools (conformers, tautomers, descriptors)\n"
112
+ result += f"• Chemical Properties: 4 tools (pka, redox_potential, bde, solubility)\n"
113
+ result += f"• Advanced Analysis: 6 tools (scan, fukui, spin_states, irc, md, etc.)\n"
114
+ result += f"• Drug Discovery: 2 tools (admet, docking)\n"
115
+ result += f"• Management Tools: 5 tools (workflow, folder, system, etc.)\n\n"
116
+
117
+ # Quick status check
118
+ try:
119
+ # Try a simple API call to test connectivity
120
+ recent_workflows = rowan.Workflow.list(size=1)
121
+ total_workflows = len(recent_workflows.get('workflows', []))
122
+ result += f"**API Connectivity:** Connected\n"
123
+ result += f"**Workflow Check:** {total_workflows} workflows accessible\n\n"
124
+ except Exception as e:
125
+ result += f"**API Connectivity:** Error: {str(e)[:100]}...\n\n"
126
+
127
+ result += f"**Usage:**\n"
128
+ result += f"• Use rowan_system_management(action='help') for tool descriptions\n"
129
+ result += f"• Use rowan_workflow_management(action='list') to see your workflows\n"
130
+ result += f"• Note: Folder management currently has API issues - use workflow organization instead\n"
131
+
132
+ return result
133
+
134
+ elif action == "server_status":
135
+ result = "**Rowan MCP Server Health Check**\n\n"
136
+
137
+ # Test API connectivity with workflow endpoint (the main one)
138
+ status_checks = []
139
+
140
+ # Test workflow list
141
+ try:
142
+ workflows = rowan.Workflow.list(size=1)
143
+ workflow_count = len(workflows.get('workflows', []))
144
+ status_checks.append(("Workflow API", "OK", f"{workflow_count} workflows accessible"))
145
+ except Exception as e:
146
+ status_checks.append(("Workflow API", "Error", str(e)[:50]))
147
+
148
+ # Test workflow retrieve (if we have any workflows)
149
+ try:
150
+ workflows = rowan.Workflow.list(size=1)
151
+ if workflows.get('workflows'):
152
+ first_workflow_uuid = workflows['workflows'][0]['uuid']
153
+ rowan.Workflow.retrieve(uuid=first_workflow_uuid)
154
+ status_checks.append(("Workflow Retrieve", "OK", "Can access workflow details"))
155
+ else:
156
+ status_checks.append(("Workflow Retrieve", "Skipped", "No workflows to test"))
157
+ except Exception as e:
158
+ status_checks.append(("Workflow Retrieve", "Error", str(e)[:50]))
159
+
160
+ # Display results
161
+ for check_name, status, details in status_checks:
162
+ result += f"**{check_name}:** {status}\n"
163
+ result += f" Details: {details}\n\n"
164
+
165
+ # Overall status
166
+ all_ok = all("OK" in check[1] or "Skipped" in check[1] for check in status_checks)
167
+ result += f"**Overall Status:** {'All Systems Operational' if all_ok else 'Some Issues Detected'}\n\n"
168
+
169
+ if not all_ok:
170
+ result += f"**Troubleshooting:**\n"
171
+ result += f"• Check your ROWAN_API_KEY environment variable\n"
172
+ result += f"• Verify internet connectivity\n"
173
+ result += f"• Check Rowan service status at labs.rowansci.com\n"
174
+
175
+ return result
176
+
177
+ elif action == "set_log_level":
178
+ if not log_level:
179
+ return "Error: 'log_level' is required for set_log_level action"
180
+
181
+ valid_levels = ["DEBUG", "INFO", "WARNING", "ERROR"]
182
+ log_level = log_level.upper()
183
+
184
+ if log_level not in valid_levels:
185
+ return f"Invalid log level. Use one of: {', '.join(valid_levels)}"
186
+
187
+ logger.setLevel(getattr(logging, log_level))
188
+ logger.info(f"Log level changed to: {log_level}")
189
+
190
+ return f"Log level set to {log_level}"
191
+
192
+ elif action == "job_redirect":
193
+ if not job_uuid:
194
+ return "Error: 'job_uuid' is required for job_redirect action"
195
+
196
+ # Try to treat the job_uuid as a workflow_uuid and retrieve results directly
197
+ try:
198
+ workflow = rowan.Workflow.retrieve(uuid=job_uuid)
199
+
200
+ # Get status and interpret it
201
+ status = workflow.get('object_status', 'Unknown')
202
+ status_names = {
203
+ 0: "Queued",
204
+ 1: "Running",
205
+ 2: "Completed",
206
+ 3: "Failed",
207
+ 4: "Stopped",
208
+ 5: "Awaiting Queue"
209
+ }
210
+ status_name = status_names.get(status, f"Unknown ({status})")
211
+
212
+ formatted = f"**Found Workflow {job_uuid}:**\n\n"
213
+ formatted += f"Name: {workflow.get('name', 'N/A')}\n"
214
+ formatted += f"Type: {workflow.get('object_type', 'N/A')}\n"
215
+ formatted += f"Status: {status_name} ({status})\n"
216
+ formatted += f"Created: {workflow.get('created_at', 'N/A')}\n"
217
+ formatted += f"Elapsed: {workflow.get('elapsed', 0):.2f}s\n\n"
218
+
219
+ if status == 2: # Completed
220
+ formatted += f"**Getting Results...**\n\n"
221
+
222
+ # Try to retrieve calculation results
223
+ try:
224
+ calc_result = rowan.Calculation.retrieve(uuid=job_uuid)
225
+
226
+ # Extract workflow type to provide specific result formatting
227
+ workflow_type = workflow.get('object_type', '')
228
+
229
+ if workflow_type == 'electronic_properties':
230
+ formatted += f"**Electronic Properties Results:**\n\n"
231
+
232
+ # Extract key electronic properties from the result
233
+ object_data = calc_result.get('object_data', {})
234
+
235
+ # Molecular orbital energies (HOMO/LUMO)
236
+ if 'molecular_orbitals' in object_data:
237
+ mo_data = object_data['molecular_orbitals']
238
+ if isinstance(mo_data, dict) and 'energies' in mo_data:
239
+ energies = mo_data['energies']
240
+ if isinstance(energies, list) and len(energies) > 0:
241
+ # Find HOMO/LUMO
242
+ occupations = mo_data.get('occupations', [])
243
+ if occupations:
244
+ homo_idx = None
245
+ lumo_idx = None
246
+ for i, occ in enumerate(occupations):
247
+ if occ > 0.5: # Occupied
248
+ homo_idx = i
249
+ elif occ < 0.5 and lumo_idx is None: # First unoccupied
250
+ lumo_idx = i
251
+ break
252
+
253
+ if homo_idx is not None and lumo_idx is not None:
254
+ homo_energy = energies[homo_idx]
255
+ lumo_energy = energies[lumo_idx]
256
+ gap = lumo_energy - homo_energy
257
+
258
+ formatted += f"• HOMO Energy: {homo_energy:.4f} hartree ({homo_energy * 27.2114:.2f} eV)\n"
259
+ formatted += f"• LUMO Energy: {lumo_energy:.4f} hartree ({lumo_energy * 27.2114:.2f} eV)\n"
260
+ formatted += f"• HOMO-LUMO Gap: {gap:.4f} hartree ({gap * 27.2114:.2f} eV)\n\n"
261
+
262
+ # Dipole moment
263
+ if 'dipole' in object_data:
264
+ dipole = object_data['dipole']
265
+ if isinstance(dipole, dict) and 'magnitude' in dipole:
266
+ formatted += f"**Dipole Moment:** {dipole['magnitude']:.4f} Debye\n\n"
267
+ elif isinstance(dipole, (int, float)):
268
+ formatted += f"**Dipole Moment:** {dipole:.4f} Debye\n\n"
269
+
270
+ # If no specific electronic properties found, show available keys
271
+ if not any(key in object_data for key in ['molecular_orbitals', 'dipole']):
272
+ if object_data:
273
+ formatted += f"**Available Properties:** {', '.join(object_data.keys())}\n\n"
274
+ else:
275
+ formatted += f"**No electronic properties data found in results**\n\n"
276
+
277
+ else:
278
+ # For other workflow types, show general calculation results
279
+ formatted += f"**{workflow_type.replace('_', ' ').title()} Results:**\n\n"
280
+
281
+ object_data = calc_result.get('object_data', {})
282
+ if object_data:
283
+ # Show first few key-value pairs
284
+ count = 0
285
+ for key, value in object_data.items():
286
+ if count >= 5: # Limit to first 5 items for job_redirect
287
+ formatted += f" ... and {len(object_data) - 5} more properties\n"
288
+ break
289
+
290
+ # Format the value nicely
291
+ if isinstance(value, (int, float)):
292
+ formatted += f"• **{key}**: {value}\n"
293
+ elif isinstance(value, str):
294
+ formatted += f"• **{key}**: {value[:50]}{'...' if len(value) > 50 else ''}\n"
295
+ elif isinstance(value, list):
296
+ formatted += f"• **{key}**: {len(value)} items\n"
297
+ elif isinstance(value, dict):
298
+ formatted += f"• **{key}**: {len(value)} properties\n"
299
+ else:
300
+ formatted += f"• **{key}**: {type(value).__name__}\n"
301
+ count += 1
302
+ formatted += "\n"
303
+ else:
304
+ formatted += f"**No calculation data found in results**\n\n"
305
+
306
+ except Exception as retrieve_error:
307
+ formatted += f"**Results retrieval failed:** {str(retrieve_error)}\n\n"
308
+
309
+ elif status in [0, 1, 5]: # Still running
310
+ formatted += f"**Workflow is still {status_name.lower()}**\n"
311
+ formatted += f"Check back later for results\n\n"
312
+
313
+ elif status == 3: # Failed
314
+ formatted += f"**Workflow failed**\n"
315
+ formatted += f"Check the workflow parameters and try again\n\n"
316
+
317
+ formatted += f"**For more details:**\n"
318
+ formatted += f"• Use rowan_workflow_management(action='retrieve', workflow_uuid='{job_uuid}') for full workflow details\n"
319
+ formatted += f"• Use rowan_calculation_retrieve('{job_uuid}') for raw calculation data\n"
320
+
321
+ return formatted
322
+
323
+ except Exception as e:
324
+ # If workflow retrieval fails, provide the legacy guidance
325
+ formatted = f"**Could not find workflow {job_uuid}:** {str(e)}\n\n"
326
+ formatted += f"**Important Note:**\n"
327
+ formatted += f"Rowan manages computations through workflows, not individual jobs.\n"
328
+ formatted += f"The job/results concept is legacy from older versions.\n\n"
329
+ formatted += f"**To find your workflow:**\n"
330
+ formatted += f"• Use rowan_workflow_management(action='list') to see all workflows\n"
331
+ formatted += f"• Look for workflows with similar names or recent creation times\n"
332
+ formatted += f"• Use rowan_workflow_management(action='retrieve', workflow_uuid='UUID') to get results\n\n"
333
+ formatted += f"**Migration Guide:**\n"
334
+ formatted += f"• Old: rowan_job_status('{job_uuid}') → New: rowan_workflow_management(action='status', workflow_uuid='UUID')\n"
335
+ formatted += f"• Old: rowan_job_results('{job_uuid}') → New: rowan_workflow_management(action='retrieve', workflow_uuid='UUID')\n"
336
+
337
+ return formatted
338
+
339
+ else:
340
+ return f"Error: Unknown action '{action}'. Available actions: help, server_info, server_status, set_log_level, job_redirect"
341
+
342
+ except Exception as e:
343
+ return f"Error in system {action}: {str(e)}"
344
+
345
+
346
+ def test_rowan_system_management():
347
+ """Test the rowan_system_management function."""
348
+ try:
349
+ # Test the help action
350
+ result = rowan_system_management(action="help")
351
+ print("System management test successful!")
352
+ print(f"Result length: {len(result)} characters")
353
+ print(f"First line: {result.split(chr(10))[0]}")
354
+ return True
355
+ except Exception as e:
356
+ print(f"System management test failed: {e}")
357
+ return False
358
+
359
+
360
+ if __name__ == "__main__":
361
+ test_rowan_system_management()
@@ -0,0 +1,88 @@
1
+ """
2
+ Rowan tautomers function for drug design.
3
+ """
4
+
5
+ import os
6
+ import logging
7
+ import time
8
+ from typing import Optional
9
+
10
+ try:
11
+ import rowan
12
+ except ImportError:
13
+ rowan = None
14
+
15
+ # Setup logging
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+
20
+ def log_rowan_api_call(workflow_type: str, **kwargs):
21
+ """Log Rowan API calls with detailed parameters."""
22
+
23
+ try:
24
+ start_time = time.time()
25
+ result = rowan.compute(workflow_type=workflow_type, **kwargs)
26
+ api_time = time.time() - start_time
27
+
28
+ if isinstance(result, dict) and 'uuid' in result:
29
+ job_status = result.get('status', result.get('object_status', 'Unknown'))
30
+ status_names = {0: "Queued", 1: "Running", 2: "Completed", 3: "Failed", 4: "Stopped", 5: "Awaiting Queue"}
31
+ status_text = status_names.get(job_status, f"Unknown ({job_status})")
32
+
33
+ return result
34
+
35
+ except Exception as e:
36
+ api_time = time.time() - start_time
37
+ raise e
38
+
39
+ def rowan_tautomers(
40
+ name: str,
41
+ molecule: str,
42
+ folder_uuid: Optional[str] = None,
43
+ blocking: bool = True,
44
+ ping_interval: int = 5
45
+ ) -> str:
46
+ """Enumerate and rank tautomers by stability.
47
+
48
+ Finds all possible tautomeric forms and ranks them by relative energy:
49
+ - Prototropic tautomers (keto-enol, etc.)
50
+ - Relative populations at room temperature
51
+ - Dominant tautomeric forms
52
+
53
+ Use this for: Drug design, understanding protonation states, reaction mechanisms
54
+
55
+ Args:
56
+ name: Name for the calculation
57
+ molecule: Molecule SMILES string
58
+ folder_uuid: Optional folder UUID for organization
59
+ blocking: Whether to wait for completion (default: True)
60
+ ping_interval: Check status interval in seconds (default: 5)
61
+
62
+ Returns:
63
+ Tautomer enumeration and ranking results
64
+ """
65
+ result = log_rowan_api_call(
66
+ workflow_type="tautomers",
67
+ name=name,
68
+ molecule=molecule,
69
+ folder_uuid=folder_uuid,
70
+ blocking=blocking,
71
+ ping_interval=ping_interval
72
+ )
73
+ return str(result)
74
+
75
+ def test_rowan_tautomers():
76
+ """Test the rowan_tautomers function."""
77
+ try:
78
+ # Test with a simple molecule (non-blocking to avoid long wait)
79
+ result = rowan_tautomers("test_tautomers", "CCO", blocking=False)
80
+ print(" Tautomers test successful!")
81
+ print(f"Result length: {len(result)} characters")
82
+ return True
83
+ except Exception as e:
84
+ print(f" Tautomers test failed: {e}")
85
+ return False
86
+
87
+ if __name__ == "__main__":
88
+ test_rowan_tautomers()