@barivia/barsom-mcp 0.22.1 → 0.22.4
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.
- package/README.md +6 -3
- package/dist/index.js +1 -1
- package/dist/job_status_format.js +23 -0
- package/dist/prepare_training_prompt.js +15 -5
- package/dist/shared.js +62 -7
- package/dist/tools/account.js +16 -77
- package/dist/tools/datasets.js +20 -8
- package/dist/tools/explore_map.js +37 -18
- package/dist/tools/results.js +7 -1
- package/dist/tools/train.js +11 -4
- package/dist/tools/training_core.js +6 -1
- package/dist/tools/training_monitor.js +80 -37
- package/dist/train_submit_message.js +5 -7
- package/dist/training_monitor_curve.js +32 -0
- package/dist/ui-delivery.js +185 -0
- package/dist/views/src/views/results-explorer/index.html +16 -13
- package/dist/views/src/views/training-monitor/index.html +30 -18
- package/dist/viz_links.js +25 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -163,12 +163,11 @@ All actions use a frozen trained map — no retraining. Derived columns use **`d
|
|
|
163
163
|
| Action | Use when |
|
|
164
164
|
|--------|----------|
|
|
165
165
|
| `status` | Before large jobs — plan tier, GPU availability, queue depth, credit balance, training time estimates |
|
|
166
|
-
| `request_compute` | Upgrading to cloud burst. Leave tier blank to list options. |
|
|
167
|
-
| `compute_status` | Checking active lease time remaining |
|
|
168
|
-
| `release_compute` | Manually stopping a lease to stop billing |
|
|
169
166
|
| `history` | Viewing recent compute usage and spend |
|
|
170
167
|
| `add_funds` | Getting instructions to add credits |
|
|
171
168
|
|
|
169
|
+
Capacity is shared LOCAL/GKE worker pools (no per-key cloud burst lease).
|
|
170
|
+
|
|
172
171
|
### MCP App tools (optional UIs)
|
|
173
172
|
|
|
174
173
|
| Tool | Role |
|
|
@@ -191,6 +190,10 @@ The right viewer depends on **(MCP App support)** **and** **(can the human reach
|
|
|
191
190
|
|
|
192
191
|
### Migration notes
|
|
193
192
|
|
|
193
|
+
- **Account surface (0.22.0, breaking):** `account(request_compute|compute_status|release_compute)` removed — AWS cloud burst leases are gone. Use `account(status|history|add_funds)`. Capacity is shared LOCAL/GKE pools.
|
|
194
|
+
- **Structured results + train hints (0.21.x, non-breaking):**
|
|
195
|
+
- **`results(action=get)`** returns **`structuredContent`** (job id/type, label, summary metrics, file list) alongside the text summary — same pattern as `training_monitor` / `results_explorer`. Agent hosts can read fields without parsing prose.
|
|
196
|
+
- **`train`** submit responses may include a **`results_hint`** string pointing agents to the next post-train step.
|
|
194
197
|
- **Fixed-panel live TE (0.20.4):** mid-training TE curves use a fixed evaluation panel (`te_panel_size` on `train`). Monitors show **Panel TE** and **Map TE** separately; curve tail no longer snaps to map TE.
|
|
195
198
|
- **Features (0.20.0, non-breaking):**
|
|
196
199
|
- **FLooP-SIOM maps are now projectable.** `inference(action=project_columns)`, `inference(action=predict)`, and `datasets(action=add_expression, project_onto_job=<floop_job>)` work on FLooP-SIOM (free/chain) maps — values render onto the FLooP Voronoi layout instead of failing. Grid-only ops (`inference(action=transition_flow | impute_column | render_variant)`) return a clear `unsupported_topology_for_inference` message; use a fixed-grid SOM/SIOM for those. No client change required.
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{McpServer as e}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as t}from"@modelcontextprotocol/sdk/server/stdio.js";import{z as o}from"zod";import{getUiCapability as r,registerAppResource as s,RESOURCE_MIME_TYPE as n}from"@modelcontextprotocol/ext-apps/server";import{startVizServer as i}from"./viz-server.js";import{API_KEY as a,apiCall as l,apiRawCall as c,loadViewHtml as p,setVizPort as m,setClientSupportsMcpApps as u,CLIENT_VERSION as d}from"./shared.js";import{registerDatasetsTool as f}from"./tools/datasets.js";import{registerTrainTool as g}from"./tools/train.js";import{registerJobsTool as h,JOBS_DESCRIPTION_BASE as _}from"./tools/jobs.js";import{registerResultsTool as b}from"./tools/results.js";import{registerExploreMapTool as w,RESULTS_EXPLORER_URI as y}from"./tools/explore_map.js";import{registerAccountTool as j}from"./tools/account.js";import{registerInferenceTool as v}from"./tools/inference.js";import{registerGuideBarsomTool as x}from"./tools/guide_barsom.js";import{registerTrainingGuidanceTool as P}from"./tools/training_guidance.js";import{registerFeedbackTool as k}from"./tools/feedback.js";import{registerTrainingMonitorTool as I,TRAINING_MONITOR_URI as A}from"./tools/training_monitor.js";import{resolvePrepareTrainingPromptText as M}from"./prepare_training_prompt.js";a||(console.error("Error: BARIVIA_API_KEY not set. Set it in your MCP client config."),process.exit(1));const T=new e({name:"analytics-engine",version:d,instructions:'# Barivia Mapping Analytics Engine\n\nSelf-organizing map (SOM) analytics: project high-dimensional data to a 2D grid for clusters, gradients, and anomalies.\n\n## Workflow (short)\n\nUpload (`datasets(upload)`) → `datasets(preview)` and `datasets(analyze)` before train → submit one of `train(action=map)`, `train(action=siom_map)`, or `train(action=floop_siom)` (only if plan allows FLooP) → poll `jobs(status)` every 10–15s until `completed` → `results(get)` for metrics and figures (there is no separate analyze tool), then `results_explorer(job_id)` to browse the figures interactively. Then `jobs(compare)`, `results(download/recolor)`, or `inference` (predict / compare / project_columns / impute_column / transition_flow) as needed.\n\n**Full detail:** Call `guide_barsom_workflow` for plan-scoped tool map, training modes, async rules, optional MCP App UIs, and step-by-step SOP (from the Barivia API when online).\n\n## Tool taxonomy (one tool per stage — do not mix)\n\n| Stage | Tool(s) | Contract |\n|-------|---------|----------|\n| Data | `datasets` | upload, preview, analyze, list, **get** (status/staging), subset, add_expression, reduce_spectral, delete — ingest + feature engineering, no map |\n| Train submit | `train` | map, siom_map, impute (sparse → map + dense imputed.csv), floop_siom (entitled). Async, returns job_id. Submit only |\n| Lifecycle | `jobs` | status, list, compare (**training runs**), cancel, delete. No training/scoring here |\n| Artifacts | `results` | get (default=combined only; figures="none" for metrics-only sweeps; figures="all" or results_explorer/download for every plot), export, download, recolor (async). Read/render completed jobs |\n| Frozen-map ops | `inference` | predict (regime-aware; "compact"|"annotated"), batch_predict, impute_column (neighbor-pool fill for a non-training column), compare (cohort density-diff), project_columns, transition_flow (time-ordered rows only), report. No weight updates |\n| Account | `account` | status, burst/compute actions, history, add_funds |\n| Bootstrap | `guide_barsom_workflow`, `train`, `training_guidance`, `prepare_training` prompt | orientation, submit map/siom/impute/floop, parameter hints, narrative checklist (API/tier-scoped) |\n| Explore (UI) | `results_explorer`, `training_monitor` | richer UX over the same API; `jobs(status)`+`results(get)` still suffice headless |\n| Other | `send_feedback` | only after user agrees |\n\n### Avoid these mis-routes\n- `jobs(compare)` = metrics table across **training runs** (pick best in a sweep). `inference(compare)` = density-diff of one map vs a **second dataset** (drift/cohort). Different tools.\n- Post-training map analysis (predict, compare, project_columns, impute_column, **transition_flow**) is all **inference** — not `results`. `results` is presentation/artifacts only (`recolor` stays here: it re-renders, it doesn\'t touch data).\n- Project a quantity onto the map: existing columns → `inference(project_columns)`; a formula → `datasets(add_expression, project_onto_job=<job>)`.\n\n## Async pattern\n\n- **Manual poll:** Training submits return `job_id` immediately — poll `jobs(status)` every 10–15s. **Running is not failed**; large grids or FLooP-SIOM can take many minutes. `max_nodes` (FLooP) is a total node budget, not grid side length.\n- **Often auto-polled:** `inference` actions (incl. `transition_flow`) and `results(recolor)` may wait in-proxy; if you get a `job_id`, poll `jobs(status)` the same way.\n\nCredits: jobs consume compute credits; check `account(status)` before big runs. Per-request HTTP timeout defaults to 60s (`datasets(analyze)` is async + auto-polled, so it is not bound by it); on slow networks raise `BARIVIA_FETCH_TIMEOUT_MS`. Timeouts are NOT auto-retried (avoids duplicating slow/expensive work).\n\n## Constraints\n\n- Prep ladder: `prepare_training` prompt = narrative checklist; `training_guidance` = structured hints (presets, resolved normalization/transforms, grid/epochs). Review these, then submit with `train`. Do not guess tiers or FLooP entitlement.\n- `inference(predict)`: prefer `dataset_id` for batch and for SIOM/irregular maps; single-row `rows` uses a fast path that can fail on some topologies — retry with `dataset_id`. FLooP-SIOM: if predict jobs fail while grid SIOM works, capture errors + `job_id`.\n- Column names are case-sensitive — match `datasets(preview)`.\n- Default training path is numeric/cyclic/temporal; use explicit `categorical_features` for baseline categoricals. `predict` must match the model contract.\n- After `results(recolor)`, `inference(transition_flow)`, or `inference(project_columns)`, use the **new** `job_id` returned for follow-up `results` if applicable.\n- **Large results / MCP timeouts:** For sweeps, `jobs(compare)`, or metrics-only reads use `results(action=get, figures="none")`. Default `get` inlines combined only. For every plot use `results_explorer` or `results(action=download)`, not `figures="all"` in chat unless the user wants all images inline.'});s(T,y,y,{mimeType:n},async()=>{const e=await p("results-explorer");return{contents:[{uri:y,mimeType:n,text:e??"<html><body>Results Explorer view not built yet. Run: npm run build:views</body></html>"}]}}),s(T,A,A,{mimeType:n},async()=>{const e=await p("training-monitor");return{contents:[{uri:A,mimeType:n,text:e??"<html><body>Training Monitor view not built yet.</body></html>"}]}}),x(T),w(T),I(T),f(T),g(T),h(T,_),b(T),j(T),v(T),P(T),k(T),T.prompt("info","Short orientation for the Barivia Mapping MCP. For full plan-scoped workflow, tool map, and SOP, the model should call guide_barsom_workflow. Use when the user asks what this MCP can do or how to get started.",{},()=>({messages:[{role:"user",content:{type:"text",text:["Give a concise, scannable answer (headers + bullets):","","**What it is:** MCP client to the Barivia mapping engine (2D SOM / SIOM / FLooP-SIOM when entitled) over HTTPS.","","**First step:** Call `guide_barsom_workflow` for plan-scoped bootstrap (full tool list, async rules, training modes, optional MCP Apps, SOP).","","**Core path:** `datasets(upload)` → `datasets(preview)` + `datasets(analyze)` → choose training action → poll `jobs(status)` every 10–15s until completed → `results(get)` (all main figures/metrics; no separate analyze tool) → `results_explorer(job_id)` to browse figures.","",'**Key tools:** `datasets` (data; reduce_spectral for spectra/long blocks), `train` (map/siom_map/impute/floop_siom; pass `label` for readable compare rows; preset=quick for a fast first map), `jobs` (status/list/compare/cancel/delete — lifecycle only), `results` (get/download/export/recolor; figures="none" for metrics-only), `inference` (predict; batch_predict; impute_column for topology-neighbor pool fill; compare; project_columns; transition_flow; report), `account` (status/credits/queue).',"","**Prep help:** `prepare_training` prompt (checklist) · `training_guidance` (presets/JSON hints + resolved normalization/params) — review, then submit with `train`.","","**Browse results:** `results_explorer(job_id)` is the preferred way to explore figures after a first `results(get)` glance; `training_monitor` shows live progress. Both optional — `results` + `jobs(status)` suffice headless.","","**After training:** `jobs(compare)` across runs, `results(recolor)`, `inference(project_columns)` for variables not in training, `inference(transition_flow)` only if rows are time-ordered.","","**Rules:** Running ≠ failed. Column names must match `datasets(preview)` exactly. Do not call `_fetch_figure` from chat (host/UI only); use `results(get)` or `results_explorer`.","","Offer `send_feedback` only after asking the user."].join("\n")}}]})),T.prompt("prepare_training","Narrative pre-training checklist (prompt). Use after upload and before train. Content is tier-scoped from the API when online. Prep ladder: this prompt = story checklist; training_guidance tool = JSON presets/parameter hints. Review, then submit with train.",{dataset_id:o.string().describe("Dataset ID to prepare for training")},async({dataset_id:e})=>({messages:[{role:"user",content:{type:"text",text:await M(e)}}]}));const S=new t;(async function(){try{const e=await i(l,c,p);m(e)}catch(e){process.env.BARIVIA_VIZ_PORT&&console.error("Barivia viz server failed to start:",e)}const e=T.server;e.oninitialized=()=>{const t=e.getClientCapabilities(),o=r(t);u(!!o?.mimeTypes?.includes(n))},await T.connect(S)})().catch(console.error);
|
|
2
|
+
import{McpServer as e}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as t}from"@modelcontextprotocol/sdk/server/stdio.js";import{z as o}from"zod";import{getUiCapability as r,registerAppResource as s,RESOURCE_MIME_TYPE as n}from"@modelcontextprotocol/ext-apps/server";import{startVizServer as a}from"./viz-server.js";import{API_KEY as i,apiCall as l,apiRawCall as p,loadViewHtml as c,setVizPort as u,setClientSupportsMcpApps as d,CLIENT_VERSION as m}from"./shared.js";import{registerDatasetsTool as f}from"./tools/datasets.js";import{registerTrainTool as g}from"./tools/train.js";import{registerJobsTool as h,JOBS_DESCRIPTION_BASE as _}from"./tools/jobs.js";import{registerResultsTool as b}from"./tools/results.js";import{registerExploreMapTool as y,RESULTS_EXPLORER_URI as w}from"./tools/explore_map.js";import{registerAccountTool as v}from"./tools/account.js";import{registerInferenceTool as j}from"./tools/inference.js";import{registerGuideBarsomTool as x}from"./tools/guide_barsom.js";import{registerTrainingGuidanceTool as k}from"./tools/training_guidance.js";import{registerFeedbackTool as P}from"./tools/feedback.js";import{registerTrainingMonitorTool as A,TRAINING_MONITOR_URI as I}from"./tools/training_monitor.js";import{resolvePrepareTrainingPrompt as C}from"./prepare_training_prompt.js";i||(console.error("Error: BARIVIA_API_KEY not set. Set it in your MCP client config."),process.exit(1));const M=new e({name:"analytics-engine",version:m,instructions:'# Barivia Mapping Analytics Engine\n\nSelf-organizing map (SOM) analytics: project high-dimensional data to a 2D grid for clusters, gradients, and anomalies.\n\n## Workflow (short)\n\nUpload (`datasets(upload)`) → `datasets(preview)` and `datasets(analyze)` before train → submit one of `train(action=map)`, `train(action=siom_map)`, or `train(action=floop_siom)` (only if plan allows FLooP) → poll `jobs(status)` every 10–15s until `completed` → `results(get)` for metrics and figures (there is no separate analyze tool), then `results_explorer(job_id)` to browse the figures interactively. Then `jobs(compare)`, `results(download/recolor)`, or `inference` (predict / compare / project_columns / impute_column / transition_flow) as needed.\n\n**Full detail:** Call `guide_barsom_workflow` for plan-scoped tool map, training modes, async rules, optional MCP App UIs, and step-by-step SOP (from the Barivia API when online).\n\n## Tool taxonomy (one tool per stage — do not mix)\n\n| Stage | Tool(s) | Contract |\n|-------|---------|----------|\n| Data | `datasets` | upload, preview, analyze, list, **get** (status/staging), subset, add_expression, reduce_spectral, delete — ingest + feature engineering, no map |\n| Train submit | `train` | map, siom_map, impute (sparse → map + dense imputed.csv), floop_siom (entitled). Async, returns job_id. Submit only |\n| Lifecycle | `jobs` | status, list, compare (**training runs**), cancel, delete. No training/scoring here |\n| Artifacts | `results` | get (default=combined only; figures="none" for metrics-only sweeps; figures="all" or results_explorer/download for every plot), export, download, recolor (async). Read/render completed jobs |\n| Frozen-map ops | `inference` | predict (regime-aware; "compact"|"annotated"), batch_predict, impute_column (neighbor-pool fill for a non-training column), compare (cohort density-diff), project_columns, transition_flow (time-ordered rows only), report. No weight updates |\n| Account | `account` | status, history, add_funds |\n| Bootstrap | `guide_barsom_workflow`, `train`, `training_guidance`, `prepare_training` prompt | orientation, submit map/siom/impute/floop, parameter hints, narrative checklist (API/tier-scoped) |\n| Explore (UI) | `results_explorer`, `training_monitor` | richer UX over the same API; `jobs(status)`+`results(get)` still suffice headless |\n| Other | `send_feedback` | only after user agrees |\n\n### Avoid these mis-routes\n- `jobs(compare)` = metrics table across **training runs** (pick best in a sweep). `inference(compare)` = density-diff of one map vs a **second dataset** (drift/cohort). Different tools.\n- Post-training map analysis (predict, compare, project_columns, impute_column, **transition_flow**) is all **inference** — not `results`. `results` is presentation/artifacts only (`recolor` stays here: it re-renders, it doesn\'t touch data).\n- Project a quantity onto the map: existing columns → `inference(project_columns)`; a formula → `datasets(add_expression, project_onto_job=<job>)`.\n\n## Async pattern\n\n- **Manual poll:** Training submits return `job_id` immediately — poll `jobs(status)` every 10–15s. **Running is not failed**; large grids or FLooP-SIOM can take many minutes. `max_nodes` (FLooP) is a total node budget, not grid side length.\n- **Often auto-polled:** `inference` actions (incl. `transition_flow`) and `results(recolor)` may wait in-proxy; if you get a `job_id`, poll `jobs(status)` the same way.\n\nCredits: jobs consume compute credits; check `account(status)` before big runs. Per-request HTTP timeout defaults to 60s (`datasets(analyze)` is async + auto-polled, so it is not bound by it); on slow networks raise `BARIVIA_FETCH_TIMEOUT_MS`. Timeouts are NOT auto-retried (avoids duplicating slow/expensive work).\n\n## Constraints\n\n- **Do not auto-subset:** Never call `datasets(subset)` or `sample_n` unless the user explicitly asks for a filter, slice, or random sample. Train and analyze on the uploaded `dataset_id` at full row count — GPU staging/prepare handles hundreds of thousands of rows quickly; silent downsampling hides real prepare/train behavior and wastes scale demos. Use small named fixtures (e.g. sample.csv, periodic_load) only when the user or runbook names them.\n- Prep ladder: `prepare_training` prompt = narrative checklist; `training_guidance` = structured hints (presets, resolved normalization/transforms, grid/epochs). Review these, then submit with `train`. Do not guess tiers or FLooP entitlement.\n- `inference(predict)`: prefer `dataset_id` for batch and for SIOM/irregular maps; single-row `rows` uses a fast path that can fail on some topologies — retry with `dataset_id`. FLooP-SIOM: if predict jobs fail while grid SIOM works, capture errors + `job_id`.\n- Column names are case-sensitive — match `datasets(preview)`.\n- Default training path is numeric/cyclic/temporal; use explicit `categorical_features` for baseline categoricals. `predict` must match the model contract.\n- After `results(recolor)`, `inference(transition_flow)`, or `inference(project_columns)`, use the **new** `job_id` returned for follow-up `results` if applicable.\n- **Large results / MCP timeouts:** For sweeps, `jobs(compare)`, or metrics-only reads use `results(action=get, figures="none")`. Default `get` inlines combined only. For every plot use `results_explorer` or `results(action=download)`, not `figures="all"` in chat unless the user wants all images inline.\n\n## UI delivery (heterogeneous MCP clients)\n\nHosts differ (Cursor, Claude Desktop, CLI agents): some embed MCP App panels; others list `ui://` but never mount them (expected on some Cursor builds); others are text-only. Tool results **always lead with a standalone localhost URL** — that is the intended fallback, not a broken App.\n\n| Tier | When | Agent should |\n|------|------|--------------|\n| **embedded** | Client advertises MCP Apps | Let the panel render **and** surface the standalone localhost URL from tool output / `ui_delivery.urls` (hosts often list Apps but do not mount). |\n| **localhost** | No MCP Apps; viz server running | Post the markdown link from tool output to the user (never broken workspace paths). Re-post after job completes if needed. |\n| **inline_image** | No Apps / override `inline` | Summarize metrics; inline raster (combined.png, learning curve) is attached automatically — show it in the reply. |\n| **text_only** | Viz server down | Use `jobs(status)` + `results(get)`; no panel or localhost link. |\n\nEvery `training_monitor` / `results_explorer` response includes a structured `ui_delivery` block (`delivery`, `urls`, `hint_for_agent`). Follow `hint_for_agent` without user prompting. Standalone viz pages cross-link monitor ↔ results for the same `job_id`.\n\n**Env overrides:** `BARIVIA_UI_DELIVERY=auto|apps|localhost|inline` (default `auto`). `BARIVIA_VIZ_PORT` pins the localhost viz port across proxy restarts. **Diagnostics:** `account(action=status)` reports detected MCP Apps support, viz port, and active tier.'});s(M,w,w,{mimeType:n},async()=>{const e=await c("results-explorer");return{contents:[{uri:w,mimeType:n,text:e??"<html><body>Results Explorer view not built yet. Run: npm run build:views</body></html>"}]}}),s(M,I,I,{mimeType:n},async()=>{const e=await c("training-monitor");return{contents:[{uri:I,mimeType:n,text:e??"<html><body>Training Monitor view not built yet.</body></html>"}]}}),x(M),y(M),A(M),f(M),g(M),h(M,_),b(M),v(M),j(M),k(M),P(M),M.prompt("info","Short orientation for the Barivia Mapping MCP. For full plan-scoped workflow, tool map, and SOP, the model should call guide_barsom_workflow. Use when the user asks what this MCP can do or how to get started.",{},()=>({messages:[{role:"user",content:{type:"text",text:["Give a concise, scannable answer (headers + bullets):","","**What it is:** MCP client to the Barivia mapping engine (2D SOM / SIOM / FLooP-SIOM when entitled) over HTTPS.","","**First step:** Call `guide_barsom_workflow` for plan-scoped bootstrap (full tool list, async rules, training modes, optional MCP Apps, SOP).","","**Core path:** `datasets(upload)` → `datasets(preview)` + `datasets(analyze)` → choose training action → poll `jobs(status)` every 10–15s until completed → `results(get)` (all main figures/metrics; no separate analyze tool) → `results_explorer(job_id)` to browse figures.","",'**Key tools:** `datasets` (data; reduce_spectral for spectra/long blocks), `train` (map/siom_map/impute/floop_siom; pass `label` for readable compare rows; preset=quick for a fast first map), `jobs` (status/list/compare/cancel/delete — lifecycle only), `results` (get/download/export/recolor; figures="none" for metrics-only), `inference` (predict; batch_predict; impute_column for topology-neighbor pool fill; compare; project_columns; transition_flow; report), `account` (status/credits/queue).',"","**Prep help:** `prepare_training` prompt (checklist) · `training_guidance` (presets/JSON hints + resolved normalization/params) — review, then submit with `train`.","","**Browse results:** `results_explorer(job_id)` is the preferred way to explore figures after a first `results(get)` glance; `training_monitor` shows live progress. Both optional — `results` + `jobs(status)` suffice headless.","","**After training:** `jobs(compare)` across runs, `results(recolor)`, `inference(project_columns)` for variables not in training, `inference(transition_flow)` only if rows are time-ordered.","","**Rules:** Running ≠ failed. Column names must match `datasets(preview)` exactly. Do not auto-subset — never call `datasets(subset)` / `sample_n` unless the user explicitly asks; train on the full uploaded `dataset_id`. Do not call `_fetch_figure` from chat (host/UI only); use `results(get)` or `results_explorer`.","","Offer `send_feedback` only after asking the user."].join("\n")}}]})),M.prompt("prepare_training","Narrative pre-training checklist (prompt). Use after upload and before train. Content is tier-scoped from the API when online. Prep ladder: this prompt = story checklist; training_guidance tool = JSON presets/parameter hints. Review, then submit with train.",{dataset_id:o.string().describe("Dataset ID to prepare for training")},async({dataset_id:e})=>{const t=await C(e);return{messages:[{role:"user",content:{type:"text",text:(t.used_fallback?`[offline fallback ${t.fallback_version}]\n\n`:"")+t.text}}]}});const T=new t;(async function(){try{const e=await a(l,p,c);u(e)}catch(e){process.env.BARIVIA_VIZ_PORT&&console.error("Barivia viz server failed to start:",e)}const e=M.server;e.oninitialized=()=>{const t=e.getClientCapabilities(),o=r(t);d(!!o?.mimeTypes?.includes(n))},await M.connect(T)})().catch(console.error);
|
|
@@ -70,6 +70,17 @@ export function formatJobStatusText(job_id, data) {
|
|
|
70
70
|
if (epoch != null && totalEpochs != null && totalEpochs > 0) {
|
|
71
71
|
parts.push(`epoch ${epoch}/${totalEpochs}`);
|
|
72
72
|
}
|
|
73
|
+
const panelTe = data.panel_te != null && !Number.isNaN(Number(data.panel_te))
|
|
74
|
+
? Number(data.panel_te)
|
|
75
|
+
: data.panel_topographic_error != null && !Number.isNaN(Number(data.panel_topographic_error))
|
|
76
|
+
? Number(data.panel_topographic_error)
|
|
77
|
+
: null;
|
|
78
|
+
if (panelTe != null) {
|
|
79
|
+
parts.push(`panel_te: ${panelTe.toFixed(4)}`);
|
|
80
|
+
}
|
|
81
|
+
if (data.kernel_complete === true) {
|
|
82
|
+
parts.push("kernel_complete");
|
|
83
|
+
}
|
|
73
84
|
}
|
|
74
85
|
const datasetRows = data.dataset_rows != null ? Number(data.dataset_rows) : null;
|
|
75
86
|
const datasetName = data.dataset_name != null ? String(data.dataset_name) : null;
|
|
@@ -120,6 +131,18 @@ export function formatJobStatusText(job_id, data) {
|
|
|
120
131
|
else if (status === "cancelled") {
|
|
121
132
|
parts.push(`Cancelled. Poll again to confirm; dataset and partial worker state are unchanged.`);
|
|
122
133
|
}
|
|
134
|
+
const suggested = data.suggested_next_step != null && String(data.suggested_next_step) !== ""
|
|
135
|
+
? String(data.suggested_next_step)
|
|
136
|
+
: status === "completed"
|
|
137
|
+
? `results(action=get, job_id="${job_id}") then results_explorer(job_id="${job_id}")`
|
|
138
|
+
: status === "failed"
|
|
139
|
+
? `Inspect failure_stage/error; fix data or params; resubmit train or jobs.`
|
|
140
|
+
: status === "running" || status === "pending"
|
|
141
|
+
? `jobs(action=status, job_id="${job_id}") again in 10–15s (or training_monitor).`
|
|
142
|
+
: null;
|
|
143
|
+
if (suggested) {
|
|
144
|
+
parts.push(`suggested_next_step: ${suggested}`);
|
|
145
|
+
}
|
|
123
146
|
return parts.join(" | ");
|
|
124
147
|
}
|
|
125
148
|
export function formatJobCancelText(job_id, data) {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { apiCall } from "./shared.js";
|
|
2
|
+
export const PREPARE_TRAINING_FALLBACK_VERSION = "2026.07-offline";
|
|
2
3
|
const FALLBACK_LINES = [
|
|
3
4
|
"Before upload: for train(action=map) ensure training columns have no NaNs; for train(action=impute) missing cells in training columns are OK (plain numeric only).",
|
|
5
|
+
"Do not auto-subset: never call datasets(subset) or sample_n unless the user explicitly asks — train on the full uploaded dataset_id.",
|
|
4
6
|
'Please run datasets(action=preview, dataset_id="{id}") to inspect columns, then datasets(action=analyze, dataset_id="{id}") to see which columns and temporal periods are most informative.',
|
|
5
7
|
"Choose the training path:",
|
|
6
8
|
'- train(action=map, dataset_id="{id}", ...) for complete-case data',
|
|
@@ -9,16 +11,24 @@ const FALLBACK_LINES = [
|
|
|
9
11
|
'- train(action=floop_siom, dataset_id="{id}", ...) for FLooP-SIOM (default topology=free / CHL; optional topology=chain)',
|
|
10
12
|
];
|
|
11
13
|
/** Used by the `prepare_training` MCP prompt; prefers tier-scoped text from the API when online. */
|
|
12
|
-
export async function
|
|
13
|
-
|
|
14
|
+
export async function resolvePrepareTrainingPrompt(datasetId) {
|
|
15
|
+
const fallbackText = FALLBACK_LINES.join("\n").replaceAll("{id}", datasetId);
|
|
14
16
|
try {
|
|
15
17
|
const data = (await apiCall("GET", `/v1/docs/prepare_training?dataset_id=${datasetId}`));
|
|
16
18
|
if (typeof data.prompt === "string" && data.prompt.trim()) {
|
|
17
|
-
|
|
19
|
+
return { text: data.prompt.trim(), used_fallback: false, fallback_version: null };
|
|
18
20
|
}
|
|
19
21
|
}
|
|
20
22
|
catch {
|
|
21
|
-
//
|
|
23
|
+
// fall through
|
|
22
24
|
}
|
|
23
|
-
return
|
|
25
|
+
return {
|
|
26
|
+
text: fallbackText,
|
|
27
|
+
used_fallback: true,
|
|
28
|
+
fallback_version: PREPARE_TRAINING_FALLBACK_VERSION,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/** @deprecated Prefer resolvePrepareTrainingPrompt for structured fallback metadata. */
|
|
32
|
+
export async function resolvePrepareTrainingPromptText(datasetId) {
|
|
33
|
+
return (await resolvePrepareTrainingPrompt(datasetId)).text;
|
|
24
34
|
}
|
package/dist/shared.js
CHANGED
|
@@ -35,7 +35,7 @@ function newRequestId() {
|
|
|
35
35
|
* X-Barsom-Client-Version so the server can annotate tool guidance with the
|
|
36
36
|
* wrapper version each action requires. Keep in sync with package.json on bump.
|
|
37
37
|
*/
|
|
38
|
-
export const CLIENT_VERSION = "0.22.
|
|
38
|
+
export const CLIENT_VERSION = "0.22.4";
|
|
39
39
|
/** User-facing links; keep aligned with barivia.se / api.barivia.se. */
|
|
40
40
|
export const PUBLIC_SITE_ORIGIN = "https://barivia.se";
|
|
41
41
|
/** Self-serve account dashboard (manage plan, billing, and API keys). */
|
|
@@ -73,6 +73,51 @@ export const LARGE_UPLOAD_BYTES = 64 * 1024 * 1024; // 64 MB
|
|
|
73
73
|
export const PRESIGNED_PUT_TIMEOUT_MS = 30 * 60_000; // 30 min
|
|
74
74
|
/** Poll window for the async stage_dataset job (large files take minutes). */
|
|
75
75
|
export const POLL_STAGE_MAX_MS = 30 * 60_000; // 30 min
|
|
76
|
+
export const BARSOM_VIZ_VIEWS = {
|
|
77
|
+
trainingMonitor: "training-monitor",
|
|
78
|
+
resultsExplorer: "results-explorer",
|
|
79
|
+
};
|
|
80
|
+
export function buildStandaloneVizPageUrl(port, viewPath, jobId) {
|
|
81
|
+
if (!port)
|
|
82
|
+
return undefined;
|
|
83
|
+
return `http://localhost:${port}/viz/${viewPath}?mode=standalone&job_id=${encodeURIComponent(jobId)}`;
|
|
84
|
+
}
|
|
85
|
+
export function buildVizRelatedUrls(port, jobId, views) {
|
|
86
|
+
if (!port)
|
|
87
|
+
return undefined;
|
|
88
|
+
return {
|
|
89
|
+
trainingMonitor: buildStandaloneVizPageUrl(port, views.trainingMonitor, jobId),
|
|
90
|
+
resultsExplorer: buildStandaloneVizPageUrl(port, views.resultsExplorer, jobId),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/** Prominent standalone URL blocks for tool results — always emitted when a viz port exists. */
|
|
94
|
+
export function buildVizUrlContentBlocks(options) {
|
|
95
|
+
const { label, toolName, standaloneUrl, port, jobId, clientSupportsMcpApps } = options;
|
|
96
|
+
if (!standaloneUrl)
|
|
97
|
+
return [];
|
|
98
|
+
const embedNote = clientSupportsMcpApps
|
|
99
|
+
? "Embedded panel if your host supports MCP Apps — otherwise open the standalone URL below (expected on some Cursor builds)."
|
|
100
|
+
: "Open the standalone URL below when no inline panel appears.";
|
|
101
|
+
const health = port > 0
|
|
102
|
+
? ` Health check: http://localhost:${port}/api/health?job_id=${encodeURIComponent(jobId)}`
|
|
103
|
+
: "";
|
|
104
|
+
const blocks = [
|
|
105
|
+
{
|
|
106
|
+
type: "text",
|
|
107
|
+
text: `${embedNote}\n` +
|
|
108
|
+
`Standalone ${label} URL (copy if Open is blocked):\n${standaloneUrl}\n` +
|
|
109
|
+
`AGENT: surface this URL prominently in your reply — use a markdown link "[Open ${label}](${standaloneUrl})" or tell the user to copy/paste into a browser. Do not bury it after metrics.\n` +
|
|
110
|
+
`This localhost port is per MCP session and goes stale if the proxy restarts — re-run ${toolName} for a fresh URL, or set BARIVIA_VIZ_PORT for a persistent port.${health}`,
|
|
111
|
+
},
|
|
112
|
+
];
|
|
113
|
+
if (!clientSupportsMcpApps) {
|
|
114
|
+
blocks.push({
|
|
115
|
+
type: "text",
|
|
116
|
+
text: `[Open ${label}](${standaloneUrl})`,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return blocks;
|
|
120
|
+
}
|
|
76
121
|
// ---------------------------------------------------------------------------
|
|
77
122
|
// Shared state (mutable)
|
|
78
123
|
// ---------------------------------------------------------------------------
|
|
@@ -860,12 +905,17 @@ export function getResultsImagesToFetch(jobType, summary, figures, includeIndivi
|
|
|
860
905
|
}
|
|
861
906
|
// train_som / train_siom
|
|
862
907
|
const features = summary.features ?? [];
|
|
863
|
-
const
|
|
864
|
-
const
|
|
865
|
-
const
|
|
866
|
-
const
|
|
908
|
+
const summaryFiles = summary.files ?? [];
|
|
909
|
+
const imageFilesFromSummary = summaryFiles.filter((f) => /\.(png|pdf|svg)$/i.test(f));
|
|
910
|
+
const combinedName = imageFilesFromSummary.find((f) => f.startsWith("combined.")) ?? `combined.${ext}`;
|
|
911
|
+
const umatrixName = imageFilesFromSummary.find((f) => f.startsWith("umatrix.")) ?? `umatrix.${ext}`;
|
|
912
|
+
const hitHistName = imageFilesFromSummary.find((f) => f.startsWith("hit_histogram.")) ?? `hit_histogram.${ext}`;
|
|
913
|
+
const correlationName = imageFilesFromSummary.find((f) => f.startsWith("correlation.")) ?? `correlation.${ext}`;
|
|
867
914
|
const componentNames = features.map((f, i) => `component_${i + 1}_${f.replace(/[^a-zA-Z0-9_]/g, "_")}.${ext}`);
|
|
868
|
-
const
|
|
915
|
+
const publishedComponents = imageFilesFromSummary.filter((f) => /^component_\d+_/.test(f));
|
|
916
|
+
const allList = imageFilesFromSummary.length > 0
|
|
917
|
+
? imageFilesFromSummary
|
|
918
|
+
: [combinedName, umatrixName, hitHistName, correlationName, ...componentNames];
|
|
869
919
|
if (figures === undefined || figures === "default") {
|
|
870
920
|
return includeIndividual ? allList : [combinedName];
|
|
871
921
|
}
|
|
@@ -880,8 +930,13 @@ export function getResultsImagesToFetch(jobType, summary, figures, includeIndivi
|
|
|
880
930
|
hit_histogram: hitHistName,
|
|
881
931
|
correlation: correlationName,
|
|
882
932
|
};
|
|
933
|
+
for (const file of imageFilesFromSummary) {
|
|
934
|
+
const key = file.replace(/\.(png|pdf|svg)$/i, "");
|
|
935
|
+
nameToFile[key] = file;
|
|
936
|
+
}
|
|
883
937
|
features.forEach((_, i) => {
|
|
884
|
-
|
|
938
|
+
const published = publishedComponents.find((f) => f.startsWith(`component_${i + 1}_`));
|
|
939
|
+
nameToFile[`component_${i + 1}`] = published ?? componentNames[i];
|
|
885
940
|
});
|
|
886
941
|
return figures
|
|
887
942
|
.map((key) => {
|
package/dist/tools/account.js
CHANGED
|
@@ -1,29 +1,25 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { registerAuditedTool } from "../audit.js";
|
|
3
|
-
import { apiCall } from "../shared.js";
|
|
3
|
+
import { apiCall, CLIENT_VERSION } from "../shared.js";
|
|
4
|
+
import { getMcpClientDiagnostics } from "../ui-delivery.js";
|
|
4
5
|
export function registerAccountTool(server) {
|
|
5
|
-
registerAuditedTool(server, "account", `Manage your Barivia account — check plan/license info,
|
|
6
|
+
registerAuditedTool(server, "account", `Manage your Barivia account — check plan/license info, view usage history, add funds.
|
|
6
7
|
|
|
7
8
|
| Action | Use when |
|
|
8
9
|
|--------|----------|
|
|
9
10
|
| status | Before large jobs — see plan tier, GPU availability, queue depth, training time estimates, credit balance |
|
|
10
|
-
|
|
|
11
|
-
| compute_status | Checking if a burst lease is active and how much time remains |
|
|
12
|
-
| release_compute | Manually terminating an active lease to stop billing |
|
|
13
|
-
| history | Viewing recent compute leases and credit spend |
|
|
11
|
+
| history | Viewing recent compute usage and credit spend |
|
|
14
12
|
| add_funds | Getting instructions to add credits |
|
|
15
13
|
|
|
16
14
|
action=status: Returns plan tier, compute class (CPU/GPU), usage limits, live queue state, training time estimates, and credit balance.
|
|
17
15
|
Use BEFORE large jobs to check GPU availability and estimate wait time.
|
|
18
|
-
|
|
19
|
-
NOT FOR: Training itself — use train(action=map). This tool only manages the account
|
|
16
|
+
Capacity is shared LOCAL/GKE worker pools — there is no per-key cloud burst lease.
|
|
17
|
+
NOT FOR: Training itself — use train(action=map). This tool only manages the account.`, {
|
|
20
18
|
action: z
|
|
21
|
-
.enum(["status", "
|
|
22
|
-
.describe("status: plan/license/queue info;
|
|
23
|
-
tier: z.string().optional().describe("action=request_compute: tier ID — CPU: cpu-mini, cpu-2, cpu-8..cpu-48 (C7i); GPU: gpu-t4-small, gpu-t4..gpu-t4xxx, gpu-t4-12x (G4dn), gpu-p4d (P4d), gpu-l4, gpu-a10. Omit to list options."),
|
|
24
|
-
duration_minutes: z.number().optional().describe("action=request_compute: lease duration in minutes (default: 60)"),
|
|
19
|
+
.enum(["status", "history", "add_funds"])
|
|
20
|
+
.describe("status: plan/license/queue info; history: recent compute usage; add_funds: instructions"),
|
|
25
21
|
limit: z.number().optional().describe("action=history: number of records to return (default: 10)"),
|
|
26
|
-
}, async ({ action,
|
|
22
|
+
}, async ({ action, limit }) => {
|
|
27
23
|
if (action === "status") {
|
|
28
24
|
const data = (await apiCall("GET", "/v1/system/info"));
|
|
29
25
|
const plan = data.plan ?? {};
|
|
@@ -35,7 +31,6 @@ NOT FOR: Training itself — use train(action=map). This tool only manages the a
|
|
|
35
31
|
const computeDesc = gpuEnabled ? (backend.gpu_model ? `GPU (${backend.gpu_model}${backend.gpu_vram_gb ? `, ${backend.gpu_vram_gb}GB` : ""})` : "GPU") : "CPU only";
|
|
36
32
|
const fmtLimit = (v) => v === -1 || v === "-1" ? "unlimited" : String(v ?? "?");
|
|
37
33
|
const historyData = await apiCall("GET", "/v1/compute/history?limit=5").catch(() => null);
|
|
38
|
-
const leaseData = await apiCall("GET", "/v1/compute/lease").catch(() => null);
|
|
39
34
|
const algoNames = {
|
|
40
35
|
train_som: "SOM",
|
|
41
36
|
train_impute: "Imputation",
|
|
@@ -62,7 +57,6 @@ NOT FOR: Training itself — use train(action=map). This tool only manages the a
|
|
|
62
57
|
lines.push(` Credits: $${(historyData.credit_balance_cents / 100).toFixed(2)} remaining`);
|
|
63
58
|
}
|
|
64
59
|
else {
|
|
65
|
-
// Unknown billing mode: avoid misleading $0.00 for postpaid accounts
|
|
66
60
|
const cents = historyData.credit_balance_cents ?? 0;
|
|
67
61
|
if (cents === 0) {
|
|
68
62
|
lines.push(` Credits: N/A (postpaid or zero balance — check plan)`);
|
|
@@ -74,9 +68,6 @@ NOT FOR: Training itself — use train(action=map). This tool only manages the a
|
|
|
74
68
|
}
|
|
75
69
|
if (backend.memory_gb)
|
|
76
70
|
lines.push(` Backend Memory: ${backend.memory_gb} GB`);
|
|
77
|
-
if (leaseData && leaseData.lease_id) {
|
|
78
|
-
lines.push(``, `Active Burst Lease: ${leaseData.tier} | ${Math.round(leaseData.time_remaining_ms / 60000)} min left`);
|
|
79
|
-
}
|
|
80
71
|
const running = Number(status.running_jobs ?? data.running_jobs ?? 0);
|
|
81
72
|
const pending = Number(status.pending_jobs ?? data.pending_jobs ?? 0);
|
|
82
73
|
const compute_eta = Number(estimates?.total || 0);
|
|
@@ -90,64 +81,12 @@ NOT FOR: Training itself — use train(action=map). This tool only manages the a
|
|
|
90
81
|
lines.push(` ${k}: ~${v}s`);
|
|
91
82
|
}
|
|
92
83
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
try {
|
|
98
|
-
const tiersData = await apiCall("GET", "/v1/compute/tiers");
|
|
99
|
-
const lines = ["Available Compute Tiers:"];
|
|
100
|
-
for (const [tId, tData] of Object.entries(tiersData.tiers)) {
|
|
101
|
-
lines.push(` ${tId}: ${tData.desc}`);
|
|
102
|
-
}
|
|
103
|
-
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
104
|
-
}
|
|
105
|
-
catch (err) {
|
|
106
|
-
const msg = err?.message ?? "Failed to fetch compute tiers. Ensure you have burst access enabled.";
|
|
107
|
-
return { content: [{ type: "text", text: msg }] };
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
const data = await apiCall("POST", "/v1/compute/lease", { tier, duration_minutes });
|
|
111
|
-
const billingLine = data.billing_mode === "postpaid"
|
|
112
|
-
? `Billing: Postpaid (usage logged, billed retrospectively)\nAccrued Balance: $${(data.credit_balance_cents / 100).toFixed(2)}`
|
|
113
|
-
: `Credits Remaining After Reserve: $${(data.credit_balance_cents / 100).toFixed(2)}`;
|
|
114
|
-
return {
|
|
115
|
-
content: [{ type: "text", text: `Compute Lease Requested:
|
|
116
|
-
Lease ID: ${data.lease_id}
|
|
117
|
-
Status: ${data.status}
|
|
118
|
-
Estimated Wait: ${data.estimated_wait_minutes} minutes
|
|
119
|
-
Estimated Cost: $${(data.estimated_cost_cents / 100).toFixed(2)}
|
|
120
|
-
${billingLine}
|
|
121
|
-
|
|
122
|
-
IMPORTANT: Cloud burst active. Data is pulled from shared Cloudflare R2, so you do NOT need to re-upload datasets. Just wait ~3 minutes and check status.` }]
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
if (action === "compute_status") {
|
|
126
|
-
const data = await apiCall("GET", "/v1/compute/lease");
|
|
127
|
-
if (data.status === "none" || !data.lease_id) {
|
|
128
|
-
return { content: [{ type: "text", text: "No active lease -- running on default Primary Server." }] };
|
|
84
|
+
const mcpDiag = getMcpClientDiagnostics();
|
|
85
|
+
lines.push(``, `MCP client (local proxy @ ${CLIENT_VERSION}):`, ` MCP Apps detected: ${mcpDiag.mcp_apps_supported ? "yes" : "no"}`, ` Viz port: ${mcpDiag.viz_port ?? "none"}${mcpDiag.viz_port_pinned ? " (BARIVIA_VIZ_PORT pinned)" : " (ephemeral — set BARIVIA_VIZ_PORT for stable URLs)"}`, ` UI delivery override: ${mcpDiag.ui_delivery_override}`, ` Active delivery tier: ${mcpDiag.active_delivery_tier}`);
|
|
86
|
+
if (mcpDiag.health_url) {
|
|
87
|
+
lines.push(` Viz health: ${mcpDiag.health_url}`);
|
|
129
88
|
}
|
|
130
|
-
return {
|
|
131
|
-
content: [{ type: "text", text: `Active Compute Lease:
|
|
132
|
-
Lease ID: ${data.lease_id}
|
|
133
|
-
Status: ${data.status}
|
|
134
|
-
Tier: ${data.tier} (${data.instance_type})
|
|
135
|
-
Time Remaining: ${Math.round(data.time_remaining_ms / 60000)} minutes` }]
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
if (action === "release_compute") {
|
|
139
|
-
const data = await apiCall("DELETE", "/v1/compute/lease");
|
|
140
|
-
const costLine = data.billing_mode === "postpaid"
|
|
141
|
-
? `Cost Logged: $${(data.cost_cents / 100).toFixed(2)} (postpaid — billed retrospectively)`
|
|
142
|
-
: `Credits Deducted: $${((data.credits_deducted || data.cost_cents) / 100).toFixed(2)}`;
|
|
143
|
-
return {
|
|
144
|
-
content: [{ type: "text", text: `Compute Released:
|
|
145
|
-
Duration Billed: ${data.duration_minutes} minutes
|
|
146
|
-
${costLine}
|
|
147
|
-
Balance: $${(data.final_balance_cents / 100).toFixed(2)}
|
|
148
|
-
|
|
149
|
-
Routing reverted to default Primary Server.` }]
|
|
150
|
-
};
|
|
89
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
151
90
|
}
|
|
152
91
|
if (action === "history") {
|
|
153
92
|
const data = await apiCall("GET", `/v1/compute/history?limit=${limit || 10}`);
|
|
@@ -159,11 +98,11 @@ Routing reverted to default Primary Server.` }]
|
|
|
159
98
|
if (action === "add_funds") {
|
|
160
99
|
return {
|
|
161
100
|
content: [{ type: "text", text: `To add funds to your account, please visit the Barivia Billing Portal (integration pending) or ask your administrator to use the CLI tool:
|
|
162
|
-
bash scripts/manage-credits.sh add <org_id> <amount_usd>` }]
|
|
101
|
+
bash scripts/billing/manage-credits.sh add <org_id> <amount_usd>` }]
|
|
163
102
|
};
|
|
164
103
|
}
|
|
165
104
|
return {
|
|
166
|
-
content: [{ type: "text", text: `Unknown action: ${action}. Valid: status,
|
|
105
|
+
content: [{ type: "text", text: `Unknown action: ${action}. Valid: status, history, add_funds.` }]
|
|
167
106
|
};
|
|
168
107
|
});
|
|
169
108
|
}
|
package/dist/tools/datasets.js
CHANGED
|
@@ -4,7 +4,7 @@ import { gzipSync } from "node:zlib";
|
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import { registerAuditedTool } from "../audit.js";
|
|
7
|
-
import { apiCall, getWorkspaceRootAsync, resolveFilePathForUpload, textResult, pollUntilComplete, POLL_DERIVE_MAX_MS, POLL_ANALYZE_MAX_MS, UPLOAD_DATASET_TIMEOUT_MS, LARGE_UPLOAD_BYTES, PRESIGNED_PUT_TIMEOUT_MS, POLL_STAGE_MAX_MS, streamFileSha256, putPresignedStream, resolveUploadContentType, suggestDatasetPreview, } from "../shared.js";
|
|
7
|
+
import { apiCall, getWorkspaceRootAsync, resolveFilePathForUpload, textResult, structuredTextResult, pollUntilComplete, POLL_DERIVE_MAX_MS, POLL_ANALYZE_MAX_MS, UPLOAD_DATASET_TIMEOUT_MS, LARGE_UPLOAD_BYTES, PRESIGNED_PUT_TIMEOUT_MS, POLL_STAGE_MAX_MS, streamFileSha256, putPresignedStream, resolveUploadContentType, suggestDatasetPreview, } from "../shared.js";
|
|
8
8
|
import { GZIP_UPLOAD_HINT } from "../job_status_format.js";
|
|
9
9
|
/**
|
|
10
10
|
* Normalize a nullable string field from the API. Returns "" for absent values,
|
|
@@ -139,7 +139,7 @@ Formats: plain CSV/TSV or gzip (.csv.gz / .tsv.gz). For files above ~100 MB, pre
|
|
|
139
139
|
| analyze | Pre-training column analysis: correlation matrix, periodicity ranking, column recommendations (train / drop / project later). Run after preview, before train_map. |
|
|
140
140
|
| list | Finding dataset IDs for train_map, preview, or subset — see all available datasets |
|
|
141
141
|
| get | Fetch one dataset by id — status, staging fields, ingest_error (use after upload or when staging is slow) |
|
|
142
|
-
| subset |
|
|
142
|
+
| subset | ONLY when the user asks for row_range, filters, or sample_n — never auto-shrink before train/analyze |
|
|
143
143
|
| add_expression | Add a computed column from an expression (dataset_id + name + expression). With project_onto_job=<training_job_id>, instead projects the computed expression onto that map as a component plane in one step (no column added). |
|
|
144
144
|
| reduce_spectral | Collapse a long ordered numeric block (e.g. 1000-point spectrum, time series, sensor fingerprint) into a small per-row feature set so the SOM can train on signal-dense features. Methods: pca, log_sample, uniform_sample, stats. |
|
|
145
145
|
| delete | Cleaning up after experiments or freeing the dataset slot |
|
|
@@ -153,13 +153,15 @@ action=preview: Show columns, stats, sample rows, cyclic/datetime detections. AL
|
|
|
153
153
|
action=analyze: Pre-training analysis on numeric columns — Pearson correlation, autocorrelation periodicity scores, and column recommendations (train, consider_dropping, project_later, low_variance). Use to choose which columns to include in training and which to project onto the map after training.
|
|
154
154
|
action=list: List all datasets belonging to the organisation with id, name, rows, cols, created_at (use created_at or id to distinguish datasets with the same name).
|
|
155
155
|
action=get: Fetch one dataset by dataset_id — returns status, staged_prefix, staged_version, ingest_error, and stage_job_id when staging is pending.
|
|
156
|
+
DO NOT AUTO-SUBSET: Call action=subset only when the user explicitly requests a filter, slice, or random sample. Default path is upload → preview/analyze → train on the full dataset_id (GPU handles scale). Never downsample to "save time" or "be helpful".
|
|
157
|
+
|
|
156
158
|
action=subset: Create a new dataset from a subset of an existing one. Requires name and at least one of row_range, filters, or sample_n.
|
|
157
159
|
- row_range: [start, end] 1-based inclusive (e.g. [1, 2000] for first 2000 rows)
|
|
158
160
|
- filters: array of conditions, ALL must match (AND logic). Each: { column, op, value }.
|
|
159
161
|
Operators: eq, ne, in, gt, lt, gte, lte, between
|
|
160
162
|
Examples: { column: "region", op: "eq", value: "Europe" } | { column: "age", op: "between", value: [18, 65] }
|
|
161
|
-
- sample_n: keep a random N-row sample (seeded via sample_seed, default 42; row order preserved).
|
|
162
|
-
- Combine row_range + filters + sample_n to slice rows, values, then downsample.
|
|
163
|
+
- sample_n: keep a random N-row sample (seeded via sample_seed, default 42; row order preserved). User/ops tool only — not an agent default. Applied after row_range/filters; can also be used alone.
|
|
164
|
+
- Combine row_range + filters + sample_n to slice rows, values, then downsample when the user asked for it.
|
|
163
165
|
- Single filter object is also accepted (auto-wrapped).
|
|
164
166
|
action=reduce_spectral: Run a pre-training reducer over an ordered block of numeric columns. All four methods produce one feature vector per row (rows in = rows out; only the column dimension is collapsed) and append derived columns to the dataset. Choose by data shape:
|
|
165
167
|
- pca: top-k principal components — general first try when many columns are correlated (spectroscopy, gene panels, sensor arrays). Returns explained_variance_ratio.
|
|
@@ -258,7 +260,7 @@ ESCALATION: If upload fails with column errors, open the file locally and verify
|
|
|
258
260
|
.int()
|
|
259
261
|
.min(1)
|
|
260
262
|
.optional()
|
|
261
|
-
.describe("action=subset: keep a random N-row sample (seeded, row order preserved).
|
|
263
|
+
.describe("action=subset: keep a random N-row sample (seeded, row order preserved). Only when the user explicitly requests downsampling — not an agent default. Combine with row_range/filters, or use alone."),
|
|
262
264
|
sample_seed: z
|
|
263
265
|
.number()
|
|
264
266
|
.int()
|
|
@@ -481,10 +483,20 @@ ESCALATION: If upload fails with column errors, open the file locally and verify
|
|
|
481
483
|
throw new Error("analyze did not return a job id");
|
|
482
484
|
const poll = await pollUntilComplete(jobId, POLL_ANALYZE_MAX_MS);
|
|
483
485
|
if (poll.status === "failed") {
|
|
484
|
-
|
|
486
|
+
const rid = poll.request_id;
|
|
487
|
+
const errText = `datasets(analyze) job ${jobId} failed: ${poll.error ?? "unknown error"}`;
|
|
488
|
+
return structuredTextResult({
|
|
489
|
+
error: true,
|
|
490
|
+
action: "analyze",
|
|
491
|
+
job_id: jobId,
|
|
492
|
+
request_id: rid ?? null,
|
|
493
|
+
message: errText,
|
|
494
|
+
suggested_next_step: "Do not auto-subset. Report the error to the user; full-dataset analyze is supported at scale. Use datasets(subset) only if the user explicitly asks for downsampling.",
|
|
495
|
+
}, errText + (rid ? ` (request_id=${rid})` : ""));
|
|
485
496
|
}
|
|
486
497
|
if (poll.status !== "completed") {
|
|
487
|
-
|
|
498
|
+
const text = `datasets(analyze) job ${jobId} is still running. Poll with jobs(action=status, job_id="${jobId}") then results(action=get, job_id="${jobId}").`;
|
|
499
|
+
return structuredTextResult({ error: false, action: "analyze", job_id: jobId, status: poll.status, message: text }, text);
|
|
488
500
|
}
|
|
489
501
|
const results = (await apiCall("GET", `/v1/results/${jobId}`));
|
|
490
502
|
data = (results.summary ?? results);
|
|
@@ -495,7 +507,7 @@ ESCALATION: If upload fails with column errors, open the file locally and verify
|
|
|
495
507
|
}
|
|
496
508
|
else {
|
|
497
509
|
const msg = err instanceof Error ? err.message : String(err);
|
|
498
|
-
throw new Error(`datasets(analyze) failed: ${msg}.
|
|
510
|
+
throw new Error(`datasets(analyze) failed: ${msg}. Do not auto-subset — use datasets(subset) only if the user explicitly asked.`);
|
|
499
511
|
}
|
|
500
512
|
}
|
|
501
513
|
return { content: [{ type: "text", text: formatAnalyzeResult(data, dataset_id) }] };
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { registerAppTool } from "@modelcontextprotocol/ext-apps/server";
|
|
3
3
|
import { registerAuditedTool, runMcpToolAudit } from "../audit.js";
|
|
4
|
-
import { apiCall, apiRawCall,
|
|
4
|
+
import { apiCall, apiRawCall, BARSOM_VIZ_VIEWS, buildStandaloneVizPageUrl, buildVizRelatedUrls, getVizPort, getCaptionForImage, getResultsImagesToFetch, mimeForFilename, structuredTextResult, tryAttachImage, } from "../shared.js";
|
|
5
|
+
import { buildVizStandaloneUrl } from "../viz_links.js";
|
|
6
|
+
import { appendUiDeliveryContent, beginInlineFallback, buildStandaloneVizUrl, resolveUiDelivery, } from "../ui-delivery.js";
|
|
5
7
|
export const RESULTS_EXPLORER_URI = "ui://barsom/results-explorer";
|
|
6
8
|
function labelForFigure(filename) {
|
|
7
9
|
const base = filename.replace(/\.(png|pdf|svg)$/i, "");
|
|
@@ -111,6 +113,10 @@ export function buildPayload(jobId, data) {
|
|
|
111
113
|
summary.selected_columns ? `Variables: ${summary.selected_columns.join(", ")}` : "",
|
|
112
114
|
].filter(Boolean);
|
|
113
115
|
const port = getVizPort();
|
|
116
|
+
const standaloneUrl = buildStandaloneVizPageUrl(port, BARSOM_VIZ_VIEWS.resultsExplorer, jobId) ??
|
|
117
|
+
buildVizStandaloneUrl("results-explorer", jobId);
|
|
118
|
+
const trainingMonitorUrl = buildStandaloneVizPageUrl(port, BARSOM_VIZ_VIEWS.trainingMonitor, jobId) ??
|
|
119
|
+
buildVizStandaloneUrl("training-monitor", jobId);
|
|
114
120
|
return {
|
|
115
121
|
type: "results-explorer",
|
|
116
122
|
jobId,
|
|
@@ -123,42 +129,55 @@ export function buildPayload(jobId, data) {
|
|
|
123
129
|
availableFigures: figures,
|
|
124
130
|
defaultFigureKey: figures.find((figure) => figure.key === "combined")?.key
|
|
125
131
|
?? figures[0]?.key,
|
|
126
|
-
standaloneUrl
|
|
132
|
+
standaloneUrl,
|
|
133
|
+
trainingMonitorUrl,
|
|
134
|
+
relatedUrls: buildVizRelatedUrls(port, jobId, BARSOM_VIZ_VIEWS),
|
|
127
135
|
};
|
|
128
136
|
}
|
|
129
137
|
async function handleResultsExplorer(job_id) {
|
|
138
|
+
beginInlineFallback();
|
|
130
139
|
const data = (await apiCall("GET", `/v1/results/${job_id}`));
|
|
131
140
|
const payload = buildPayload(job_id, data);
|
|
132
141
|
const summary = (data.summary ?? {});
|
|
133
|
-
const
|
|
134
|
-
{
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
payload.highlights[0] ?? "",
|
|
141
|
-
].filter(Boolean).join("\n"),
|
|
142
|
-
},
|
|
143
|
-
];
|
|
142
|
+
const summaryText = [
|
|
143
|
+
`Results Explorer ready for job ${job_id}.`,
|
|
144
|
+
payload.gridLabel ? `Grid: ${payload.gridLabel}` : "",
|
|
145
|
+
payload.subtitle ? `Label: ${payload.subtitle}` : "",
|
|
146
|
+
payload.highlights[0] ?? "",
|
|
147
|
+
].filter(Boolean).join("\n");
|
|
148
|
+
const content = [{ type: "text", text: summaryText }];
|
|
144
149
|
const defaultFigure = payload.availableFigures.find((figure) => figure.key === payload.defaultFigureKey);
|
|
150
|
+
let inlineImageAttached = false;
|
|
145
151
|
if (defaultFigure) {
|
|
152
|
+
const before = content.length;
|
|
146
153
|
await tryAttachImage(content, job_id, defaultFigure.filename);
|
|
154
|
+
inlineImageAttached = content.length > before && content.some((c) => c.type === "image");
|
|
147
155
|
}
|
|
148
156
|
else {
|
|
149
157
|
const ext = summary.output_format ?? "png";
|
|
158
|
+
const before = content.length;
|
|
150
159
|
await tryAttachImage(content, job_id, `combined.${ext}`);
|
|
160
|
+
inlineImageAttached = content.length > before && content.some((c) => c.type === "image");
|
|
151
161
|
}
|
|
152
|
-
|
|
162
|
+
const uiDelivery = resolveUiDelivery({
|
|
163
|
+
tool: "results_explorer",
|
|
164
|
+
jobId: job_id,
|
|
165
|
+
jobStatus: "completed",
|
|
166
|
+
inlineImageAttached,
|
|
167
|
+
});
|
|
168
|
+
appendUiDeliveryContent(content, uiDelivery, {
|
|
169
|
+
linkLabel: "Open results explorer",
|
|
170
|
+
primaryUrl: payload.standaloneUrl ?? buildStandaloneVizUrl("results_explorer", job_id),
|
|
171
|
+
});
|
|
172
|
+
const monitorUrl = payload.trainingMonitorUrl ?? buildStandaloneVizUrl("training_monitor", job_id);
|
|
173
|
+
if (monitorUrl) {
|
|
153
174
|
content.push({
|
|
154
175
|
type: "text",
|
|
155
|
-
text: `
|
|
156
|
-
`AGENT: surface this to the user as a clickable markdown link (e.g. "[Open results explorer](${payload.standaloneUrl})") in your reply — do not leave it buried in tool output. ` +
|
|
157
|
-
`This localhost port is assigned per MCP session and changes if the proxy restarts — if the page won't load, re-run results_explorer for a fresh URL, or set BARIVIA_VIZ_PORT in your MCP env for a persistent port.`,
|
|
176
|
+
text: `Review training curves: [Open training monitor](${monitorUrl})`,
|
|
158
177
|
});
|
|
159
178
|
}
|
|
160
179
|
return {
|
|
161
|
-
...structuredTextResult(payload, undefined, content),
|
|
180
|
+
...structuredTextResult({ ...payload, ui_delivery: uiDelivery }, undefined, content),
|
|
162
181
|
_meta: { ui: { resourceUri: RESULTS_EXPLORER_URI } },
|
|
163
182
|
};
|
|
164
183
|
}
|
package/dist/tools/results.js
CHANGED
|
@@ -19,7 +19,7 @@ ONLY call this after jobs(action=status) returns "completed".
|
|
|
19
19
|
ESCALATION: If job not found, verify job_id. If "job not complete", poll with jobs(action=status).
|
|
20
20
|
|
|
21
21
|
action=get: Returns text summary with quality metrics and inline images.
|
|
22
|
-
- figures: omit = combined only. "all" = all plots. "none" = metrics text only, no images (recommended for sweeps and LLM clients to keep tool payloads small). Array = specific logical names (combined, umatrix, hit_histogram, learning_curve, correlation, component_1..N).
|
|
22
|
+
- figures: omit = combined only. "all" = all published plots (excludes unpublished cyclic cos/sin expansions unless train used viz_upload_cyclic_components=true). "none" = metrics text only, no images (recommended for sweeps and LLM clients to keep tool payloads small). Array = specific logical names (combined, umatrix, hit_histogram, learning_curve, correlation, component_1..N). Request unpublished cyclic planes via results(recolor, figures=[component_N, ...]).
|
|
23
23
|
- include_individual: if true (and figures omitted), inlines every component plane.
|
|
24
24
|
- After recolor or project, use the job_id returned by that operation to get the new artifacts, not the original training job_id.
|
|
25
25
|
- After showing results, guide the user: QE interpretation, whether to retrain, which features to explore.
|
|
@@ -574,14 +574,20 @@ NOT FOR: Jobs that haven't completed (use jobs(action=status) first), or tempora
|
|
|
574
574
|
throw new Error("results(download) requires folder");
|
|
575
575
|
const data = (await apiCall("GET", `/v1/results/${job_id}`));
|
|
576
576
|
const summary = (data.summary ?? {});
|
|
577
|
+
const downloadUrls = (data.download_urls ?? {});
|
|
577
578
|
const jobLabel = data.label != null && data.label !== "" ? String(data.label) : null;
|
|
578
579
|
const files = summary.files ?? [];
|
|
579
580
|
const jobType = summary.job_type ?? "train_som";
|
|
580
581
|
const needsAllFiles = ["enrich_dataset", "predict", "impute_column", "compare_datasets"].includes(jobType);
|
|
581
582
|
const isImage = (f) => f.endsWith(".png") || f.endsWith(".svg") || f.endsWith(".pdf");
|
|
583
|
+
const hasUrlIndex = Object.keys(downloadUrls).length > 0;
|
|
584
|
+
const isPublished = (f) => !hasUrlIndex || f in downloadUrls;
|
|
582
585
|
let toDownload;
|
|
583
586
|
if (figures === "all" || figures === "images" || figures === undefined) {
|
|
584
587
|
toDownload = needsAllFiles || include_json ? files : files.filter(isImage);
|
|
588
|
+
if (hasUrlIndex) {
|
|
589
|
+
toDownload = toDownload.filter((f) => !isImage(f) || isPublished(f));
|
|
590
|
+
}
|
|
585
591
|
}
|
|
586
592
|
else if (Array.isArray(figures)) {
|
|
587
593
|
toDownload = figures;
|