uxarray-mcp 0.3.0__tar.gz → 2026.9.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 (63) hide show
  1. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/PKG-INFO +131 -29
  2. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/README.md +125 -25
  3. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/pyproject.toml +7 -4
  4. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/pyproject.toml.orig +32 -4
  5. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/__init__.py +1 -1
  6. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/app.py +17 -9
  7. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/cli.py +36 -0
  8. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/domain/__init__.py +20 -1
  9. uxarray_mcp-2026.9.0/src/uxarray_mcp/domain/anomaly_coverage.py +109 -0
  10. uxarray_mcp-2026.9.0/src/uxarray_mcp/domain/area.py +128 -0
  11. uxarray_mcp-2026.9.0/src/uxarray_mcp/domain/export_fidelity.py +157 -0
  12. uxarray_mcp-2026.9.0/src/uxarray_mcp/domain/mesh_coverage.py +242 -0
  13. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/domain/plotting.py +21 -1
  14. uxarray_mcp-2026.9.0/src/uxarray_mcp/domain/profile_coverage.py +90 -0
  15. uxarray_mcp-2026.9.0/src/uxarray_mcp/domain/remap_backend.py +110 -0
  16. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/domain/remap_coverage.py +46 -3
  17. uxarray_mcp-2026.9.0/src/uxarray_mcp/domain/subset_coverage.py +158 -0
  18. uxarray_mcp-2026.9.0/src/uxarray_mcp/domain/temporal_coverage.py +156 -0
  19. uxarray_mcp-2026.9.0/src/uxarray_mcp/domain/variable.py +130 -0
  20. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/domain/vector_calc.py +61 -1
  21. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/domain/zonal.py +7 -0
  22. uxarray_mcp-2026.9.0/src/uxarray_mcp/json_safe.py +107 -0
  23. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/postconditions.py +67 -71
  24. uxarray_mcp-2026.9.0/src/uxarray_mcp/preconditions.py +784 -0
  25. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/provenance.py +20 -7
  26. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/registry.py +191 -4
  27. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/remote/agent.py +175 -4
  28. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/remote/compute_functions.py +1021 -23
  29. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/remote/config.py +86 -0
  30. uxarray_mcp-2026.9.0/src/uxarray_mcp/remote/gcp.py +194 -0
  31. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/remote/health.py +11 -4
  32. uxarray_mcp-2026.9.0/src/uxarray_mcp/remote/transfer.py +637 -0
  33. uxarray_mcp-2026.9.0/src/uxarray_mcp/resources.py +300 -0
  34. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/response_contract.py +104 -4
  35. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/state.py +191 -22
  36. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/tools/__init__.py +10 -0
  37. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/tools/advanced.py +624 -84
  38. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/tools/catalog.py +128 -15
  39. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/tools/execution_control.py +135 -0
  40. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/tools/frontdoor.py +365 -38
  41. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/tools/inspection.py +20 -3
  42. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/tools/orchestration.py +71 -27
  43. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/tools/plotting.py +77 -76
  44. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/tools/remote_tools.py +319 -28
  45. uxarray_mcp-2026.9.0/src/uxarray_mcp/tools/transfer_tools.py +305 -0
  46. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/tools/vector_calc.py +12 -0
  47. uxarray_mcp-2026.9.0/src/uxarray_mcp/transfer_setup.py +545 -0
  48. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/typed_results.py +162 -28
  49. uxarray_mcp-0.3.0/src/uxarray_mcp/domain/area.py +0 -37
  50. uxarray_mcp-0.3.0/src/uxarray_mcp/domain/variable.py +0 -77
  51. uxarray_mcp-0.3.0/src/uxarray_mcp/preconditions.py +0 -330
  52. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/LICENSE +0 -0
  53. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/__main__.py +0 -0
  54. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/content_blocks.py +0 -0
  55. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/domain/dims.py +0 -0
  56. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/domain/mesh.py +0 -0
  57. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/next_steps.py +0 -0
  58. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/py.typed +0 -0
  59. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/remote/__init__.py +0 -0
  60. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/tools/capabilities.py +0 -0
  61. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/tools/contracts.py +0 -0
  62. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.0}/src/uxarray_mcp/tools/scientific_agent.py +0 -0
  63. {uxarray_mcp-0.3.0 → uxarray_mcp-2026.9.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.3.0
3
+ Version: 2026.9.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
@@ -218,17 +218,18 @@ Classifier: Programming Language :: Python :: 3.13
218
218
  Classifier: License :: OSI Approved :: Apache Software License
219
219
  Requires-Dist: toolregistry-server[mcp]>=0.5.0
220
220
  Requires-Dist: toolregistry>=0.16.0
221
- Requires-Dist: mcp>=1.24,<3
221
+ Requires-Dist: mcp>=1.27,<3
222
222
  Requires-Dist: holoviews>=1.19.0
223
223
  Requires-Dist: matplotlib>=3.9.0
224
- Requires-Dist: pyyaml>=6.0
225
- Requires-Dist: uxarray>=2026.8.0
224
+ Requires-Dist: pyyaml>=6.0.1
225
+ Requires-Dist: uxarray>=2026.9.0,<2026.10
226
226
  Requires-Dist: sphinx>=7.0 ; extra == 'docs'
227
227
  Requires-Dist: sphinx-book-theme>=1.1.0 ; extra == 'docs'
228
228
  Requires-Dist: myst-parser>=3.0 ; extra == 'docs'
229
229
  Requires-Dist: academy-py>=0.3.1 ; extra == 'hpc'
230
230
  Requires-Dist: globus-compute-sdk>=4.5.0 ; extra == 'hpc'
231
231
  Requires-Dist: toolregistry-server[openapi]>=0.5.0 ; extra == 'openapi'
232
+ Requires-Dist: globus-cli>=3.30 ; extra == 'transfer'
232
233
  Requires-Python: >=3.11
233
234
  Project-URL: Documentation, https://uxarray-mcp-server.readthedocs.io
234
235
  Project-URL: Source, https://github.com/UXARRAY/uxarray-mcp-server
@@ -236,12 +237,13 @@ Project-URL: Tracker, https://github.com/UXARRAY/uxarray-mcp-server/issues
236
237
  Provides-Extra: docs
237
238
  Provides-Extra: hpc
238
239
  Provides-Extra: openapi
240
+ Provides-Extra: transfer
239
241
  Description-Content-Type: text/markdown
240
242
 
241
243
  # UXarray MCP Server
242
244
 
243
- An MCP server that lets an AI assistant (Claude Code, Claude Desktop, Cursor,
244
- or any MCP client) analyze unstructured climate meshes with
245
+ An MCP server that lets an AI assistant (Claude Code, Claude Desktop, Codex,
246
+ opencode, Cursor, or any MCP client) analyze unstructured climate meshes with
245
247
  [UXarray](https://uxarray.readthedocs.io/) — locally on your machine, or
246
248
  remotely on an HPC system you have access to.
247
249
 
@@ -288,6 +290,15 @@ You are most likely one of:
288
290
  5. **Just trying it out, or running an agent harness** — you don't want to
289
291
  install a scientific Python stack at all. → [Docker](#docker).
290
292
 
293
+ **Want to see what this is for before installing anything?** Read the worked
294
+ case study: [**a 10-year CONUS precipitation map from 37 GB that never
295
+ moved**](case-studies/conus-precipitation-gdex/README.md). One paragraph of
296
+ plain English on a laptop, ten years of 6-hourly CESM output on the NCAR
297
+ filesystem, all the compute on a Casper worker, and a 178 KB PNG plus an
298
+ auditable provenance record coming back. It covers what MCP is, what actually
299
+ ran where, why it took 7.7 minutes, what it cost in tokens, and how to set the
300
+ same thing up yourself.
301
+
291
302
  ---
292
303
 
293
304
  ## Docker
@@ -374,16 +385,22 @@ uv tool install --python 3.12 uxarray-mcp
374
385
 
375
386
  # Or from a fresh clone (developer path)
376
387
  git clone https://github.com/UXARRAY/uxarray-mcp-server.git
377
- cd uxarray-mcp-server && uv sync --python 3.12
378
- # or: bash SETUP.sh (does the sync + runs the local test suite in one step)
388
+ cd uxarray-mcp-server && uv sync --python 3.12 --extra hpc --extra transfer
389
+ # or: bash SETUP.sh (local-only sync + runs the local test suite in one step)
379
390
  ```
380
391
 
381
- > **Why `--python 3.12`?** The server uses Globus Compute to submit work to
382
- > HPC endpoints, and Globus Compute's serializer is fragile across Python
383
- > minor versions — a 3.13 submitter against a 3.12 endpoint worker raises
384
- > `WorkerLost` on non-trivial payloads. HPC sites broadly ship 3.12 conda
385
- > stacks today, so we pin the install to match. Tracking removal of this pin
386
- > at [globus/globus-compute#2139](https://github.com/globus/globus-compute/issues/2139).
392
+ The `hpc` and `transfer` extras hold the Globus Compute and Globus Transfer
393
+ SDKs. Leave them off for a laptop-only install. Note that `uv sync` installs
394
+ *exactly* the requested set: running a plain `uv sync` later removes the
395
+ extras again, and `hpc/endpoint_status` will then report `unreachable` with
396
+ `No module named 'globus_compute_sdk'`.
397
+
398
+ > **Why `--python 3.12`?** Only the HPC path needs it. Globus Compute's
399
+ > serializer is fragile across Python minor versions — a 3.13 submitter
400
+ > against a 3.12 endpoint worker raises `WorkerLost` on non-trivial payloads,
401
+ > and HPC sites broadly ship 3.12 conda stacks today. Local-only use works on
402
+ > 3.11–3.13. Tracking removal of this constraint at
403
+ > [globus/globus-compute#2139](https://github.com/globus/globus-compute/issues/2139).
387
404
  > `uv` downloads 3.12 automatically if your system doesn't have it.
388
405
 
389
406
  ### Step 2 — Write a starter config
@@ -395,16 +412,32 @@ uxarray-mcp setup
395
412
  Creates `~/.config/uxarray-mcp/config.yaml` with sensible defaults. Local mode
396
413
  needs nothing more.
397
414
 
415
+ Three environment variables adjust where the server looks and writes:
416
+ `UXARRAY_MCP_CONFIG` (path to a config file, checked before
417
+ `~/.config/uxarray-mcp/config.yaml`), `UXARRAY_MCP_STATE_DIR` (sessions,
418
+ result handles and rendered plots; default `~/.uxarray_mcp_server`), and
419
+ `UXARRAY_MCP_VERDICT_POLICY` (`full`, `reference_only` or `off` for the
420
+ postcondition block on every result).
421
+
398
422
  ### Step 3 — Connect your AI client
399
423
 
400
424
  **Claude Desktop**
401
425
 
402
426
  ```bash
403
- uxarray-mcp install-claude # merges the mcpServers block into your config
427
+ # merges the mcpServers block into the config file you name
428
+ uxarray-mcp install-claude --config-path ~/Library/Application\ Support/Claude/claude_desktop_config.json
404
429
  # or
405
430
  uxarray-mcp install-claude --print-only # prints the JSON to paste manually
406
431
  ```
407
432
 
433
+ Without `--config-path` the command only prints the block; it never guesses
434
+ where your Claude Desktop config lives.
435
+
436
+ If you installed from a clone rather than `uv tool install`, the `uxarray-mcp`
437
+ binary lives in the project `.venv`. In every client config below, use
438
+ `uv --directory /path/to/uxarray-mcp-server run uxarray-mcp serve` as the
439
+ command instead of a bare `uxarray-mcp serve`.
440
+
408
441
  Restart Claude Desktop. The `uxarray` server should appear in Settings →
409
442
  Developer.
410
443
 
@@ -416,10 +449,63 @@ claude mcp add uxarray --transport stdio -- uxarray-mcp serve
416
449
 
417
450
  Then `/mcp` in Claude Code; pick `uxarray`.
418
451
 
419
- **Cursor / other MCP clients**
452
+ **Codex CLI**
453
+
454
+ ```bash
455
+ codex mcp add uxarray -- uxarray-mcp serve
456
+ ```
457
+
458
+ Or write `~/.codex/config.toml` directly:
459
+
460
+ ```toml
461
+ [mcp_servers.uxarray]
462
+ command = "uxarray-mcp"
463
+ args = ["serve"]
464
+ ```
465
+
466
+ Then `/mcp` in a Codex session.
420
467
 
421
- Add an MCP server entry pointing at `uxarray-mcp serve` over stdio. See your
422
- client's MCP docs.
468
+ **opencode**
469
+
470
+ Add to `~/.config/opencode/opencode.json`:
471
+
472
+ ```json
473
+ {
474
+ "mcp": {
475
+ "uxarray": {
476
+ "type": "local",
477
+ "command": ["uxarray-mcp", "serve"],
478
+ "enabled": true
479
+ }
480
+ }
481
+ }
482
+ ```
483
+
484
+ The server registers 33 tools, which is a large tool schema to carry on every
485
+ request. `"enabled": false` turns it off for sessions that are not doing mesh
486
+ analysis.
487
+
488
+ **Cursor**
489
+
490
+ Add to `~/.cursor/mcp.json` (or a project-local `.cursor/mcp.json`):
491
+
492
+ ```json
493
+ {
494
+ "mcpServers": {
495
+ "uxarray": {
496
+ "command": "uxarray-mcp",
497
+ "args": ["serve"]
498
+ }
499
+ }
500
+ }
501
+ ```
502
+
503
+ **Any other MCP client**
504
+
505
+ The server speaks stdio, so every client wants the same two facts — the
506
+ command `uxarray-mcp` and the argument `serve`. `uxarray-mcp install-claude
507
+ --print-only` prints the `mcpServers` JSON block that most clients accept
508
+ verbatim.
423
509
 
424
510
  ### Step 4 — Sanity check
425
511
 
@@ -476,16 +562,27 @@ Intent-shaped tools, not raw UXarray bindings — all local by default:
476
562
  - `get_capabilities` — what can I do with this mesh?
477
563
  - `analyze_dataset` — deterministic first-look: inspect, validate, area, zonal mean, plots.
478
564
  - `run_analysis` — one operation at a time (gradient, curl, subset, remap, …).
479
- - `plot_dataset` — mesh, geographic, variable, or zonal-mean plots.
565
+ Remaps take `method`: `nearest_neighbor`, `inverse_distance_weighted` or
566
+ `bilinear` on UXarray's own engine, or `conservative`, `nnn`, `dnn`,
567
+ `average` on [YAC](https://dkrz-sw.gitlab-pages.dkrz.de/yac/) (equivalently
568
+ `backend="yac"` with `yac_method`). Only `conservative` preserves the field
569
+ integral, and it needs YAC importable where the remap runs — build it with
570
+ `scripts/build_yac_local.sh` on a laptop or `scripts/hpc_build_yac.py` on a
571
+ worker, and put its `site-packages` on `PYTHONPATH`.
572
+ - `plot_dataset` — `plot_type` of `mesh`, `mesh_geo`, `variable`, or `zonal_mean`.
480
573
  - `run_workflow`, `resume_workflow`, `get_status`, `get_result`, `manage_session` —
481
574
  persisted sessions and multi-step workflows.
482
575
 
483
- Full schema: [docs/tools.md](docs/tools.md).
576
+ Helper namespaces also appear in `tools/list`: `session/*`, `hpc/*`,
577
+ `io/list_datasets`, `contract/*` and `prompt/*`.
578
+
579
+ Full schema and every `run_analysis` parameter: [docs/tools.md](docs/tools.md).
484
580
 
485
581
  **Protocol version.** We do not implement MCP directly; servers are built
486
582
  through `toolregistry-server`, which depends on the `mcp` Python SDK. As of
487
583
  `toolregistry-server` 0.5.0 and `toolregistry` 0.16.0 the SDK cap is lifted, so
488
- we resolve `mcp` 2.1.1 and negotiate spec **`2026-07-28`** (stateless core,
584
+ we resolve `mcp` 1.27 or 2.x (2.1.1 at the last lock) and negotiate spec
585
+ **`2026-07-28`** (stateless core,
489
586
  cacheable list results, MRTR). 0.16.0 also widens the recognized content-block
490
587
  set to audio, `resource_link`, and embedded resources.
491
588
 
@@ -508,10 +605,13 @@ auditable and the server actively flags common scientific pitfalls:
508
605
  the tool that ran, timestamp, input arguments, `execution_venue`
509
606
  (`local` or `hpc:<endpoint>`), and the UXarray/Python versions used.
510
607
  - **Derivative unit convention is never hidden.** `gradient`, `curl`, and
511
- `divergence` echo `scale_by_radius` in both the result and provenance, so a
512
- unit-sphere result can never be mistaken for a physical (per-metre) one.
513
- Gradient and curl default to physical scaling, matching UXarray; pass
514
- `scale_by_radius=False` explicitly for unit-sphere output.
608
+ `divergence` echo `scale_by_radius` and a `radius_basis` block (the radius
609
+ used and whether it came from the grid or the caller), so a unit-sphere
610
+ result can never be mistaken for a physical (per-metre) one. Most grid files
611
+ declare no `sphere_radius`; pass `sphere_radius=6371000` (metres) to attach
612
+ Earth's. Without it, or with `scale_by_radius=False`, the call is refused
613
+ with `outcome="input_required"` until you pass `acknowledge`. The same
614
+ `sphere_radius` argument turns `calculate_area` from steradians into m².
515
615
  - **Vector-calculus sanity guard.** `curl`/`divergence` warn (without blocking)
516
616
  when the two inputs are the same field, or when neither carries a
517
617
  velocity/flux-like `units` attribute — the classic "vorticity from two random
@@ -533,12 +633,14 @@ auditable and the server actively flags common scientific pitfalls:
533
633
 
534
634
  | Command | Purpose |
535
635
  |---|---|
536
- | `uxarray-mcp serve` | Run the MCP server (used by your AI client) |
636
+ | `uxarray-mcp serve` | Run the MCP server (used by your AI client); `--profile core\|deferred-full`, `--transport stdio\|sse\|http` — see [docs/serving.md](docs/serving.md) |
637
+ | `uxarray-mcp openapi` | Print the OpenAPI document for the HTTP transport |
537
638
  | `uxarray-mcp setup` | Write a starter config |
538
639
  | `uxarray-mcp endpoints add NAME UUID` | Register a Globus Compute endpoint |
539
- | `uxarray-mcp endpoints list` | Show configured endpoints |
640
+ | `uxarray-mcp endpoints list` / `remove NAME` | Show or drop configured endpoints |
641
+ | `uxarray-mcp transfer setup` | Check and fix everything a Globus Transfer needs, including the browser login an MCP server cannot open |
540
642
  | `uxarray-mcp doctor` | Validate local + (optionally) remote setup |
541
- | `uxarray-mcp install-claude` | Merge or print the Claude Desktop config block |
643
+ | `uxarray-mcp install-claude --config-path FILE` | Merge (or `--print-only`) the Claude Desktop config block |
542
644
 
543
645
  ---
544
646
 
@@ -579,7 +681,7 @@ see **[SECURITY.md](SECURITY.md)**.
579
681
  ## Development
580
682
 
581
683
  ```bash
582
- uv sync --extra hpc --extra docs --dev
684
+ uv sync --extra hpc --extra transfer --extra docs
583
685
  uv run pre-commit run --all-files
584
686
  uv run pytest tests/ --ignore=tests/test_remote_agent.py
585
687
  uv run sphinx-build -b html docs docs/_build/html
@@ -1,7 +1,7 @@
1
1
  # UXarray MCP Server
2
2
 
3
- An MCP server that lets an AI assistant (Claude Code, Claude Desktop, Cursor,
4
- or any MCP client) analyze unstructured climate meshes with
3
+ An MCP server that lets an AI assistant (Claude Code, Claude Desktop, Codex,
4
+ opencode, Cursor, or any MCP client) analyze unstructured climate meshes with
5
5
  [UXarray](https://uxarray.readthedocs.io/) — locally on your machine, or
6
6
  remotely on an HPC system you have access to.
7
7
 
@@ -48,6 +48,15 @@ You are most likely one of:
48
48
  5. **Just trying it out, or running an agent harness** — you don't want to
49
49
  install a scientific Python stack at all. → [Docker](#docker).
50
50
 
51
+ **Want to see what this is for before installing anything?** Read the worked
52
+ case study: [**a 10-year CONUS precipitation map from 37 GB that never
53
+ moved**](case-studies/conus-precipitation-gdex/README.md). One paragraph of
54
+ plain English on a laptop, ten years of 6-hourly CESM output on the NCAR
55
+ filesystem, all the compute on a Casper worker, and a 178 KB PNG plus an
56
+ auditable provenance record coming back. It covers what MCP is, what actually
57
+ ran where, why it took 7.7 minutes, what it cost in tokens, and how to set the
58
+ same thing up yourself.
59
+
51
60
  ---
52
61
 
53
62
  ## Docker
@@ -134,16 +143,22 @@ uv tool install --python 3.12 uxarray-mcp
134
143
 
135
144
  # Or from a fresh clone (developer path)
136
145
  git clone https://github.com/UXARRAY/uxarray-mcp-server.git
137
- cd uxarray-mcp-server && uv sync --python 3.12
138
- # or: bash SETUP.sh (does the sync + runs the local test suite in one step)
146
+ cd uxarray-mcp-server && uv sync --python 3.12 --extra hpc --extra transfer
147
+ # or: bash SETUP.sh (local-only sync + runs the local test suite in one step)
139
148
  ```
140
149
 
141
- > **Why `--python 3.12`?** The server uses Globus Compute to submit work to
142
- > HPC endpoints, and Globus Compute's serializer is fragile across Python
143
- > minor versions — a 3.13 submitter against a 3.12 endpoint worker raises
144
- > `WorkerLost` on non-trivial payloads. HPC sites broadly ship 3.12 conda
145
- > stacks today, so we pin the install to match. Tracking removal of this pin
146
- > at [globus/globus-compute#2139](https://github.com/globus/globus-compute/issues/2139).
150
+ The `hpc` and `transfer` extras hold the Globus Compute and Globus Transfer
151
+ SDKs. Leave them off for a laptop-only install. Note that `uv sync` installs
152
+ *exactly* the requested set: running a plain `uv sync` later removes the
153
+ extras again, and `hpc/endpoint_status` will then report `unreachable` with
154
+ `No module named 'globus_compute_sdk'`.
155
+
156
+ > **Why `--python 3.12`?** Only the HPC path needs it. Globus Compute's
157
+ > serializer is fragile across Python minor versions — a 3.13 submitter
158
+ > against a 3.12 endpoint worker raises `WorkerLost` on non-trivial payloads,
159
+ > and HPC sites broadly ship 3.12 conda stacks today. Local-only use works on
160
+ > 3.11–3.13. Tracking removal of this constraint at
161
+ > [globus/globus-compute#2139](https://github.com/globus/globus-compute/issues/2139).
147
162
  > `uv` downloads 3.12 automatically if your system doesn't have it.
148
163
 
149
164
  ### Step 2 — Write a starter config
@@ -155,16 +170,32 @@ uxarray-mcp setup
155
170
  Creates `~/.config/uxarray-mcp/config.yaml` with sensible defaults. Local mode
156
171
  needs nothing more.
157
172
 
173
+ Three environment variables adjust where the server looks and writes:
174
+ `UXARRAY_MCP_CONFIG` (path to a config file, checked before
175
+ `~/.config/uxarray-mcp/config.yaml`), `UXARRAY_MCP_STATE_DIR` (sessions,
176
+ result handles and rendered plots; default `~/.uxarray_mcp_server`), and
177
+ `UXARRAY_MCP_VERDICT_POLICY` (`full`, `reference_only` or `off` for the
178
+ postcondition block on every result).
179
+
158
180
  ### Step 3 — Connect your AI client
159
181
 
160
182
  **Claude Desktop**
161
183
 
162
184
  ```bash
163
- uxarray-mcp install-claude # merges the mcpServers block into your config
185
+ # merges the mcpServers block into the config file you name
186
+ uxarray-mcp install-claude --config-path ~/Library/Application\ Support/Claude/claude_desktop_config.json
164
187
  # or
165
188
  uxarray-mcp install-claude --print-only # prints the JSON to paste manually
166
189
  ```
167
190
 
191
+ Without `--config-path` the command only prints the block; it never guesses
192
+ where your Claude Desktop config lives.
193
+
194
+ If you installed from a clone rather than `uv tool install`, the `uxarray-mcp`
195
+ binary lives in the project `.venv`. In every client config below, use
196
+ `uv --directory /path/to/uxarray-mcp-server run uxarray-mcp serve` as the
197
+ command instead of a bare `uxarray-mcp serve`.
198
+
168
199
  Restart Claude Desktop. The `uxarray` server should appear in Settings →
169
200
  Developer.
170
201
 
@@ -176,10 +207,63 @@ claude mcp add uxarray --transport stdio -- uxarray-mcp serve
176
207
 
177
208
  Then `/mcp` in Claude Code; pick `uxarray`.
178
209
 
179
- **Cursor / other MCP clients**
210
+ **Codex CLI**
211
+
212
+ ```bash
213
+ codex mcp add uxarray -- uxarray-mcp serve
214
+ ```
215
+
216
+ Or write `~/.codex/config.toml` directly:
217
+
218
+ ```toml
219
+ [mcp_servers.uxarray]
220
+ command = "uxarray-mcp"
221
+ args = ["serve"]
222
+ ```
223
+
224
+ Then `/mcp` in a Codex session.
180
225
 
181
- Add an MCP server entry pointing at `uxarray-mcp serve` over stdio. See your
182
- client's MCP docs.
226
+ **opencode**
227
+
228
+ Add to `~/.config/opencode/opencode.json`:
229
+
230
+ ```json
231
+ {
232
+ "mcp": {
233
+ "uxarray": {
234
+ "type": "local",
235
+ "command": ["uxarray-mcp", "serve"],
236
+ "enabled": true
237
+ }
238
+ }
239
+ }
240
+ ```
241
+
242
+ The server registers 33 tools, which is a large tool schema to carry on every
243
+ request. `"enabled": false` turns it off for sessions that are not doing mesh
244
+ analysis.
245
+
246
+ **Cursor**
247
+
248
+ Add to `~/.cursor/mcp.json` (or a project-local `.cursor/mcp.json`):
249
+
250
+ ```json
251
+ {
252
+ "mcpServers": {
253
+ "uxarray": {
254
+ "command": "uxarray-mcp",
255
+ "args": ["serve"]
256
+ }
257
+ }
258
+ }
259
+ ```
260
+
261
+ **Any other MCP client**
262
+
263
+ The server speaks stdio, so every client wants the same two facts — the
264
+ command `uxarray-mcp` and the argument `serve`. `uxarray-mcp install-claude
265
+ --print-only` prints the `mcpServers` JSON block that most clients accept
266
+ verbatim.
183
267
 
184
268
  ### Step 4 — Sanity check
185
269
 
@@ -236,16 +320,27 @@ Intent-shaped tools, not raw UXarray bindings — all local by default:
236
320
  - `get_capabilities` — what can I do with this mesh?
237
321
  - `analyze_dataset` — deterministic first-look: inspect, validate, area, zonal mean, plots.
238
322
  - `run_analysis` — one operation at a time (gradient, curl, subset, remap, …).
239
- - `plot_dataset` — mesh, geographic, variable, or zonal-mean plots.
323
+ Remaps take `method`: `nearest_neighbor`, `inverse_distance_weighted` or
324
+ `bilinear` on UXarray's own engine, or `conservative`, `nnn`, `dnn`,
325
+ `average` on [YAC](https://dkrz-sw.gitlab-pages.dkrz.de/yac/) (equivalently
326
+ `backend="yac"` with `yac_method`). Only `conservative` preserves the field
327
+ integral, and it needs YAC importable where the remap runs — build it with
328
+ `scripts/build_yac_local.sh` on a laptop or `scripts/hpc_build_yac.py` on a
329
+ worker, and put its `site-packages` on `PYTHONPATH`.
330
+ - `plot_dataset` — `plot_type` of `mesh`, `mesh_geo`, `variable`, or `zonal_mean`.
240
331
  - `run_workflow`, `resume_workflow`, `get_status`, `get_result`, `manage_session` —
241
332
  persisted sessions and multi-step workflows.
242
333
 
243
- Full schema: [docs/tools.md](docs/tools.md).
334
+ Helper namespaces also appear in `tools/list`: `session/*`, `hpc/*`,
335
+ `io/list_datasets`, `contract/*` and `prompt/*`.
336
+
337
+ Full schema and every `run_analysis` parameter: [docs/tools.md](docs/tools.md).
244
338
 
245
339
  **Protocol version.** We do not implement MCP directly; servers are built
246
340
  through `toolregistry-server`, which depends on the `mcp` Python SDK. As of
247
341
  `toolregistry-server` 0.5.0 and `toolregistry` 0.16.0 the SDK cap is lifted, so
248
- we resolve `mcp` 2.1.1 and negotiate spec **`2026-07-28`** (stateless core,
342
+ we resolve `mcp` 1.27 or 2.x (2.1.1 at the last lock) and negotiate spec
343
+ **`2026-07-28`** (stateless core,
249
344
  cacheable list results, MRTR). 0.16.0 also widens the recognized content-block
250
345
  set to audio, `resource_link`, and embedded resources.
251
346
 
@@ -268,10 +363,13 @@ auditable and the server actively flags common scientific pitfalls:
268
363
  the tool that ran, timestamp, input arguments, `execution_venue`
269
364
  (`local` or `hpc:<endpoint>`), and the UXarray/Python versions used.
270
365
  - **Derivative unit convention is never hidden.** `gradient`, `curl`, and
271
- `divergence` echo `scale_by_radius` in both the result and provenance, so a
272
- unit-sphere result can never be mistaken for a physical (per-metre) one.
273
- Gradient and curl default to physical scaling, matching UXarray; pass
274
- `scale_by_radius=False` explicitly for unit-sphere output.
366
+ `divergence` echo `scale_by_radius` and a `radius_basis` block (the radius
367
+ used and whether it came from the grid or the caller), so a unit-sphere
368
+ result can never be mistaken for a physical (per-metre) one. Most grid files
369
+ declare no `sphere_radius`; pass `sphere_radius=6371000` (metres) to attach
370
+ Earth's. Without it, or with `scale_by_radius=False`, the call is refused
371
+ with `outcome="input_required"` until you pass `acknowledge`. The same
372
+ `sphere_radius` argument turns `calculate_area` from steradians into m².
275
373
  - **Vector-calculus sanity guard.** `curl`/`divergence` warn (without blocking)
276
374
  when the two inputs are the same field, or when neither carries a
277
375
  velocity/flux-like `units` attribute — the classic "vorticity from two random
@@ -293,12 +391,14 @@ auditable and the server actively flags common scientific pitfalls:
293
391
 
294
392
  | Command | Purpose |
295
393
  |---|---|
296
- | `uxarray-mcp serve` | Run the MCP server (used by your AI client) |
394
+ | `uxarray-mcp serve` | Run the MCP server (used by your AI client); `--profile core\|deferred-full`, `--transport stdio\|sse\|http` — see [docs/serving.md](docs/serving.md) |
395
+ | `uxarray-mcp openapi` | Print the OpenAPI document for the HTTP transport |
297
396
  | `uxarray-mcp setup` | Write a starter config |
298
397
  | `uxarray-mcp endpoints add NAME UUID` | Register a Globus Compute endpoint |
299
- | `uxarray-mcp endpoints list` | Show configured endpoints |
398
+ | `uxarray-mcp endpoints list` / `remove NAME` | Show or drop configured endpoints |
399
+ | `uxarray-mcp transfer setup` | Check and fix everything a Globus Transfer needs, including the browser login an MCP server cannot open |
300
400
  | `uxarray-mcp doctor` | Validate local + (optionally) remote setup |
301
- | `uxarray-mcp install-claude` | Merge or print the Claude Desktop config block |
401
+ | `uxarray-mcp install-claude --config-path FILE` | Merge (or `--print-only`) the Claude Desktop config block |
302
402
 
303
403
  ---
304
404
 
@@ -339,7 +439,7 @@ see **[SECURITY.md](SECURITY.md)**.
339
439
  ## Development
340
440
 
341
441
  ```bash
342
- uv sync --extra hpc --extra docs --dev
442
+ uv sync --extra hpc --extra transfer --extra docs
343
443
  uv run pre-commit run --all-files
344
444
  uv run pytest tests/ --ignore=tests/test_remote_agent.py
345
445
  uv run sphinx-build -b html docs docs/_build/html
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "uxarray-mcp"
3
- version = "0.3.0"
3
+ version = "2026.9.0"
4
4
  description = "MCP server for analyzing unstructured meshes with UXarray"
5
5
  readme = "README.md"
6
6
  keywords = [
@@ -25,11 +25,11 @@ requires-python = ">=3.11"
25
25
  dependencies = [
26
26
  "toolregistry-server[mcp]>=0.5.0",
27
27
  "toolregistry>=0.16.0",
28
- "mcp>=1.24,<3",
28
+ "mcp>=1.27,<3",
29
29
  "holoviews>=1.19.0",
30
30
  "matplotlib>=3.9.0",
31
- "pyyaml>=6.0",
32
- "uxarray>=2026.8.0",
31
+ "pyyaml>=6.0.1",
32
+ "uxarray>=2026.9.0,<2026.10",
33
33
  ]
34
34
 
35
35
  [[project.authors]]
@@ -49,6 +49,7 @@ hpc = [
49
49
  "academy-py>=0.3.1",
50
50
  "globus-compute-sdk>=4.5.0",
51
51
  ]
52
+ transfer = ["globus-cli>=3.30"]
52
53
  docs = [
53
54
  "sphinx>=7.0",
54
55
  "sphinx-book-theme>=1.1.0",
@@ -65,7 +66,9 @@ Tracker = "https://github.com/UXARRAY/uxarray-mcp-server/issues"
65
66
 
66
67
  [dependency-groups]
67
68
  dev = [
69
+ "jsonschema>=4.20.0",
68
70
  "mypy>=1.10.0",
71
+ "packaging>=23.0",
69
72
  "pre-commit>=4.3.0",
70
73
  "pytest>=9.0.2",
71
74
  "pytest-asyncio>=1.0.0",
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "uxarray-mcp"
3
- version = "0.3.0"
3
+ version = "2026.9.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"]
@@ -45,11 +45,22 @@ dependencies = [
45
45
  # is_content_block_list about audio/resource_link/resource; on 0.15.0 those
46
46
  # blocks are silently stringified into one TextContent instead of failing.
47
47
  "toolregistry>=0.16.0",
48
- "mcp>=1.24,<3",
48
+ # Both SDK generations are in range and register handlers differently:
49
+ # 1.27.2 has the per-method decorators and no `add_request_handler`, 2.1.1
50
+ # has `add_request_handler` and no decorators. `resources.py` registers
51
+ # through whichever is present, and `tests/test_artifact_resources.py`
52
+ # passes on both.
53
+ "mcp>=1.27,<3",
49
54
  "holoviews>=1.19.0",
50
55
  "matplotlib>=3.9.0",
51
- "pyyaml>=6.0",
52
- "uxarray>=2026.8.0",
56
+ # 6.0.1, not 6.0: PyYAML 6.0 ships no wheel for CPython 3.12 and its sdist
57
+ # cannot build against a modern Cython, failing with `'build_ext' object has
58
+ # no attribute 'cython_sources'`. Nothing hit it in normal CI because that
59
+ # resolves to the newest PyYAML; the weekly `lowest-direct` job installs the
60
+ # floor we actually declare, and had failed on it every run since
61
+ # 2026-08-10.
62
+ "pyyaml>=6.0.1",
63
+ "uxarray>=2026.9.0,<2026.10",
53
64
  ]
54
65
 
55
66
  [project.optional-dependencies]
@@ -60,6 +71,14 @@ hpc = [
60
71
  "academy-py>=0.3.1",
61
72
  "globus-compute-sdk>=4.5.0",
62
73
  ]
74
+ # Moving files is separate from running code on them, and is done by running
75
+ # the `globus` command-line client rather than by linking a second SDK. The CLI
76
+ # owns the tokens, the consents and the session, so this package holds no auth
77
+ # code at all -- which is the only reason a two-person project can offer data
78
+ # movement without also signing up to track Globus's scope rules.
79
+ transfer = [
80
+ "globus-cli>=3.30",
81
+ ]
63
82
  docs = [
64
83
  "sphinx>=7.0",
65
84
  "sphinx-book-theme>=1.1.0",
@@ -76,7 +95,16 @@ Tracker = "https://github.com/UXARRAY/uxarray-mcp-server/issues"
76
95
 
77
96
  [dependency-groups]
78
97
  dev = [
98
+ # Declared so the outputSchema conformance tests cannot silently skip.
99
+ # jsonschema arrives transitively today, which means a resolver change
100
+ # would turn the checks that hold every result to its published envelope
101
+ # into a green no-op.
102
+ "jsonschema>=4.20.0",
79
103
  "mypy>=1.10.0",
104
+ # Same reason as jsonschema: the suite-integrity check reads the declared
105
+ # uxarray floor out of the packaging metadata so it cannot drift from this
106
+ # file, and it needs a specifier parser to do that.
107
+ "packaging>=23.0",
80
108
  "pre-commit>=4.3.0",
81
109
  "pytest>=9.0.2",
82
110
  "pytest-asyncio>=1.0.0",
@@ -3,4 +3,4 @@
3
3
  from uxarray_mcp.tools import inspect_mesh
4
4
 
5
5
  __all__ = ["inspect_mesh"]
6
- __version__ = "0.3.0"
6
+ __version__ = "2026.9.0"
@@ -23,6 +23,7 @@ from toolregistry_server.app import App
23
23
 
24
24
  from . import __version__
25
25
  from .registry import Profile
26
+ from .resources import attach_artifact_resources
26
27
 
27
28
  if TYPE_CHECKING:
28
29
  from toolregistry import ToolRegistry
@@ -101,12 +102,16 @@ class UXarrayApp(App):
101
102
  name = kwargs.pop("name", identity.name)
102
103
  kwargs.pop("profile", None)
103
104
 
104
- MCPAdapter(
105
+ adapter = MCPAdapter(
105
106
  route_table,
106
107
  name=name,
107
108
  list_tools_ttl_ms=LIST_TOOLS_TTL_MS,
108
109
  list_tools_cache_scope=LIST_TOOLS_CACHE_SCOPE,
109
- ).run(**kwargs)
110
+ )
111
+ # Large figures go back as ``resource_link``, which is only honest if
112
+ # something answers ``resources/read`` for the URI (#14).
113
+ attach_artifact_resources(adapter.server)
114
+ adapter.run(**kwargs)
110
115
 
111
116
 
112
117
  # ---------------------------------------------------------------------------
@@ -122,16 +127,19 @@ def make_registry(*, profile: Profile = "core") -> ToolRegistry:
122
127
  def make_mcp_server(*, profile: Profile = "core"):
123
128
  """Build a configured MCP server ready for any transport.
124
129
 
125
- Carries the same ``tools/list`` cache hints as :meth:`UXarrayApp.serve_mcp`
126
- so tests and scripts exercise the surface the CLI actually serves.
130
+ Carries the same ``tools/list`` cache hints and the same artifact
131
+ resource handlers as :meth:`UXarrayApp.serve_mcp`, so tests and scripts
132
+ exercise the surface the CLI actually serves.
127
133
  """
128
134
  from toolregistry_server.adapters.mcp import route_table_to_mcp_server
129
135
  from toolregistry_server.route_table import RouteTable
130
136
 
131
137
  registry = make_registry(profile=profile)
132
- return route_table_to_mcp_server(
133
- RouteTable(registry),
134
- "UXarray MCP",
135
- list_tools_ttl_ms=LIST_TOOLS_TTL_MS,
136
- list_tools_cache_scope=LIST_TOOLS_CACHE_SCOPE,
138
+ return attach_artifact_resources(
139
+ route_table_to_mcp_server(
140
+ RouteTable(registry),
141
+ "UXarray MCP",
142
+ list_tools_ttl_ms=LIST_TOOLS_TTL_MS,
143
+ list_tools_cache_scope=LIST_TOOLS_CACHE_SCOPE,
144
+ )
137
145
  )