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,382 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Rowan v2 API: Workflow Management Tools
|
|
3
|
+
MCP tools for interacting with Workflow objects returned by v2 API submission functions.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Dict, Any, List, Annotated
|
|
7
|
+
import rowan
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def workflow_get_status(
|
|
11
|
+
workflow_uuid: Annotated[str, "UUID of the workflow to check status"]
|
|
12
|
+
) -> Dict[str, Any]:
|
|
13
|
+
"""Get the current status of a workflow.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
workflow_uuid: UUID of the workflow to check status
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
Dictionary with status information
|
|
20
|
+
"""
|
|
21
|
+
workflow = rowan.retrieve_workflow(workflow_uuid)
|
|
22
|
+
status = workflow.get_status()
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
"uuid": workflow_uuid,
|
|
26
|
+
"status": status,
|
|
27
|
+
"name": workflow.name,
|
|
28
|
+
"is_finished": workflow.is_finished()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def workflow_wait_for_result(
|
|
33
|
+
workflow_uuid: Annotated[str, "UUID of the workflow to wait for completion"],
|
|
34
|
+
poll_interval: Annotated[int, "Seconds between status checks while waiting"] = 5
|
|
35
|
+
) -> Dict[str, Any]:
|
|
36
|
+
"""Wait for a workflow to complete and return the result.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
workflow_uuid: UUID of the workflow to wait for completion
|
|
40
|
+
poll_interval: Seconds between status checks while waiting
|
|
41
|
+
|
|
42
|
+
Essential for chaining dependent workflows where subsequent calculations
|
|
43
|
+
require results from previous ones. Blocks execution until the workflow
|
|
44
|
+
completes, then returns the full results.
|
|
45
|
+
|
|
46
|
+
Common use cases:
|
|
47
|
+
- Conformer search → Redox potential for each conformer
|
|
48
|
+
- Optimization → Frequency calculation on optimized geometry
|
|
49
|
+
- Multiple sequential optimizations with different methods
|
|
50
|
+
- Any workflow chain where results feed into next calculation
|
|
51
|
+
|
|
52
|
+
Example workflow chain:
|
|
53
|
+
1. Submit conformer search
|
|
54
|
+
2. Wait for conformer search to complete (using this function)
|
|
55
|
+
3. Extract conformer geometries from results
|
|
56
|
+
4. Submit new workflows using those geometries
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Dictionary containing the completed workflow data including results
|
|
60
|
+
needed for dependent workflows (e.g., conformer_uuids, optimized
|
|
61
|
+
geometries, calculation_uuids)
|
|
62
|
+
"""
|
|
63
|
+
workflow = rowan.retrieve_workflow(workflow_uuid)
|
|
64
|
+
|
|
65
|
+
# Use the built-in wait_for_result method
|
|
66
|
+
workflow.wait_for_result(poll_interval=poll_interval)
|
|
67
|
+
|
|
68
|
+
# Return complete workflow data
|
|
69
|
+
return {
|
|
70
|
+
"uuid": workflow.uuid,
|
|
71
|
+
"status": workflow.status,
|
|
72
|
+
"name": workflow.name,
|
|
73
|
+
"created_at": workflow.created_at,
|
|
74
|
+
"updated_at": workflow.updated_at,
|
|
75
|
+
"completed_at": workflow.completed_at,
|
|
76
|
+
"parent_uuid": workflow.parent_uuid,
|
|
77
|
+
"workflow_type": workflow.workflow_type,
|
|
78
|
+
"data": workflow.data,
|
|
79
|
+
"credits_charged": workflow.credits_charged,
|
|
80
|
+
"elapsed": workflow.elapsed
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def workflow_stop(
|
|
85
|
+
workflow_uuid: Annotated[str, "UUID of the running workflow to stop"]
|
|
86
|
+
) -> Dict[str, str]:
|
|
87
|
+
"""Stop a running workflow.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
workflow_uuid: UUID of the running workflow to stop
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Dictionary with confirmation message
|
|
94
|
+
"""
|
|
95
|
+
workflow = rowan.retrieve_workflow(workflow_uuid)
|
|
96
|
+
workflow.stop()
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
"message": f"Workflow {workflow_uuid} stop request submitted",
|
|
100
|
+
"uuid": workflow_uuid
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def workflow_delete(
|
|
105
|
+
workflow_uuid: Annotated[str, "UUID of the workflow to permanently delete"]
|
|
106
|
+
) -> Dict[str, str]:
|
|
107
|
+
"""Delete a workflow.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
workflow_uuid: UUID of the workflow to permanently delete
|
|
111
|
+
|
|
112
|
+
This permanently removes the workflow and its results from the database.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Dictionary with confirmation message
|
|
116
|
+
"""
|
|
117
|
+
workflow = rowan.retrieve_workflow(workflow_uuid)
|
|
118
|
+
workflow.delete()
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
"message": f"Workflow {workflow_uuid} deleted successfully",
|
|
122
|
+
"uuid": workflow_uuid
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def workflow_fetch_latest(
|
|
127
|
+
workflow_uuid: Annotated[str, "UUID of the workflow to fetch latest data"],
|
|
128
|
+
in_place: Annotated[bool, "Whether to update the workflow object in place"] = False
|
|
129
|
+
) -> Dict[str, Any]:
|
|
130
|
+
"""Fetch the latest workflow data from the database.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
workflow_uuid: UUID of the workflow to fetch latest data
|
|
134
|
+
in_place: Whether to update the workflow object in place
|
|
135
|
+
|
|
136
|
+
Updates the workflow object with the most recent status and results.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
Dictionary containing the updated workflow data including status and results
|
|
140
|
+
"""
|
|
141
|
+
workflow = rowan.retrieve_workflow(workflow_uuid)
|
|
142
|
+
|
|
143
|
+
# Fetch latest updates
|
|
144
|
+
workflow.fetch_latest(in_place=in_place)
|
|
145
|
+
|
|
146
|
+
# Return workflow data as dict
|
|
147
|
+
return {
|
|
148
|
+
"uuid": workflow.uuid,
|
|
149
|
+
"status": workflow.status,
|
|
150
|
+
"name": workflow.name,
|
|
151
|
+
"created_at": workflow.created_at,
|
|
152
|
+
"updated_at": workflow.updated_at,
|
|
153
|
+
"started_at": workflow.started_at,
|
|
154
|
+
"completed_at": workflow.completed_at,
|
|
155
|
+
"parent_uuid": workflow.parent_uuid,
|
|
156
|
+
"workflow_type": workflow.workflow_type,
|
|
157
|
+
"data": workflow.data,
|
|
158
|
+
"notes": workflow.notes,
|
|
159
|
+
"starred": workflow.starred,
|
|
160
|
+
"public": workflow.public,
|
|
161
|
+
"credits_charged": workflow.credits_charged,
|
|
162
|
+
"elapsed": workflow.elapsed
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def retrieve_workflow(
|
|
167
|
+
uuid: Annotated[str, "UUID of the workflow to retrieve"]
|
|
168
|
+
) -> Dict[str, Any]:
|
|
169
|
+
"""Retrieve a workflow from the API.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
uuid: UUID of the workflow to retrieve
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
Dictionary containing the complete workflow data
|
|
176
|
+
|
|
177
|
+
Raises:
|
|
178
|
+
HTTPError: If the API request fails
|
|
179
|
+
"""
|
|
180
|
+
workflow = rowan.retrieve_workflow(uuid)
|
|
181
|
+
|
|
182
|
+
# Convert workflow object to dict
|
|
183
|
+
return {
|
|
184
|
+
"uuid": workflow.uuid,
|
|
185
|
+
"status": workflow.status,
|
|
186
|
+
"name": workflow.name,
|
|
187
|
+
"created_at": workflow.created_at,
|
|
188
|
+
"updated_at": workflow.updated_at,
|
|
189
|
+
"started_at": workflow.started_at,
|
|
190
|
+
"completed_at": workflow.completed_at,
|
|
191
|
+
"parent_uuid": workflow.parent_uuid,
|
|
192
|
+
"workflow_type": workflow.workflow_type,
|
|
193
|
+
"data": workflow.data,
|
|
194
|
+
"notes": workflow.notes,
|
|
195
|
+
"starred": workflow.starred,
|
|
196
|
+
"public": workflow.public,
|
|
197
|
+
"email_when_complete": workflow.email_when_complete,
|
|
198
|
+
"max_credits": workflow.max_credits,
|
|
199
|
+
"elapsed": workflow.elapsed,
|
|
200
|
+
"credits_charged": workflow.credits_charged
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def list_workflows(
|
|
205
|
+
parent_uuid: Annotated[str, "UUID of parent folder to filter by. Empty string for all folders"] = "",
|
|
206
|
+
name_contains: Annotated[str, "Substring to search for in workflow names. Empty string for all names"] = "",
|
|
207
|
+
public: Annotated[str, "Filter by public status ('true'/'false'). Empty string for both"] = "",
|
|
208
|
+
starred: Annotated[str, "Filter by starred status ('true'/'false'). Empty string for both"] = "",
|
|
209
|
+
status: Annotated[str, "Filter by workflow status code. Empty string for all statuses"] = "",
|
|
210
|
+
workflow_type: Annotated[str, "Filter by workflow type (e.g., 'conformer_search', 'pka'). Empty string for all types"] = "",
|
|
211
|
+
page: Annotated[int, "Page number for pagination (0-indexed)"] = 0,
|
|
212
|
+
size: Annotated[int, "Number of workflows per page"] = 10
|
|
213
|
+
):
|
|
214
|
+
"""List workflows subject to the specified criteria.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
parent_uuid: UUID of parent folder to filter by. Empty string for all folders
|
|
218
|
+
name_contains: Substring to search for in workflow names. Empty string for all names
|
|
219
|
+
public: Filter by public status ("true"/"false"). Empty string for both
|
|
220
|
+
starred: Filter by starred status ("true"/"false"). Empty string for both
|
|
221
|
+
status: Filter by workflow status code. Empty string for all statuses
|
|
222
|
+
workflow_type: Filter by workflow type (e.g., 'conformer_search', 'pka'). Empty string for all types
|
|
223
|
+
page: Page number for pagination (0-indexed)
|
|
224
|
+
size: Number of workflows per page
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
List of workflow dictionaries that match the search criteria
|
|
228
|
+
|
|
229
|
+
Raises:
|
|
230
|
+
HTTPError: If the request to the API fails
|
|
231
|
+
"""
|
|
232
|
+
# Use direct API call to avoid Workflow validation issues
|
|
233
|
+
with rowan.api_client() as client:
|
|
234
|
+
params = {
|
|
235
|
+
"page": page,
|
|
236
|
+
"size": size
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
# Add non-empty filters
|
|
240
|
+
if parent_uuid:
|
|
241
|
+
params["parent_uuid"] = parent_uuid
|
|
242
|
+
if name_contains:
|
|
243
|
+
params["name_contains"] = name_contains
|
|
244
|
+
if public:
|
|
245
|
+
params["public"] = public.lower() == "true"
|
|
246
|
+
if starred:
|
|
247
|
+
params["starred"] = starred.lower() == "true"
|
|
248
|
+
if status:
|
|
249
|
+
params["object_status"] = int(status)
|
|
250
|
+
if workflow_type:
|
|
251
|
+
params["object_type"] = workflow_type
|
|
252
|
+
|
|
253
|
+
response = client.get("/workflow", params=params)
|
|
254
|
+
response.raise_for_status()
|
|
255
|
+
|
|
256
|
+
data = response.json()
|
|
257
|
+
# Extract workflows from the paginated response
|
|
258
|
+
return data.get("workflows", [])
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def retrieve_calculation_molecules(
|
|
262
|
+
uuid: Annotated[str, "UUID of the calculation to retrieve molecules from"]
|
|
263
|
+
) -> List[Dict[str, Any]]:
|
|
264
|
+
"""Retrieve a list of molecules from a calculation.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
uuid: UUID of the calculation to retrieve molecules from
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
List of dictionaries representing the molecules in the calculation
|
|
271
|
+
|
|
272
|
+
Raises:
|
|
273
|
+
HTTPError: If the API request fails
|
|
274
|
+
"""
|
|
275
|
+
molecules = rowan.retrieve_calculation_molecules(uuid)
|
|
276
|
+
|
|
277
|
+
# Convert molecules to list of dicts
|
|
278
|
+
result = []
|
|
279
|
+
for mol in molecules:
|
|
280
|
+
mol_dict = {
|
|
281
|
+
"smiles": mol.get("smiles"),
|
|
282
|
+
"name": mol.get("name"),
|
|
283
|
+
"charge": mol.get("charge"),
|
|
284
|
+
"multiplicity": mol.get("multiplicity"),
|
|
285
|
+
"energy": mol.get("energy"),
|
|
286
|
+
"coordinates": mol.get("coordinates"),
|
|
287
|
+
"properties": mol.get("properties", {})
|
|
288
|
+
}
|
|
289
|
+
# Remove None values
|
|
290
|
+
mol_dict = {k: v for k, v in mol_dict.items() if v is not None}
|
|
291
|
+
result.append(mol_dict)
|
|
292
|
+
|
|
293
|
+
return result
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def workflow_update(
|
|
297
|
+
workflow_uuid: Annotated[str, "UUID of the workflow to update"],
|
|
298
|
+
name: Annotated[str, "New name for the workflow. Empty string to keep current name"] = "",
|
|
299
|
+
notes: Annotated[str, "New notes/description for the workflow. Empty string to keep current notes"] = "",
|
|
300
|
+
starred: Annotated[str, "Set starred status ('true'/'false'). Empty string to keep current status"] = "",
|
|
301
|
+
public: Annotated[str, "Set public visibility ('true'/'false'). Empty string to keep current status"] = ""
|
|
302
|
+
) -> Dict[str, Any]:
|
|
303
|
+
"""Update workflow details.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
workflow_uuid: UUID of the workflow to update
|
|
307
|
+
name: New name for the workflow. Empty string to keep current name
|
|
308
|
+
notes: New notes/description for the workflow. Empty string to keep current notes
|
|
309
|
+
starred: Set starred status ("true"/"false"). Empty string to keep current status
|
|
310
|
+
public: Set public visibility ("true"/"false"). Empty string to keep current status
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
Dictionary with updated workflow information
|
|
314
|
+
"""
|
|
315
|
+
workflow = rowan.retrieve_workflow(workflow_uuid)
|
|
316
|
+
|
|
317
|
+
# Parse string boolean inputs
|
|
318
|
+
parsed_starred = None
|
|
319
|
+
if starred:
|
|
320
|
+
parsed_starred = starred.lower() == "true"
|
|
321
|
+
|
|
322
|
+
parsed_public = None
|
|
323
|
+
if public:
|
|
324
|
+
parsed_public = public.lower() == "true"
|
|
325
|
+
|
|
326
|
+
# Update the workflow
|
|
327
|
+
workflow.update(
|
|
328
|
+
name=name if name else None,
|
|
329
|
+
notes=notes if notes else None,
|
|
330
|
+
starred=parsed_starred,
|
|
331
|
+
public=parsed_public
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
return {
|
|
335
|
+
"uuid": workflow.uuid,
|
|
336
|
+
"name": workflow.name,
|
|
337
|
+
"notes": workflow.notes,
|
|
338
|
+
"starred": workflow.starred,
|
|
339
|
+
"public": workflow.public,
|
|
340
|
+
"message": "Workflow updated successfully"
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def workflow_is_finished(
|
|
345
|
+
workflow_uuid: Annotated[str, "UUID of the workflow to check completion status"]
|
|
346
|
+
) -> Dict[str, Any]:
|
|
347
|
+
"""Check if a workflow is finished.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
workflow_uuid: UUID of the workflow to check completion status
|
|
351
|
+
|
|
352
|
+
Returns:
|
|
353
|
+
Dictionary with workflow completion status
|
|
354
|
+
"""
|
|
355
|
+
workflow = rowan.retrieve_workflow(workflow_uuid)
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
"uuid": workflow_uuid,
|
|
359
|
+
"is_finished": workflow.is_finished(),
|
|
360
|
+
"status": workflow.status,
|
|
361
|
+
"name": workflow.name
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def workflow_delete_data(
|
|
366
|
+
workflow_uuid: Annotated[str, "UUID of the workflow whose data to delete (keeps workflow record)"]
|
|
367
|
+
) -> Dict[str, str]:
|
|
368
|
+
"""Delete workflow data while keeping the workflow record.
|
|
369
|
+
|
|
370
|
+
Args:
|
|
371
|
+
workflow_uuid: UUID of the workflow whose data to delete (keeps workflow record)
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
Dictionary with confirmation message
|
|
375
|
+
"""
|
|
376
|
+
workflow = rowan.retrieve_workflow(workflow_uuid)
|
|
377
|
+
workflow.delete_data()
|
|
378
|
+
|
|
379
|
+
return {
|
|
380
|
+
"message": f"Data for workflow {workflow_uuid} deleted successfully",
|
|
381
|
+
"uuid": workflow_uuid
|
|
382
|
+
}
|
rowan_mcp/server.py
CHANGED
|
@@ -1,169 +1,134 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Rowan MCP Server Implementation using FastMCP
|
|
3
|
-
|
|
4
|
-
This module implements the Model Context Protocol server for Rowan's
|
|
5
|
-
computational chemistry platform using the FastMCP framework.
|
|
6
|
-
Supports both STDIO and HTTP transports.
|
|
7
3
|
"""
|
|
8
4
|
|
|
9
5
|
import os
|
|
10
6
|
import sys
|
|
11
|
-
import time
|
|
12
|
-
import traceback
|
|
13
|
-
from typing import Any, Dict, List, Optional, Literal, Union
|
|
14
|
-
from enum import Enum
|
|
15
|
-
|
|
16
7
|
from fastmcp import FastMCP
|
|
17
|
-
from pydantic import BaseModel, Field
|
|
18
|
-
from stjames import Molecule
|
|
19
|
-
|
|
20
|
-
# Import functions from functions module
|
|
21
|
-
from .functions.scan import rowan_scan as scan_function
|
|
22
|
-
from .functions.scan_analyzer import rowan_scan_analyzer as scan_analyzer_function
|
|
23
|
-
from .functions.admet import rowan_admet as admet_function
|
|
24
|
-
|
|
25
|
-
from .functions.multistage_opt import rowan_multistage_opt as multistage_opt_function
|
|
26
|
-
from .functions.descriptors import rowan_descriptors as descriptors_function
|
|
27
|
-
from .functions.tautomers import rowan_tautomers as tautomers_function
|
|
28
|
-
|
|
29
|
-
from .functions.redox_potential import rowan_redox_potential as redox_potential_function
|
|
30
|
-
from .functions.conformers import rowan_conformers as conformers_function
|
|
31
|
-
from .functions.electronic_properties import rowan_electronic_properties as electronic_properties_function
|
|
32
|
-
from .functions.fukui import rowan_fukui as fukui_function
|
|
33
|
-
from .functions.spin_states import rowan_spin_states as spin_states_function
|
|
34
|
-
from .functions.solubility import rowan_solubility as solubility_function
|
|
35
|
-
from .functions.molecular_dynamics import rowan_molecular_dynamics as molecular_dynamics_function
|
|
36
|
-
from .functions.irc import rowan_irc as irc_function
|
|
37
|
-
from .functions.docking import rowan_docking as docking_function, rowan_docking_pdb_id as docking_pdb_id_function
|
|
38
|
-
from .functions.docking_enhanced import rowan_docking_enhanced as docking_enhanced_function
|
|
39
|
-
from .functions.workflow_management import rowan_workflow_management as workflow_management_function
|
|
40
|
-
# from .functions.calculation_retrieve import rowan_calculation_retrieve as calculation_retrieve_function
|
|
41
|
-
from .functions.pka import rowan_pka as pka_function
|
|
42
|
-
from .functions.macropka import rowan_macropka as macropka_function
|
|
43
|
-
from .functions.hydrogen_bond_basicity import rowan_hydrogen_bond_basicity as hydrogen_bond_basicity_function
|
|
44
|
-
from .functions.bde import rowan_bde as bde_function
|
|
45
|
-
from .functions.folder_management import rowan_folder_management as folder_management_function
|
|
46
|
-
from .functions.system_management import rowan_system_management as system_management_function
|
|
47
|
-
|
|
48
|
-
# Import molecule lookup from functions
|
|
49
|
-
from .functions.molecule_lookup import rowan_molecule_lookup as molecule_lookup_function
|
|
50
8
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
9
|
+
|
|
10
|
+
# Import v2 API functions
|
|
11
|
+
from .functions_v2.submit_basic_calculation_workflow import submit_basic_calculation_workflow
|
|
12
|
+
from .functions_v2.submit_conformer_search_workflow import submit_conformer_search_workflow
|
|
13
|
+
from .functions_v2.submit_solubility_workflow import submit_solubility_workflow
|
|
14
|
+
from .functions_v2.submit_pka_workflow import submit_pka_workflow
|
|
15
|
+
from .functions_v2.submit_redox_potential_workflow import submit_redox_potential_workflow
|
|
16
|
+
from .functions_v2.submit_fukui_workflow import submit_fukui_workflow
|
|
17
|
+
from .functions_v2.submit_tautomer_search_workflow import submit_tautomer_search_workflow
|
|
18
|
+
from .functions_v2.submit_descriptors_workflow import submit_descriptors_workflow
|
|
19
|
+
from .functions_v2.submit_scan_workflow import submit_scan_workflow
|
|
20
|
+
from .functions_v2.submit_irc_workflow import submit_irc_workflow
|
|
21
|
+
from .functions_v2.submit_protein_cofolding_workflow import submit_protein_cofolding_workflow
|
|
22
|
+
from .functions_v2.submit_docking_workflow import submit_docking_workflow
|
|
23
|
+
from .functions_v2.submit_macropka_workflow import submit_macropka_workflow
|
|
24
|
+
|
|
25
|
+
# Import molecule lookup functions
|
|
26
|
+
from .functions_v2.molecule_lookup import (
|
|
27
|
+
molecule_lookup,
|
|
28
|
+
batch_molecule_lookup,
|
|
29
|
+
validate_smiles
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Import workflow management functions
|
|
33
|
+
from .functions_v2.workflow_management_v2 import (
|
|
34
|
+
workflow_fetch_latest,
|
|
35
|
+
workflow_wait_for_result,
|
|
36
|
+
workflow_get_status,
|
|
37
|
+
workflow_stop,
|
|
38
|
+
workflow_delete,
|
|
39
|
+
retrieve_workflow,
|
|
40
|
+
retrieve_calculation_molecules,
|
|
41
|
+
list_workflows,
|
|
42
|
+
workflow_update,
|
|
43
|
+
workflow_is_finished,
|
|
44
|
+
workflow_delete_data
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Import protein management functions
|
|
48
|
+
from .functions_v2.protein_management import (
|
|
49
|
+
create_protein_from_pdb_id,
|
|
50
|
+
retrieve_protein,
|
|
51
|
+
list_proteins,
|
|
52
|
+
upload_protein,
|
|
53
|
+
delete_protein,
|
|
54
|
+
sanitize_protein
|
|
55
|
+
)
|
|
55
56
|
|
|
56
57
|
try:
|
|
57
58
|
from dotenv import load_dotenv
|
|
58
|
-
load_dotenv()
|
|
59
|
+
load_dotenv()
|
|
59
60
|
except ImportError:
|
|
60
|
-
pass
|
|
61
|
+
pass
|
|
61
62
|
|
|
62
63
|
# Initialize FastMCP server
|
|
63
64
|
mcp = FastMCP("Rowan MCP Server")
|
|
64
65
|
|
|
65
|
-
# Register
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
66
|
+
# Register v2 API tools
|
|
67
|
+
mcp.tool()(submit_basic_calculation_workflow)
|
|
68
|
+
mcp.tool()(submit_conformer_search_workflow)
|
|
69
|
+
mcp.tool()(submit_solubility_workflow)
|
|
70
|
+
mcp.tool()(submit_pka_workflow)
|
|
71
|
+
mcp.tool()(submit_redox_potential_workflow)
|
|
72
|
+
mcp.tool()(submit_fukui_workflow)
|
|
73
|
+
mcp.tool()(submit_tautomer_search_workflow)
|
|
74
|
+
mcp.tool()(submit_descriptors_workflow)
|
|
75
|
+
mcp.tool()(submit_scan_workflow)
|
|
76
|
+
mcp.tool()(submit_irc_workflow)
|
|
77
|
+
mcp.tool()(submit_protein_cofolding_workflow)
|
|
78
|
+
mcp.tool()(submit_docking_workflow)
|
|
79
|
+
mcp.tool()(submit_macropka_workflow)
|
|
80
|
+
|
|
81
|
+
# Register molecule lookup tools
|
|
82
|
+
mcp.tool()(molecule_lookup)
|
|
83
|
+
mcp.tool()(batch_molecule_lookup)
|
|
84
|
+
mcp.tool()(validate_smiles)
|
|
85
|
+
|
|
86
|
+
# Register workflow management tools
|
|
87
|
+
mcp.tool()(workflow_fetch_latest)
|
|
88
|
+
mcp.tool()(workflow_wait_for_result)
|
|
89
|
+
mcp.tool()(workflow_get_status)
|
|
90
|
+
mcp.tool()(workflow_stop)
|
|
91
|
+
mcp.tool()(workflow_delete)
|
|
92
|
+
mcp.tool()(retrieve_workflow)
|
|
93
|
+
mcp.tool()(retrieve_calculation_molecules)
|
|
94
|
+
mcp.tool()(list_workflows)
|
|
95
|
+
mcp.tool()(workflow_update)
|
|
96
|
+
mcp.tool()(workflow_is_finished)
|
|
97
|
+
mcp.tool()(workflow_delete_data)
|
|
98
|
+
|
|
99
|
+
# Register protein management tools
|
|
100
|
+
mcp.tool()(create_protein_from_pdb_id)
|
|
101
|
+
mcp.tool()(retrieve_protein)
|
|
102
|
+
mcp.tool()(list_proteins)
|
|
103
|
+
mcp.tool()(upload_protein)
|
|
104
|
+
mcp.tool()(delete_protein)
|
|
105
|
+
mcp.tool()(sanitize_protein)
|
|
106
|
+
|
|
107
|
+
# Validate required configuration
|
|
108
|
+
if not os.getenv("ROWAN_API_KEY"):
|
|
98
109
|
raise ValueError(
|
|
99
110
|
"ROWAN_API_KEY environment variable is required. "
|
|
100
111
|
"Get your API key from https://labs.rowansci.com"
|
|
101
112
|
)
|
|
102
113
|
|
|
103
|
-
if rowan is None:
|
|
104
|
-
raise ImportError(
|
|
105
|
-
"rowan-python package is required. Install with: pip install rowan-python"
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
rowan.api_key = api_key
|
|
109
114
|
|
|
110
115
|
def main() -> None:
|
|
111
|
-
"""Main entry point for the MCP server."""
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
#
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
print(" rowan-mcp --http # HTTP/SSE transport")
|
|
128
|
-
print("")
|
|
129
|
-
print("Development usage:")
|
|
130
|
-
print(" python -m rowan_mcp # Default STDIO transport")
|
|
131
|
-
print(" python -m rowan_mcp --stdio # STDIO transport")
|
|
132
|
-
print(" python -m rowan_mcp --http # HTTP/SSE transport")
|
|
133
|
-
print("")
|
|
134
|
-
print("Environment variables:")
|
|
135
|
-
print(" ROWAN_API_KEY # Required: Your Rowan API key")
|
|
136
|
-
print(" ROWAN_MCP_TRANSPORT # Optional: 'stdio' or 'http' (default: stdio)")
|
|
137
|
-
print(" ROWAN_MCP_HOST # Optional: HTTP host (default: 127.0.0.1)")
|
|
138
|
-
print(" ROWAN_MCP_PORT # Optional: HTTP port (default: 6276)")
|
|
139
|
-
print("")
|
|
140
|
-
print("HTTP/SSE mode endpoint: http://host:port/sse")
|
|
141
|
-
return
|
|
142
|
-
|
|
143
|
-
if transport_mode == "http":
|
|
144
|
-
host = os.getenv("ROWAN_MCP_HOST", "127.0.0.1")
|
|
145
|
-
port = int(os.getenv("ROWAN_MCP_PORT", "6276"))
|
|
146
|
-
|
|
147
|
-
print("🚀 Starting Rowan MCP Server (HTTP/SSE mode)")
|
|
148
|
-
print(f"📡 Server will be available at: http://{host}:{port}/sse")
|
|
149
|
-
print(f"🔑 API Key loaded: {'✓' if api_key else '✗'}")
|
|
150
|
-
print(f"🛠️ Available tools: {len([attr for attr in dir() if attr.startswith('rowan_')])}")
|
|
151
|
-
print("🔗 Connect your MCP client to this endpoint!")
|
|
152
|
-
print("\nPress Ctrl+C to stop the server")
|
|
153
|
-
|
|
154
|
-
mcp.run(transport="sse", host=host, port=port)
|
|
155
|
-
else:
|
|
156
|
-
print("🚀 Starting Rowan MCP Server (STDIO mode)")
|
|
157
|
-
print(f"🔑 API Key loaded: {'✓' if api_key else '✗'}")
|
|
158
|
-
print(f"🛠️ Available tools: {len([attr for attr in dir() if attr.startswith('rowan_')])}")
|
|
159
|
-
|
|
160
|
-
mcp.run() # Default STDIO transport
|
|
161
|
-
|
|
162
|
-
except KeyboardInterrupt:
|
|
163
|
-
print("\n👋 Server shutdown requested by user")
|
|
164
|
-
except Exception as e:
|
|
165
|
-
print(f"❌ Server error: {e}")
|
|
166
|
-
traceback.print_exc()
|
|
116
|
+
"""Main entry point for the HTTP MCP server."""
|
|
117
|
+
if len(sys.argv) > 1 and sys.argv[1] == "--help":
|
|
118
|
+
print("Rowan MCP Server")
|
|
119
|
+
print("Usage: rowan-mcp")
|
|
120
|
+
print("Environment variables:")
|
|
121
|
+
print(" ROWAN_API_KEY # Required: Your Rowan API key")
|
|
122
|
+
print(" ROWAN_MCP_HOST # Optional: HTTP host (default: 127.0.0.1)")
|
|
123
|
+
print(" ROWAN_MCP_PORT # Optional: HTTP port (default: 6276)")
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
host = os.getenv("ROWAN_MCP_HOST", "127.0.0.1")
|
|
127
|
+
port = int(os.getenv("ROWAN_MCP_PORT", "6276"))
|
|
128
|
+
|
|
129
|
+
print(f"Starting Rowan MCP Server at http://{host}:{port}/sse")
|
|
130
|
+
|
|
131
|
+
mcp.run(transport="sse", host=host, port=port)
|
|
167
132
|
|
|
168
133
|
if __name__ == "__main__":
|
|
169
|
-
main()
|
|
134
|
+
main()
|
|
File without changes
|