uxarray-mcp 0.1.2__tar.gz → 0.1.3__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.
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/PKG-INFO +69 -8
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/README.md +68 -7
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/pyproject.toml +5 -1
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/src/uxarray_mcp/__init__.py +1 -1
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/src/uxarray_mcp/cli.py +46 -12
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/src/uxarray_mcp/domain/mesh.py +8 -3
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/src/uxarray_mcp/domain/vector_calc.py +84 -0
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/src/uxarray_mcp/registry.py +4 -4
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/src/uxarray_mcp/remote/agent.py +34 -2
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/src/uxarray_mcp/remote/compute_functions.py +448 -16
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/src/uxarray_mcp/state.py +0 -9
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/src/uxarray_mcp/tools/advanced.py +170 -2
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/src/uxarray_mcp/tools/capabilities.py +151 -42
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/src/uxarray_mcp/tools/execution_control.py +35 -6
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/src/uxarray_mcp/tools/frontdoor.py +41 -0
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/src/uxarray_mcp/tools/inspection.py +57 -16
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/src/uxarray_mcp/tools/vector_calc.py +8 -2
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/LICENSE +0 -0
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/src/uxarray_mcp/__main__.py +0 -0
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/src/uxarray_mcp/app.py +0 -0
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/src/uxarray_mcp/domain/__init__.py +0 -0
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/src/uxarray_mcp/domain/area.py +0 -0
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/src/uxarray_mcp/domain/plotting.py +0 -0
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/src/uxarray_mcp/domain/variable.py +0 -0
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/src/uxarray_mcp/domain/zonal.py +0 -0
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/src/uxarray_mcp/provenance.py +0 -0
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/src/uxarray_mcp/py.typed +0 -0
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/src/uxarray_mcp/remote/__init__.py +0 -0
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/src/uxarray_mcp/remote/config.py +0 -0
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/src/uxarray_mcp/remote/health.py +0 -0
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/src/uxarray_mcp/tools/__init__.py +0 -0
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/src/uxarray_mcp/tools/catalog.py +0 -0
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/src/uxarray_mcp/tools/orchestration.py +0 -0
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/src/uxarray_mcp/tools/plotting.py +0 -0
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/src/uxarray_mcp/tools/remote_tools.py +0 -0
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/src/uxarray_mcp/tools/scientific_agent.py +0 -0
- {uxarray_mcp-0.1.2 → uxarray_mcp-0.1.3}/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.
|
|
3
|
+
Version: 0.1.3
|
|
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
|
-
|
|
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,49 @@ 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
|
+
- **Vector-calculus sanity guard.** `curl`/`divergence` warn (without blocking)
|
|
431
|
+
when the two inputs are the same field, or when neither carries a
|
|
432
|
+
velocity/flux-like `units` attribute — the classic "vorticity from two random
|
|
433
|
+
scalars" mistake now surfaces a warning in `_provenance.warnings`.
|
|
434
|
+
- **Local/remote version drift is surfaced.** Remote results record the
|
|
435
|
+
worker's *actual* UXarray version (`remote_uxarray_version`) and emit a
|
|
436
|
+
warning when it differs from the local version, so silent numerical
|
|
437
|
+
differences between venues can't slip through.
|
|
438
|
+
- **Validation gating.** `analyze_dataset` validates a dataset (NaN/Inf/fill
|
|
439
|
+
checks) before computing statistics like the zonal mean.
|
|
440
|
+
|
|
398
441
|
---
|
|
399
442
|
|
|
400
443
|
## CLI reference
|
|
@@ -410,6 +453,24 @@ Full schema: [docs/tools.md](docs/tools.md).
|
|
|
410
453
|
|
|
411
454
|
---
|
|
412
455
|
|
|
456
|
+
## Upgrading
|
|
457
|
+
|
|
458
|
+
```bash
|
|
459
|
+
uv tool upgrade --python 3.12 uxarray-mcp # or your original install method
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
> **⚠️ Restart your AI client after upgrading.** MCP servers are launched once
|
|
463
|
+
> when your client (Claude Desktop, Claude Code, Cursor, …) starts and are **not
|
|
464
|
+
> hot-reloaded**. After upgrading the package, **fully quit and reopen your AI
|
|
465
|
+
> client** so it relaunches `uxarray-mcp serve` with the new code. Until you do,
|
|
466
|
+
> the running server keeps executing the *old* version — new tools and fixes
|
|
467
|
+
> won't appear, and you may see confusing errors (for example, a `use_remote`
|
|
468
|
+
> call on an HPC-only path failing with "file not found" because the old,
|
|
469
|
+
> local-only tool is still loaded). If in doubt, run `uxarray-mcp doctor` and
|
|
470
|
+
> check the reported version.
|
|
471
|
+
|
|
472
|
+
---
|
|
473
|
+
|
|
413
474
|
## Risks (read before relying on output)
|
|
414
475
|
|
|
415
476
|
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
|
-
|
|
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,49 @@ 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
|
+
- **Vector-calculus sanity guard.** `curl`/`divergence` warn (without blocking)
|
|
195
|
+
when the two inputs are the same field, or when neither carries a
|
|
196
|
+
velocity/flux-like `units` attribute — the classic "vorticity from two random
|
|
197
|
+
scalars" mistake now surfaces a warning in `_provenance.warnings`.
|
|
198
|
+
- **Local/remote version drift is surfaced.** Remote results record the
|
|
199
|
+
worker's *actual* UXarray version (`remote_uxarray_version`) and emit a
|
|
200
|
+
warning when it differs from the local version, so silent numerical
|
|
201
|
+
differences between venues can't slip through.
|
|
202
|
+
- **Validation gating.** `analyze_dataset` validates a dataset (NaN/Inf/fill
|
|
203
|
+
checks) before computing statistics like the zonal mean.
|
|
204
|
+
|
|
162
205
|
---
|
|
163
206
|
|
|
164
207
|
## CLI reference
|
|
@@ -174,6 +217,24 @@ Full schema: [docs/tools.md](docs/tools.md).
|
|
|
174
217
|
|
|
175
218
|
---
|
|
176
219
|
|
|
220
|
+
## Upgrading
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
uv tool upgrade --python 3.12 uxarray-mcp # or your original install method
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
> **⚠️ Restart your AI client after upgrading.** MCP servers are launched once
|
|
227
|
+
> when your client (Claude Desktop, Claude Code, Cursor, …) starts and are **not
|
|
228
|
+
> hot-reloaded**. After upgrading the package, **fully quit and reopen your AI
|
|
229
|
+
> client** so it relaunches `uxarray-mcp serve` with the new code. Until you do,
|
|
230
|
+
> the running server keeps executing the *old* version — new tools and fixes
|
|
231
|
+
> won't appear, and you may see confusing errors (for example, a `use_remote`
|
|
232
|
+
> call on an HPC-only path failing with "file not found" because the old,
|
|
233
|
+
> local-only tool is still loaded). If in doubt, run `uxarray-mcp doctor` and
|
|
234
|
+
> check the reported version.
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
177
238
|
## Risks (read before relying on output)
|
|
178
239
|
|
|
179
240
|
AI agents can misread prompts, pick the wrong file, get units wrong (e.g.,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "uxarray-mcp"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.3"
|
|
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"]
|
|
@@ -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 (~
|
|
312
|
-
"deferred-full: also load
|
|
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
|
|
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 (~
|
|
334
|
-
"deferred-full: also load
|
|
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=
|
|
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(
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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(
|
|
@@ -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
|
|
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.
|
|
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.
|
|
66
|
+
return ux.UxDataset(xr.open_dataset(data_path), uxgrid=grid)
|
|
62
67
|
|
|
63
68
|
return ux.open_dataset(grid_path, data_path)
|
|
@@ -4,6 +4,80 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
|
+
# Units that look like a genuine 2-D vector (velocity/flux) component. Used only
|
|
8
|
+
# to raise a soft, non-blocking warning when curl/divergence inputs do not look
|
|
9
|
+
# like real vector components — the math is still valid, but the result is only
|
|
10
|
+
# physically meaningful for true vector fields.
|
|
11
|
+
_VELOCITY_LIKE_UNIT_HINTS = (
|
|
12
|
+
"m/s",
|
|
13
|
+
"m s-1",
|
|
14
|
+
"m s^-1",
|
|
15
|
+
"meter/second",
|
|
16
|
+
"meters/second",
|
|
17
|
+
"cm/s",
|
|
18
|
+
"km/h",
|
|
19
|
+
"kg/m2/s",
|
|
20
|
+
"kg m-2 s-1",
|
|
21
|
+
"pa/s",
|
|
22
|
+
"n/m2",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _vector_component_warnings(
|
|
27
|
+
u_variable: str,
|
|
28
|
+
v_variable: str,
|
|
29
|
+
u: Any,
|
|
30
|
+
v: Any,
|
|
31
|
+
operation: str,
|
|
32
|
+
) -> list[str]:
|
|
33
|
+
"""Return soft warnings when (u, v) do not look like real vector components.
|
|
34
|
+
|
|
35
|
+
curl and divergence are only physically meaningful when ``u`` and ``v`` are
|
|
36
|
+
the two horizontal components of a genuine vector field (e.g. eastward and
|
|
37
|
+
northward velocity). The underlying finite-volume operators will happily
|
|
38
|
+
compute a number from *any* two scalar fields, so this guardrail flags the
|
|
39
|
+
common misuse patterns without blocking the computation:
|
|
40
|
+
|
|
41
|
+
1. ``u`` and ``v`` are the same variable (a scalar used as both components).
|
|
42
|
+
2. Neither component carries a velocity/flux-like ``units`` attribute.
|
|
43
|
+
|
|
44
|
+
Warnings are attached to the result so the caller — and any downstream
|
|
45
|
+
scientist — can see that the inputs were suspicious.
|
|
46
|
+
"""
|
|
47
|
+
warnings: list[str] = []
|
|
48
|
+
|
|
49
|
+
if u_variable == v_variable:
|
|
50
|
+
warnings.append(
|
|
51
|
+
f"{operation}: u_variable and v_variable are the same field "
|
|
52
|
+
f"('{u_variable}'). {operation} is only physically meaningful for a "
|
|
53
|
+
"true 2-D vector field (distinct eastward/northward components); "
|
|
54
|
+
"the result here is a mathematical artifact, not a physical "
|
|
55
|
+
f"{operation}."
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def _unit(var: Any) -> str:
|
|
59
|
+
attrs = getattr(var, "attrs", {}) or {}
|
|
60
|
+
return str(attrs.get("units", "")).strip().lower()
|
|
61
|
+
|
|
62
|
+
u_unit, v_unit = _unit(u), _unit(v)
|
|
63
|
+
|
|
64
|
+
def _looks_velocity(unit: str) -> bool:
|
|
65
|
+
return any(hint in unit for hint in _VELOCITY_LIKE_UNIT_HINTS)
|
|
66
|
+
|
|
67
|
+
if not (_looks_velocity(u_unit) or _looks_velocity(v_unit)):
|
|
68
|
+
seen = ", ".join(
|
|
69
|
+
f"{name}='{unit or 'unset'}'"
|
|
70
|
+
for name, unit in ((u_variable, u_unit), (v_variable, v_unit))
|
|
71
|
+
)
|
|
72
|
+
warnings.append(
|
|
73
|
+
f"{operation}: neither component has a velocity/flux-like 'units' "
|
|
74
|
+
f"attribute ({seen}). Verify that '{u_variable}' and '{v_variable}' "
|
|
75
|
+
"are genuine vector components (e.g. m/s) before interpreting the "
|
|
76
|
+
f"{operation} physically."
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
return warnings
|
|
80
|
+
|
|
7
81
|
|
|
8
82
|
def compute_gradient(
|
|
9
83
|
uxds: Any, variable_name: str, scale_by_radius: bool = False
|
|
@@ -115,6 +189,10 @@ def compute_curl(
|
|
|
115
189
|
|
|
116
190
|
import numpy as np
|
|
117
191
|
|
|
192
|
+
component_warnings = _vector_component_warnings(
|
|
193
|
+
u_variable, v_variable, u, v, "curl"
|
|
194
|
+
)
|
|
195
|
+
|
|
118
196
|
result = u.curl(v, scale_by_radius=scale_by_radius)
|
|
119
197
|
vals = result.values
|
|
120
198
|
finite = vals[np.isfinite(vals)]
|
|
@@ -137,6 +215,7 @@ def compute_curl(
|
|
|
137
215
|
"n_face": int(uxds.uxgrid.n_face),
|
|
138
216
|
"scale_by_radius": bool(scale_by_radius),
|
|
139
217
|
"stats": stats,
|
|
218
|
+
"component_warnings": component_warnings,
|
|
140
219
|
}
|
|
141
220
|
|
|
142
221
|
|
|
@@ -179,6 +258,10 @@ def compute_divergence(uxds: Any, u_variable: str, v_variable: str) -> dict:
|
|
|
179
258
|
|
|
180
259
|
import numpy as np
|
|
181
260
|
|
|
261
|
+
component_warnings = _vector_component_warnings(
|
|
262
|
+
u_variable, v_variable, u, v, "divergence"
|
|
263
|
+
)
|
|
264
|
+
|
|
182
265
|
result = u.divergence(v)
|
|
183
266
|
vals = result.values
|
|
184
267
|
finite = vals[np.isfinite(vals)]
|
|
@@ -200,6 +283,7 @@ def compute_divergence(uxds: Any, u_variable: str, v_variable: str) -> dict:
|
|
|
200
283
|
"interpretation": "horizontal divergence ∂u/∂x + ∂v/∂y",
|
|
201
284
|
"n_face": int(uxds.uxgrid.n_face),
|
|
202
285
|
"stats": stats,
|
|
286
|
+
"component_warnings": component_warnings,
|
|
203
287
|
}
|
|
204
288
|
|
|
205
289
|
|
|
@@ -5,9 +5,9 @@ Two profiles are supported:
|
|
|
5
5
|
* ``"core"`` (default) — small, predictable surface visible to LLMs.
|
|
6
6
|
Mirrors the original MCP server's 11 front-door tools, adds 12
|
|
7
7
|
control/status tools, the ``list_datasets`` discovery helper, and
|
|
8
|
-
|
|
8
|
+
seven prompt-as-tool helpers (former ``@mcp.prompt()`` decorators).
|
|
9
9
|
* ``"deferred-full"`` — loads every public function with the core set
|
|
10
|
-
enabled and
|
|
10
|
+
enabled and 32 raw implementation tools marked ``defer=True``.
|
|
11
11
|
Includes ``discover_tools`` (BM25 search) so LLMs find deferred
|
|
12
12
|
tools by intent.
|
|
13
13
|
|
|
@@ -592,9 +592,9 @@ def build_registry(
|
|
|
592
592
|
"""Build a ``ToolRegistry`` for the chosen profile.
|
|
593
593
|
|
|
594
594
|
Args:
|
|
595
|
-
profile: ``"core"`` for the small default surface (~
|
|
595
|
+
profile: ``"core"`` for the small default surface (~31 tools),
|
|
596
596
|
``"deferred-full"`` for the complete pool (core visible,
|
|
597
|
-
|
|
597
|
+
32 raw tools deferred, ``discover_tools`` added).
|
|
598
598
|
registry_name: Identifier for server titles and labels.
|
|
599
599
|
|
|
600
600
|
Returns:
|
|
@@ -424,15 +424,47 @@ class UXarrayComputeAgent(_AcademyAgent):
|
|
|
424
424
|
|
|
425
425
|
# Attach provenance with the correct HPC venue — the remote functions
|
|
426
426
|
# are self contained and don't call attach_provenance themselves.
|
|
427
|
-
from uxarray_mcp.provenance import attach_provenance
|
|
427
|
+
from uxarray_mcp.provenance import _get_uxarray_version, attach_provenance
|
|
428
428
|
|
|
429
429
|
endpoint_label = self.config.endpoint_name or "configured"
|
|
430
|
-
|
|
430
|
+
|
|
431
|
+
# Capture the worker's true software versions (when the remote function
|
|
432
|
+
# reports them) so provenance reflects what actually computed the result
|
|
433
|
+
# — not the local submitter — and warn on local/remote version drift.
|
|
434
|
+
drift_warnings: list[str] = []
|
|
435
|
+
worker_uxarray = None
|
|
436
|
+
if isinstance(result, dict):
|
|
437
|
+
worker_uxarray = result.pop("_worker_uxarray_version", None)
|
|
438
|
+
result.pop("_worker_python_version", None)
|
|
439
|
+
local_uxarray = _get_uxarray_version()
|
|
440
|
+
if (
|
|
441
|
+
worker_uxarray
|
|
442
|
+
and local_uxarray != "unknown"
|
|
443
|
+
and worker_uxarray != local_uxarray
|
|
444
|
+
):
|
|
445
|
+
drift_warnings.append(
|
|
446
|
+
f"UXarray version drift: local={local_uxarray}, "
|
|
447
|
+
f"remote(worker)={worker_uxarray}. Numerical results may differ "
|
|
448
|
+
"between local and remote runs; compare with care."
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
# Fold any warnings the remote function itself produced (e.g. vector
|
|
452
|
+
# component guardrails) into provenance.
|
|
453
|
+
if isinstance(result, dict):
|
|
454
|
+
fn_warnings = result.get("component_warnings") or []
|
|
455
|
+
drift_warnings.extend(fn_warnings)
|
|
456
|
+
|
|
457
|
+
annotated = attach_provenance(
|
|
431
458
|
result,
|
|
432
459
|
tool=func.__name__,
|
|
433
460
|
inputs={"args": [str(a) for a in args]},
|
|
434
461
|
venue=f"hpc:{endpoint_label}",
|
|
462
|
+
warnings=drift_warnings or None,
|
|
435
463
|
)
|
|
464
|
+
# Record the worker's uxarray version explicitly alongside the local one.
|
|
465
|
+
if worker_uxarray:
|
|
466
|
+
annotated["_provenance"]["remote_uxarray_version"] = worker_uxarray
|
|
467
|
+
return annotated
|
|
436
468
|
|
|
437
469
|
def _run_local_inspect_mesh(self, file_path: str) -> Dict[str, Any]:
|
|
438
470
|
"""Execute inspect_mesh locally as fallback."""
|