rowan-mcp 1.0.2__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 (57) hide show
  1. rowan_mcp/__init__.py +1 -1
  2. rowan_mcp/__main__.py +3 -5
  3. rowan_mcp/functions/admet.py +0 -5
  4. rowan_mcp/functions/bde.py +1 -8
  5. rowan_mcp/functions/conformers.py +1 -4
  6. rowan_mcp/functions/descriptors.py +1 -4
  7. rowan_mcp/functions/docking.py +6 -56
  8. rowan_mcp/functions/electronic_properties.py +1 -4
  9. rowan_mcp/functions/folder_management.py +1 -8
  10. rowan_mcp/functions/fukui.py +1 -4
  11. rowan_mcp/functions/hydrogen_bond_basicity.py +1 -8
  12. rowan_mcp/functions/multistage_opt.py +1 -4
  13. rowan_mcp/functions/pka.py +1 -8
  14. rowan_mcp/functions/redox_potential.py +2 -5
  15. rowan_mcp/functions/system_management.py +1 -8
  16. rowan_mcp/functions/tautomers.py +1 -4
  17. rowan_mcp/functions_v2/BENCHMARK.md +86 -0
  18. rowan_mcp/functions_v2/molecule_lookup.py +232 -0
  19. rowan_mcp/functions_v2/protein_management.py +141 -0
  20. rowan_mcp/functions_v2/submit_basic_calculation_workflow.py +195 -0
  21. rowan_mcp/functions_v2/submit_conformer_search_workflow.py +158 -0
  22. rowan_mcp/functions_v2/submit_descriptors_workflow.py +52 -0
  23. rowan_mcp/functions_v2/submit_docking_workflow.py +244 -0
  24. rowan_mcp/functions_v2/submit_fukui_workflow.py +114 -0
  25. rowan_mcp/functions_v2/submit_irc_workflow.py +58 -0
  26. rowan_mcp/functions_v2/submit_macropka_workflow.py +99 -0
  27. rowan_mcp/functions_v2/submit_pka_workflow.py +72 -0
  28. rowan_mcp/functions_v2/submit_protein_cofolding_workflow.py +88 -0
  29. rowan_mcp/functions_v2/submit_redox_potential_workflow.py +55 -0
  30. rowan_mcp/functions_v2/submit_scan_workflow.py +82 -0
  31. rowan_mcp/functions_v2/submit_solubility_workflow.py +157 -0
  32. rowan_mcp/functions_v2/submit_tautomer_search_workflow.py +51 -0
  33. rowan_mcp/functions_v2/workflow_management_v2.py +382 -0
  34. rowan_mcp/server.py +109 -144
  35. rowan_mcp/tests/basic_calculation_from_json.py +0 -0
  36. rowan_mcp/tests/basic_calculation_with_constraint.py +33 -0
  37. rowan_mcp/tests/basic_calculation_with_solvent.py +0 -0
  38. rowan_mcp/tests/bde.py +37 -0
  39. rowan_mcp/tests/benchmark_queries.md +120 -0
  40. rowan_mcp/tests/cofolding_screen.py +131 -0
  41. rowan_mcp/tests/conformer_dependent_redox.py +37 -0
  42. rowan_mcp/tests/conformers.py +31 -0
  43. rowan_mcp/tests/data.json +189 -0
  44. rowan_mcp/tests/docking_screen.py +157 -0
  45. rowan_mcp/tests/irc.py +24 -0
  46. rowan_mcp/tests/macropka.py +13 -0
  47. rowan_mcp/tests/multistage_opt.py +13 -0
  48. rowan_mcp/tests/optimization.py +21 -0
  49. rowan_mcp/tests/phenol_pka.py +36 -0
  50. rowan_mcp/tests/pka.py +36 -0
  51. rowan_mcp/tests/protein_cofolding.py +17 -0
  52. rowan_mcp/tests/scan.py +28 -0
  53. {rowan_mcp-1.0.2.dist-info → rowan_mcp-2.0.1.dist-info}/METADATA +38 -45
  54. rowan_mcp-2.0.1.dist-info/RECORD +69 -0
  55. rowan_mcp-1.0.2.dist-info/RECORD +0 -34
  56. {rowan_mcp-1.0.2.dist-info → rowan_mcp-2.0.1.dist-info}/WHEEL +0 -0
  57. {rowan_mcp-1.0.2.dist-info → rowan_mcp-2.0.1.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
- try:
52
- import rowan
53
- except ImportError:
54
- rowan = None
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() # Load .env file if available
59
+ load_dotenv()
59
60
  except ImportError:
60
- pass # dotenv not required, but helpful if available
61
+ pass
61
62
 
62
63
  # Initialize FastMCP server
63
64
  mcp = FastMCP("Rowan MCP Server")
64
65
 
65
- # Register imported functions as MCP tools
66
- rowan_scan = mcp.tool()(scan_function)
67
- rowan_scan_analyzer = mcp.tool()(scan_analyzer_function)
68
- rowan_admet = mcp.tool()(admet_function)
69
-
70
- rowan_multistage_opt = mcp.tool()(multistage_opt_function)
71
- rowan_descriptors = mcp.tool()(descriptors_function)
72
- rowan_tautomers = mcp.tool()(tautomers_function)
73
-
74
- rowan_redox_potential = mcp.tool()(redox_potential_function)
75
- rowan_conformers = mcp.tool()(conformers_function)
76
- rowan_electronic_properties = mcp.tool()(electronic_properties_function)
77
- rowan_fukui = mcp.tool()(fukui_function)
78
- rowan_spin_states = mcp.tool()(spin_states_function)
79
- rowan_solubility = mcp.tool()(solubility_function)
80
- rowan_molecular_dynamics = mcp.tool()(molecular_dynamics_function)
81
- rowan_irc = mcp.tool()(irc_function)
82
- rowan_docking = mcp.tool()(docking_function)
83
- rowan_docking_pdb_id = mcp.tool()(docking_pdb_id_function)
84
- rowan_docking_enhanced = mcp.tool()(docking_enhanced_function)
85
- rowan_workflow_management = mcp.tool()(workflow_management_function)
86
- # rowan_calculation_retrieve = mcp.tool()(calculation_retrieve_function)
87
- rowan_molecule_lookup = mcp.tool()(molecule_lookup_function)
88
- rowan_pka = mcp.tool()(pka_function)
89
- rowan_macropka = mcp.tool()(macropka_function)
90
- rowan_hydrogen_bond_basicity = mcp.tool()(hydrogen_bond_basicity_function)
91
- rowan_bde = mcp.tool()(bde_function)
92
- rowan_folder_management = mcp.tool()(folder_management_function)
93
- rowan_system_management = mcp.tool()(system_management_function)
94
-
95
- # Setup API key
96
- api_key = os.getenv("ROWAN_API_KEY")
97
- if not api_key:
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
- try:
113
- # Check for transport mode from command line args or environment
114
- transport_mode = os.getenv("ROWAN_MCP_TRANSPORT", "stdio").lower()
115
-
116
- # Allow override from command line
117
- if len(sys.argv) > 1:
118
- if sys.argv[1] == "--http":
119
- transport_mode = "http"
120
- elif sys.argv[1] == "--stdio":
121
- transport_mode = "stdio"
122
- elif sys.argv[1] == "--help":
123
- print("Rowan MCP Server")
124
- print("Usage:")
125
- print(" rowan-mcp # Default STDIO transport")
126
- print(" rowan-mcp --stdio # STDIO transport")
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