@barivia/barsom-mcp 0.11.1 → 0.13.0

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @barivia/barsom-mcp
2
2
 
3
- MCP proxy for the Barivia Analytics Engine — connects any stdio MCP client (Cursor, Claude Desktop, etc.) to the barSOM cloud API. The npm package is **`@barivia/barsom-mcp`**; many configs label the server **`analytics-engine`** (the MCP server name in the client JSON). **`guide_barsom_workflow`** is the canonical bootstrap: it loads plan-scoped workflow text from the Barivia API when online (tool map, async rules, training modes, SOP). If the API is unreachable, it returns a short offline stub. Core tools are `datasets`, `jobs` (`train_map`, `train_siom_map`, `train_floop_siom` where entitled; `train_floop_chain` is deprecated), `results`, and `inference`. **Optional MCP App tools** (`training_prep`, `training_monitor`, `results_explorer`) add embedded or localhost viz; they are **not required** to complete upload → train → poll → results.
3
+ MCP proxy for the Barivia Analytics Engine — connects any stdio MCP client (Cursor, Claude Desktop, etc.) to the barSOM cloud API. The npm package is **`@barivia/barsom-mcp`**; many configs label the server **`analytics-engine`** (the MCP server name in the client JSON). **`guide_barsom_workflow`** is the canonical bootstrap: it loads plan-scoped workflow text from the Barivia API when online (tool map, async rules, training modes, SOP). If the API is unreachable, it returns a short offline stub. Core tools are `datasets`, `jobs` (`train_map`, `train_siom_map`, `train_floop_siom` where entitled), `results`, and `inference`. **Optional MCP App tools** (`training_prep`, `training_monitor`, `results_explorer`) add embedded or localhost viz; they are **not required** to complete upload → train → poll → results.
4
4
 
5
5
  **Pre-training help (pick one):** **`prepare_training`** prompt = narrative checklist (tier-scoped from the API when online); **`training_guidance`** tool = structured presets and parameter hints; **`training_prep`** tool = interactive UI plus **`submit_prepared_training`**.
6
6
 
@@ -75,7 +75,7 @@ All multi-action tools follow the `datasets` / `jobs` / `results` / `inference`
75
75
  | Inference | `inference` |
76
76
  | Account | `account` |
77
77
  | Optional UI | `training_prep`, `training_monitor`, `results_explorer` |
78
- | Advanced / legacy | `training_guidance`, `explore_map` (deprecated alias) |
78
+ | Advanced | `training_guidance` |
79
79
 
80
80
  Agents should not call **`_fetch_figure`** from chat; it exists for the Results Explorer MCP App host. Use `results(action=get)` or `results_explorer` instead.
81
81
 
@@ -103,7 +103,6 @@ Call at the **start of mapping work** (or when the user asks what the MCP can do
103
103
  | `train_impute` | Sparse training data: accelerated missSOM / SIOM missSOM — trains a map and imputes missing cells in one job. Defaults: `model=auto`, `cv_folds=5`. Returns map artifacts + `imputed.csv`, `imputation_mask.csv`, optional `quality.csv` / `imputation_uncertainty.csv`. `quality.csv` reports two labeled numbers per column: `kfold_holdout_pool` (honest held-out) and `resubstitution_optimistic` (in-sample, optimistic — not validation). Plain numeric columns only. Valid parent for `inference(impute_column)`. |
104
104
  | `train_siom_map` | Submitting a self-interacting map training job — same grid-map workflow plus SIOM controls such as `gamma`, `siom_decay`, and penalty selection. |
105
105
  | `train_floop_siom` | Submitting a FLooP-SIOM job — growing node-budget manifold; default `topology=free` (CHL); optional `topology=chain` for strict 1D linked list. |
106
- | `train_floop_chain` | Deprecated alias for `train_floop_siom` — same behavior. |
107
106
  | `status` | Polling after any async job — every 10–15s; map jobs expose `progress_phase` (ordering, convergence, cv, artifacts) |
108
107
  | `list` | Finding job IDs, checking pipeline state |
109
108
  | `compare` | Picking the best run from a set (QE, TE, silhouette table) |
@@ -154,7 +153,6 @@ All actions use a frozen trained map — no retraining. Derived columns use **`d
154
153
  | `submit_prepared_training` | Submit the reviewed prep (`review_token` + `explicit_confirm=true`) |
155
154
  | `training_monitor` | Visual progress for a `job_id`; optional — `jobs(action=status)` is enough |
156
155
  | `results_explorer` | Browse metrics and figures after training completes |
157
- | `explore_map` | **Deprecated** — alias of `results_explorer`; migrate configs to `results_explorer` |
158
156
  | `_fetch_figure` | **Host / App only** — Results Explorer invokes this for one raster figure; not for agent chat |
159
157
 
160
158
  When the client does not advertise MCP Apps support, the proxy starts **`viz-server`** on **`127.0.0.1`** (localhost-only). Tool responses can include `http://127.0.0.1:PORT/viz/...` links. See **Local viz fallback** under Environment Variables.
@@ -171,7 +169,8 @@ The right viewer depends on **(MCP App support)** **and** **(can the human reach
171
169
 
172
170
  ### Migration notes
173
171
 
174
- - **`explore_map` `results_explorer`:** Update Cursor, Claude Desktop, or other MCP configs that still reference `explore_map`. The alias remains for backward compatibility.
172
+ - **`explore_map` removed (0.13.0):** the deprecated alias of `results_explorer` is gone. Update any Cursor / Claude Desktop / MCP config or call site to use **`results_explorer`**.
173
+ - **`train_floop_chain` removed (0.13.0):** the deprecated alias of `jobs(action=train_floop_siom)` is gone. Use **`jobs(action=train_floop_siom)`**.
175
174
  - **Shorter `info` prompt:** Clients that relied on the old long `info` text should use **`guide_barsom_workflow`** or server **instructions** for the full story.
176
175
 
177
176
  ### `send_feedback`
@@ -219,7 +218,7 @@ npm run build # Compile to dist/
219
218
  npm test # Vitest
220
219
  ```
221
220
 
222
- **Post-change spot-check (real MCP client):** confirm `guide_barsom_workflow` appears near the top of the tool list, `explore_map` still works as an alias of `results_explorer`, and Results Explorer can still load figures (it uses `_fetch_figure` internally — agents should rely on `results` / `results_explorer`).
221
+ **Post-change spot-check (real MCP client):** confirm `guide_barsom_workflow` appears near the top of the tool list, the removed `explore_map` / `train_floop_chain` aliases are no longer listed, and Results Explorer can still load figures (it uses `_fetch_figure` internally — agents should rely on `results` / `results_explorer`).
223
222
 
224
223
  For local development against a local API stack:
225
224
 
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 n,RESOURCE_MIME_TYPE as s}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 m,setClientSupportsMcpApps as d,CLIENT_VERSION as u}from"./shared.js";import{registerDatasetsTool as f}from"./tools/datasets.js";import{registerJobsTool as g,JOBS_DESCRIPTION_BASE as _}from"./tools/jobs.js";import{registerResultsTool as b}from"./tools/results.js";import{registerExploreMapTool as h,RESULTS_EXPLORER_URI as y}from"./tools/explore_map.js";import{registerAccountTool as w}from"./tools/account.js";import{registerInferenceTool as j}from"./tools/inference.js";import{registerGuideBarsomTool as v}from"./tools/guide_barsom.js";import{registerTrainingGuidanceTool as P}from"./tools/training_guidance.js";import{registerFeedbackTool as x}from"./tools/feedback.js";import{registerTrainingPrepTools as I,TRAINING_PREP_URI as k}from"./tools/training_prep.js";import{registerTrainingMonitorTool as T,TRAINING_MONITOR_URI as M}from"./tools/training_monitor.js";import{resolvePrepareTrainingPromptText as O}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 A=new e({name:"analytics-engine",version:u,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 `jobs(train_map)`, `jobs(train_siom_map)`, or `jobs(train_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 `jobs(compare)`, `results(download/recolor/transition_flow)`, or `inference` 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 map (compact)\n\n| Area | Tool | Notes |\n|------|------|--------|\n| Data | `datasets` | upload, preview, analyze, list, subset, add_expression, reduce_spectral (pca/log_sample/uniform_sample/stats for long ordered numeric blocks), delete |\n| Jobs | `jobs` | train_map, train_impute (sparse data: map + dense imputed.csv), train_siom_map, train_floop_siom (entitled), status, list, compare, cancel, delete, batch_predict, run_baseline_study; `train_floop_chain` = deprecated alias for train_floop_siom |\n| Results | `results` | get (figures="none" for metrics-only), export, download, recolor (async), transition_flow (async; time-ordered rows only) |\n| Inference | `inference` | predict (regime-aware; output="compact"|"annotated"), impute_column (neighbor-pool fill for a non-training column), compare, project_columns, report |\n| Account | `account` | status, burst/compute actions, history, add_funds |\n| Bootstrap | `guide_barsom_workflow` | orientation + SOP |\n| Parameters | `training_guidance` | presets and field hints (API-scoped) |\n| Prep | `prepare_training` prompt, `training_prep` + `submit_prepared_training` | checklist / interactive UI |\n| Explore | `results_explorer`, `training_monitor` | optional MCP Apps; `explore_map` = deprecated alias of `results_explorer`; `jobs(status)` and `results(get)` suffice without them |\n| Other | `send_feedback` | only after user agrees |\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, `results(recolor)`, `results(transition_flow)` 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; `training_prep` = UI + guarded submit. 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 `recolor`, `transition_flow`, or `project_columns`, use the **new** `job_id` returned for follow-up `results` if applicable.'});n(A,y,y,{mimeType:s},async()=>{const e=await c("results-explorer");return{contents:[{uri:y,mimeType:s,text:e??"<html><body>Results Explorer view not built yet. Run: npm run build:views</body></html>"}]}}),n(A,k,k,{mimeType:s},async()=>{const e=await c("training-prep");return{contents:[{uri:k,mimeType:s,text:e??"<html><body>Training Preparation view not built yet.</body></html>"}]}}),n(A,M,M,{mimeType:s},async()=>{const e=await c("training-monitor");return{contents:[{uri:M,mimeType:s,text:e??"<html><body>Training Monitor view not built yet.</body></html>"}]}}),v(A),h(A),I(A),T(A),f(A),g(A,_),b(A),w(A),j(A),P(A),x(A),A.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).","",'**Key tools:** `datasets` (data; reduce_spectral for spectra/long blocks), `jobs` (train/poll/compare/…; train_map accepts an optional `label` for readable compare rows), `results` (get/download/export/recolor/transition_flow; figures="none" for metrics-only), `inference` (predict; impute_column for topology-neighbor pool fill; compare; project_columns; report), `account` (status/credits/queue).',"","**Prep help:** `prepare_training` prompt (checklist) · `training_guidance` (presets/JSON hints) · `training_prep` + `submit_prepared_training` (interactive UI).","","**Optional UI:** `results_explorer`, `training_monitor` — nice for browsing; not required if you use `results` + `jobs(status)`.","","**After training:** `jobs(compare)` across runs, `results(recolor)`, `inference(project_columns)` for variables not in training, `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")}}]})),A.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; training_prep tool = interactive UI + submit_prepared_training.",{dataset_id:o.string().describe("Dataset ID to prepare for training")},async({dataset_id:e})=>({messages:[{role:"user",content:{type:"text",text:await O(e)}}]}));const S=new t;(async function(){try{const e=await a(l,p,c);m(e)}catch(e){process.env.BARIVIA_VIZ_PORT&&console.error("Barivia viz server failed to start:",e)}const e=A.server;e.oninitialized=()=>{const t=e.getClientCapabilities(),o=r(t);d(!!o?.mimeTypes?.includes(s))},await A.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 n,RESOURCE_MIME_TYPE as s}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 m,setClientSupportsMcpApps as d,CLIENT_VERSION as u}from"./shared.js";import{registerDatasetsTool as f}from"./tools/datasets.js";import{registerJobsTool as g,JOBS_DESCRIPTION_BASE as _}from"./tools/jobs.js";import{registerResultsTool as b}from"./tools/results.js";import{registerExploreMapTool as h,RESULTS_EXPLORER_URI as y}from"./tools/explore_map.js";import{registerAccountTool as w}from"./tools/account.js";import{registerInferenceTool as j}from"./tools/inference.js";import{registerGuideBarsomTool as v}from"./tools/guide_barsom.js";import{registerTrainingGuidanceTool as P}from"./tools/training_guidance.js";import{registerFeedbackTool as x}from"./tools/feedback.js";import{registerTrainingPrepTools as I,TRAINING_PREP_URI as k}from"./tools/training_prep.js";import{registerTrainingMonitorTool as T,TRAINING_MONITOR_URI as M}from"./tools/training_monitor.js";import{resolvePrepareTrainingPromptText as O}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 A=new e({name:"analytics-engine",version:u,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 `jobs(train_map)`, `jobs(train_siom_map)`, or `jobs(train_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 `jobs(compare)`, `results(download/recolor/transition_flow)`, or `inference` 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 map (compact)\n\n| Area | Tool | Notes |\n|------|------|--------|\n| Data | `datasets` | upload, preview, analyze, list, subset, add_expression, reduce_spectral (pca/log_sample/uniform_sample/stats for long ordered numeric blocks), delete |\n| Jobs | `jobs` | train_map, train_impute (sparse data: map + dense imputed.csv), train_siom_map, train_floop_siom (entitled), status, list, compare, cancel, delete, batch_predict, run_baseline_study |\n| Results | `results` | get (figures="none" for metrics-only), export, download, recolor (async), transition_flow (async; time-ordered rows only) |\n| Inference | `inference` | predict (regime-aware; output="compact"|"annotated"), impute_column (neighbor-pool fill for a non-training column), compare, project_columns, report |\n| Account | `account` | status, burst/compute actions, history, add_funds |\n| Bootstrap | `guide_barsom_workflow` | orientation + SOP |\n| Parameters | `training_guidance` | presets and field hints (API-scoped) |\n| Prep | `prepare_training` prompt, `training_prep` + `submit_prepared_training` | checklist / interactive UI |\n| Explore | `results_explorer`, `training_monitor` | optional MCP Apps; `jobs(status)` and `results(get)` suffice without them |\n| Other | `send_feedback` | only after user agrees |\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, `results(recolor)`, `results(transition_flow)` 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; `training_prep` = UI + guarded submit. 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 `recolor`, `transition_flow`, or `project_columns`, use the **new** `job_id` returned for follow-up `results` if applicable.'});n(A,y,y,{mimeType:s},async()=>{const e=await c("results-explorer");return{contents:[{uri:y,mimeType:s,text:e??"<html><body>Results Explorer view not built yet. Run: npm run build:views</body></html>"}]}}),n(A,k,k,{mimeType:s},async()=>{const e=await c("training-prep");return{contents:[{uri:k,mimeType:s,text:e??"<html><body>Training Preparation view not built yet.</body></html>"}]}}),n(A,M,M,{mimeType:s},async()=>{const e=await c("training-monitor");return{contents:[{uri:M,mimeType:s,text:e??"<html><body>Training Monitor view not built yet.</body></html>"}]}}),v(A),h(A),I(A),T(A),f(A),g(A,_),b(A),w(A),j(A),P(A),x(A),A.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).","",'**Key tools:** `datasets` (data; reduce_spectral for spectra/long blocks), `jobs` (train/poll/compare/…; train_map accepts an optional `label` for readable compare rows), `results` (get/download/export/recolor/transition_flow; figures="none" for metrics-only), `inference` (predict; impute_column for topology-neighbor pool fill; compare; project_columns; report), `account` (status/credits/queue).',"","**Prep help:** `prepare_training` prompt (checklist) · `training_guidance` (presets/JSON hints) · `training_prep` + `submit_prepared_training` (interactive UI).","","**Optional UI:** `results_explorer`, `training_monitor` — nice for browsing; not required if you use `results` + `jobs(status)`.","","**After training:** `jobs(compare)` across runs, `results(recolor)`, `inference(project_columns)` for variables not in training, `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")}}]})),A.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; training_prep tool = interactive UI + submit_prepared_training.",{dataset_id:o.string().describe("Dataset ID to prepare for training")},async({dataset_id:e})=>({messages:[{role:"user",content:{type:"text",text:await O(e)}}]}));const S=new t;(async function(){try{const e=await a(l,p,c);m(e)}catch(e){process.env.BARIVIA_VIZ_PORT&&console.error("Barivia viz server failed to start:",e)}const e=A.server;e.oninitialized=()=>{const t=e.getClientCapabilities(),o=r(t);d(!!o?.mimeTypes?.includes(s))},await A.connect(S)})().catch(console.error);
package/dist/shared.js CHANGED
@@ -24,7 +24,7 @@ export const RETRYABLE_STATUS = new Set([502, 503, 504]);
24
24
  * X-Barsom-Client-Version so the server can annotate tool guidance with the
25
25
  * wrapper version each action requires. Keep in sync with package.json on bump.
26
26
  */
27
- export const CLIENT_VERSION = "0.11.1";
27
+ export const CLIENT_VERSION = "0.13.0";
28
28
  /** User-facing links; keep aligned with barivia.se / api.barivia.se. */
29
29
  export const PUBLIC_SITE_ORIGIN = "https://barivia.se";
30
30
  /** Self-serve account dashboard (manage plan, billing, and API keys). */
@@ -102,14 +102,14 @@ export function registerDatasetsTool(server) {
102
102
  | analyze | Pre-training column analysis: correlation matrix, periodicity ranking, column recommendations (train / drop / project later). Run after preview, before train_map. |
103
103
  | list | Finding dataset IDs for train_map, preview, or subset — see all available datasets |
104
104
  | subset | Creating a filtered/sliced view without re-uploading the full CSV |
105
- | add_expression | Add a computed column from an expression (same as project(expression) without project_onto_job) dataset_id + name + expression |
105
+ | 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). |
106
106
  | 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. |
107
107
  | delete | Cleaning up after experiments or freeing the dataset slot |
108
108
 
109
109
  action=upload: PREFER file_path — server reads from workspace root (token-efficient; no file content in context). Use csv_data only for small inline pastes (e.g. <10KB). Returns dataset ID. Then use datasets(action=preview) before jobs(action=train_map).
110
110
 
111
111
  Step 0 (before upload): (1) CSV with header; one row per observation. (2) Columns you will use for training must be numeric (or datetime if you will use temporal extraction). (3) Complete-case path (jobs action=train_map): no NaNs in training columns. Sparse path (jobs action=train_impute): missing cells in training columns are OK — the job trains missSOM and returns dense imputed.csv; plain numeric columns only. (4) Categoricals: encode (e.g. one-hot, label) or exclude — do not use raw categorical columns as training features; preview shows which columns are non-numeric. (5) Optional: if you plan to use transition_flow, sort rows chronologically before upload.
112
- Step 1 (after upload): Upload, then always datasets(action=preview) to verify column types and spot cyclics/datetime. Add derived columns with datasets(action=add_expression, dataset_id=..., name=..., expression=...); choose train_map (complete-case) or train_impute (sparse matrix) before submit.
112
+ Step 1 (after upload): always datasets(action=preview) to verify column types and spot cyclics/datetime, then choose train_map (complete-case) or train_impute (sparse) before submit.
113
113
 
114
114
  action=preview: Show columns, stats, sample rows, cyclic/datetime detections. ALWAYS preview before jobs(action=train_map) on an unfamiliar dataset.
115
115
  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.
@@ -135,7 +135,7 @@ NOT FOR: Real-time data streams or binary files — upload a snapshot CSV instea
135
135
  ESCALATION: If upload fails with column errors, open the file locally and verify the header row. If preview shows nulls in training columns: use jobs(action=train_impute) for sparse data, or clean/subset for jobs(action=train_map).`, {
136
136
  action: z
137
137
  .enum(["upload", "preview", "analyze", "list", "subset", "delete", "add_expression", "reduce_spectral"])
138
- .describe("upload: add CSV; preview: inspect columns/stats; analyze: pre-training correlation and periodicity; list: see all datasets; subset: create filtered subset; delete: remove dataset; add_expression: add derived column from expression; reduce_spectral: collapse a long ordered numeric block (e.g. spectrum, time series) into a small per-row feature set via PCA / log_sample / uniform_sample / stats"),
138
+ .describe("upload: add CSV; preview: inspect columns/stats; analyze: pre-training correlation and periodicity; list: see all datasets; subset: create filtered subset; delete: remove dataset; add_expression: add derived column from expression (or, with project_onto_job, project the expression onto a trained map as a component plane); reduce_spectral: collapse a long ordered numeric block (e.g. spectrum, time series) into a small per-row feature set via PCA / log_sample / uniform_sample / stats"),
139
139
  name: z.string().optional().describe("Dataset name (required for action=upload and subset)"),
140
140
  file_path: z.string().optional().describe("Path to local CSV (PREFERRED): absolute path, file:// URI, or relative to workspace root. Token-efficient; server reads file."),
141
141
  csv_data: z.string().optional().describe("Inline CSV string for small pastes only (<10KB). Avoid for large files — use file_path instead to avoid token explosion."),
@@ -179,6 +179,18 @@ ESCALATION: If upload fails with column errors, open the file locally and verify
179
179
  })
180
180
  .optional()
181
181
  .describe("action=add_expression: evaluation options (missing, window for rolling)"),
182
+ project_onto_job: z
183
+ .string()
184
+ .optional()
185
+ .describe("action=add_expression: a COMPLETED training job_id. When set, compute the expression and project it onto that map as a component plane in one step (instead of adding a column to the dataset). Columns in the expression are read from dataset_id when given, else the training dataset. Returns a projection job; get the figure via results(action=download)."),
186
+ aggregation: z
187
+ .enum(["mean", "median", "sum", "min", "max", "std", "count"])
188
+ .optional()
189
+ .describe("action=add_expression with project_onto_job: per-node aggregation of the derived value (default mean)."),
190
+ output_format: z
191
+ .enum(["png", "pdf", "svg"])
192
+ .optional()
193
+ .describe("action=add_expression with project_onto_job: component-plane image format (default pdf)."),
182
194
  method: z
183
195
  .enum(["pca", "log_sample", "uniform_sample", "stats"])
184
196
  .optional()
@@ -204,7 +216,7 @@ ESCALATION: If upload fails with column errors, open the file locally and verify
204
216
  .int()
205
217
  .optional()
206
218
  .describe("action=subset: RNG seed for sample_n (default 42; same seed + dataset reproduces the same sample)."),
207
- }, async ({ action, name, file_path, csv_data, dataset_id, n_rows, row_range, filters, filter, expression, options, method, columns_block, k, sample_n, sample_seed }) => {
219
+ }, async ({ action, name, file_path, csv_data, dataset_id, n_rows, row_range, filters, filter, expression, options, project_onto_job, aggregation, output_format, method, columns_block, k, sample_n, sample_seed }) => {
208
220
  if (action === "upload") {
209
221
  if (!name)
210
222
  throw new Error("datasets(upload) requires name");
@@ -402,12 +414,47 @@ ESCALATION: If upload fails with column errors, open the file locally and verify
402
414
  return { content: [{ type: "text", text: formatAnalyzeResult(data, dataset_id) }] };
403
415
  }
404
416
  if (action === "add_expression") {
405
- if (!dataset_id)
406
- throw new Error("datasets(add_expression) requires dataset_id");
407
417
  if (!name)
408
418
  throw new Error("datasets(add_expression) requires name");
409
419
  if (!expression)
410
420
  throw new Error("datasets(add_expression) requires expression");
421
+ // One-step formula -> component plane: compute the expression and project
422
+ // it onto a trained map without first materializing a dataset column.
423
+ if (project_onto_job) {
424
+ const body = { name, expression };
425
+ if (aggregation)
426
+ body.aggregation = aggregation;
427
+ if (output_format)
428
+ body.output_format = output_format;
429
+ if (dataset_id)
430
+ body.dataset_id = dataset_id; // optional: read expression columns from a different dataset
431
+ if (options)
432
+ body.options = options;
433
+ const data = (await apiCall("POST", `/v1/results/${project_onto_job}/derive`, body));
434
+ const projJobId = data.id;
435
+ const poll = await pollUntilComplete(projJobId, POLL_DERIVE_MAX_MS);
436
+ if (poll.status === "completed") {
437
+ return {
438
+ content: [
439
+ {
440
+ type: "text",
441
+ text: [
442
+ `Projected derived variable "${name}" onto map (training job ${project_onto_job}).`,
443
+ `Expression: ${expression} | Aggregation: ${aggregation ?? "mean"}`,
444
+ `Component plane ready. Get the figure with results(action=download, job_id="${projJobId}").`,
445
+ ].join("\n"),
446
+ },
447
+ ],
448
+ };
449
+ }
450
+ if (poll.status === "failed")
451
+ return { content: [{ type: "text", text: `datasets(add_expression, project_onto_job) job ${projJobId} failed: ${poll.error ?? "unknown error"}` }] };
452
+ return {
453
+ content: [{ type: "text", text: `datasets(add_expression, project_onto_job) job ${projJobId} submitted. Poll with jobs(action=status, job_id="${projJobId}").` }],
454
+ };
455
+ }
456
+ if (!dataset_id)
457
+ throw new Error("datasets(add_expression) requires dataset_id (or project_onto_job to project onto a map)");
411
458
  const body = { name, expression };
412
459
  if (options)
413
460
  body.options = options;
@@ -3,7 +3,6 @@ import { registerAppTool } from "@modelcontextprotocol/ext-apps/server";
3
3
  import { registerAuditedTool, runMcpToolAudit } from "../audit.js";
4
4
  import { apiCall, apiRawCall, getClientSupportsMcpApps, getVizPort, getCaptionForImage, getResultsImagesToFetch, mimeForFilename, structuredTextResult, tryAttachImage, } from "../shared.js";
5
5
  export const RESULTS_EXPLORER_URI = "ui://barsom/results-explorer";
6
- export const MAP_EXPLORER_URI = RESULTS_EXPLORER_URI;
7
6
  function labelForFigure(filename) {
8
7
  const base = filename.replace(/\.(png|pdf|svg)$/i, "");
9
8
  if (base === "combined")
@@ -166,12 +165,12 @@ export function registerExploreMapTool(server) {
166
165
  _meta: { ui: { resourceUri: RESULTS_EXPLORER_URI } },
167
166
  };
168
167
  registerAppTool(server, "results_explorer", toolConfig, async (args) => runMcpToolAudit("results_explorer", "default", args, () => handleResultsExplorer(String(args.job_id))));
169
- registerAppTool(server, "explore_map", {
170
- ...toolConfig,
171
- title: "Results Explorer",
172
- description: `${toolConfig.description} Deprecated alias for results_explorer migrate configs to results_explorer.`,
173
- }, async (args) => runMcpToolAudit("explore_map", "default", args, () => handleResultsExplorer(String(args.job_id))));
174
- registerAuditedTool(server, "_fetch_figure", "Host / MCP App use only do NOT invoke from agent chat. Results Explorer calls this to load one raster figure as base64; agents should use results(action=get) or results_explorer instead.", {
168
+ // NOTE: this MUST stay a registered tool — the Results Explorer MCP App view
169
+ // calls it through the App bridge (mcpApp.callServerTool) to lazy-load non-default
170
+ // raster figures, and the bridge can only reach registered server tools. It cannot
171
+ // be hidden from tools/list without breaking figure-switching in the App. The guard
172
+ // below + SERVER_INSTRUCTIONS keep agents from calling it directly.
173
+ registerAuditedTool(server, "_fetch_figure", "[INTERNAL — host / MCP App only; agents MUST NOT call this.] The Results Explorer App view uses it to lazy-load one raster figure as base64. From chat, use results(action=get) or results_explorer instead; calling this directly just returns a single image with no context.", {
175
174
  job_id: z.string(),
176
175
  filename: z.string(),
177
176
  }, async ({ job_id, filename }) => {
@@ -37,7 +37,7 @@ export function registerInferenceTool(server) {
37
37
  |--------|----------|--------|
38
38
  | predict | Scoring rows against the trained map (new data OR the training set itself) | 5–120s |
39
39
  | impute_column | Fill a numeric column (not used in training) by pooling observed values on the BMU plus topology neighbors (typically 6 on hex; periodic maps wrap) | 5–120s |
40
- | compare | Comparing hit distributions of a second dataset against training (drift, A/B) | 30–120s |
40
+ | compare | Comparing hit distributions of a second dataset against training (drift, A/B); needs one trained job_id + dataset B. NOT jobs(compare), which tabulates metrics across multiple training runs. | 30–120s |
41
41
  | project_columns | Project one or more dataset columns onto the map (component planes); dataset can be training set or partial-feature set | 10–90s |
42
42
  | report | Get a report manifest (artifact keys + URLs) to build your own report in Quarto/Notebook/script | Immediate (sync) |
43
43
 
@@ -54,16 +54,14 @@ action=predict: Score rows against the trained map.
54
54
  Output style (output param, default "compact"):
55
55
  - "compact" → predictions.csv (row_id, bmu_x, bmu_y, bmu_node_index, cluster_id [, quantization_error, potential_anomaly]).
56
56
  - "annotated" → annotated.csv (full source CSV with bmu_x, bmu_y, bmu_node_index, cluster_id appended). Requires a dataset (no inline rows).
57
- Regime auto-detected:
58
- - If the resolved dataset matches the parent training dataset, regime="training" and QE / qe_p95 / potential_anomaly fields are omitted from the compact output. QE on training data is fitting error, not a generalisation metric, and the p95 anomaly flag would be circular. Use a held-out dataset for quality assessment.
59
- - Otherwise regime="new" and the full QE columns are returned.
57
+ Regime (auto): if the resolved dataset is the parent training dataset, regime="training" and QE / qe_p95 / potential_anomaly are omitted (QE on training data is fitting error, not generalisation — use a held-out dataset for quality). Otherwise regime="new" and full QE columns are returned.
60
58
  Schema rules: dataset / batch rows must match the trained feature set including cyclic-expanded columns (e.g. key_cos, key_sin, not raw key). For a single inline row, the proxy uses the stateless model endpoint — raw categorical strings are allowed for baseline categorical_features models, and raw cyclic inputs are expanded from training config.
61
59
  Routing: prefer dataset_id for many rows or whenever the map uses irregular SIOM / GeneralTopology layouts — the async worker path is the supported batch scorer. Single-row rows take a fast stateless path that may return invalid_inference_input on some topologies; if so, retry with dataset_id (a one-row dataset is fine). FLooP-SIOM: use dataset_id predict first.
62
60
  When the scored set has at most ${PREDICT_PREVIEW_ROW_CAP} rows, completed responses include a short per-line preview in the tool text for chat agents.
63
61
 
64
62
  action=impute_column: Map-local imputation on the frozen trained map (read-only; not a held-out validity claim). Requires dataset_id + target_column; the dataset must contain all training features (same names + cyclic expansion as predict) plus the target column. target_column must NOT have been a training feature. Pools finite target values from rows on this row's BMU and its topology neighbors (BMU + neighbors; ~7 on hex interior, fewer on non-periodic borders), neighbourhood-distance-weighted by default (weighting="uniform" for a flat pool); excludes the current row. only_missing (default true) keeps observed values; impute_aggregation mean|median. Optional cv_folds (2-20) → quality.csv (held-out MAE/RMSE/R2). target_column_kind: categorical (mode) / cumulative (warns). Output imputed.csv columns: row_id, target_original, target_imputed, impute_source (observed|imputed|insufficient_data), bmu_node_index, n_patch_nodes, n_pool_rows, pool_std, pool_p5, pool_p95.
65
63
 
66
- action=compare: dataset_id must refer to a dataset with the same feature set as training (same column names and preprocessing, including cyclic expansion). A = training dataset; B = cohort to compare. Density-diff: positive = B gained vs A; negative = A had more. Returns density-diff heatmap (e.g. density_diff.png).
64
+ action=compare: density-diff of a cohort dataset against the training set on ONE trained map — NOT jobs(action=compare) (which tabulates QE/TE/EV metrics across multiple training runs to pick the best). dataset_id must refer to a dataset with the same feature set as training (same column names and preprocessing, including cyclic expansion). A = training dataset; B = cohort to compare. Density-diff: positive = B gained vs A; negative = A had more. Returns density-diff heatmap (e.g. density_diff.png).
67
65
  action=project_columns: Project one or more columns from a dataset onto the trained map. Pass dataset_id (the dataset containing the columns) and columns (array of column names). Uses cached BMUs when dataset is the training set; supports partial-feature mapping when dataset has only a subset of training features. Returns one component plane image per column. Get files via results(action=download, job_id=<returned_job_id>).
68
66
  action=report: Returns a report manifest for the given job_id (job must be completed). Includes figure_manifest (logical names → filenames), download_urls for all artifacts, cluster_summary when available, and summary metrics. Stakeholder report PDF (if generated) is available via results(action=download, job_id=<training_job_id>), filename e.g. report.pdf.`, {
69
67
  action: z
@@ -7,12 +7,11 @@ export const JOBS_DESCRIPTION_BASE = `Manage and inspect jobs.
7
7
  |--------|----------|
8
8
  | status | Polling after any async job submission — call every 10–15s |
9
9
  | list | Finding job IDs, checking what is pending/completed, reviewing hyperparameters. Response includes job_type (train_map, report, recolor, project, transition_flow, compare, predict, impute_column, annotated_dataset, reduce_spectral) to filter or display. |
10
- | compare | Picking the best training run from a set of completed jobs |
10
+ | compare | Picking the best TRAINING run from a set of completed jobs (metrics table). NOT inference(compare), which diffs one dataset's distribution against the training set. |
11
11
  | train_map | Submitting a new map training job — returns job_id for polling |
12
12
  | train_impute | Sparse training data: train a map AND impute missing cells in one job (accelerated missSOM) — returns map + imputed.csv |
13
13
  | train_siom_map | Submitting a self-interacting map training job — same map flow with SIOM coverage control |
14
14
  | train_floop_siom | Submitting a FLooP-SIOM job (growing manifold; default topology=free / CHL) — requires Premium or Enterprise plan (all_algorithms) |
15
- | train_floop_chain | Deprecated alias for train_floop_siom — same behavior; prefer train_floop_siom |
16
15
  | cancel | Stopping a running or pending job to free the worker |
17
16
  | delete | Permanently removing a job and all its S3 result files |
18
17
 
@@ -32,19 +31,12 @@ ESCALATION (action=status):
32
31
  - column missing: verify with datasets(action=preview)
33
32
  - NaN error on train_map: use jobs(action=train_impute) for sparse training columns, or clean the CSV for complete-case train_map
34
33
 
35
- action=train_map / train_siom_map: Submits a grid-map training job. Returns job_idpoll with jobs(action=status, job_id=...).
36
- action=train_impute: Submits missing-tolerant map training (accelerated missSOM / SIOM missSOM). Use when many cells are missing across training columns; use inference(impute_column) when you already have a complete-case map and need to fill one held-out column. Plain numeric columns only (no cyclic/temporal/categorical). Defaults: model=auto (SIOM on som_siom+ plans), cv_folds=5 → quality.csv. quality.csv reports two clearly-labeled numbers per column: kfold_holdout_pool (honest held-out) and resubstitution_optimistic (in-sample, optimistic). status returns progress_phase (e.g. ordering/convergence/cv/upload). High-dimensional datasets automatically adjust visualization density, metric set, and backend (call training_guidance for specifics). Params: viz_mode, viz_top_components, emit_cell_uncertainty, quality_metrics. Artifacts: imputed.csv, imputation_mask.csv, optional imputation_uncertainty.csv. Completed train_impute job_id is a valid parent for inference(impute_column).
37
- Presets: quick | standard | refined | high_resuse preset=... for grid/epochs/batch defaults; call training_guidance for details.
38
- Presets refined/high_res may use GPU. On CPU-only hosts pass backend=cpu. API expects strings "cpu" | "gpu" | "gpu_graphs" (no colon). Future backends (e.g. non-CUDA) may be added under the same contract.
39
- normalize: "auto" (default) = scale only non-cyclic features; "all" = scale every feature. Use "auto" when using cyclic_features.
40
- categorical_features: optional baseline categorical support using explicit weighted one-hot encoding. Provide the raw column name, allowed categories, and a weight. Advanced categorical embeddings are intentionally outside this default tool surface.
41
- label (optional, ≤120 chars): user-supplied run name; appears in jobs(list) and jobs(compare) output. Strongly recommended for sweeps so compare rows stay readable.
42
- train_siom_map only: siom_feature_geometry l2 (default) | mixed | auto — torus distance on cyclic (cos,sin) pairs when mixed or auto with cyclic encodings. siom_qe_backend cpu|cuda|auto and siom_qe_batch_size align siom_qe with training geometry when mixed.
43
- action=train_floop_siom (preferred) or train_floop_chain (deprecated alias): Submits FLooP-SIOM — a growing node-budget manifold instead of a fixed hex grid. **Only if the API key’s plan includes all_algorithms** (Premium or Enterprise); otherwise the API returns a clear upgrade message. Default topology is free (CHL dynamic graph); topology=chain is optional for a strict 1D linked-list backbone.
44
- Key params: topology (default free), max_nodes, effort, gamma, siom_decay.
45
- max_nodes is a total node budget, not a grid width or area. If omitted, the backend uses a dataset-size heuristic (roughly 2*sqrt(n_samples), capped for stability).
46
- effort controls passes only: quick≈15, standard≈30, thorough≈60. If coverage collapses, reduce max_nodes before increasing effort.
47
- action=compare: Returns a metrics table (QE, TE, explained variance, silhouette) for 2+ jobs. The table only shows hyperparameter columns that actually differ across the compared runs (preset, backend, batch_size, normalize, periodic, n_features, etc.) so multi-run sweeps stay readable. Tip: pass label="sweep_a" / "sweep_b" on train_map to make rows self-explanatory.
34
+ Parameter details (grid, epochs, batch, model, presets, normalize, categorical_features, backend, FLooP max_nodes/effort, SIOM geometry) live in the **training_guidance** tool / **prepare_training** prompt call them instead of inlining here. Backend strings are "cpu" | "gpu" | "gpu_graphs" (no colon); on CPU-only hosts pass backend=cpu.
35
+
36
+ action=train_map / train_siom_map: Submits a grid-map training job. Returns job_idpoll with jobs(action=status, job_id=...). train_siom_map adds SIOM coverage control.
37
+ action=train_impute: Missing-tolerant map training (accelerated missSOM / SIOM). Use when many cells are missing across training columns; use inference(impute_column) when you already have a complete-case map and need to fill one held-out column. Plain numeric columns only (no cyclic/temporal/categorical). Defaults model=auto, cv_folds=5 → quality.csv with two labeled numbers per column: kfold_holdout_pool (honest held-out) and resubstitution_optimistic (in-sample, optimistic). status returns progress_phase (ordering/convergence/cv/upload). Artifacts: imputed.csv, imputation_mask.csv, optional imputation_uncertainty.csv. A completed train_impute job_id is a valid parent for inference(impute_column).
38
+ action=train_floop_siom: growing node-budget manifold instead of a fixed hex grid. Requires a plan with all_algorithms (Premium/Enterprise); otherwise the API returns a clear upgrade message. Default topology=free (CHL); topology=chain for a strict 1D backbone. max_nodes is a TOTAL node budget (not grid width/area). If coverage collapses, reduce max_nodes before increasing effort.
39
+ action=compare: metrics table (QE, TE, explained variance, silhouette) for 2+ COMPLETED TRAINING jobs pick the best run in a sweep. NOT inference(action=compare) (single trained job + second dataset → density-diff heatmap). Only hyperparameter columns that differ across runs are shown; pass label="sweep_a"/"sweep_b" on train_map for readable rows.
48
40
  action=cancel: Not instant — worker checks between phases. Expect up to 30s delay.
49
41
  action=delete: WARNING — job ID will no longer work with results or any other tool.`;
50
42
  export async function fetchTrainingPresets() {
@@ -227,8 +219,8 @@ export function buildTrainMapParams(args, presets) {
227
219
  export function registerJobsTool(server, description) {
228
220
  registerAuditedTool(server, "jobs", description, {
229
221
  action: z
230
- .enum(["status", "list", "compare", "cancel", "delete", "train_map", "train_impute", "train_siom_map", "train_floop_siom", "train_floop_chain", "batch_predict", "run_baseline_study"])
231
- .describe("status: check progress; list: see all jobs; compare: metrics table; cancel: stop job; delete: remove job + files; train_map: submit standard map training; train_siom_map: submit SIOM map training; train_floop_siom: submit FLooP-SIOM (preferred); train_floop_chain: deprecated alias for train_floop_siom; batch_predict: submit multiple predict jobs at once; run_baseline_study: auto-configure and train a baseline SOM"),
222
+ .enum(["status", "list", "compare", "cancel", "delete", "train_map", "train_impute", "train_siom_map", "train_floop_siom", "batch_predict", "run_baseline_study"])
223
+ .describe("status: check progress; list: see all jobs; compare: metrics table; cancel: stop job; delete: remove job + files; train_map: submit standard map training; train_siom_map: submit SIOM map training; train_floop_siom: submit FLooP-SIOM; batch_predict: submit multiple predict jobs at once; run_baseline_study: auto-configure and train a baseline SOM"),
232
224
  job_id: z
233
225
  .string()
234
226
  .optional()
@@ -474,7 +466,7 @@ export function registerJobsTool(server, description) {
474
466
  }
475
467
  return textResult(data);
476
468
  }
477
- if (action === "train_floop_siom" || action === "train_floop_chain") {
469
+ if (action === "train_floop_siom") {
478
470
  const { columns, cyclic_features, temporal_features, feature_weights, transforms, auto_log_transforms, normalize, row_range, output_format, output_dpi, colormap, topology, max_nodes, effort, n_initial, n_passes, growth_interval, neighborhood_size, edge_max_age, max_degree, gamma, siom_decay, siom_penalty, penalty_alpha, error_threshold, error_decay, ring_close_threshold, sigma_0, sigma_f, eta_0, eta_f, elastic_lambda, elastic_mu, elastic_anchor, anchor_percentile, label, } = args;
479
471
  if (!dataset_id)
480
472
  throw new Error("jobs(train_floop_siom) requires dataset_id");
@@ -565,10 +557,7 @@ export function registerJobsTool(server, description) {
565
557
  data.message =
566
558
  `Job submitted (${paramSummary}). Poll with jobs(action=status, job_id="${newJobId}"). ` +
567
559
  `When status is completed, use results(action=get, job_id="${newJobId}") to view FLooP-SIOM figures and metrics. ` +
568
- `Optional: training_monitor(job_id="${newJobId}") for charts—not required.` +
569
- (action === "train_floop_chain"
570
- ? " Note: action=train_floop_chain is deprecated; prefer train_floop_siom."
571
- : "");
560
+ `Optional: training_monitor(job_id="${newJobId}") for charts—not required.`;
572
561
  return textResult(data);
573
562
  }
574
563
  if (action === "status") {
@@ -1,10 +1,36 @@
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, fetchTrainingGuidanceFromApi, getClientSupportsMcpApps, getVizPort, structuredTextResult, } from "../shared.js";
4
+ import { apiCall, fetchTrainingGuidanceFromApi, getClientSupportsMcpApps, getVizPort, pollUntilComplete, POLL_DERIVE_MAX_MS, structuredTextResult, } from "../shared.js";
5
5
  import { buildTrainMapParams, fetchTrainingPresets, } from "./jobs.js";
6
6
  import { attachTrainingReviewPayload, consumeTrainingReview, getTrainingReview, storeTrainingReview, } from "./training_review_store.js";
7
7
  export const TRAINING_PREP_URI = "ui://barsom/training-prep";
8
+ /**
9
+ * Fetch column analysis the same way datasets(action=analyze) does: enqueue the
10
+ * async sampled analyze job, poll, then read the summary. Falls back to the legacy
11
+ * synchronous GET on older APIs (404). Analysis is advisory in prep, so a job that
12
+ * is still running / failed degrades to an empty analysis (preview + presets still
13
+ * render) rather than failing the whole prep call.
14
+ */
15
+ async function fetchAnalyzeData(datasetId) {
16
+ try {
17
+ const submit = (await apiCall("POST", `/v1/datasets/${datasetId}/analyze`));
18
+ const jobId = (submit.id ?? submit.job_id);
19
+ if (!jobId)
20
+ return {};
21
+ const poll = await pollUntilComplete(jobId, POLL_DERIVE_MAX_MS);
22
+ if (poll.status !== "completed")
23
+ return {};
24
+ const results = (await apiCall("GET", `/v1/results/${jobId}`));
25
+ return (results.summary ?? results);
26
+ }
27
+ catch (err) {
28
+ if (err?.httpStatus === 404) {
29
+ return (await apiCall("GET", `/v1/datasets/${datasetId}/analyze`));
30
+ }
31
+ throw err;
32
+ }
33
+ }
8
34
  function getNumericColumns(preview) {
9
35
  const stats = preview.column_stats ?? [];
10
36
  return stats
@@ -139,7 +165,7 @@ function buildGuidanceLines(guidanceText) {
139
165
  async function buildTrainingPrepPayload(datasetId, input) {
140
166
  const [preview, analyze, presets, guidanceText, preparePrompt] = await Promise.all([
141
167
  apiCall("GET", `/v1/datasets/${datasetId}/preview?n_rows=5`),
142
- apiCall("GET", `/v1/datasets/${datasetId}/analyze`),
168
+ fetchAnalyzeData(datasetId),
143
169
  fetchTrainingPresets().catch(() => ({})),
144
170
  fetchTrainingGuidanceFromApi().catch(() => "No training guidance available."),
145
171
  fetchPreparePrompt(datasetId),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barivia/barsom-mcp",
3
- "version": "0.11.1",
3
+ "version": "0.13.0",
4
4
  "description": "barSOM MCP proxy — connect any MCP client to the barSOM cloud API for Self-Organizing Map analytics",
5
5
  "keywords": [
6
6
  "mcp",