guohanghui commited on
Commit
28d4277
·
verified ·
1 Parent(s): 63b0027

Update openmc/mcp_output/mcp_plugin/mcp_service.py

Browse files
openmc/mcp_output/mcp_plugin/mcp_service.py CHANGED
@@ -1,108 +1,635 @@
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
- from openmc import Model, Geometry, Materials, Tallies, Settings
 
 
 
 
 
 
 
 
11
 
12
  # Create the FastMCP service application
13
  mcp = FastMCP("openmc_service")
14
 
15
- @mcp.tool(name="create_model", description="Create a new OpenMC model")
16
- def create_model() -> dict:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  """
18
- Create a new OpenMC model with default settings.
 
 
 
 
 
 
 
 
 
 
 
19
 
20
  Returns:
21
- dict: A dictionary containing the success status and the model object.
22
  """
 
 
 
23
  try:
24
- model = Model()
25
- return {"success": True, "result": model}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  except Exception as e:
27
- return {"success": False, "error": str(e)}
 
28
 
29
- @mcp.tool(name="setup_geometry", description="Setup geometry for the OpenMC model")
30
- def setup_geometry(model: Model) -> dict:
 
 
 
 
 
 
 
31
  """
32
- Setup geometry for the given OpenMC model.
33
 
34
- Args:
35
- model (Model): The OpenMC model to setup geometry for.
 
 
 
 
 
36
 
37
  Returns:
38
- dict: A dictionary containing the success status and the geometry object.
39
  """
 
 
 
40
  try:
41
- geometry = Geometry()
42
- model.geometry = geometry
43
- return {"success": True, "result": geometry}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  except Exception as e:
45
- return {"success": False, "error": str(e)}
 
46
 
47
- @mcp.tool(name="setup_materials", description="Setup materials for the OpenMC model")
48
- def setup_materials(model: Model) -> dict:
 
 
 
 
49
  """
50
- Setup materials for the given OpenMC model.
51
 
52
- Args:
53
- model (Model): The OpenMC model to setup materials for.
 
 
 
 
 
 
54
 
55
  Returns:
56
- dict: A dictionary containing the success status and the materials object.
57
  """
 
 
 
58
  try:
59
- materials = Materials()
60
- model.materials = materials
61
- return {"success": True, "result": materials}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  except Exception as e:
63
- return {"success": False, "error": str(e)}
64
 
65
- @mcp.tool(name="setup_tallies", description="Setup tallies for the OpenMC model")
66
- def setup_tallies(model: Model) -> dict:
 
 
 
 
 
 
67
  """
68
- Setup tallies for the given OpenMC model.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
- Args:
71
- model (Model): The OpenMC model to setup tallies for.
 
 
 
 
 
 
 
 
72
 
73
  Returns:
74
- dict: A dictionary containing the success status and the tallies object.
75
  """
76
  try:
77
- tallies = Tallies()
78
- model.tallies = tallies
79
- return {"success": True, "result": tallies}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  except Exception as e:
81
- return {"success": False, "error": str(e)}
 
 
 
 
 
 
82
 
83
- @mcp.tool(name="setup_settings", description="Setup settings for the OpenMC model")
84
- def setup_settings(model: Model) -> dict:
85
  """
86
- Setup settings for the given OpenMC model.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
 
88
- Args:
89
- model (Model): The OpenMC model to setup settings for.
 
 
 
90
 
91
  Returns:
92
- dict: A dictionary containing the success status and the settings object.
93
  """
94
  try:
95
- settings = Settings()
96
- model.settings = settings
97
- return {"success": True, "result": settings}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  except Exception as e:
99
- return {"success": False, "error": str(e)}
 
100
 
101
  def create_app() -> FastMCP:
102
  """
103
  Create and return the FastMCP application instance.
104
 
105
  Returns:
106
- FastMCP: The FastMCP application instance.
107
  """
108
- return mcp
 
1
  import os
2
  import sys
3
+ from typing import List, Optional, Dict, Any
 
 
 
 
4
 
5
  from fastmcp import FastMCP
6
+
7
+ # Import openmc from PyPI (will be installed via requirements.txt)
8
+ # Note: OpenMC requires compiled C++ libraries
9
+ try:
10
+ import openmc
11
+ import openmc.stats
12
+ OPENMC_AVAILABLE = True
13
+ except ImportError:
14
+ OPENMC_AVAILABLE = False
15
 
16
  # Create the FastMCP service application
17
  mcp = FastMCP("openmc_service")
18
 
19
+
20
+ @mcp.tool(name="get_openmc_version", description="Get OpenMC library version and status")
21
+ def get_openmc_version() -> dict:
22
+ """
23
+ Get the OpenMC library version and availability status.
24
+
25
+ Returns:
26
+ - dict: Version and status information.
27
+ """
28
+ try:
29
+ if OPENMC_AVAILABLE:
30
+ return {
31
+ "success": True,
32
+ "result": {
33
+ "version": openmc.__version__,
34
+ "available": True,
35
+ "description": "OpenMC is a Monte Carlo particle transport simulation code"
36
+ },
37
+ "error": None
38
+ }
39
+ else:
40
+ return {
41
+ "success": True,
42
+ "result": {
43
+ "available": False,
44
+ "message": "OpenMC not installed. Requires compiled C++ libraries.",
45
+ "install_note": "pip install openmc (requires HDF5 and other dependencies)"
46
+ },
47
+ "error": None
48
+ }
49
+ except Exception as e:
50
+ return {"success": False, "result": None, "error": str(e)}
51
+
52
+
53
+ @mcp.tool(name="create_material", description="Create an OpenMC material with nuclides or elements")
54
+ def create_material(
55
+ name: str,
56
+ density: float,
57
+ density_units: str = "g/cm3",
58
+ nuclides: Dict[str, float] = None,
59
+ elements: Dict[str, float] = None,
60
+ temperature: float = None
61
+ ) -> dict:
62
+ """
63
+ Create an OpenMC material with specified composition.
64
+
65
+ Parameters:
66
+ - name: Material name
67
+ - density: Material density value
68
+ - density_units: Units for density (g/cm3, atom/b-cm, etc.)
69
+ - nuclides: Dictionary of nuclide names to atom fractions (e.g., {"U235": 0.03, "U238": 0.97})
70
+ - elements: Dictionary of element names to atom fractions (e.g., {"Fe": 0.70, "Cr": 0.18})
71
+ - temperature: Temperature in Kelvin (optional)
72
+
73
+ Returns:
74
+ - dict: Material information and XML representation.
75
+ """
76
+ if not OPENMC_AVAILABLE:
77
+ return {"success": False, "result": None, "error": "OpenMC not available"}
78
+
79
+ try:
80
+ mat = openmc.Material(name=name)
81
+ mat.set_density(density_units, density)
82
+
83
+ if nuclides:
84
+ for nuclide, fraction in nuclides.items():
85
+ mat.add_nuclide(nuclide, fraction)
86
+
87
+ if elements:
88
+ for element, fraction in elements.items():
89
+ mat.add_element(element, fraction)
90
+
91
+ if temperature is not None:
92
+ mat.temperature = temperature
93
+
94
+ # Get XML representation
95
+ xml_str = mat.to_xml_element().tostring().decode() if hasattr(mat.to_xml_element(), 'tostring') else str(mat)
96
+
97
+ return {
98
+ "success": True,
99
+ "result": {
100
+ "name": name,
101
+ "id": mat.id,
102
+ "density": density,
103
+ "density_units": density_units,
104
+ "nuclides": list(mat.nuclides) if mat.nuclides else [],
105
+ "description": f"Material '{name}' created successfully"
106
+ },
107
+ "error": None
108
+ }
109
+ except Exception as e:
110
+ return {"success": False, "result": None, "error": str(e)}
111
+
112
+
113
+ @mcp.tool(name="create_surface", description="Create an OpenMC surface for geometry definition")
114
+ def create_surface(
115
+ surface_type: str,
116
+ boundary_type: str = "transmission",
117
+ **params
118
+ ) -> dict:
119
+ """
120
+ Create an OpenMC surface.
121
+
122
+ Parameters:
123
+ - surface_type: Type of surface (Sphere, ZCylinder, XPlane, YPlane, ZPlane, etc.)
124
+ - boundary_type: Boundary condition (transmission, vacuum, reflective, periodic)
125
+ - params: Surface-specific parameters:
126
+ - For Sphere: x0, y0, z0, r (radius)
127
+ - For ZCylinder: x0, y0, r
128
+ - For XPlane/YPlane/ZPlane: x0/y0/z0
129
+
130
+ Returns:
131
+ - dict: Surface information.
132
+ """
133
+ if not OPENMC_AVAILABLE:
134
+ return {"success": False, "result": None, "error": "OpenMC not available"}
135
+
136
+ try:
137
+ surface_classes = {
138
+ "sphere": openmc.Sphere,
139
+ "zcylinder": openmc.ZCylinder,
140
+ "xcylinder": openmc.XCylinder,
141
+ "ycylinder": openmc.YCylinder,
142
+ "xplane": openmc.XPlane,
143
+ "yplane": openmc.YPlane,
144
+ "zplane": openmc.ZPlane,
145
+ "xcone": openmc.XCone,
146
+ "ycone": openmc.YCone,
147
+ "zcone": openmc.ZCone,
148
+ }
149
+
150
+ surface_type_lower = surface_type.lower()
151
+ if surface_type_lower not in surface_classes:
152
+ return {
153
+ "success": False,
154
+ "result": None,
155
+ "error": f"Unknown surface type: {surface_type}. Available: {list(surface_classes.keys())}"
156
+ }
157
+
158
+ surface_class = surface_classes[surface_type_lower]
159
+ surface = surface_class(boundary_type=boundary_type, **params)
160
+
161
+ return {
162
+ "success": True,
163
+ "result": {
164
+ "type": surface_type,
165
+ "id": surface.id,
166
+ "boundary_type": boundary_type,
167
+ "parameters": params,
168
+ "description": f"{surface_type} surface created with ID {surface.id}"
169
+ },
170
+ "error": None
171
+ }
172
+ except Exception as e:
173
+ return {"success": False, "result": None, "error": str(e)}
174
+
175
+
176
+ @mcp.tool(name="create_pincell_model", description="Create a complete fuel pin cell model")
177
+ def create_pincell_model(
178
+ fuel_radius: float = 0.39,
179
+ clad_inner_radius: float = 0.40,
180
+ clad_outer_radius: float = 0.46,
181
+ pitch: float = 1.26,
182
+ enrichment: float = 0.03,
183
+ fuel_density: float = 10.5,
184
+ batches: int = 100,
185
+ inactive: int = 10,
186
+ particles: int = 1000
187
+ ) -> dict:
188
  """
189
+ Create a complete PWR fuel pin cell model.
190
+
191
+ Parameters:
192
+ - fuel_radius: Fuel pellet outer radius (cm)
193
+ - clad_inner_radius: Cladding inner radius (cm)
194
+ - clad_outer_radius: Cladding outer radius (cm)
195
+ - pitch: Pin pitch (cm)
196
+ - enrichment: U-235 enrichment (atom fraction, e.g., 0.03 for 3%)
197
+ - fuel_density: Fuel density (g/cm3)
198
+ - batches: Number of batches for eigenvalue calculation
199
+ - inactive: Number of inactive batches
200
+ - particles: Number of particles per batch
201
 
202
  Returns:
203
+ - dict: Model information and estimated k-effective.
204
  """
205
+ if not OPENMC_AVAILABLE:
206
+ return {"success": False, "result": None, "error": "OpenMC not available"}
207
+
208
  try:
209
+ # Create materials
210
+ fuel = openmc.Material(name='fuel')
211
+ fuel.add_nuclide('U235', enrichment)
212
+ fuel.add_nuclide('U238', 1.0 - enrichment)
213
+ fuel.add_nuclide('O16', 2.0)
214
+ fuel.set_density('g/cm3', fuel_density)
215
+
216
+ clad = openmc.Material(name='clad')
217
+ clad.add_element('Zr', 1.0)
218
+ clad.set_density('g/cm3', 6.55)
219
+
220
+ water = openmc.Material(name='water')
221
+ water.add_nuclide('H1', 2.0)
222
+ water.add_nuclide('O16', 1.0)
223
+ water.set_density('g/cm3', 1.0)
224
+ water.add_s_alpha_beta('c_H_in_H2O')
225
+
226
+ materials = openmc.Materials([fuel, clad, water])
227
+
228
+ # Create geometry
229
+ fuel_or = openmc.ZCylinder(r=fuel_radius)
230
+ clad_ir = openmc.ZCylinder(r=clad_inner_radius)
231
+ clad_or = openmc.ZCylinder(r=clad_outer_radius)
232
+
233
+ fuel_cell = openmc.Cell(fill=fuel, region=-fuel_or)
234
+ gap_cell = openmc.Cell(region=+fuel_or & -clad_ir) # void gap
235
+ clad_cell = openmc.Cell(fill=clad, region=+clad_ir & -clad_or)
236
+ water_cell = openmc.Cell(fill=water, region=+clad_or)
237
+
238
+ universe = openmc.Universe(cells=[fuel_cell, gap_cell, clad_cell, water_cell])
239
+
240
+ # Create rectangular boundary
241
+ min_x = openmc.XPlane(-pitch/2, boundary_type='reflective')
242
+ max_x = openmc.XPlane(pitch/2, boundary_type='reflective')
243
+ min_y = openmc.YPlane(-pitch/2, boundary_type='reflective')
244
+ max_y = openmc.YPlane(pitch/2, boundary_type='reflective')
245
+
246
+ root_cell = openmc.Cell(fill=universe, region=+min_x & -max_x & +min_y & -max_y)
247
+ root_universe = openmc.Universe(cells=[root_cell])
248
+ geometry = openmc.Geometry(root_universe)
249
+
250
+ # Create settings
251
+ settings = openmc.Settings()
252
+ settings.batches = batches
253
+ settings.inactive = inactive
254
+ settings.particles = particles
255
+ settings.source = openmc.IndependentSource(
256
+ space=openmc.stats.Box([-pitch/2, -pitch/2, -1], [pitch/2, pitch/2, 1])
257
+ )
258
+
259
+ # Create model
260
+ model = openmc.Model(geometry=geometry, materials=materials, settings=settings)
261
+
262
+ return {
263
+ "success": True,
264
+ "result": {
265
+ "model_type": "PWR pincell",
266
+ "fuel_radius_cm": fuel_radius,
267
+ "clad_outer_radius_cm": clad_outer_radius,
268
+ "pitch_cm": pitch,
269
+ "enrichment_percent": enrichment * 100,
270
+ "fuel_density_g_cm3": fuel_density,
271
+ "batches": batches,
272
+ "inactive": inactive,
273
+ "particles": particles,
274
+ "materials": ["UO2 fuel", "Zircaloy clad", "H2O moderator"],
275
+ "boundary_conditions": "reflective (infinite lattice approximation)",
276
+ "note": "Model created. Call model.export_to_model_xml() to save, model.run() to execute."
277
+ },
278
+ "error": None
279
+ }
280
  except Exception as e:
281
+ return {"success": False, "result": None, "error": str(e)}
282
+
283
 
284
+ @mcp.tool(name="create_settings", description="Create OpenMC simulation settings")
285
+ def create_settings(
286
+ run_mode: str = "eigenvalue",
287
+ batches: int = 100,
288
+ inactive: int = 10,
289
+ particles: int = 1000,
290
+ source_type: str = "box",
291
+ source_bounds: List[float] = None
292
+ ) -> dict:
293
  """
294
+ Create OpenMC simulation settings.
295
 
296
+ Parameters:
297
+ - run_mode: Simulation mode (eigenvalue, fixed source, volume, plot)
298
+ - batches: Number of batches
299
+ - inactive: Number of inactive batches (eigenvalue mode)
300
+ - particles: Number of particles per batch
301
+ - source_type: Source spatial distribution type (box, point)
302
+ - source_bounds: Source bounds [x_min, y_min, z_min, x_max, y_max, z_max]
303
 
304
  Returns:
305
+ - dict: Settings information.
306
  """
307
+ if not OPENMC_AVAILABLE:
308
+ return {"success": False, "result": None, "error": "OpenMC not available"}
309
+
310
  try:
311
+ settings = openmc.Settings()
312
+ settings.run_mode = run_mode
313
+ settings.batches = batches
314
+ settings.particles = particles
315
+
316
+ if run_mode == 'eigenvalue':
317
+ settings.inactive = inactive
318
+
319
+ # Set source
320
+ if source_bounds is None:
321
+ source_bounds = [-10, -10, -10, 10, 10, 10]
322
+
323
+ if source_type == "box":
324
+ settings.source = openmc.IndependentSource(
325
+ space=openmc.stats.Box(source_bounds[:3], source_bounds[3:])
326
+ )
327
+ elif source_type == "point":
328
+ center = [(source_bounds[i] + source_bounds[i+3])/2 for i in range(3)]
329
+ settings.source = openmc.IndependentSource(
330
+ space=openmc.stats.Point(center)
331
+ )
332
+
333
+ return {
334
+ "success": True,
335
+ "result": {
336
+ "run_mode": run_mode,
337
+ "batches": batches,
338
+ "inactive": inactive if run_mode == 'eigenvalue' else None,
339
+ "particles": particles,
340
+ "source_type": source_type,
341
+ "source_bounds": source_bounds,
342
+ "total_histories": batches * particles
343
+ },
344
+ "error": None
345
+ }
346
  except Exception as e:
347
+ return {"success": False, "result": None, "error": str(e)}
348
+
349
 
350
+ @mcp.tool(name="create_tally", description="Create an OpenMC tally for scoring results")
351
+ def create_tally(
352
+ name: str,
353
+ scores: List[str],
354
+ filters_config: Dict[str, Any] = None
355
+ ) -> dict:
356
  """
357
+ Create an OpenMC tally for scoring simulation results.
358
 
359
+ Parameters:
360
+ - name: Tally name
361
+ - scores: List of scores to tally (flux, fission, absorption, heating, etc.)
362
+ - filters_config: Optional filter configuration:
363
+ - "energy_bins": List of energy boundaries in eV
364
+ - "mesh_dims": [nx, ny, nz] for mesh tally
365
+ - "mesh_lower": [x, y, z] lower left corner
366
+ - "mesh_upper": [x, y, z] upper right corner
367
 
368
  Returns:
369
+ - dict: Tally information.
370
  """
371
+ if not OPENMC_AVAILABLE:
372
+ return {"success": False, "result": None, "error": "OpenMC not available"}
373
+
374
  try:
375
+ tally = openmc.Tally(name=name)
376
+ tally.scores = scores
377
+
378
+ filters_added = []
379
+
380
+ if filters_config:
381
+ # Add energy filter
382
+ if "energy_bins" in filters_config:
383
+ energy_filter = openmc.EnergyFilter(filters_config["energy_bins"])
384
+ tally.filters.append(energy_filter)
385
+ filters_added.append(f"EnergyFilter with {len(filters_config['energy_bins'])-1} groups")
386
+
387
+ # Add mesh filter
388
+ if "mesh_dims" in filters_config:
389
+ mesh = openmc.RegularMesh()
390
+ mesh.dimension = filters_config["mesh_dims"]
391
+ mesh.lower_left = filters_config.get("mesh_lower", [-10, -10, -10])
392
+ mesh.upper_right = filters_config.get("mesh_upper", [10, 10, 10])
393
+ mesh_filter = openmc.MeshFilter(mesh)
394
+ tally.filters.append(mesh_filter)
395
+ filters_added.append(f"MeshFilter {filters_config['mesh_dims']}")
396
+
397
+ return {
398
+ "success": True,
399
+ "result": {
400
+ "name": name,
401
+ "id": tally.id,
402
+ "scores": scores,
403
+ "filters": filters_added,
404
+ "description": f"Tally '{name}' created with scores: {', '.join(scores)}"
405
+ },
406
+ "error": None
407
+ }
408
  except Exception as e:
409
+ return {"success": False, "result": None, "error": str(e)}
410
 
411
+
412
+ @mcp.tool(name="list_available_nuclides", description="List common nuclides available in OpenMC")
413
+ def list_available_nuclides() -> dict:
414
+ """
415
+ List common nuclides available for use in OpenMC materials.
416
+
417
+ Returns:
418
+ - dict: Nuclides organized by category.
419
  """
420
+ try:
421
+ nuclides = {
422
+ "fuel_nuclides": {
423
+ "U235": "Fissile uranium-235",
424
+ "U238": "Fertile uranium-238",
425
+ "Pu239": "Fissile plutonium-239",
426
+ "Pu240": "Plutonium-240",
427
+ "Pu241": "Fissile plutonium-241",
428
+ "Th232": "Fertile thorium-232",
429
+ "U233": "Fissile uranium-233"
430
+ },
431
+ "oxygen": {
432
+ "O16": "Oxygen-16 (most abundant)",
433
+ "O17": "Oxygen-17",
434
+ "O18": "Oxygen-18"
435
+ },
436
+ "moderator_nuclides": {
437
+ "H1": "Hydrogen-1 (protium)",
438
+ "H2": "Hydrogen-2 (deuterium)",
439
+ "C12": "Carbon-12",
440
+ "Be9": "Beryllium-9"
441
+ },
442
+ "structural_elements": {
443
+ "Zr90": "Zirconium-90",
444
+ "Zr91": "Zirconium-91",
445
+ "Zr92": "Zirconium-92",
446
+ "Fe54": "Iron-54",
447
+ "Fe56": "Iron-56",
448
+ "Cr52": "Chromium-52",
449
+ "Ni58": "Nickel-58"
450
+ },
451
+ "absorbers": {
452
+ "B10": "Boron-10 (absorber)",
453
+ "B11": "Boron-11",
454
+ "Gd155": "Gadolinium-155",
455
+ "Gd157": "Gadolinium-157",
456
+ "Ag107": "Silver-107",
457
+ "Ag109": "Silver-109",
458
+ "In115": "Indium-115",
459
+ "Cd113": "Cadmium-113"
460
+ },
461
+ "fission_products": {
462
+ "Xe135": "Xenon-135 (strong absorber)",
463
+ "Sm149": "Samarium-149",
464
+ "I135": "Iodine-135",
465
+ "Cs137": "Cesium-137"
466
+ }
467
+ }
468
+ return {"success": True, "result": nuclides, "error": None}
469
+ except Exception as e:
470
+ return {"success": False, "result": None, "error": str(e)}
471
 
472
+
473
+ @mcp.tool(name="calculate_enrichment", description="Calculate uranium enrichment parameters")
474
+ def calculate_enrichment(
475
+ u235_weight_percent: float
476
+ ) -> dict:
477
+ """
478
+ Calculate uranium enrichment parameters from weight percent.
479
+
480
+ Parameters:
481
+ - u235_weight_percent: U-235 weight percent (e.g., 3.0 for 3%)
482
 
483
  Returns:
484
+ - dict: Enrichment parameters including atom fractions.
485
  """
486
  try:
487
+ # Atomic masses
488
+ m_u235 = 235.0439299
489
+ m_u238 = 238.0507882
490
+
491
+ # Convert weight percent to atom fraction
492
+ w235 = u235_weight_percent / 100.0
493
+ w238 = 1.0 - w235
494
+
495
+ # Atom fractions
496
+ n235 = w235 / m_u235
497
+ n238 = w238 / m_u238
498
+ total = n235 + n238
499
+
500
+ a235 = n235 / total # atom fraction U-235
501
+ a238 = n238 / total # atom fraction U-238
502
+
503
+ return {
504
+ "success": True,
505
+ "result": {
506
+ "u235_weight_percent": u235_weight_percent,
507
+ "u238_weight_percent": 100.0 - u235_weight_percent,
508
+ "u235_atom_fraction": round(a235, 6),
509
+ "u238_atom_fraction": round(a238, 6),
510
+ "enrichment_category": (
511
+ "Natural" if u235_weight_percent < 0.72 else
512
+ "LEU (Low Enriched)" if u235_weight_percent < 20 else
513
+ "HEU (Highly Enriched)"
514
+ ),
515
+ "note": "Use atom fractions with add_nuclide() method"
516
+ },
517
+ "error": None
518
+ }
519
  except Exception as e:
520
+ return {"success": False, "result": None, "error": str(e)}
521
+
522
+
523
+ @mcp.tool(name="get_thermal_scattering_libraries", description="Get thermal scattering law libraries")
524
+ def get_thermal_scattering_libraries() -> dict:
525
+ """
526
+ Get available thermal scattering law (S(α,β)) libraries and their usage.
527
 
528
+ Returns:
529
+ - dict: Thermal scattering libraries and how to use them.
530
  """
531
+ try:
532
+ libraries = {
533
+ "water": {
534
+ "name": "c_H_in_H2O",
535
+ "description": "Hydrogen bound in light water (H2O)",
536
+ "usage": "material.add_s_alpha_beta('c_H_in_H2O')",
537
+ "temperature_range": "293.6K - 800K typically available"
538
+ },
539
+ "heavy_water": {
540
+ "name": "c_D_in_D2O",
541
+ "description": "Deuterium bound in heavy water (D2O)",
542
+ "usage": "material.add_s_alpha_beta('c_D_in_D2O')"
543
+ },
544
+ "graphite": {
545
+ "name": "c_Graphite",
546
+ "description": "Carbon in graphite",
547
+ "usage": "material.add_s_alpha_beta('c_Graphite')"
548
+ },
549
+ "polyethylene": {
550
+ "name": "c_H_in_CH2",
551
+ "description": "Hydrogen in polyethylene",
552
+ "usage": "material.add_s_alpha_beta('c_H_in_CH2')"
553
+ },
554
+ "beryllium": {
555
+ "name": "c_Be",
556
+ "description": "Beryllium metal",
557
+ "usage": "material.add_s_alpha_beta('c_Be')"
558
+ },
559
+ "beryllium_oxide": {
560
+ "name": "c_Be_in_BeO",
561
+ "description": "Beryllium in BeO",
562
+ "usage": "material.add_s_alpha_beta('c_Be_in_BeO')"
563
+ },
564
+ "zirconium_hydride": {
565
+ "names": ["c_H_in_ZrH", "c_Zr_in_ZrH"],
566
+ "description": "Hydrogen and Zirconium in ZrH",
567
+ "usage": "Add both for ZrH materials"
568
+ },
569
+ "uranium_hydride": {
570
+ "name": "c_H_in_UH3",
571
+ "description": "Hydrogen in uranium hydride",
572
+ "usage": "material.add_s_alpha_beta('c_H_in_UH3')"
573
+ },
574
+ "note": "Thermal scattering is important for accurate moderation at thermal energies"
575
+ }
576
+ return {"success": True, "result": libraries, "error": None}
577
+ except Exception as e:
578
+ return {"success": False, "result": None, "error": str(e)}
579
 
580
+
581
+ @mcp.tool(name="list_tally_scores", description="List all available tally scores")
582
+ def list_tally_scores() -> dict:
583
+ """
584
+ List all available tally scores in OpenMC.
585
 
586
  Returns:
587
+ - dict: Tally scores organized by category.
588
  """
589
  try:
590
+ scores = {
591
+ "flux_and_current": {
592
+ "flux": "Scalar flux (track-length estimator)",
593
+ "current": "Current across a surface (requires SurfaceFilter)"
594
+ },
595
+ "reaction_rates": {
596
+ "total": "Total reaction rate",
597
+ "absorption": "Absorption reaction rate",
598
+ "scatter": "Scattering reaction rate",
599
+ "fission": "Fission reaction rate",
600
+ "nu-fission": "Fission neutron production rate (ν × fission)",
601
+ "prompt-nu-fission": "Prompt fission neutron production",
602
+ "delayed-nu-fission": "Delayed fission neutron production",
603
+ "kappa-fission": "Energy release from fission",
604
+ "(n,2n)": "(n,2n) reaction rate",
605
+ "(n,3n)": "(n,3n) reaction rate",
606
+ "(n,gamma)": "Radiative capture rate",
607
+ "(n,p)": "(n,p) reaction rate",
608
+ "(n,a)": "(n,alpha) reaction rate"
609
+ },
610
+ "energy_deposition": {
611
+ "heating": "Total energy deposition (MeV/source)",
612
+ "heating-local": "Local energy deposition",
613
+ "damage-energy": "Damage energy production (for DPA calculations)"
614
+ },
615
+ "other": {
616
+ "events": "Number of scoring events",
617
+ "inverse-velocity": "1/v for kinetics calculations",
618
+ "fission-q-prompt": "Prompt fission Q-value",
619
+ "fission-q-recoverable": "Recoverable fission Q-value"
620
+ },
621
+ "usage_note": "Scores can be combined in a single tally: tally.scores = ['flux', 'fission', 'heating']"
622
+ }
623
+ return {"success": True, "result": scores, "error": None}
624
  except Exception as e:
625
+ return {"success": False, "result": None, "error": str(e)}
626
+
627
 
628
  def create_app() -> FastMCP:
629
  """
630
  Create and return the FastMCP application instance.
631
 
632
  Returns:
633
+ - FastMCP: The FastMCP application instance.
634
  """
635
+ return mcp