guohanghui commited on
Commit
dc18577
·
verified ·
1 Parent(s): a3a9e1c

Update autodE/mcp_output/mcp_plugin/mcp_service.py

Browse files
autodE/mcp_output/mcp_plugin/mcp_service.py CHANGED
@@ -1,423 +1,148 @@
1
- import os
2
- import sys
3
-
4
- # Add the local source directory to sys.path
5
- source_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "source")
6
- if source_path not in sys.path:
7
- sys.path.insert(0, source_path)
8
-
9
  from fastmcp import FastMCP
10
 
11
- # Check if autodE is available
12
- try:
13
- import autode as ade
14
- from autode import Atom, Molecule, Reactant, Product, Reaction, Config
15
- from autode import Species
16
- AUTODE_AVAILABLE = True
17
- except ImportError:
18
- AUTODE_AVAILABLE = False
19
-
20
  # Create the FastMCP service application
21
  mcp = FastMCP("autode_service")
22
 
23
- # ============================================================================
24
- # Molecule and Species Creation Tools
25
- # ============================================================================
26
-
27
- @mcp.tool()
28
- def create_molecule_from_smiles(smiles: str, name: str = "molecule") -> dict:
29
  """
30
- Create a molecule from a SMILES string.
31
 
32
  Parameters:
33
- - smiles: SMILES string representation (e.g., 'CCO' for ethanol)
34
- - name: Name for the molecule (default 'molecule')
35
 
36
  Returns:
37
- - dict: Molecule information including atoms count and charge
38
  """
39
- if not AUTODE_AVAILABLE:
40
- return {"success": False, "error": "autodE not installed. Requires: rdkit, numpy, networkx, and more"}
41
-
42
  try:
43
- mol = Molecule(smiles=smiles, name=name)
44
- return {
45
- "success": True,
46
- "name": mol.name,
47
- "smiles": smiles,
48
- "n_atoms": mol.n_atoms,
49
- "charge": mol.charge,
50
- "mult": mol.mult,
51
- "formula": str(mol.formula) if hasattr(mol, 'formula') else None
52
- }
53
- except Exception as e:
54
- return {"success": False, "error": str(e)}
55
-
56
-
57
- @mcp.tool()
58
- def get_atom_properties(element: str) -> dict:
59
- """
60
- Get properties of an atom given its element symbol.
61
-
62
- Parameters:
63
- - element: Chemical symbol (e.g., 'H', 'C', 'O', 'N')
64
-
65
- Returns:
66
- - dict: Atom properties including mass and atomic number
67
- """
68
- if not AUTODE_AVAILABLE:
69
- return {"success": False, "error": "autodE not installed"}
70
-
71
- try:
72
- atom = Atom(element)
73
  return {
74
  "success": True,
75
- "element": element,
76
- "atomic_number": atom.atomic_number,
77
- "atomic_mass": atom.mass,
78
- "atomic_symbol": atom.label
79
  }
80
  except Exception as e:
81
  return {"success": False, "error": str(e)}
82
 
83
-
84
- # ============================================================================
85
- # Reaction Setup Tools
86
- # ============================================================================
87
-
88
- @mcp.tool()
89
- def create_reaction_from_smiles(reactant_smiles: str, product_smiles: str,
90
- name: str = "reaction", solvent_name: str = None) -> dict:
91
  """
92
- Create a reaction from reactant and product SMILES strings.
93
 
94
  Parameters:
95
- - reactant_smiles: SMILES of reactant(s), separate multiple with '.'
96
- - product_smiles: SMILES of product(s), separate multiple with '.'
97
- - name: Name for the reaction (default 'reaction')
98
- - solvent_name: Solvent name if needed (optional)
99
 
100
  Returns:
101
- - dict: Reaction information
102
  """
103
- if not AUTODE_AVAILABLE:
104
- return {"success": False, "error": "autodE not installed"}
105
-
106
  try:
107
- # Create reaction using simplified notation
108
- reaction_smiles = f"{reactant_smiles}>>{product_smiles}"
109
- rxn = Reaction(reaction_smiles, name=name, solvent_name=solvent_name)
 
 
 
 
 
110
 
111
  return {
112
  "success": True,
113
- "name": rxn.name,
114
- "reaction_smiles": reaction_smiles,
115
- "n_reactants": len(rxn.reacs),
116
- "n_products": len(rxn.prods),
117
- "solvent": solvent_name
118
- }
119
- except Exception as e:
120
- return {"success": False, "error": str(e)}
121
-
122
-
123
- # ============================================================================
124
- # Configuration Tools
125
- # ============================================================================
126
-
127
- @mcp.tool()
128
- def get_config_info() -> dict:
129
- """
130
- Get current autodE configuration settings.
131
-
132
- Returns:
133
- - dict: Configuration parameters
134
- """
135
- if not AUTODE_AVAILABLE:
136
- return {"success": False, "error": "autodE not installed"}
137
-
138
- try:
139
- return {
140
- "success": True,
141
- "n_cores": Config.n_cores,
142
- "max_core": Config.max_core,
143
- "hcode": Config.hcode,
144
- "lcode": Config.lcode,
145
- "ts_guess_methods": Config.ts_guess_methods if hasattr(Config, 'ts_guess_methods') else None,
146
- "num_conformers": Config.num_conformers if hasattr(Config, 'num_conformers') else None
147
  }
148
  except Exception as e:
149
  return {"success": False, "error": str(e)}
150
 
151
-
152
- @mcp.tool()
153
- def set_n_cores(n_cores: int) -> dict:
154
  """
155
- Set the number of CPU cores to use for calculations.
156
 
157
  Parameters:
158
- - n_cores: Number of cores (positive integer)
159
 
160
  Returns:
161
- - dict: Updated configuration
162
  """
163
- if not AUTODE_AVAILABLE:
164
- return {"success": False, "error": "autodE not installed"}
165
-
166
  try:
167
- if n_cores < 1:
168
- return {"success": False, "error": "n_cores must be at least 1"}
169
 
170
- Config.n_cores = n_cores
171
- return {
172
- "success": True,
173
- "n_cores": Config.n_cores,
174
- "message": f"Set to use {n_cores} cores"
175
- }
176
- except Exception as e:
177
- return {"success": False, "error": str(e)}
178
-
179
-
180
- @mcp.tool()
181
- def list_available_methods() -> dict:
182
- """
183
- List available computational chemistry methods/codes.
184
-
185
- Returns:
186
- - dict: Available high-level and low-level methods
187
- """
188
- if not AUTODE_AVAILABLE:
189
- return {"success": False, "error": "autodE not installed"}
190
-
191
- try:
192
- # Common methods autodE supports
193
- available_methods = {
194
- "high_level": [
195
- "orca",
196
- "g09", "g16", # Gaussian
197
- "qchem",
198
- "nwchem",
199
- "psi4"
200
- ],
201
- "low_level": [
202
- "xtb",
203
- "mopac",
204
- "orca"
205
- ],
206
- "description": {
207
- "orca": "ORCA - general purpose quantum chemistry",
208
- "g09/g16": "Gaussian - widely used QM package",
209
- "xtb": "GFN-xTB - fast semi-empirical method",
210
- "mopac": "MOPAC - semi-empirical methods",
211
- "qchem": "Q-Chem",
212
- "psi4": "Psi4 - open-source QM",
213
- "nwchem": "NWChem"
214
- }
215
- }
216
 
217
  return {
218
  "success": True,
219
- "methods": available_methods,
220
- "current_hcode": Config.hcode,
221
- "current_lcode": Config.lcode
222
  }
223
  except Exception as e:
224
  return {"success": False, "error": str(e)}
225
 
226
-
227
- # ============================================================================
228
- # Information and Utility Tools
229
- # ============================================================================
230
-
231
- @mcp.tool()
232
- def get_reaction_example() -> dict:
233
- """
234
- Get an example of setting up and running a reaction calculation.
235
-
236
- Returns:
237
- - dict: Example code for Diels-Alder reaction
238
- """
239
- if not AUTODE_AVAILABLE:
240
- return {"success": False, "error": "autodE not installed"}
241
-
242
- example_code = """
243
- import autode as ade
244
-
245
- # Configure
246
- ade.Config.n_cores = 4
247
-
248
- # Create reaction from SMILES (Diels-Alder)
249
- rxn = ade.Reaction('C=CC=C.C=C>>C1=CCCCC1', name='diels_alder')
250
-
251
- # Calculate reaction profile (requires QM software installed)
252
- # rxn.calculate_reaction_profile()
253
-
254
- # Access results
255
- # print(f"Reactants: {rxn.reacs}")
256
- # print(f"Products: {rxn.prods}")
257
- # print(f"Transition state: {rxn.ts}")
258
- """
259
-
260
- return {
261
- "success": True,
262
- "description": "Diels-Alder cycloaddition example",
263
- "reaction": "Butadiene + Ethylene -> Cyclohexene",
264
- "code": example_code,
265
- "note": "Requires external QM software (ORCA, Gaussian, etc.) to be installed"
266
- }
267
-
268
-
269
- @mcp.tool()
270
- def get_sn2_example() -> dict:
271
  """
272
- Get an example of an SN2 reaction setup.
273
-
274
- Returns:
275
- - dict: Example code for SN2 reaction
276
- """
277
- if not AUTODE_AVAILABLE:
278
- return {"success": False, "error": "autodE not installed"}
279
-
280
- example_code = """
281
- import autode as ade
282
-
283
- # SN2 reaction: Cl- + CH3Br -> CH3Cl + Br-
284
- rxn = ade.Reaction('[Cl-].CBr>>[Br-].CCl', name='sn2')
285
-
286
- # Calculate reaction profile
287
- # rxn.calculate_reaction_profile()
288
-
289
- # Access energies
290
- # delta_e = rxn.delta('energy')
291
- # delta_h = rxn.delta('enthalpy')
292
- # delta_g = rxn.delta('free_energy')
293
- """
294
-
295
- return {
296
- "success": True,
297
- "description": "SN2 nucleophilic substitution example",
298
- "reaction": "Cl- + CH3Br -> CH3Cl + Br-",
299
- "code": example_code
300
- }
301
-
302
-
303
- @mcp.tool()
304
- def get_molecule_from_xyz(xyz_string: str, name: str = "molecule",
305
- charge: int = 0, mult: int = 1) -> dict:
306
- """
307
- Create a molecule from XYZ coordinate string.
308
 
309
  Parameters:
310
- - xyz_string: XYZ format coordinates (element x y z per line)
311
- - name: Molecule name (default 'molecule')
312
- - charge: Molecular charge (default 0)
313
- - mult: Spin multiplicity (default 1)
314
 
315
  Returns:
316
- - dict: Molecule information
317
  """
318
- if not AUTODE_AVAILABLE:
319
- return {"success": False, "error": "autodE not installed"}
320
-
321
  try:
322
- # Parse XYZ string
323
- lines = xyz_string.strip().split('\n')
324
- atoms = []
325
 
326
- for line in lines:
327
- parts = line.split()
328
- if len(parts) >= 4:
329
- element = parts[0]
330
- x, y, z = float(parts[1]), float(parts[2]), float(parts[3])
331
- atoms.append(Atom(element, x=x, y=y, z=z))
332
 
333
- mol = Molecule(name=name, charge=charge, mult=mult, atoms=atoms)
 
334
 
335
  return {
336
  "success": True,
337
- "name": mol.name,
338
- "n_atoms": mol.n_atoms,
339
- "charge": mol.charge,
340
- "mult": mol.mult
341
  }
342
  except Exception as e:
343
  return {"success": False, "error": str(e)}
344
 
345
-
346
- @mcp.tool()
347
- def list_supported_solvents() -> dict:
348
  """
349
- List commonly supported solvent names.
350
 
351
- Returns:
352
- - dict: Available solvent names
353
- """
354
- if not AUTODE_AVAILABLE:
355
- return {"success": False, "error": "autodE not installed"}
356
-
357
- solvents = [
358
- "water", "h2o",
359
- "methanol", "meoh",
360
- "ethanol", "etoh",
361
- "acetone",
362
- "acetonitrile", "mecn",
363
- "dmso",
364
- "dmf",
365
- "thf",
366
- "dichloromethane", "dcm",
367
- "chloroform", "chcl3",
368
- "benzene",
369
- "toluene",
370
- "hexane",
371
- "diethyl ether", "ether"
372
- ]
373
-
374
- return {
375
- "success": True,
376
- "solvents": solvents,
377
- "note": "Solvent availability depends on the QM method used"
378
- }
379
-
380
-
381
- @mcp.tool()
382
- def get_autode_info() -> dict:
383
- """
384
- Get autodE version and installation information.
385
 
386
  Returns:
387
- - dict: Version and capabilities
388
  """
389
- if not AUTODE_AVAILABLE:
390
- return {
391
- "success": False,
392
- "available": False,
393
- "message": "autodE not installed. Requires: pip install autode rdkit numpy networkx scipy matplotlib"
394
- }
395
-
396
  try:
 
 
 
 
 
 
397
  return {
398
  "success": True,
399
- "available": True,
400
- "version": ade.__version__,
401
- "description": "Automated reaction profile generation for organic and organometallic reactions",
402
- "capabilities": [
403
- "Automated transition state search",
404
- "Reaction profile calculation",
405
- "Conformer generation",
406
- "NEB calculations",
407
- "Multistep reactions"
408
- ],
409
- "github": "https://github.com/duartegroup/autodE",
410
- "docs": "https://duartegroup.github.io/autodE/"
411
  }
412
  except Exception as e:
413
  return {"success": False, "error": str(e)}
414
 
415
-
416
  def create_app() -> FastMCP:
417
  """
418
  Create and return the FastMCP application instance.
419
 
420
  Returns:
421
- - FastMCP: The FastMCP service instance.
422
  """
423
  return mcp
 
 
 
 
 
 
 
 
 
1
  from fastmcp import FastMCP
2
 
 
 
 
 
 
 
 
 
 
3
  # Create the FastMCP service application
4
  mcp = FastMCP("autode_service")
5
 
6
+ @mcp.tool(name="optimize_geometry", description="Optimize the geometry of a molecule")
7
+ def optimize_geometry(molecule: dict) -> dict:
 
 
 
 
8
  """
9
+ Optimize the geometry of a molecule using autode.
10
 
11
  Parameters:
12
+ - molecule: A dictionary representing the molecule.
 
13
 
14
  Returns:
15
+ - dict: Optimized geometry and energy.
16
  """
 
 
 
17
  try:
18
+ from autode.species.molecule import Molecule
19
+ from autode.opt.optimisers import BFGS
20
+
21
+ mol = Molecule(**molecule)
22
+ mol.optimise(method=BFGS())
23
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  return {
25
  "success": True,
26
+ "optimized_geometry": mol.coordinates,
27
+ "energy": mol.energy
 
 
28
  }
29
  except Exception as e:
30
  return {"success": False, "error": str(e)}
31
 
32
+ @mcp.tool(name="calculate_reaction_energy", description="Calculate the reaction energy for a given reaction")
33
+ def calculate_reaction_energy(reactants: list, products: list) -> dict:
 
 
 
 
 
 
34
  """
35
+ Calculate the reaction energy for a given reaction.
36
 
37
  Parameters:
38
+ - reactants: A list of reactant molecules.
39
+ - products: A list of product molecules.
 
 
40
 
41
  Returns:
42
+ - dict: Reaction energy.
43
  """
 
 
 
44
  try:
45
+ from autode.reactions.reaction import Reaction
46
+ from autode.species.molecule import Molecule
47
+
48
+ reactant_mols = [Molecule(**r) for r in reactants]
49
+ product_mols = [Molecule(**p) for p in products]
50
+
51
+ reaction = Reaction(*reactant_mols, *product_mols)
52
+ reaction.calculate_reaction_energy()
53
 
54
  return {
55
  "success": True,
56
+ "reaction_energy": reaction.reaction_energy
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  }
58
  except Exception as e:
59
  return {"success": False, "error": str(e)}
60
 
61
+ @mcp.tool(name="generate_conformers", description="Generate conformers for a molecule")
62
+ def generate_conformers(molecule: dict) -> dict:
 
63
  """
64
+ Generate conformers for a molecule.
65
 
66
  Parameters:
67
+ - molecule: A dictionary representing the molecule.
68
 
69
  Returns:
70
+ - dict: Generated conformers.
71
  """
 
 
 
72
  try:
73
+ from autode.conformers.conformers import Conformers
74
+ from autode.species.molecule import Molecule
75
 
76
+ mol = Molecule(**molecule)
77
+ conformers = Conformers.generate(mol)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
  return {
80
  "success": True,
81
+ "conformers": [conf.coordinates for conf in conformers]
 
 
82
  }
83
  except Exception as e:
84
  return {"success": False, "error": str(e)}
85
 
86
+ @mcp.tool(name="locate_transition_state", description="Locate the transition state for a reaction")
87
+ def locate_transition_state(reactants: list, products: list) -> dict:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  """
89
+ Locate the transition state for a reaction.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
 
91
  Parameters:
92
+ - reactants: A list of reactant molecules.
93
+ - products: A list of product molecules.
 
 
94
 
95
  Returns:
96
+ - dict: Transition state geometry and energy.
97
  """
 
 
 
98
  try:
99
+ from autode.transition_states.transition_state import TransitionState
100
+ from autode.species.molecule import Molecule
 
101
 
102
+ reactant_mols = [Molecule(**r) for r in reactants]
103
+ product_mols = [Molecule(**p) for p in products]
 
 
 
 
104
 
105
+ ts = TransitionState(reactants=reactant_mols, products=product_mols)
106
+ ts.locate()
107
 
108
  return {
109
  "success": True,
110
+ "ts_geometry": ts.coordinates,
111
+ "ts_energy": ts.energy
 
 
112
  }
113
  except Exception as e:
114
  return {"success": False, "error": str(e)}
115
 
116
+ @mcp.tool(name="calculate_hessian", description="Calculate the Hessian matrix for a molecule")
117
+ def calculate_hessian(molecule: dict) -> dict:
 
118
  """
119
+ Calculate the Hessian matrix for a molecule.
120
 
121
+ Parameters:
122
+ - molecule: A dictionary representing the molecule.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
 
124
  Returns:
125
+ - dict: Hessian matrix.
126
  """
 
 
 
 
 
 
 
127
  try:
128
+ from autode.hessians.hessians import Hessian
129
+ from autode.species.molecule import Molecule
130
+
131
+ mol = Molecule(**molecule)
132
+ hessian = Hessian(mol)
133
+
134
  return {
135
  "success": True,
136
+ "hessian_matrix": hessian.matrix
 
 
 
 
 
 
 
 
 
 
 
137
  }
138
  except Exception as e:
139
  return {"success": False, "error": str(e)}
140
 
 
141
  def create_app() -> FastMCP:
142
  """
143
  Create and return the FastMCP application instance.
144
 
145
  Returns:
146
+ - FastMCP: The FastMCP application instance.
147
  """
148
  return mcp