uxarray-mcp 0.1.2__tar.gz → 0.2.0__tar.gz

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.
Files changed (39) hide show
  1. {uxarray_mcp-0.1.2 → uxarray_mcp-0.2.0}/PKG-INFO +75 -8
  2. {uxarray_mcp-0.1.2 → uxarray_mcp-0.2.0}/README.md +74 -7
  3. uxarray_mcp-0.2.0/pyproject.toml +108 -0
  4. uxarray_mcp-0.1.2/pyproject.toml → uxarray_mcp-0.2.0/pyproject.toml.orig +5 -1
  5. {uxarray_mcp-0.1.2 → uxarray_mcp-0.2.0}/src/uxarray_mcp/__init__.py +1 -1
  6. {uxarray_mcp-0.1.2 → uxarray_mcp-0.2.0}/src/uxarray_mcp/cli.py +46 -12
  7. {uxarray_mcp-0.1.2 → uxarray_mcp-0.2.0}/src/uxarray_mcp/domain/area.py +7 -2
  8. {uxarray_mcp-0.1.2 → uxarray_mcp-0.2.0}/src/uxarray_mcp/domain/mesh.py +8 -3
  9. uxarray_mcp-0.2.0/src/uxarray_mcp/domain/vector_calc.py +518 -0
  10. {uxarray_mcp-0.1.2 → uxarray_mcp-0.2.0}/src/uxarray_mcp/provenance.py +17 -0
  11. {uxarray_mcp-0.1.2 → uxarray_mcp-0.2.0}/src/uxarray_mcp/registry.py +4 -4
  12. {uxarray_mcp-0.1.2 → uxarray_mcp-0.2.0}/src/uxarray_mcp/remote/agent.py +58 -6
  13. {uxarray_mcp-0.1.2 → uxarray_mcp-0.2.0}/src/uxarray_mcp/remote/compute_functions.py +628 -32
  14. {uxarray_mcp-0.1.2 → uxarray_mcp-0.2.0}/src/uxarray_mcp/state.py +30 -13
  15. {uxarray_mcp-0.1.2 → uxarray_mcp-0.2.0}/src/uxarray_mcp/tools/advanced.py +170 -2
  16. {uxarray_mcp-0.1.2 → uxarray_mcp-0.2.0}/src/uxarray_mcp/tools/capabilities.py +228 -48
  17. {uxarray_mcp-0.1.2 → uxarray_mcp-0.2.0}/src/uxarray_mcp/tools/execution_control.py +35 -6
  18. {uxarray_mcp-0.1.2 → uxarray_mcp-0.2.0}/src/uxarray_mcp/tools/frontdoor.py +58 -3
  19. {uxarray_mcp-0.1.2 → uxarray_mcp-0.2.0}/src/uxarray_mcp/tools/inspection.py +57 -16
  20. {uxarray_mcp-0.1.2 → uxarray_mcp-0.2.0}/src/uxarray_mcp/tools/orchestration.py +39 -3
  21. {uxarray_mcp-0.1.2 → uxarray_mcp-0.2.0}/src/uxarray_mcp/tools/scientific_agent.py +1 -1
  22. {uxarray_mcp-0.1.2 → uxarray_mcp-0.2.0}/src/uxarray_mcp/tools/vector_calc.py +95 -12
  23. uxarray_mcp-0.1.2/src/uxarray_mcp/domain/vector_calc.py +0 -270
  24. {uxarray_mcp-0.1.2 → uxarray_mcp-0.2.0}/LICENSE +0 -0
  25. {uxarray_mcp-0.1.2 → uxarray_mcp-0.2.0}/src/uxarray_mcp/__main__.py +0 -0
  26. {uxarray_mcp-0.1.2 → uxarray_mcp-0.2.0}/src/uxarray_mcp/app.py +0 -0
  27. {uxarray_mcp-0.1.2 → uxarray_mcp-0.2.0}/src/uxarray_mcp/domain/__init__.py +0 -0
  28. {uxarray_mcp-0.1.2 → uxarray_mcp-0.2.0}/src/uxarray_mcp/domain/plotting.py +0 -0
  29. {uxarray_mcp-0.1.2 → uxarray_mcp-0.2.0}/src/uxarray_mcp/domain/variable.py +0 -0
  30. {uxarray_mcp-0.1.2 → uxarray_mcp-0.2.0}/src/uxarray_mcp/domain/zonal.py +0 -0
  31. {uxarray_mcp-0.1.2 → uxarray_mcp-0.2.0}/src/uxarray_mcp/py.typed +0 -0
  32. {uxarray_mcp-0.1.2 → uxarray_mcp-0.2.0}/src/uxarray_mcp/remote/__init__.py +0 -0
  33. {uxarray_mcp-0.1.2 → uxarray_mcp-0.2.0}/src/uxarray_mcp/remote/config.py +0 -0
  34. {uxarray_mcp-0.1.2 → uxarray_mcp-0.2.0}/src/uxarray_mcp/remote/health.py +0 -0
  35. {uxarray_mcp-0.1.2 → uxarray_mcp-0.2.0}/src/uxarray_mcp/tools/__init__.py +0 -0
  36. {uxarray_mcp-0.1.2 → uxarray_mcp-0.2.0}/src/uxarray_mcp/tools/catalog.py +0 -0
  37. {uxarray_mcp-0.1.2 → uxarray_mcp-0.2.0}/src/uxarray_mcp/tools/plotting.py +0 -0
  38. {uxarray_mcp-0.1.2 → uxarray_mcp-0.2.0}/src/uxarray_mcp/tools/remote_tools.py +0 -0
  39. {uxarray_mcp-0.1.2 → uxarray_mcp-0.2.0}/src/uxarray_mcp/tools/stateful.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: uxarray-mcp
3
- Version: 0.1.2
3
+ Version: 0.2.0
4
4
  Summary: MCP server for analyzing unstructured meshes with UXarray
5
5
  Keywords: uxarray,mcp,unstructured grids,scientific computing,globus compute
6
6
  Author: Rajeev Jain, Dayan Abdulla
@@ -299,6 +299,7 @@ uv tool install --python 3.12 uxarray-mcp
299
299
  # Or from a fresh clone (developer path)
300
300
  git clone https://github.com/UXARRAY/uxarray-mcp-server.git
301
301
  cd uxarray-mcp-server && uv sync --python 3.12
302
+ # or: bash SETUP.sh (does the sync + runs the local test suite in one step)
302
303
  ```
303
304
 
304
305
  > **Why `--python 3.12`?** The server uses Globus Compute to submit work to
@@ -350,8 +351,9 @@ client's MCP docs.
350
351
  uxarray-mcp doctor
351
352
  ```
352
353
 
353
- Should print `local execution: ok` and (if no endpoints configured) skip the
354
- remote checks.
354
+ Prints a JSON diagnostic report. With no endpoints configured it reports a
355
+ passing local setup and skips the remote checks; the process exits `0` when
356
+ `passed` is true.
355
357
 
356
358
  ### Step 5 — Ask the AI to do something
357
359
 
@@ -361,6 +363,19 @@ In your client, try:
361
363
 
362
364
  That's it for local use.
363
365
 
366
+ **A few more things to try:**
367
+
368
+ - `Use run_analysis with operation="inspect_mesh" and grid_path="healpix:4"` —
369
+ no sample file needed; HEALPix meshes are generated on the fly.
370
+ - `Run a complete scientific analysis on healpix:4` — the autonomous
371
+ Analyze → Plan → Execute → Verify agent (see
372
+ [docs/scientific-agent.md](docs/scientific-agent.md)).
373
+ - `Create a session called baseline-analysis, register <grid> and <data> in
374
+ it, then run the workflow for <variable>` — persisted, resumable
375
+ multi-step runs (see [docs/workflows.md](docs/workflows.md)).
376
+ - `Diagnose my configured endpoint status` — once you've added an endpoint
377
+ below, this is the fastest way to check it's healthy.
378
+
364
379
  ---
365
380
 
366
381
  ## Going beyond your laptop
@@ -380,21 +395,55 @@ Both paths assume you've finished local install above.
380
395
 
381
396
  ## What the MCP exposes
382
397
 
383
- Intent-shaped tools, not raw UXarray bindings:
398
+ Intent-shaped tools, not raw UXarray bindings — all local by default:
384
399
 
385
400
  - `get_capabilities` — what can I do with this mesh?
386
401
  - `analyze_dataset` — deterministic first-look: inspect, validate, area, zonal mean, plots.
387
402
  - `run_analysis` — one operation at a time (gradient, curl, subset, remap, …).
388
403
  - `plot_dataset` — mesh, geographic, variable, or zonal-mean plots.
389
- - `diagnose_endpoint`, `probe_path_access` — endpoint health + file readability.
390
404
  - `run_workflow`, `resume_workflow`, `get_status`, `get_result`, `manage_session` —
391
405
  persisted sessions and multi-step workflows.
392
406
 
393
- Tools that can run remotely take `use_remote: bool` and optional `endpoint: str`.
394
- The dispatcher falls back to local if the endpoint is unhealthy.
395
-
396
407
  Full schema: [docs/tools.md](docs/tools.md).
397
408
 
409
+ **Once you've configured an HPC endpoint** (optional — see
410
+ [Going beyond your laptop](#going-beyond-your-laptop) below): most tools above
411
+ also take `use_remote: bool` and `endpoint: str`, falling back to local if the
412
+ endpoint is unhealthy. Two more tools exist purely for that case:
413
+ `diagnose_endpoint` and `probe_path_access` (endpoint health + file
414
+ readability). Ignore all of this until you actually have an endpoint to point
415
+ at.
416
+
417
+ ---
418
+
419
+ ## Transparency & correctness safeguards
420
+
421
+ Because agent-driven analysis needs to be *trustworthy*, every result is
422
+ auditable and the server actively flags common scientific pitfalls:
423
+
424
+ - **Provenance on everything.** Each result carries a `_provenance` block:
425
+ the tool that ran, timestamp, input arguments, `execution_venue`
426
+ (`local` or `hpc:<endpoint>`), and the UXarray/Python versions used.
427
+ - **Derivative unit convention is never hidden.** `gradient`, `curl`, and
428
+ `divergence` echo `scale_by_radius` in both the result and provenance, so a
429
+ unit-sphere result can never be mistaken for a physical (per-metre) one.
430
+ Gradient and curl default to physical scaling, matching UXarray; pass
431
+ `scale_by_radius=False` explicitly for unit-sphere output.
432
+ - **Vector-calculus sanity guard.** `curl`/`divergence` warn (without blocking)
433
+ when the two inputs are the same field, or when neither carries a
434
+ velocity/flux-like `units` attribute — the classic "vorticity from two random
435
+ scalars" mistake now surfaces a warning in `_provenance.warnings` and a
436
+ machine-actionable `scientific_status` with stable warning codes.
437
+ - **Applicability is not suitability.** `get_capabilities` reports whether
438
+ vector operations are structurally computable separately from whether
439
+ metadata supports physical interpretation.
440
+ - **Local/remote version drift is surfaced.** Remote results record the
441
+ worker's *actual* UXarray version (`remote_uxarray_version`) and emit a
442
+ warning when it differs from the local version, so silent numerical
443
+ differences between venues can't slip through.
444
+ - **Validation gating.** `analyze_dataset` validates a dataset (NaN/Inf/fill
445
+ checks) before computing statistics like the zonal mean.
446
+
398
447
  ---
399
448
 
400
449
  ## CLI reference
@@ -410,6 +459,24 @@ Full schema: [docs/tools.md](docs/tools.md).
410
459
 
411
460
  ---
412
461
 
462
+ ## Upgrading
463
+
464
+ ```bash
465
+ uv tool upgrade --python 3.12 uxarray-mcp # or your original install method
466
+ ```
467
+
468
+ > **⚠️ Restart your AI client after upgrading.** MCP servers are launched once
469
+ > when your client (Claude Desktop, Claude Code, Cursor, …) starts and are **not
470
+ > hot-reloaded**. After upgrading the package, **fully quit and reopen your AI
471
+ > client** so it relaunches `uxarray-mcp serve` with the new code. Until you do,
472
+ > the running server keeps executing the *old* version — new tools and fixes
473
+ > won't appear, and you may see confusing errors (for example, a `use_remote`
474
+ > call on an HPC-only path failing with "file not found" because the old,
475
+ > local-only tool is still loaded). If in doubt, run `uxarray-mcp doctor` and
476
+ > check the reported version.
477
+
478
+ ---
479
+
413
480
  ## Risks (read before relying on output)
414
481
 
415
482
  AI agents can misread prompts, pick the wrong file, get units wrong (e.g.,
@@ -63,6 +63,7 @@ uv tool install --python 3.12 uxarray-mcp
63
63
  # Or from a fresh clone (developer path)
64
64
  git clone https://github.com/UXARRAY/uxarray-mcp-server.git
65
65
  cd uxarray-mcp-server && uv sync --python 3.12
66
+ # or: bash SETUP.sh (does the sync + runs the local test suite in one step)
66
67
  ```
67
68
 
68
69
  > **Why `--python 3.12`?** The server uses Globus Compute to submit work to
@@ -114,8 +115,9 @@ client's MCP docs.
114
115
  uxarray-mcp doctor
115
116
  ```
116
117
 
117
- Should print `local execution: ok` and (if no endpoints configured) skip the
118
- remote checks.
118
+ Prints a JSON diagnostic report. With no endpoints configured it reports a
119
+ passing local setup and skips the remote checks; the process exits `0` when
120
+ `passed` is true.
119
121
 
120
122
  ### Step 5 — Ask the AI to do something
121
123
 
@@ -125,6 +127,19 @@ In your client, try:
125
127
 
126
128
  That's it for local use.
127
129
 
130
+ **A few more things to try:**
131
+
132
+ - `Use run_analysis with operation="inspect_mesh" and grid_path="healpix:4"` —
133
+ no sample file needed; HEALPix meshes are generated on the fly.
134
+ - `Run a complete scientific analysis on healpix:4` — the autonomous
135
+ Analyze → Plan → Execute → Verify agent (see
136
+ [docs/scientific-agent.md](docs/scientific-agent.md)).
137
+ - `Create a session called baseline-analysis, register <grid> and <data> in
138
+ it, then run the workflow for <variable>` — persisted, resumable
139
+ multi-step runs (see [docs/workflows.md](docs/workflows.md)).
140
+ - `Diagnose my configured endpoint status` — once you've added an endpoint
141
+ below, this is the fastest way to check it's healthy.
142
+
128
143
  ---
129
144
 
130
145
  ## Going beyond your laptop
@@ -144,21 +159,55 @@ Both paths assume you've finished local install above.
144
159
 
145
160
  ## What the MCP exposes
146
161
 
147
- Intent-shaped tools, not raw UXarray bindings:
162
+ Intent-shaped tools, not raw UXarray bindings — all local by default:
148
163
 
149
164
  - `get_capabilities` — what can I do with this mesh?
150
165
  - `analyze_dataset` — deterministic first-look: inspect, validate, area, zonal mean, plots.
151
166
  - `run_analysis` — one operation at a time (gradient, curl, subset, remap, …).
152
167
  - `plot_dataset` — mesh, geographic, variable, or zonal-mean plots.
153
- - `diagnose_endpoint`, `probe_path_access` — endpoint health + file readability.
154
168
  - `run_workflow`, `resume_workflow`, `get_status`, `get_result`, `manage_session` —
155
169
  persisted sessions and multi-step workflows.
156
170
 
157
- Tools that can run remotely take `use_remote: bool` and optional `endpoint: str`.
158
- The dispatcher falls back to local if the endpoint is unhealthy.
159
-
160
171
  Full schema: [docs/tools.md](docs/tools.md).
161
172
 
173
+ **Once you've configured an HPC endpoint** (optional — see
174
+ [Going beyond your laptop](#going-beyond-your-laptop) below): most tools above
175
+ also take `use_remote: bool` and `endpoint: str`, falling back to local if the
176
+ endpoint is unhealthy. Two more tools exist purely for that case:
177
+ `diagnose_endpoint` and `probe_path_access` (endpoint health + file
178
+ readability). Ignore all of this until you actually have an endpoint to point
179
+ at.
180
+
181
+ ---
182
+
183
+ ## Transparency & correctness safeguards
184
+
185
+ Because agent-driven analysis needs to be *trustworthy*, every result is
186
+ auditable and the server actively flags common scientific pitfalls:
187
+
188
+ - **Provenance on everything.** Each result carries a `_provenance` block:
189
+ the tool that ran, timestamp, input arguments, `execution_venue`
190
+ (`local` or `hpc:<endpoint>`), and the UXarray/Python versions used.
191
+ - **Derivative unit convention is never hidden.** `gradient`, `curl`, and
192
+ `divergence` echo `scale_by_radius` in both the result and provenance, so a
193
+ unit-sphere result can never be mistaken for a physical (per-metre) one.
194
+ Gradient and curl default to physical scaling, matching UXarray; pass
195
+ `scale_by_radius=False` explicitly for unit-sphere output.
196
+ - **Vector-calculus sanity guard.** `curl`/`divergence` warn (without blocking)
197
+ when the two inputs are the same field, or when neither carries a
198
+ velocity/flux-like `units` attribute — the classic "vorticity from two random
199
+ scalars" mistake now surfaces a warning in `_provenance.warnings` and a
200
+ machine-actionable `scientific_status` with stable warning codes.
201
+ - **Applicability is not suitability.** `get_capabilities` reports whether
202
+ vector operations are structurally computable separately from whether
203
+ metadata supports physical interpretation.
204
+ - **Local/remote version drift is surfaced.** Remote results record the
205
+ worker's *actual* UXarray version (`remote_uxarray_version`) and emit a
206
+ warning when it differs from the local version, so silent numerical
207
+ differences between venues can't slip through.
208
+ - **Validation gating.** `analyze_dataset` validates a dataset (NaN/Inf/fill
209
+ checks) before computing statistics like the zonal mean.
210
+
162
211
  ---
163
212
 
164
213
  ## CLI reference
@@ -174,6 +223,24 @@ Full schema: [docs/tools.md](docs/tools.md).
174
223
 
175
224
  ---
176
225
 
226
+ ## Upgrading
227
+
228
+ ```bash
229
+ uv tool upgrade --python 3.12 uxarray-mcp # or your original install method
230
+ ```
231
+
232
+ > **⚠️ Restart your AI client after upgrading.** MCP servers are launched once
233
+ > when your client (Claude Desktop, Claude Code, Cursor, …) starts and are **not
234
+ > hot-reloaded**. After upgrading the package, **fully quit and reopen your AI
235
+ > client** so it relaunches `uxarray-mcp serve` with the new code. Until you do,
236
+ > the running server keeps executing the *old* version — new tools and fixes
237
+ > won't appear, and you may see confusing errors (for example, a `use_remote`
238
+ > call on an HPC-only path failing with "file not found" because the old,
239
+ > local-only tool is still loaded). If in doubt, run `uxarray-mcp doctor` and
240
+ > check the reported version.
241
+
242
+ ---
243
+
177
244
  ## Risks (read before relying on output)
178
245
 
179
246
  AI agents can misread prompts, pick the wrong file, get units wrong (e.g.,
@@ -0,0 +1,108 @@
1
+ [project]
2
+ name = "uxarray-mcp"
3
+ version = "0.2.0"
4
+ description = "MCP server for analyzing unstructured meshes with UXarray"
5
+ readme = "README.md"
6
+ keywords = [
7
+ "uxarray",
8
+ "mcp",
9
+ "unstructured grids",
10
+ "scientific computing",
11
+ "globus compute",
12
+ ]
13
+ classifiers = [
14
+ "Operating System :: OS Independent",
15
+ "Intended Audience :: Science/Research",
16
+ "Topic :: Scientific/Engineering",
17
+ "Programming Language :: Python",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.12",
20
+ "License :: OSI Approved :: Apache Software License",
21
+ ]
22
+ requires-python = ">=3.12,<3.13"
23
+ dependencies = [
24
+ "toolregistry-server[mcp]>=0.4.0",
25
+ "holoviews>=1.19.0",
26
+ "matplotlib>=3.9.0",
27
+ "pyyaml>=6.0",
28
+ "uxarray>=2026.6.0",
29
+ ]
30
+
31
+ [[project.authors]]
32
+ name = "Rajeev Jain"
33
+ email = "rajeeja@gmail.com"
34
+
35
+ [[project.authors]]
36
+ name = "Dayan Abdulla"
37
+ email = "dayanabdulla000@gmail.com"
38
+
39
+ [project.license]
40
+ file = "LICENSE"
41
+
42
+ [project.optional-dependencies]
43
+ openapi = ["toolregistry-server[openapi]>=0.4.0"]
44
+ hpc = [
45
+ "academy-py>=0.3.1",
46
+ "globus-compute-sdk>=4.5.0",
47
+ ]
48
+ docs = [
49
+ "sphinx>=7.0",
50
+ "sphinx-book-theme>=1.1.0",
51
+ "myst-parser>=3.0",
52
+ ]
53
+
54
+ [project.scripts]
55
+ uxarray-mcp = "uxarray_mcp.cli:main"
56
+
57
+ [project.urls]
58
+ Documentation = "https://uxarray-mcp-server.readthedocs.io"
59
+ Source = "https://github.com/UXARRAY/uxarray-mcp-server"
60
+ Tracker = "https://github.com/UXARRAY/uxarray-mcp-server/issues"
61
+
62
+ [dependency-groups]
63
+ dev = [
64
+ "mypy>=1.10.0",
65
+ "pre-commit>=4.3.0",
66
+ "pytest>=9.0.2",
67
+ "pytest-asyncio>=1.0.0",
68
+ "ruff>=0.15.2",
69
+ ]
70
+
71
+ [tool.mypy]
72
+ python_version = "3.12"
73
+ check_untyped_defs = true
74
+ ignore_missing_imports = true
75
+ warn_unused_ignores = true
76
+ warn_return_any = false
77
+ exclude = [
78
+ "tests/",
79
+ "docs/",
80
+ ]
81
+
82
+ [[tool.mypy.overrides]]
83
+ module = "yaml"
84
+ ignore_missing_imports = true
85
+
86
+ [[tool.mypy.overrides]]
87
+ module = "uxarray_mcp.remote.agent"
88
+ warn_unused_ignores = false
89
+
90
+ [tool.ruff]
91
+ extend-exclude = ["docs/architecture.html"]
92
+
93
+ [tool.ruff.lint]
94
+ extend-select = ["I"]
95
+
96
+ [tool.ruff.lint.per-file-ignores]
97
+ "notebooks/*.py" = ["E402"]
98
+ "notebooks/*.ipynb" = ["E402"]
99
+
100
+ [tool.ruff.lint.isort]
101
+ known-first-party = ["uxarray_mcp"]
102
+
103
+ [tool.ruff.format]
104
+ docstring-code-format = true
105
+
106
+ [build-system]
107
+ requires = ["uv_build>=0.9.26,<0.10.0"]
108
+ build-backend = "uv_build"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "uxarray-mcp"
3
- version = "0.1.2"
3
+ version = "0.2.0"
4
4
  description = "MCP server for analyzing unstructured meshes with UXarray"
5
5
  readme = "README.md"
6
6
  keywords = ["uxarray", "mcp", "unstructured grids", "scientific computing", "globus compute"]
@@ -93,6 +93,10 @@ extend-exclude = ["docs/architecture.html"]
93
93
 
94
94
  [tool.ruff.lint]
95
95
  extend-select = ["I"]
96
+ # Notebooks (and their jupytext-paired .py scripts) intentionally scatter
97
+ # imports across per-cell (# %%) blocks, so E402 (module-import-not-at-top)
98
+ # is expected style there, not a lint violation.
99
+ per-file-ignores = { "notebooks/*.py" = ["E402"], "notebooks/*.ipynb" = ["E402"] }
96
100
 
97
101
  [tool.ruff.lint.isort]
98
102
  known-first-party = ["uxarray_mcp"]
@@ -3,4 +3,4 @@
3
3
  from uxarray_mcp.tools import inspect_mesh
4
4
 
5
5
  __all__ = ["inspect_mesh"]
6
- __version__ = "0.1.2"
6
+ __version__ = "0.2.0"
@@ -308,8 +308,8 @@ def build_parser() -> argparse.ArgumentParser:
308
308
  choices=("core", "deferred-full"),
309
309
  default="core",
310
310
  help=(
311
- "core: gateway + control + list_datasets + prompts (~27 tools). "
312
- "deferred-full: also load 30 raw tools as deferred, gated "
311
+ "core: gateway + control + list_datasets + prompts (~31 tools). "
312
+ "deferred-full: also load 32 raw tools as deferred, gated "
313
313
  "behind discover_tools / admin promotion."
314
314
  ),
315
315
  )
@@ -317,7 +317,7 @@ def build_parser() -> argparse.ArgumentParser:
317
317
  "--transport",
318
318
  choices=("stdio", "sse", "http"),
319
319
  default="stdio",
320
- help="MCP transport. stdio for Claude Desktop subprocess use.",
320
+ help="MCP transport. stdio for subprocess use by MCP clients.",
321
321
  )
322
322
  serve.add_argument("--host", default="127.0.0.1", help="Bind host for SSE/HTTP.")
323
323
  serve.add_argument("--port", type=int, default=8001, help="Port for SSE/HTTP.")
@@ -330,8 +330,8 @@ def build_parser() -> argparse.ArgumentParser:
330
330
  choices=("core", "deferred-full"),
331
331
  default="core",
332
332
  help=(
333
- "core: gateway + control + list_datasets + prompts (~27 tools). "
334
- "deferred-full: also load 30 raw tools as deferred, gated "
333
+ "core: gateway + control + list_datasets + prompts (~31 tools). "
334
+ "deferred-full: also load 32 raw tools as deferred, gated "
335
335
  "behind discover_tools / admin promotion."
336
336
  ),
337
337
  )
@@ -347,7 +347,11 @@ def build_parser() -> argparse.ArgumentParser:
347
347
  "--execution-mode",
348
348
  default="auto",
349
349
  choices=["local", "auto", "hpc"],
350
- help="Default execution mode (default: auto).",
350
+ help=(
351
+ "Default execution mode (default: auto). 'auto' runs local-only "
352
+ "until you add an endpoint with `endpoints add`, then uses it "
353
+ "automatically. Most users never need to change this."
354
+ ),
351
355
  )
352
356
  setup.add_argument("--force", action="store_true", help="Overwrite existing file.")
353
357
  setup.set_defaults(func=cmd_setup)
@@ -377,12 +381,42 @@ def build_parser() -> argparse.ArgumentParser:
377
381
  ep_remove.add_argument("name")
378
382
  ep_remove.set_defaults(func=cmd_endpoints_remove)
379
383
 
380
- doctor = sub.add_parser("doctor", help="Validate HPC readiness.")
381
- doctor.add_argument("--sample-path", action="append", default=[])
382
- doctor.add_argument("--timeout-seconds", type=int, default=180)
383
- doctor.add_argument("--endpoint", default=None)
384
- doctor.add_argument("--skip-remote-probe", action="store_true")
385
- doctor.add_argument("--no-netcdf", action="store_true")
384
+ doctor = sub.add_parser(
385
+ "doctor",
386
+ help="Check that the install is healthy (local-only or HPC).",
387
+ description=(
388
+ "Run local-only by default; add an HPC endpoint (`endpoints add`) "
389
+ "to also validate remote readiness. With no endpoint configured, "
390
+ "this reports a passing local-only result — HPC is opt-in."
391
+ ),
392
+ )
393
+ doctor.add_argument(
394
+ "--sample-path",
395
+ action="append",
396
+ default=[],
397
+ help="Remote file path to test-read (repeatable). Requires an endpoint.",
398
+ )
399
+ doctor.add_argument(
400
+ "--timeout-seconds",
401
+ type=int,
402
+ default=180,
403
+ help="Seconds to wait for the remote worker probe (ignored if no endpoint).",
404
+ )
405
+ doctor.add_argument(
406
+ "--endpoint",
407
+ default=None,
408
+ help="Named endpoint to validate. Omit to check the default/local-only setup.",
409
+ )
410
+ doctor.add_argument(
411
+ "--skip-remote-probe",
412
+ action="store_true",
413
+ help="Skip submitting a test job to the endpoint (config/auth checks only).",
414
+ )
415
+ doctor.add_argument(
416
+ "--no-netcdf",
417
+ action="store_true",
418
+ help="Skip opening --sample-path as NetCDF; just check readability.",
419
+ )
386
420
  doctor.set_defaults(func=cmd_doctor)
387
421
 
388
422
  claude = sub.add_parser(
@@ -14,11 +14,16 @@ def compute_area_stats(grid: Any) -> dict:
14
14
  Returns
15
15
  -------
16
16
  dict
17
- Keys: total_area, mean_area, min_area, max_area, area_units, n_face
17
+ Keys: total_area, mean_area, min_area, max_area, area_units, n_face.
18
+ ``area_units`` is ``None`` when the grid carries no ``units``
19
+ attribute at all -- reporting a fabricated ``"m^2"`` default in
20
+ that case would silently invent metadata the source file never
21
+ provided, which is the exact failure mode this server's
22
+ provenance and guardrail mechanisms exist to prevent.
18
23
  """
19
24
  face_areas = grid.face_areas
20
25
 
21
- area_units = "m^2"
26
+ area_units = None
22
27
  if hasattr(face_areas, "attrs") and "units" in face_areas.attrs:
23
28
  area_units = face_areas.attrs["units"]
24
29
 
@@ -47,17 +47,22 @@ def load_dataset(grid_path: str, data_path: str) -> Any:
47
47
  Loaded dataset object.
48
48
  """
49
49
  import uxarray as ux
50
+ import xarray as xr
50
51
 
51
- # HEALPix is a special case (usually grid-only, but we support it if matched)
52
+ # HEALPix and GIS grids don't round-trip through ux.open_dataset() as a
53
+ # "grid file" the way a real UGRID/MPAS/SCRIP file does: grid.to_xarray()
54
+ # returns a minimal representation (e.g. HEALPix has no node coordinates)
55
+ # that the generic UGRID reader rejects. Attach the data directly to the
56
+ # already-loaded Grid object instead.
52
57
  if grid_path.lower().startswith("healpix"):
53
58
  parts = grid_path.split(":")
54
59
  zoom = int(parts[1]) if len(parts) > 1 else 1
55
60
  grid = ux.Grid.from_healpix(zoom=zoom)
56
- return ux.open_dataset(grid.to_xarray(), data_path)
61
+ return ux.UxDataset(xr.open_dataset(data_path), uxgrid=grid)
57
62
 
58
63
  ext = os.path.splitext(grid_path.lower())[1]
59
64
  if ext in [".shp", ".geojson"]:
60
65
  grid = ux.Grid.from_file(grid_path, backend="geopandas")
61
- return ux.open_dataset(grid.to_xarray(), data_path)
66
+ return ux.UxDataset(xr.open_dataset(data_path), uxgrid=grid)
62
67
 
63
68
  return ux.open_dataset(grid_path, data_path)