guohanghui commited on
Commit
5ed2dd8
·
verified ·
1 Parent(s): a071eef

Update xmitgcm/mcp_output/mcp_plugin/mcp_service.py

Browse files
xmitgcm/mcp_output/mcp_plugin/mcp_service.py CHANGED
@@ -1,146 +1,414 @@
 
 
 
 
 
 
 
 
 
1
  from fastmcp import FastMCP
2
- from xmitgcm import open_mdsdataset
3
 
4
- # Create the FastMCP service application
 
 
 
5
  mcp = FastMCP("xmitgcm_service")
6
 
7
- @mcp.tool(name="list_available_datasets", description="List all available MITgcm datasets")
8
- def list_available_datasets() -> dict:
 
 
 
9
  """
10
- List all available MITgcm datasets.
11
 
12
  Returns:
13
- - dict: A dictionary with success status and list of available datasets.
14
  """
15
  try:
16
- # Example implementation (replace with actual logic)
17
- datasets = ["dataset1", "dataset2", "dataset3"]
18
- return {"success": True, "datasets": datasets}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  except Exception as e:
20
- return {"success": False, "error": str(e)}
21
 
22
- @mcp.tool(name="get_dataset_info", description="Get information about a specific dataset")
23
- def get_dataset_info(dataset_name: str) -> dict:
 
24
  """
25
- Get detailed information about a specific MITgcm dataset.
26
 
27
  Parameters:
28
- - dataset_name: Name of the dataset
29
 
30
  Returns:
31
- - dict: Dataset information including dimensions, variables, etc.
32
  """
33
  try:
34
- # Example implementation (replace with actual logic)
35
- info = {
36
- "name": dataset_name,
37
- "dimensions": ["X", "Y", "Z", "time"],
38
- "variables": ["temperature", "salinity", "velocity"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  }
40
- return {"success": True, "info": info}
 
 
 
 
 
 
 
 
41
  except Exception as e:
42
- return {"success": False, "error": str(e)}
 
43
 
44
- @mcp.tool(name="load_dataset", description="Load a MITgcm dataset")
45
- def load_dataset(dataset_path: str, grid_dir: str = None, iters: list = None) -> dict:
 
 
46
  """
47
- Load a MITgcm dataset.
48
 
49
  Parameters:
50
- - dataset_path: Path to the dataset directory
51
- - grid_dir: Path to the grid directory (optional)
52
- - iters: List of iterations to load (optional)
53
 
54
  Returns:
55
- - dict: Information about the loaded dataset
56
  """
57
  try:
58
- ds = open_mdsdataset(dataset_path, grid_dir=grid_dir, iters=iters)
59
- return {"success": True, "dataset": str(ds)}
 
 
 
 
 
 
 
 
 
60
  except Exception as e:
61
- return {"success": False, "error": str(e)}
 
62
 
63
- @mcp.tool(name="download_dataset", description="Download a MITgcm dataset")
64
- def download_dataset(dataset_url: str, output_dir: str) -> dict:
 
 
65
  """
66
- Download a MITgcm dataset from a given URL.
67
 
68
  Parameters:
69
- - dataset_url: URL of the dataset to download
70
- - output_dir: Directory to save the downloaded dataset
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
 
72
  Returns:
73
- - dict: Status of the download
74
  """
75
  try:
76
- # Example implementation (replace with actual logic)
77
- # Download logic here
78
- return {"success": True, "message": f"Dataset downloaded to {output_dir}"}
79
  except Exception as e:
80
- return {"success": False, "error": str(e)}
81
 
82
- @mcp.tool(name="list_model_benchmarks", description="List available model benchmarks")
83
- def list_model_benchmarks() -> dict:
 
 
 
84
  """
85
- List available model benchmarks.
 
 
 
86
 
87
  Returns:
88
- - dict: A dictionary with success status and list of benchmarks.
89
  """
90
  try:
91
- # Example implementation (replace with actual logic)
92
- benchmarks = ["benchmark1", "benchmark2", "benchmark3"]
93
- return {"success": True, "benchmarks": benchmarks}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  except Exception as e:
95
- return {"success": False, "error": str(e)}
 
96
 
97
- @mcp.tool(name="get_model_benchmark", description="Get benchmark results for a specific model")
98
- def get_model_benchmark(model_name: str, dataset_name: str) -> dict:
 
 
99
  """
100
- Get benchmark results for a specific model and dataset.
101
 
102
  Parameters:
103
- - model_name: Name of the model
104
- - dataset_name: Name of the dataset
105
 
106
  Returns:
107
- - dict: Benchmark results
108
  """
109
  try:
110
- # Example implementation (replace with actual logic)
111
- results = {
112
- "model": model_name,
113
- "dataset": dataset_name,
114
- "accuracy": 0.95,
115
- "runtime": "10 minutes"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  }
117
- return {"success": True, "results": results}
118
  except Exception as e:
119
- return {"success": False, "error": str(e)}
120
 
121
- @mcp.tool(name="export_dataset_to_yolo", description="Export MITgcm dataset to YOLO format")
122
- def export_dataset_to_yolo(dataset_name: str, output_dir: str) -> dict:
 
 
 
123
  """
124
- Export a MITgcm dataset to YOLO format.
125
 
126
  Parameters:
127
- - dataset_name: Name of the dataset to export
128
- - output_dir: Directory to save the YOLO formatted dataset
129
 
130
  Returns:
131
- - dict: Status of the export
132
  """
133
  try:
134
- # Example implementation (replace with actual logic)
135
- return {"success": True, "message": f"Dataset {dataset_name} exported to {output_dir} in YOLO format."}
136
  except Exception as e:
137
- return {"success": False, "error": str(e)}
 
138
 
139
  def create_app() -> FastMCP:
140
  """
141
  Create and return the FastMCP application instance.
142
 
143
  Returns:
144
- - FastMCP: The FastMCP application instance.
145
  """
146
  return mcp
 
1
+ import os
2
+ import sys
3
+ from typing import List, Optional, Dict, Any
4
+
5
+ # Add the local source directory to sys.path
6
+ source_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "source")
7
+ if source_path not in sys.path:
8
+ sys.path.insert(0, source_path)
9
+
10
  from fastmcp import FastMCP
 
11
 
12
+ # Import xmitgcm modules
13
+ from xmitgcm import utils, file_utils
14
+ from xmitgcm.llcreader import known_models
15
+
16
  mcp = FastMCP("xmitgcm_service")
17
 
18
+
19
+ # ===================== LLC Model Tools =====================
20
+
21
+ @mcp.tool(name="get_known_models", description="Retrieve known LLC models")
22
+ def get_known_models_tool() -> dict:
23
  """
24
+ Retrieve list of known LLC (Lat-Lon-Cap) models available in xmitgcm.
25
 
26
  Returns:
27
+ - Dictionary containing available model classes and their descriptions.
28
  """
29
  try:
30
+ models_info = {
31
+ "LLC90Model": {
32
+ "nx": 90,
33
+ "nz": 50,
34
+ "delta_t": 3600,
35
+ "description": "LLC90 model configuration"
36
+ },
37
+ "LLC2160Model": {
38
+ "nx": 2160,
39
+ "nz": 90,
40
+ "delta_t": 45,
41
+ "description": "High-resolution LLC2160 model"
42
+ },
43
+ "LLC4320Model": {
44
+ "nx": 4320,
45
+ "nz": 90,
46
+ "delta_t": 25,
47
+ "description": "Ultra high-resolution LLC4320 model"
48
+ },
49
+ "ASTE270Model": {
50
+ "nx": 270,
51
+ "nz": 50,
52
+ "nface": 6,
53
+ "domain": "aste",
54
+ "description": "Arctic Subpolar gyre sTate Estimate model"
55
+ },
56
+ "ECCOPortalLLC2160Model": {
57
+ "description": "ECCO Portal LLC2160 model with remote data access"
58
+ },
59
+ "ECCOPortalLLC4320Model": {
60
+ "description": "ECCO Portal LLC4320 model with remote data access"
61
+ },
62
+ "CRIOSPortalASTE270Model": {
63
+ "description": "CRIOS Portal ASTE270 model with remote data access"
64
+ }
65
+ }
66
+ return {"success": True, "result": models_info, "error": None}
67
  except Exception as e:
68
+ return {"success": False, "result": None, "error": str(e)}
69
 
70
+
71
+ @mcp.tool(name="load_llc_model", description="Load LLC model data")
72
+ def load_llc_model(model_name: str) -> dict:
73
  """
74
+ Get information about an LLC model configuration.
75
 
76
  Parameters:
77
+ - model_name: Name of the LLC model (e.g., 'LLC90Model', 'LLC2160Model', 'LLC4320Model', 'ASTE270Model').
78
 
79
  Returns:
80
+ - Dictionary with model configuration details.
81
  """
82
  try:
83
+ model_configs = {
84
+ "LLC90Model": {
85
+ "nx": 90, "nz": 50, "delta_t": 3600,
86
+ "time_units": "seconds since 1948-01-01 12:00:00",
87
+ "calendar": "gregorian"
88
+ },
89
+ "LLC2160Model": {
90
+ "nx": 2160, "nz": 90, "delta_t": 45,
91
+ "iter_start": 92160, "iter_stop": 1586401, "iter_step": 80,
92
+ "time_units": "seconds since 2011-01-17",
93
+ "calendar": "gregorian",
94
+ "varnames": ['Eta', 'KPPhbl', 'oceFWflx', 'oceQnet', 'oceQsw', 'oceSflux',
95
+ 'oceTAUX', 'oceTAUY', 'PhiBot', 'Salt', 'SIarea', 'SIheff',
96
+ 'SIhsalt', 'SIhsnow', 'SIuice', 'SIvice', 'Theta', 'U', 'V', 'W']
97
+ },
98
+ "LLC4320Model": {
99
+ "nx": 4320, "nz": 90, "delta_t": 25,
100
+ "iter_start": 10368, "iter_stop": 1495153, "iter_step": 144,
101
+ "time_units": "seconds since 2011-09-10",
102
+ "calendar": "gregorian",
103
+ "varnames": ['Eta', 'KPPhbl', 'oceFWflx', 'oceQnet', 'oceQsw', 'oceSflux',
104
+ 'oceTAUX', 'oceTAUY', 'PhiBot', 'Salt', 'SIarea', 'SIheff',
105
+ 'SIhsalt', 'SIhsnow', 'SIuice', 'SIvice', 'Theta', 'U', 'V', 'W']
106
+ },
107
+ "ASTE270Model": {
108
+ "nface": 6, "nx": 270, "nz": 50, "domain": "aste",
109
+ "delta_t": 600,
110
+ "time_units": "seconds since 2002-01-01",
111
+ "calendar": "gregorian"
112
+ }
113
  }
114
+
115
+ if model_name in model_configs:
116
+ return {"success": True, "result": model_configs[model_name], "error": None}
117
+ else:
118
+ return {
119
+ "success": False,
120
+ "result": None,
121
+ "error": f"Unknown model: {model_name}. Available models: {list(model_configs.keys())}"
122
+ }
123
  except Exception as e:
124
+ return {"success": False, "result": None, "error": str(e)}
125
+
126
 
127
+ # ===================== MDS File Utilities =====================
128
+
129
+ @mcp.tool(name="parse_meta_file", description="Parse MITgcm .meta file to extract metadata")
130
+ def parse_meta_file_tool(fname: str) -> dict:
131
  """
132
+ Parse an MITgcm .meta file and extract metadata.
133
 
134
  Parameters:
135
+ - fname: Path to the .meta file.
 
 
136
 
137
  Returns:
138
+ - Dictionary containing parsed metadata (dimList, nDims, nrecords, dataprec, etc.).
139
  """
140
  try:
141
+ meta = utils.parse_meta_file(fname)
142
+ # Convert numpy types to Python native types for JSON serialization
143
+ result = {}
144
+ for key, value in meta.items():
145
+ if hasattr(value, 'tolist'):
146
+ result[key] = value.tolist()
147
+ elif hasattr(value, 'name'): # dtype
148
+ result[key] = str(value)
149
+ else:
150
+ result[key] = value
151
+ return {"success": True, "result": result, "error": None}
152
  except Exception as e:
153
+ return {"success": False, "result": None, "error": str(e)}
154
+
155
 
156
+ # ===================== File Utilities =====================
157
+
158
+ @mcp.tool(name="list_directory", description="List contents of a directory with caching")
159
+ def list_directory_tool(path: str) -> dict:
160
  """
161
+ List contents of a directory (with caching for performance).
162
 
163
  Parameters:
164
+ - path: Path to the directory.
165
+
166
+ Returns:
167
+ - List of files and directories in the path.
168
+ """
169
+ try:
170
+ files = file_utils.listdir(path)
171
+ return {"success": True, "result": files, "error": None}
172
+ except Exception as e:
173
+ return {"success": False, "result": None, "error": str(e)}
174
+
175
+
176
+ @mcp.tool(name="list_files_by_pattern", description="List files matching a pattern")
177
+ def list_files_by_pattern(path: str, pattern: str, match_type: str = "fnmatch") -> dict:
178
+ """
179
+ List files in a directory matching a specific pattern.
180
+
181
+ Parameters:
182
+ - path: Path to the directory.
183
+ - pattern: Pattern to match.
184
+ - match_type: Type of matching - 'startswith', 'endswith', or 'fnmatch' (default).
185
+
186
+ Returns:
187
+ - List of matching files.
188
+ """
189
+ try:
190
+ if match_type == "startswith":
191
+ files = file_utils.listdir_startswith(path, pattern)
192
+ elif match_type == "endswith":
193
+ files = file_utils.listdir_endswith(path, pattern)
194
+ else:
195
+ files = file_utils.listdir_fnmatch(path, pattern)
196
+ return {"success": True, "result": files, "error": None}
197
+ except Exception as e:
198
+ return {"success": False, "result": None, "error": str(e)}
199
+
200
+
201
+ @mcp.tool(name="clear_file_cache", description="Clear the file listing cache")
202
+ def clear_file_cache() -> dict:
203
+ """
204
+ Clear the cached file listings.
205
 
206
  Returns:
207
+ - Success status.
208
  """
209
  try:
210
+ file_utils.clear_cache()
211
+ return {"success": True, "result": "Cache cleared successfully", "error": None}
 
212
  except Exception as e:
213
+ return {"success": False, "result": None, "error": str(e)}
214
 
215
+
216
+ # ===================== Grid Metrics Tools =====================
217
+
218
+ @mcp.tool(name="calculate_grid_metrics", description="Calculate grid metrics")
219
+ def calculate_grid_metrics(grid_dir: str) -> dict:
220
  """
221
+ Get information about grid variables available in MITgcm.
222
+
223
+ Parameters:
224
+ - grid_dir: Directory containing grid files.
225
 
226
  Returns:
227
+ - Dictionary with grid variable information.
228
  """
229
  try:
230
+ # Return information about standard MITgcm grid variables
231
+ grid_info = {
232
+ "horizontal_grid_variables": [
233
+ "XC", "YC", "XG", "YG", "dxC", "dyC", "dxG", "dyG",
234
+ "dxF", "dyF", "dxV", "dyU", "rA", "rAw", "rAs", "rAz"
235
+ ],
236
+ "vertical_grid_variables": [
237
+ "drC", "drF", "Z", "Zp1", "Zu", "Zl", "PHrefC", "PHrefF"
238
+ ],
239
+ "volume_grid_variables": [
240
+ "hFacC", "hFacW", "hFacS", "Depth"
241
+ ],
242
+ "description": "Standard MITgcm grid variables for staggered Arakawa C-grid"
243
+ }
244
+
245
+ # Check if directory exists and list available grid files
246
+ if os.path.exists(grid_dir):
247
+ meta_files = [f for f in os.listdir(grid_dir) if f.endswith('.meta')]
248
+ data_files = [f for f in os.listdir(grid_dir) if f.endswith('.data')]
249
+ grid_info["available_meta_files"] = meta_files[:20] # Limit to 20
250
+ grid_info["available_data_files"] = data_files[:20]
251
+ grid_info["grid_dir_exists"] = True
252
+ else:
253
+ grid_info["grid_dir_exists"] = False
254
+ grid_info["message"] = f"Directory {grid_dir} does not exist"
255
+
256
+ return {"success": True, "result": grid_info, "error": None}
257
  except Exception as e:
258
+ return {"success": False, "result": None, "error": str(e)}
259
+
260
 
261
+ # ===================== Variable Metadata Tools =====================
262
+
263
+ @mcp.tool(name="get_variable_info", description="Get metadata for MITgcm variables")
264
+ def get_variable_info(variable_name: Optional[str] = None) -> dict:
265
  """
266
+ Get metadata information for MITgcm state variables.
267
 
268
  Parameters:
269
+ - variable_name: Optional specific variable name. If None, returns all available variables.
 
270
 
271
  Returns:
272
+ - Dictionary with variable metadata.
273
  """
274
  try:
275
+ from xmitgcm.variables import state_variables, package_state_variables
276
+
277
+ all_vars = {}
278
+ all_vars.update(state_variables)
279
+ all_vars.update(package_state_variables)
280
+
281
+ if variable_name:
282
+ if variable_name in all_vars:
283
+ var_info = all_vars[variable_name]
284
+ # Convert to JSON-serializable format
285
+ result = {
286
+ "name": variable_name,
287
+ "dims": var_info.get("dims", []),
288
+ "attrs": var_info.get("attrs", {})
289
+ }
290
+ return {"success": True, "result": result, "error": None}
291
+ else:
292
+ return {
293
+ "success": False,
294
+ "result": None,
295
+ "error": f"Variable '{variable_name}' not found. Use without variable_name to list all."
296
+ }
297
+ else:
298
+ # Return list of available variables
299
+ var_list = list(all_vars.keys())
300
+ return {"success": True, "result": {"available_variables": var_list, "count": len(var_list)}, "error": None}
301
+ except Exception as e:
302
+ return {"success": False, "result": None, "error": str(e)}
303
+
304
+
305
+ # ===================== Dimension and Coordinate Tools =====================
306
+
307
+ @mcp.tool(name="get_dimension_info", description="Get MITgcm grid dimension information")
308
+ def get_dimension_info() -> dict:
309
+ """
310
+ Get information about MITgcm grid dimensions and coordinates.
311
+
312
+ Returns:
313
+ - Dictionary with dimension metadata.
314
+ """
315
+ try:
316
+ from xmitgcm.variables import dimensions, vertical_coordinates
317
+
318
+ dims_info = {}
319
+ for dim_name, dim_data in dimensions.items():
320
+ dims_info[dim_name] = {
321
+ "dims": dim_data.get("dims", []),
322
+ "attrs": dim_data.get("attrs", {})
323
+ }
324
+
325
+ vert_info = {}
326
+ for coord_name, coord_data in vertical_coordinates.items():
327
+ vert_info[coord_name] = {
328
+ "dims": coord_data.get("dims", []),
329
+ "attrs": coord_data.get("attrs", {})
330
+ }
331
+
332
+ result = {
333
+ "dimensions": dims_info,
334
+ "vertical_coordinates": vert_info,
335
+ "description": "MITgcm uses staggered Arakawa C-grid with i,j horizontal and k vertical indices"
336
+ }
337
+ return {"success": True, "result": result, "error": None}
338
+ except Exception as e:
339
+ return {"success": False, "result": None, "error": str(e)}
340
+
341
+
342
+ # ===================== Geometry Tools =====================
343
+
344
+ @mcp.tool(name="get_geometry_info", description="Get information about supported grid geometries")
345
+ def get_geometry_info() -> dict:
346
+ """
347
+ Get information about MITgcm grid geometries supported by xmitgcm.
348
+
349
+ Returns:
350
+ - Dictionary describing available geometries.
351
+ """
352
+ try:
353
+ geometries = {
354
+ "sphericalpolar": {
355
+ "description": "Standard lat-lon spherical polar grid",
356
+ "coordinates": ["XC", "YC", "XG", "YG"],
357
+ "use_case": "Regional and global ocean models"
358
+ },
359
+ "cartesian": {
360
+ "description": "Cartesian coordinate grid",
361
+ "coordinates": ["XC", "YC", "XG", "YG"],
362
+ "use_case": "Idealized simulations, process studies"
363
+ },
364
+ "llc": {
365
+ "description": "Lat-Lon-Cap grid (13 faces)",
366
+ "coordinates": ["XC", "YC", "XG", "YG", "AngleCS", "AngleSN"],
367
+ "use_case": "Global ECCO simulations (LLC90, LLC2160, LLC4320)",
368
+ "num_faces": 13
369
+ },
370
+ "curvilinear": {
371
+ "description": "Curvilinear orthogonal grid",
372
+ "coordinates": ["XC", "YC", "XG", "YG", "AngleCS", "AngleSN"],
373
+ "use_case": "Regional models with complex boundaries"
374
+ },
375
+ "cs": {
376
+ "description": "Cubed-sphere grid (6 faces)",
377
+ "coordinates": ["XC", "YC", "XG", "YG", "AngleCS", "AngleSN"],
378
+ "use_case": "Global atmospheric and ocean models",
379
+ "num_faces": 6
380
+ }
381
  }
382
+ return {"success": True, "result": geometries, "error": None}
383
  except Exception as e:
384
+ return {"success": False, "result": None, "error": str(e)}
385
 
386
+
387
+ # ===================== Extra Metadata Tools =====================
388
+
389
+ @mcp.tool(name="get_extra_metadata", description="Get extra metadata for LLC/ASTE configurations")
390
+ def get_extra_metadata_tool(geometry: str = "llc90") -> dict:
391
  """
392
+ Get extra metadata needed for LLC and ASTE grid configurations.
393
 
394
  Parameters:
395
+ - geometry: Grid geometry type ('llc90', 'llc', 'aste', etc.)
 
396
 
397
  Returns:
398
+ - Dictionary with extra metadata for the specified geometry.
399
  """
400
  try:
401
+ extra_meta = utils.get_extra_metadata(geometry)
402
+ return {"success": True, "result": extra_meta, "error": None}
403
  except Exception as e:
404
+ return {"success": False, "result": None, "error": str(e)}
405
+
406
 
407
  def create_app() -> FastMCP:
408
  """
409
  Create and return the FastMCP application instance.
410
 
411
  Returns:
412
+ - FastMCP instance.
413
  """
414
  return mcp