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 dependencysrc/data_processing/— drillhole desurvey + compositing engine; the single source of hole geometry (the viewer uses it too); no MCP dependencysrc/deep_modelling/— property-modelling engine, ported from thedeep-modellingresearch repo; no MCP dependency, so the web app can call it toosrc/nexus/— NEXUS estimation engine (torch, CUDA when present; no scipy); runs inside the estimate workersrc/geometry/— spatial geometry engine: wireframes (RBF + marching cubes) and block models (rotated, sub-blocked, wireframe-constrained); no MCP dependencysrc/eda/— EDA engine: statistics and plot data from arrays (univariate, bivariate, outliers, section, grade–tonnage, swath);src/project/eda.pyfeeds it project objectssrc/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.pyfeeds it project objects; no MCP dependencysrc/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,geometryorspatialchecks.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 formodelling_start_run).drillholesis 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 ofsrc/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 ininside(e.g.LITH,["SAP"]).geometry_grade_shell(drillholes, column, cutoff, options?, name?, project?)— shell around composites ≥ cutoff (indicator by default;transform: log|none|autointerpolates 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, aDOMAINcode per block (first listed wireframe wins). Outputsblock_model.csv(world centroids, sizes, IJK, sub-block indices, domain),block_model.json(definition) andreport.jsonin 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 inserver.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; sameopenasproject_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 withcascade=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;nullclears 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.pycreates (or reuses) a demo project with a synthetic dataset (add--largefor the 10k-hole fluidity dataset, or--qaqcfordata/test-dataset-QAQCwith its QC files) and prints the page URLs.npm run dev(insrc/web-app/) hot-reloads the app, proxying/apiand/filesto that server (defaulthttp://127.0.0.1:8765, override withVL_PROXY).npm run test(vitest) andnpm 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)
| File | Size | Uploaded | |
|---|---|---|---|
| variancelabs_mcp-0.3.1.tar.gz | 3.6 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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
|