Skip to main content

VarianceLabs MCP

MCP server exposing VarianceLabs mineral-estimation & reporting tools for agentic workflows: QAQC of assays and drillhole data, a 3D viewer, property modelling (block-model estimation: NEXUS non-stationary geostatistics by default, LVA-DKNN neural), geometry (implicit wireframes, block models) and EDA (exploratory data analysis of samples and estimates).

  • src/variancelabs_mcp/ — MCP server and tool layers: qaqc/, project/, processing/, modelling/, geometry/, eda/, app/ (the localhost app server and its committed bundle)
  • src/project/ — projects: objects (drillholes, QC data, composites, wireframes, block models, estimates) with ids, recipes and links between them, rebuilds, the files the viewer and Data page read; no MCP dependency
  • src/data_processing/ — drillhole desurvey + compositing engine; the single source of hole geometry (the viewer uses it too); no MCP dependency
  • src/deep_modelling/ — property-modelling engine, ported from the deep-modelling research repo; no MCP dependency, so the web app can call it too
  • src/nexus/ — NEXUS estimation engine (torch, CUDA when present; no scipy); runs inside the estimate worker
  • src/geometry/ — spatial geometry engine: wireframes (RBF + marching cubes) and block models (rotated, sub-blocked, wireframe-constrained); no MCP dependency
  • src/eda/ — EDA engine: statistics and plot data from arrays (univariate, bivariate, outliers, section, grade–tonnage, swath); src/project/eda.py feeds it project objects
  • src/qaqc/ — QAQC engine: CRM control charts (Nelson rules), blanks, duplicates, grade, drillhole-geometry and spatial checks, flags and settings, from arrays and tables; src/project/qaqc.py feeds it project objects; no MCP dependency
  • src/web-app/ — browser app (Data, QAQC and EDA pages, 3D viewer)

Develop

uv sync --extra dev --extra modelling      # modelling = PyTorch (CUDA build on Win/Linux)
uv run python -m variancelabs_mcp.server   # stdio MCP server
uv run pytest                              # default test suite (~3 min)
uv run pytest -m integration               # slow end-to-end: real worker, demo data
uv run python scripts/benchmark_nexus.py   # NEXUS on the synthetic truths of scripts/generate_synthetic_3d.py

Install as an MCP server (one-time)

Installed editable as a uv tool, then registered at user scope so the tools are available in every project — not just this repo.

uv tool install --python 3.11 --editable ".[modelling]"  # repo root; drop [modelling] to skip torch
claude mcp add --scope user variancelabs ~/.local/bin/variancelabs-mcp

On Windows the executable is %USERPROFILE%\.local\bin\variancelabs-mcp.exe; register that absolute path. uv tool run variancelabs-mcp does not work — the package is not on any index, so the registration must point at the exe.

Verify with claude mcp list (expect variancelabs: … - ✓ Connected).

Updating an installed MCP

Because the install is editable, its .pth points straight at this repo's src/. The server runs whatever is in your working tree — including after a git checkout of another branch.

You changed What to do
Python source (src/variancelabs_mcp/) Restart the Claude Code session, or /mcp → variancelabs → reconnect
Deps or [project.scripts] in pyproject.toml uv tool install --python 3.11 --editable ".[modelling]" --force, then restart
Web app (src/web-app/) npm run build (writes the committed bundle at src/variancelabs_mcp/app/static/), then restart

The tool venv is separate from .venv, which is why dependency changes need the --force reinstall — a plain restart will not pick them up. On Windows, stop every running server first (close the Claude sessions using it): --force deletes the venv's Lib/ and then fails on the locked Scripts/, leaving it broken. To repair or update in place while a server runs: uv pip install --python "%APPDATA%\uv\tools\variancelabs-mcp\Scripts\python.exe" -e ".[modelling]". The version recorded in the tool's metadata is baked at install time and can lag pyproject.toml; that is cosmetic, the code served is always the working tree.

QAQC tools (qaqc_*)

QC files (CRM results with certified values, duplicates, blanks) are loaded as a QC data object nested under raw drillholes; the checks of grades, drillhole geometry and spatial outliers need only the drillholes. Every check emits flags that can be reviewed.

  • project_load_qaqc(drillholes, standards?, reference?, duplicates?, blanks?, mapping?, name?, project?) — copy the QC files into the project; columns are detected and reported.
  • qaqc_overview(drillholes?) — start here: panels with data, verdicts, open flags, settings in force, suggested next tools.
  • qaqc_standards(element?, standard?, ...) — CRM bias and Nelson-rule events by batch.
  • qaqc_blanks(element?, ...) — contamination against the detection-limit threshold.
  • qaqc_duplicates(element?, set?, ...) — HARD precision and bias per pair set (duplicates, repeats, re-assay columns).
  • qaqc_checks(family, element?, ...) — grades, geometry or spatial checks.
  • qaqc_issues(family?, check?, severity?, status?, element?, hole?, limit?) — the flag register.
  • qaqc_review(status, flag_ids? | check?, element?, hole?, comment?, only_open?) — mark flags open, resolved or dismissed; reviews and settings are stored in <project>/qaqc.json.
  • qaqc_settings(values?) — detection limits, blank factor, HARD thresholds, minimum recovery, spike and spatial ratios ("auto" restores one).

Like EDA, each tool returns the numbers and a url opening the QAQC page (/qaqc/, shared chart core and UI in src/web-app/src/analysis/) on the same view; the page computes on demand through GET /api/qaqc/<panel>. Design: docs/superpowers/specs/2026-10-06-qaqc-module-design.md.

Data processing tools (processing_*)

  • processing_composite(drillholes, options?, name?, project?) — fixed-length composites with X/Y/Z at midpoints, added to the project (ready for modelling_start_run). drillholes is a drillholes/composites object id or {collar, intervals, survey?, mapping?, desurvey?} CSV paths.
  • processing_desurvey(collar, table, survey?, at?, options?) — X/Y/Z for intervals (midpoint or both ends) or depth points; a standalone export, not a project object.

Every option is optional: unset ones (composite length, value columns, coordinate system, dip sign) are chosen from the data, and the returned summary says what was used and why; desurvey method (minimum curvature by default, balanced tangent, tangent) and azimuth_correction (degrees) are fixed defaults. processing_composite writes into the project folder (objects/<object_id>/v<n>/); processing_desurvey writes to ./variancelabs-output/processing/<run-id>/ (*.csv + report.json). Design: docs/superpowers/specs/2026-09-27-data-processing-design.md.

Property modelling tools (modelling_*)

A run estimates one grade at every block of a block-model object, optionally domain by domain (hard boundaries); the result is an estimate object nested under its block model. Two methods (method, fixed per estimate):

  • nexus (default) — non-stationary geostatistics (NEXUS.md): stationarity audit, multiscale robust trend, local scale / nugget / anisotropy fields, kriging with a Paciorek–Schervish covariance, cross-validated settings; ordinary kriging when the data are stationary. Needs a hole id and at least 5 holes per domain.
  • lva_dknn — the neural estimator of src/deep_modelling/, conditioned on local data.

Runs execute in a detached worker (python -m project.estimate run <run_dir>), so the MCP server never loads torch; the worker commits the result into the project. The run folder runs/<object_id>/ keeps status.json and worker.log.

  • modelling_start_run(block_model, samples, value, method?, domains?, columns?, options?, name?, project?)
  • modelling_run_status(object_id, project?) — progress; metrics and output paths when done.
  • modelling_cancel_run(object_id, project?).

NEXUS outputs (objects/<object_id>/v<n>/): estimate.csv (<value>_est block mean, _trend, _p10/_p50/_p90, _sd, _rel_unc, _p_gt_<cutoff>, n_samples, n_holes, mean_dist, flag), metrics.json (audit, every parameter with its reason, held-out skill against ordinary kriging and inverse distance, calibration, trend reproduction), validation.csv, fields.npz. The Data page creates (+ Add estimate on a block model) and edits NEXUS estimates. An upstream rebuild marks the estimate out of date (project_update(object_id, {}) re-runs it). Designs: docs/superpowers/specs/2026-10-10-nexus-estimator-design.md, docs/superpowers/specs/2026-09-27-property-modelling-design.md, docs/superpowers/specs/2026-10-01-block-model-estimates-design.md.

Geometry tools (geometry_*)

Closed wireframes from drillholes: a biharmonic RBF (greedy-fitted, dual form) of the signed distance to inside/outside contacts, contoured by marching cubes. Every build is added to the project as an object.

  • geometry_domain_wireframe(drillholes, column, inside, options?, name?, project?) — solid around intervals whose code is in inside (e.g. LITH, ["SAP"]).
  • geometry_grade_shell(drillholes, column, cutoff, options?, name?, project?) — shell around composites ≥ cutoff (indicator by default; transform: log|none|auto interpolates the grade).

drillholes is a drillholes/composites object id or {collar, intervals, survey?, mapping?, desurvey?} CSV paths. Every option is optional (resolution, extent, trend — auto from the contacts —, kernel, drift, accuracy, max_centres, max_distance…); the summary says what was used and why. Outputs land in the project folder (objects/<object_id>/v<n>/): wireframe.obj in world coordinates, points.csv, report.json. Design: docs/superpowers/specs/2026-09-28-geometry-wireframes-design.md.

  • geometry_block_model(wireframes?, drillholes?, options?, name?, project?) — block-model geometry, no values yet: parent blocks (optionally rotated), regular sub-blocks where wireframe boundaries cut them, a DOMAIN code per block (first listed wireframe wins). Outputs block_model.csv (world centroids, sizes, IJK, sub-block indices, domain), block_model.json (definition) and report.json in the project folder; estimates attach to it (see Property modelling). Design: docs/superpowers/specs/2026-09-28-geometry-block-model-design.md.

EDA tools (eda_*)

Read-only analyses of the project's samples (drillholes / composites) and estimates. Each returns the numbers (statistics, tables, the choices made and why) and a url opening the EDA page on the same analysis (open=true opens it; or project_open(open="eda")).

  • eda_overview(project?) — what can be analysed, per-column summaries, suggested order.
  • eda_univariate(source?, property?, group_by?, weight?, log?, bins?) — statistics per group, histogram (length-weighted by default).
  • eda_bivariate(source?, x?, y?, group_by?, log?, method?) — Pearson / log / Spearman, regression, correlation matrix.
  • eda_outliers(source?, property?, group_by?, weight?) — top-cut diagnostics: decile analysis, cap candidates with metal removed, suggested cap, flagged samples.
  • eda_section(source?, start?, end?, width?, property?) — vertical section along A–A′ with a corridor (auto: the collars' long axis).
  • eda_grade_tonnage(estimate, density?, cutoffs?, domain?) — grade–tonnage curve (volume without a density).
  • eda_swath(estimate, axis?, width?, domain?) — estimate against its samples along X, Y, Z.

The page (/eda/) computes the same analyses on demand through GET /api/eda/catalog and GET /api/eda/<analysis> and draws them with the shared chart core of src/web-app/src/analysis/ (Canvas; zoom, pan, hover; no chart dependency), also used by the QAQC page. Design: docs/superpowers/specs/2026-10-01-eda-module-design.md.

Extending

  • New vertical: add a subpackage with register(mcp) and call it in server.build_server().

Projects (project_* tools)

Every loaded or built object (drillholes, composites, wireframes, block models, estimates) lives in a project under variancelabs-output/projects/<project-id>/. With a single project in the working folder, tools use it by default (creating and naming one after the folder the first time); with several, the tools ask which one. processing_* and geometry_* tools write their outputs into the project and return object_id.

  • project_open(project?, open?) — resolve (or default-create) a project; open: 'data' | 'qaqc' | 'eda' | 'viewer' opens that page in the browser. Returns the object list, summaries and the page URLs.
  • project_create(name, open?) — a new, empty project; same open as project_open.
  • project_load_drillholes(collar?, intervals?, survey?, desurveyed?, load_as?, mapping?, desurvey?, name?, project?) — load raw tables (editable later) or an already desurveyed/composited table (load_as: 'drillholes' | 'composites', not editable).
  • project_load_qaqc(drillholes, standards?, ...) — QC files for raw drillholes (see QAQC tools).
  • project_load_wireframe(path, name?, project?) — import an OBJ wireframe.
  • project_update(object_id, params, project?) — change an object's parameters (as on the Data page's Edit panel) and rebuild it and everything built from it.
  • project_remove(object_ids, cascade?, project?) — remove objects; dependents must be confirmed with cascade=true.
  • project_set_main_commodity(column, project?) — the column (e.g. AU1) every page and eda/qaqc tool shows first where an object has it; null clears it. No rebuild.

Opening a page starts a localhost app server (one per MCP process, one token, ?token= in the URL) serving the built web app. Positions in the 3D viewer are staged relative to a local origin (float64 on the Python side) so UTM-scale coordinates render without float32 jitter; the grid labels show real-world coordinates. Numeric interval columns become elements; text columns with ≤ 64 distinct values (e.g. LITH, DOMAIN) are staged as categories.

Display colours holes by element: Continuous (linear or log scale, editable range, Auto = 2nd–98th percentile), Classed (grade shells by equal interval, quantile or custom breaks such as 0.3, 1, 3) or Categorical (text columns, or numeric codes with ≤ 64 values). Palettes are grouped as grade/sequential, topography, diverging and categorical; each can be reversed, and legends show metres per class or category.

Wireframes appear under their own tree node and are selectable in 3D like holes (holes win when both are under the cursor). The Display tab styles the selected wireframes: Solid · Solid + edges · Edges · Points, colour, edge colour and opacity; the Object tab gives volume / parts / extent cuts.

Block models are coloured by domain, block size or one of their estimates (palette and range; blocks without a value grey); the Object tab lists each estimate's held-out R². They are selectable in 3D like wireframes. Display › Rendering › Ray-traced draws the whole model from GPU 3D textures instead of its visible shell: Voxels or Translucent, opacity (blocks are otherwise opaque: unsorted instances cannot blend in depth order), and a cutoff (Show ≥ / ≤) on the estimate shown. It streams a multi-resolution chunk tree (sub-blocks → parents → coarser), finer where blocks cover more than a pixel, within a 1.5 GB GPU budget (src/web-app/src/viewer/volume/, staged by src/project/volume.py; /files/ answers HTTP range requests). A model with more than 2 M visible blocks is ray-traced only.

Viewer controls (the Project tree on the left; Properties pinned bottom-left with Display styling and Object facts about the selection):

Gesture Effect
Drag / right-drag / wheel orbit / pan / zoom
Double-click a hole, wireframe or block model (3D or tree) select only that one; on empty space, clear
Ctrl+click a hole or wireframe (3D or tree) add / remove it
Click, Shift+click, Ctrl+Shift+click (tree) select one / a range / add a range
↑ ↓, Shift+↑ ↓ (tree) move / extend the selection
Double-click Drillholes select all its holes
Ctrl+A select all of the selected type; again for everything
F frame the selection (everything if none)
Esc clear the selection

Web app (src/web-app/)

The browser side is a multi-page app: a left rail with the VarianceLabs mark (assets/icons/) above the module tiles (src/shell/), one page per module at /<module>/, each fully defined by its URL (?project=&token=) so an MCP tool can open it directly. Four modules: data/ (the Data page, src/data/), qaqc/ (the QAQC page, src/qaqc/), eda/ (the EDA page, src/eda/) and viewer/ (the 3D viewer, src/viewer/); / redirects to viewer/. To add a module: a src/<module>/main.ts that calls mountShell("<module>"), a <module>/index.html, an entry in src/shell/modules.ts and one in the Vite input map.

  • uv run python scripts/dev_app_server.py creates (or reuses) a demo project with a synthetic dataset (add --large for the 10k-hole fluidity dataset, or --qaqc for data/test-dataset-QAQC with its QC files) and prints the page URLs.
  • npm run dev (in src/web-app/) hot-reloads the app, proxying /api and /files to that server (default http://127.0.0.1:8765, override with VL_PROXY).
  • npm run test (vitest) and npm run build (type-gate + bundle).
  • The bundle is committed at src/variancelabs_mcp/app/static/ so the MCP needs no Node at runtime: rebuild + commit after engine changes.

Metadata

Release files for variancelabs-mcp 0.3.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for variancelabs-mcp 0.3.1
File Size Uploaded
variancelabs_mcp-0.3.1.tar.gz 3.6 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for variancelabs-mcp 0.3.1
File Interpreter ABI Platform
variancelabs_mcp-0.3.1-py3-none-any.whl Python 3 none any Details

Total release size: 4.3 MB

Release files / variancelabs_mcp-0.3.1.tar.gz

Download URL variancelabs_mcp-0.3.1.tar.gz
Size 3.6 MB
Tags Source
SHA-256 checksum
How to use checksums
becd2da2e0295b5731c762d67974f65d0935fa5df5bcad6dac2dbff81e2ed231
BLAKE2b-256 checksum
How to use checksums
68b4e36cceb2d4eb21b6c82f31191637ec81d5f28ccd2c70956b33f4a52e6084
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.10.20

Release files / variancelabs_mcp-0.3.1-py3-none-any.whl

Download URL variancelabs_mcp-0.3.1-py3-none-any.whl
Size 622.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
19936240b92f193143360bb0f027153fd8cf118695fe902ef6ce39dedf63b9cd
BLAKE2b-256 checksum
How to use checksums
83a78ef0371506e34682e34733c3f96bc6f1de413cecf887e9569439b0798ec0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.10.20

Release history Release notifications | RSS feed

This release

0.3.1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page