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,340 @@
1
+ """
2
+ Rowan docking function for MCP tool integration.
3
+ Implements protein-ligand docking following the stjames-public workflow pattern.
4
+ """
5
+
6
+ from typing import Optional, Union, List, Tuple, Dict, Any
7
+ import rowan
8
+ import logging
9
+ import os
10
+ import requests
11
+
12
+ # Set up logging
13
+ logger = logging.getLogger(__name__)
14
+
15
+ # Get API key from environment
16
+ api_key = os.environ.get("ROWAN_API_KEY")
17
+ if api_key:
18
+ rowan.api_key = api_key
19
+ else:
20
+ logger.warning("ROWAN_API_KEY not found in environment")
21
+
22
+ def fetch_pdb_content(pdb_id: str) -> Optional[str]:
23
+ """Fetch PDB content from RCSB PDB database.
24
+
25
+ Args:
26
+ pdb_id: 4-character PDB ID (e.g., "1COX")
27
+
28
+ Returns:
29
+ PDB file content as string, or None if fetch fails
30
+ """
31
+ try:
32
+ url = f"https://files.rcsb.org/download/{pdb_id.upper()}.pdb"
33
+ response = requests.get(url, timeout=30)
34
+ response.raise_for_status()
35
+ return response.text
36
+ except Exception as e:
37
+ logger.error(f"Failed to fetch PDB {pdb_id}: {str(e)}")
38
+ return None
39
+
40
+ def rowan_docking(
41
+ name: str,
42
+ # Ligand specification (following stjames priority)
43
+ molecules: Optional[List[str]] = None,
44
+ smiles: Optional[Union[str, List[str]]] = None,
45
+ # Protein specification
46
+ target: Optional[str] = None,
47
+ target_uuid: Optional[str] = None,
48
+ # Pocket specification
49
+ pocket: Optional[Tuple[Tuple[float, float, float], Tuple[float, float, float]]] = None,
50
+ # Workflow parameters
51
+ do_csearch: bool = True,
52
+ do_optimization: bool = True,
53
+ do_pose_refinement: bool = True,
54
+ conformers: Optional[List[str]] = None,
55
+ # MCP workflow parameters
56
+ folder_uuid: Optional[str] = None,
57
+ blocking: bool = True,
58
+ ping_interval: int = 5
59
+ ) -> str:
60
+ """Perform protein-ligand docking using Rowan's ML workflow.
61
+
62
+ Performs comprehensive protein-ligand docking with conformer generation,
63
+ strain filtering, AutoDock Vina docking, and pose refinement.
64
+
65
+ IMPORTANT: For docking, you need to provide the protein target in one of two ways:
66
+ 1. target_uuid: UUID of a pre-uploaded protein in your Rowan account
67
+ 2. target: Reserved for future use (currently not supported via API)
68
+
69
+ To dock against a known protein like 1COX, you should:
70
+ 1. Upload the PDB file to your Rowan account via the web interface
71
+ 2. Get the protein UUID from your account
72
+ 3. Use that UUID with the target_uuid parameter
73
+
74
+ Args:
75
+ name: Name for the docking calculation
76
+ molecules: List of molecules to dock (SMILES strings)
77
+ smiles: SMILES string(s) of ligands (can be string or list)
78
+ target: Not currently supported - use target_uuid instead
79
+ target_uuid: UUID of pre-uploaded protein target from your Rowan account
80
+ pocket: Tuple of (center, size) where each is (x, y, z) coordinates
81
+ Example: ((10.0, 15.0, 20.0), (20.0, 20.0, 20.0))
82
+ do_csearch: Whether to perform conformer search (default: True)
83
+ do_optimization: Whether to optimize starting structures (default: True)
84
+ do_pose_refinement: Whether to optimize non-rotatable bonds in output poses (default: True)
85
+ conformers: List of pre-optimized conformer UUIDs (optional)
86
+ folder_uuid: Optional folder UUID to organize the calculation
87
+ blocking: Whether to wait for completion (default: True)
88
+ ping_interval: Interval in seconds to check calculation status
89
+
90
+ Returns:
91
+ Formatted string with workflow results or submission confirmation
92
+
93
+ Examples:
94
+ # Using pre-uploaded protein
95
+ rowan_docking(
96
+ name="aspirin_docking",
97
+ smiles="CC(=O)Oc1ccccc1C(=O)O",
98
+ target_uuid="your-protein-uuid-here",
99
+ pocket=((10.0, 15.0, 20.0), (20.0, 20.0, 20.0))
100
+ )
101
+
102
+ # Multiple molecules
103
+ rowan_docking(
104
+ name="multi_ligand_docking",
105
+ molecules=["CCO", "CC(C)O", "CCCO"],
106
+ target_uuid="your-protein-uuid-here",
107
+ pocket=((0.0, 0.0, 0.0), (25.0, 25.0, 25.0))
108
+ )
109
+ """
110
+
111
+ # Validate that we have ligand input
112
+ if molecules is None and smiles is None:
113
+ return "Error: Either 'molecules' or 'smiles' parameter must be provided"
114
+
115
+ # Validate that we have protein input
116
+ if target is None and target_uuid is None:
117
+ return "Error: Either 'target' or 'target_uuid' parameter must be provided"
118
+
119
+ # If target is provided, inform user it's not currently supported
120
+ if target is not None:
121
+ return ("Error: Direct PDB content upload is not currently supported through this API. "
122
+ "Please use one of these alternatives:\n"
123
+ "1. Upload your PDB file through the Rowan web interface at https://labs.rowansci.com\n"
124
+ "2. Get the protein UUID from your account\n"
125
+ "3. Use the 'target_uuid' parameter with that UUID\n\n"
126
+ "For known proteins like 1COX, 2COX, etc., they may already be available "
127
+ "in the Rowan protein library.")
128
+
129
+ # Validate pocket if provided
130
+ if pocket is not None:
131
+ try:
132
+ center, size = pocket
133
+ if len(center) != 3 or len(size) != 3:
134
+ return "Error: Pocket must be ((x,y,z), (x,y,z)) format"
135
+
136
+ # Validate that size dimensions are positive
137
+ if any(s <= 0 for s in size):
138
+ return "Error: Pocket size dimensions must be positive"
139
+
140
+ except (TypeError, ValueError) as e:
141
+ return f"Error: Invalid pocket format. Expected ((x,y,z), (x,y,z)). Got: {pocket}"
142
+ else:
143
+ # Default pocket if not provided
144
+ pocket = ((0.0, 0.0, 0.0), (20.0, 20.0, 20.0))
145
+ logger.info("Using default pocket: center=(0,0,0), size=(20,20,20)")
146
+
147
+ # Determine initial_molecule for rowan.compute()
148
+ # For docking, the 'molecule' parameter is the ligand to dock
149
+ initial_molecule = None
150
+ ligands_list = None
151
+
152
+ if molecules and len(molecules) > 0:
153
+ initial_molecule = molecules[0]
154
+ ligands_list = molecules
155
+ elif isinstance(smiles, str):
156
+ initial_molecule = smiles
157
+ ligands_list = [smiles]
158
+ elif isinstance(smiles, list) and len(smiles) > 0:
159
+ initial_molecule = smiles[0]
160
+ ligands_list = smiles
161
+ else:
162
+ return "Error: Could not determine initial molecule from inputs"
163
+
164
+ try:
165
+ # Build parameters following stjames DockingWorkflow structure
166
+ compute_params = {
167
+ "name": name,
168
+ "molecule": initial_molecule, # Required by rowan.compute() - this is the ligand
169
+ "workflow_type": "docking",
170
+ "folder_uuid": folder_uuid,
171
+ "blocking": blocking,
172
+ "ping_interval": ping_interval,
173
+ # DockingWorkflow specific parameters
174
+ "do_csearch": do_csearch,
175
+ "do_optimization": do_optimization,
176
+ "do_pose_refinement": do_pose_refinement,
177
+ "pocket": pocket,
178
+ }
179
+
180
+ # For multiple ligands, pass them as 'molecules' parameter
181
+ # This allows docking multiple ligands in one workflow
182
+ if ligands_list and len(ligands_list) > 1:
183
+ compute_params["molecules"] = ligands_list
184
+
185
+ # Add protein specification (only one of these should be present)
186
+ if target_uuid is not None:
187
+ compute_params["target_uuid"] = target_uuid
188
+ # Note: Direct PDB upload is not supported in current implementation
189
+ # Users must pre-upload proteins through the Rowan web interface
190
+
191
+ # Add conformers if provided
192
+ if conformers is not None:
193
+ compute_params["conformers"] = conformers
194
+
195
+ # Submit docking calculation
196
+ result = rowan.compute(**compute_params)
197
+
198
+ # Format results
199
+ uuid = result.get('uuid', 'N/A')
200
+ status = result.get('status', 'unknown')
201
+
202
+ if blocking:
203
+ # Blocking mode - check if successful
204
+ if status == "success":
205
+ formatted = f"✅ Docking calculation '{name}' completed successfully!\n"
206
+ formatted += f"🔖 Workflow UUID: {uuid}\n"
207
+ formatted += f"📊 Status: {status}\n\n"
208
+
209
+ # Extract docking results if available
210
+ object_data = result.get("object_data", {})
211
+ scores = object_data.get("scores", [])
212
+
213
+ if scores:
214
+ formatted += f"🎯 Docking Results: {len(scores)} poses generated\n"
215
+ formatted += f"📈 Best docking score: {scores[0] if scores else 'N/A'}\n"
216
+
217
+ # Show top poses
218
+ formatted += "\nTop poses:\n"
219
+ for i, score in enumerate(scores[:5]):
220
+ formatted += f" {i+1}. Score: {score}\n"
221
+
222
+ if len(scores) > 5:
223
+ formatted += f" ... and {len(scores) - 5} more poses\n"
224
+ else:
225
+ formatted += "📈 Results: Check workflow details for docking data\n"
226
+
227
+ return formatted
228
+ else:
229
+ # Failed calculation
230
+ return f"❌ Docking calculation failed\n🔖 UUID: {uuid}\n📋 Status: {status}\n💬 Check workflow details for more information"
231
+ else:
232
+ # Non-blocking mode
233
+ formatted = f"📋 Docking calculation '{name}' submitted!\n"
234
+ formatted += f"🔖 Workflow UUID: {uuid}\n"
235
+ formatted += f"⏳ Status: Running...\n"
236
+ formatted += f"💡 Use rowan_workflow_management to check status\n\n"
237
+
238
+ formatted += f"Docking Details:\n"
239
+ formatted += f"🧬 Ligand: {initial_molecule}\n"
240
+ formatted += f"🎯 Target: {target_uuid or target[:50] + '...' if target and len(target) > 50 else target}\n"
241
+ formatted += f"📍 Pocket: center={pocket[0]}, size={pocket[1]}\n"
242
+ formatted += f"⚙️ Settings: csearch={do_csearch}, optimize={do_optimization}, refine={do_pose_refinement}\n"
243
+
244
+ if conformers:
245
+ formatted += f"🔬 Pre-optimized conformers: {len(conformers)}\n"
246
+
247
+ return formatted
248
+
249
+ except Exception as e:
250
+ logger.error(f"Error in rowan_docking: {str(e)}")
251
+ return f"❌ Docking calculation failed: {str(e)}"
252
+
253
+ def rowan_docking_pdb_id(
254
+ name: str,
255
+ pdb_id: str,
256
+ # Ligand specification
257
+ molecules: Optional[List[str]] = None,
258
+ smiles: Optional[Union[str, List[str]]] = None,
259
+ # Other parameters same as rowan_docking
260
+ pocket: Optional[Tuple[Tuple[float, float, float], Tuple[float, float, float]]] = None,
261
+ do_csearch: bool = True,
262
+ do_optimization: bool = True,
263
+ do_pose_refinement: bool = True,
264
+ conformers: Optional[List[str]] = None,
265
+ folder_uuid: Optional[str] = None,
266
+ blocking: bool = True,
267
+ ping_interval: int = 5
268
+ ) -> str:
269
+ """Helper function that explains how to dock with a PDB ID.
270
+
271
+ NOTE: Direct PDB upload is not currently supported through the API.
272
+ This function provides guidance on how to dock against known proteins.
273
+
274
+ Args:
275
+ name: Name for the docking calculation
276
+ pdb_id: 4-character PDB ID (e.g., "1COX", "2COX", "6COX")
277
+ (other parameters same as rowan_docking)
278
+
279
+ Returns:
280
+ Instructions on how to perform docking with the given PDB
281
+ """
282
+ # Check if we can fetch the PDB to verify it exists
283
+ logger.info(f"Checking if PDB {pdb_id} exists...")
284
+ pdb_content = fetch_pdb_content(pdb_id)
285
+
286
+ if pdb_content is None:
287
+ return (f"Error: PDB ID '{pdb_id}' not found in RCSB database. "
288
+ f"Please check that the PDB ID is valid.")
289
+
290
+ # Provide instructions for the user
291
+ ligand_param = ""
292
+ if molecules:
293
+ ligand_param = f" molecules={molecules},\n"
294
+ elif smiles:
295
+ if isinstance(smiles, str):
296
+ ligand_param = f' smiles="{smiles}",\n'
297
+ else:
298
+ ligand_param = f" smiles={smiles},\n"
299
+
300
+ return (f"✅ PDB {pdb_id} found in RCSB database!\n\n"
301
+ f"To perform docking with this protein:\n\n"
302
+ f"1. Go to https://labs.rowansci.com\n"
303
+ f"2. Upload the PDB file for {pdb_id}\n"
304
+ f" - You can download it from: https://files.rcsb.org/download/{pdb_id.upper()}.pdb\n"
305
+ f"3. Once uploaded, find the protein UUID in your account\n"
306
+ f"4. Use the docking function with target_uuid:\n\n"
307
+ f"```python\n"
308
+ f"rowan_docking(\n"
309
+ f" name=\"{name}\",\n"
310
+ f"{ligand_param}"
311
+ f" target_uuid=\"your-protein-uuid-here\",\n"
312
+ f" pocket={pocket},\n"
313
+ f" do_csearch={do_csearch},\n"
314
+ f" do_optimization={do_optimization},\n"
315
+ f" do_pose_refinement={do_pose_refinement}\n"
316
+ f")\n"
317
+ f"```\n\n"
318
+ f"Note: Some common proteins may already be available in the Rowan protein library.\n"
319
+ f"Check your account for pre-uploaded structures.")
320
+
321
+ def test_rowan_docking():
322
+ """Test the rowan_docking function."""
323
+ try:
324
+ # Test with minimal parameters
325
+ result = rowan_docking(
326
+ name="test_docking",
327
+ smiles="CCO", # Ethanol
328
+ target_uuid="test-protein-uuid", # Would need real UUID
329
+ pocket=((0.0, 0.0, 0.0), (20.0, 20.0, 20.0)),
330
+ blocking=False
331
+ )
332
+ print("✅ Docking test result:")
333
+ print(result)
334
+ return True
335
+ except Exception as e:
336
+ print(f"❌ Docking test failed: {e}")
337
+ return False
338
+
339
+ if __name__ == "__main__":
340
+ test_rowan_docking()
@@ -0,0 +1,174 @@
1
+ """
2
+ Enhanced docking function that accepts PDB IDs directly
3
+ """
4
+
5
+ from typing import Optional, Union, List, Tuple
6
+ import logging
7
+ from .docking import rowan_docking
8
+ from .pdb_handler import handle_pdb_input
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ def rowan_docking_enhanced(
13
+ name: str,
14
+ # Ligand specification
15
+ smiles: Optional[Union[str, List[str]]] = None,
16
+ molecules: Optional[List[str]] = None,
17
+ # Protein specification - NEW: accepts PDB ID or UUID
18
+ target: Optional[str] = None, # Can be PDB ID (e.g., "4Z18") or UUID
19
+ # Pocket specification
20
+ pocket: Optional[Tuple[Tuple[float, float, float], Tuple[float, float, float]]] = None,
21
+ # Workflow parameters
22
+ do_csearch: bool = True,
23
+ do_optimization: bool = True,
24
+ do_pose_refinement: bool = True,
25
+ conformers: Optional[List[str]] = None,
26
+ # MCP workflow parameters
27
+ folder_uuid: Optional[str] = None,
28
+ blocking: bool = True,
29
+ ping_interval: int = 5
30
+ ) -> str:
31
+ """
32
+ Enhanced docking function that accepts PDB IDs directly.
33
+
34
+ This function intelligently handles protein input:
35
+ - If target looks like a UUID (36 chars with dashes), uses it directly
36
+ - If target looks like a PDB ID (e.g., "4Z18"), attempts to fetch and use it
37
+ - Provides clear instructions when manual upload is needed
38
+
39
+ Args:
40
+ name: Name for the docking calculation
41
+ smiles: SMILES string(s) of ligands
42
+ molecules: Alternative to smiles - list of molecules
43
+ target: PDB ID (e.g., "4Z18") or protein UUID
44
+ pocket: Pocket specification ((center), (size))
45
+ (other parameters same as rowan_docking)
46
+
47
+ Returns:
48
+ Docking results or clear instructions on what to do
49
+
50
+ Examples:
51
+ # Using PDB ID directly
52
+ rowan_docking_enhanced(
53
+ name="my_docking",
54
+ smiles="CC(=O)Oc1ccccc1C(=O)O", # Aspirin
55
+ target="4Z18", # PDB ID
56
+ pocket=((10.0, 15.0, 20.0), (20.0, 20.0, 20.0))
57
+ )
58
+
59
+ # Using UUID (if you already have one)
60
+ rowan_docking_enhanced(
61
+ name="my_docking",
62
+ smiles="CC(=O)Oc1ccccc1C(=O)O",
63
+ target="abc123de-f456-7890-ghij-klmnopqrstuv", # UUID
64
+ pocket=((10.0, 15.0, 20.0), (20.0, 20.0, 20.0))
65
+ )
66
+ """
67
+
68
+ # Validate ligand input
69
+ if smiles is None and molecules is None:
70
+ return "Error: Either 'smiles' or 'molecules' must be provided"
71
+
72
+ # Validate protein input
73
+ if target is None:
74
+ return "Error: 'target' parameter is required (PDB ID or protein UUID)"
75
+
76
+ # Handle the target input
77
+ logger.info(f"Processing target input: {target}")
78
+ pdb_result = handle_pdb_input(target, folder_uuid)
79
+
80
+ # If we successfully got a UUID, proceed with docking
81
+ if pdb_result['success'] and pdb_result['uuid']:
82
+ logger.info(f"Successfully resolved target to UUID: {pdb_result['uuid']}")
83
+
84
+ # Call the main docking function with the UUID
85
+ result = rowan_docking(
86
+ name=name,
87
+ smiles=smiles,
88
+ molecules=molecules,
89
+ target_uuid=pdb_result['uuid'],
90
+ pocket=pocket,
91
+ do_csearch=do_csearch,
92
+ do_optimization=do_optimization,
93
+ do_pose_refinement=do_pose_refinement,
94
+ conformers=conformers,
95
+ folder_uuid=folder_uuid,
96
+ blocking=blocking,
97
+ ping_interval=ping_interval
98
+ )
99
+
100
+ # Add context about PDB if it was used
101
+ if 'pdb_id' in pdb_result:
102
+ result = f"🧬 Using PDB {pdb_result['pdb_id']}:\n\n{result}"
103
+
104
+ return result
105
+
106
+ # If we couldn't get a UUID, provide helpful information
107
+ else:
108
+ pdb_id = pdb_result.get('pdb_id', target)
109
+
110
+ # Format a helpful response
111
+ response = f"🔍 PDB Input Detected: {pdb_id}\n\n"
112
+ response += f"❌ {pdb_result['message']}\n\n"
113
+
114
+ if pdb_result.get('instructions'):
115
+ response += pdb_result['instructions']
116
+ else:
117
+ response += f"""
118
+ 📋 To dock with PDB {pdb_id}:
119
+
120
+ 1. **Download the PDB file**:
121
+ https://files.rcsb.org/download/{pdb_id}.pdb
122
+
123
+ 2. **Upload to Rowan**:
124
+ https://labs.rowansci.com
125
+
126
+ 3. **Get the protein UUID** from your account
127
+
128
+ 4. **Run docking with the UUID**:
129
+ ```python
130
+ rowan_docking_enhanced(
131
+ name="{name}",
132
+ smiles={f'"{smiles}"' if isinstance(smiles, str) else smiles},
133
+ target="your-protein-uuid-here",
134
+ pocket={pocket}
135
+ )
136
+ ```
137
+
138
+ Note: Direct PDB upload through the API is not currently supported.
139
+ Proteins must be uploaded through the web interface.
140
+ """
141
+
142
+ return response
143
+
144
+ def test_enhanced_docking():
145
+ """Test the enhanced docking function"""
146
+
147
+ print("Testing enhanced docking with PDB ID...")
148
+
149
+ # Test with PDB ID
150
+ result = rowan_docking_enhanced(
151
+ name="test_4z18_docking",
152
+ smiles="CC(=O)Oc1ccccc1C(=O)O", # Aspirin
153
+ target="4Z18", # PDB ID
154
+ pocket=((10.0, 15.0, 20.0), (20.0, 20.0, 20.0)),
155
+ blocking=False
156
+ )
157
+
158
+ print(result)
159
+
160
+ # Test with UUID
161
+ print("\n" + "="*60 + "\n")
162
+ print("Testing with UUID...")
163
+
164
+ result = rowan_docking_enhanced(
165
+ name="test_uuid_docking",
166
+ smiles="CC(=O)Oc1ccccc1C(=O)O",
167
+ target="12345678-1234-5678-1234-567812345678", # Fake UUID format
168
+ blocking=False
169
+ )
170
+
171
+ print(result)
172
+
173
+ if __name__ == "__main__":
174
+ test_enhanced_docking()