@barivia/barsom-mcp 0.20.1 → 0.20.3

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
@@ -29,7 +29,7 @@ MCP clients typically run it with **`npx`** (downloads on first use):
29
29
 
30
30
  **Audit logging (0.10.3+):** Each tool invocation emits a structured **`mcp_tool_call`** line on stderr for support correlation — not sent to the Barivia API. Fields: `tool`, `action`, `duration_ms`, `outcome`, **`rid`** (matches API `X-Request-ID`), **`job_id`** / **`dataset_id`** when the action returns or accepts them, scale hints **`grid_x`** / **`grid_y`** on training submits, and **`timeout_hit`** / **`output_truncated`** when proxy fetch/output limits apply. No API keys or file paths. Grep client stderr alongside API/worker logs by `rid`; see `docs/OPERATIONS_MANUAL.md` §5b.
31
31
 
32
- **Also available:** hosted HTTP MCP at **`https://mcp.barivia.se/mcp`** (same API key; no npm) for clients that support remote MCP.
32
+ **Hosted HTTP MCP paused (2026-06):** `https://mcp.barivia.se/mcp` returns **503** (`hosted_mcp_paused`). Use this npm package only.
33
33
 
34
34
  **Cursor / multi-account note:** Cursor's tool dispatcher resolves tools under a `serverIdentifier` (e.g. `user-barivia-bbb-maps`), which can differ from the key you wrote in `mcp.json` (e.g. `barivia-bbb-maps`). If a tool call fails with **"server does not exist"** or similar, list the resolved identifier with your client's tool inspector (`mcp.list-tools` or equivalent) and use that identifier — not the `mcp.json` key.
35
35
 
@@ -91,7 +91,7 @@ export function formatSnapshotLine(s) {
91
91
  if (s.qe != null)
92
92
  parts.push(`QE ${s.qe.toFixed(4)}`);
93
93
  if (s.te != null)
94
- parts.push(`TE ${s.te.toFixed(4)}`);
94
+ parts.push(`Epoch TE ${s.te.toFixed(4)}`);
95
95
  if (s.eta_sec != null)
96
96
  parts.push(`ETA ~${s.eta_sec}s`);
97
97
  if (s.ordering_errors_tail?.length) {
package/dist/shared.js CHANGED
@@ -26,7 +26,7 @@ export const RETRYABLE_STATUS = new Set([502, 503, 504]);
26
26
  * X-Barsom-Client-Version so the server can annotate tool guidance with the
27
27
  * wrapper version each action requires. Keep in sync with package.json on bump.
28
28
  */
29
- export const CLIENT_VERSION = "0.20.1";
29
+ export const CLIENT_VERSION = "0.20.3";
30
30
  /** User-facing links; keep aligned with barivia.se / api.barivia.se. */
31
31
  export const PUBLIC_SITE_ORIGIN = "https://barivia.se";
32
32
  /** Self-serve account dashboard (manage plan, billing, and API keys). */
@@ -157,7 +157,10 @@ async function handleResultsExplorer(job_id) {
157
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.`,
158
158
  });
159
159
  }
160
- return structuredTextResult(payload, undefined, content);
160
+ return {
161
+ ...structuredTextResult(payload, undefined, content),
162
+ _meta: { ui: { resourceUri: RESULTS_EXPLORER_URI } },
163
+ };
161
164
  }
162
165
  export function registerExploreMapTool(server) {
163
166
  const toolConfig = {
@@ -2,6 +2,7 @@ import { z } from "zod";
2
2
  import { registerAppTool } from "@modelcontextprotocol/ext-apps/server";
3
3
  import { runMcpToolAudit } from "../audit.js";
4
4
  import { apiCall, getClientSupportsMcpApps, getVizPort, structuredTextResult, } from "../shared.js";
5
+ import { lastEpochTeFromCurves, snapTeCurvesToMapTe } from "../training_monitor_curve.js";
5
6
  export const TRAINING_MONITOR_URI = "ui://barsom/training-monitor";
6
7
  export const TRAINING_MONITOR_REFRESH_MS = 5000;
7
8
  function hasCurveArrays(data) {
@@ -24,31 +25,60 @@ function buildStructuredContent(job_id, data) {
24
25
  refresh_interval_ms: TRAINING_MONITOR_REFRESH_MS,
25
26
  };
26
27
  }
28
+ function hasTeCurveArrays(data) {
29
+ return ((Array.isArray(data.ordering_topographic_errors) &&
30
+ data.ordering_topographic_errors.length > 0) ||
31
+ (Array.isArray(data.convergence_topographic_errors) &&
32
+ data.convergence_topographic_errors.length > 0));
33
+ }
34
+ function needsTrainingLogEnrichment(data) {
35
+ const status = String(data.status ?? "");
36
+ if (status !== "completed" && status !== "failed" && status !== "cancelled")
37
+ return false;
38
+ if (!hasCurveArrays(data))
39
+ return true;
40
+ if (!hasTeCurveArrays(data))
41
+ return true;
42
+ if (data.map_topographic_error == null && data.topographic_error == null)
43
+ return true;
44
+ return false;
45
+ }
27
46
  async function enrichWithTrainingLog(job_id, data) {
28
47
  const status = String(data.status ?? "");
29
- if (status !== "completed" && status !== "failed")
30
- return data;
31
- if (hasCurveArrays(data))
48
+ if (status !== "completed" && status !== "failed" && status !== "cancelled")
32
49
  return data;
50
+ const epochTe = data.epoch_topographic_error ??
51
+ data.topographic_error ??
52
+ lastEpochTeFromCurves(data);
33
53
  try {
34
54
  const log = (await apiCall("GET", `/v1/results/${job_id}/training-log`));
35
- return {
55
+ const merged = {
36
56
  ...data,
37
57
  ordering_errors: log.ordering_errors ?? data.ordering_errors,
38
58
  convergence_errors: log.convergence_errors ?? data.convergence_errors,
39
59
  ordering_topographic_errors: log.ordering_topographic_errors ?? data.ordering_topographic_errors,
40
60
  convergence_topographic_errors: log.convergence_topographic_errors ?? data.convergence_topographic_errors,
41
61
  quantization_error: log.quantization_error ?? data.quantization_error,
42
- topographic_error: log.topographic_error ?? data.topographic_error,
43
62
  grid: log.grid ?? data.grid,
44
63
  model: log.model ?? data.model,
45
64
  epochs: log.epochs ?? data.epochs,
46
65
  n_samples: log.n_samples ?? data.n_samples,
47
66
  n_features: log.n_features ?? data.n_features,
67
+ training_curve_source_batches: log.training_curve_source_batches ?? data.training_curve_source_batches,
48
68
  };
69
+ const mapTe = log.topographic_error ?? data.map_topographic_error ?? data.topographic_error;
70
+ merged.epoch_topographic_error = epochTe ?? lastEpochTeFromCurves(merged);
71
+ merged.map_topographic_error = mapTe;
72
+ if (status === "completed" || status === "failed" || status === "cancelled") {
73
+ merged.topographic_error = mapTe;
74
+ }
75
+ return snapTeCurvesToMapTe(merged);
49
76
  }
50
77
  catch {
51
- return data;
78
+ if (epochTe != null) {
79
+ return snapTeCurvesToMapTe({ ...data, epoch_topographic_error: epochTe });
80
+ }
81
+ return data.kernel_complete === true ? snapTeCurvesToMapTe(data) : data;
52
82
  }
53
83
  }
54
84
  export function registerTrainingMonitorTool(server) {
@@ -67,8 +97,7 @@ export function registerTrainingMonitorTool(server) {
67
97
  const { job_id, fetch_training_log } = args;
68
98
  let data = (await apiCall("GET", `/v1/jobs/${job_id}`));
69
99
  const jobStatus = String(data.status ?? "");
70
- const terminal = jobStatus === "completed" || jobStatus === "failed";
71
- if (fetch_training_log || (terminal && !hasCurveArrays(data))) {
100
+ if (fetch_training_log || needsTrainingLogEnrichment(data)) {
72
101
  data = await enrichWithTrainingLog(job_id, data);
73
102
  }
74
103
  const structuredContent = buildStructuredContent(job_id, data);
@@ -101,6 +130,9 @@ export function registerTrainingMonitorTool(server) {
101
130
  `This localhost port is assigned per MCP session and changes if the proxy restarts — if the page stays at "running 0%" or stops updating, the link is stale: re-run training_monitor for a fresh URL, or set BARIVIA_VIZ_PORT in your MCP env for a persistent port. Health check: http://localhost:${port}/api/health?job_id=${encodeURIComponent(job_id)}`,
102
131
  });
103
132
  }
104
- return structuredTextResult(structuredContent, text, content);
133
+ return {
134
+ ...structuredTextResult(structuredContent, text, content),
135
+ _meta: { ui: { resourceUri: TRAINING_MONITOR_URI } },
136
+ };
105
137
  }));
106
138
  }
@@ -9,13 +9,38 @@ export function combinedBatchAxis(nOrd, nConv) {
9
9
  return [];
10
10
  return [...batchSampleAxis(nOrd, 0), ...batchSampleAxis(nConv, nOrd)];
11
11
  }
12
+ export function isKernelTrainingComplete(data, status) {
13
+ if (status === "failed" || status === "cancelled")
14
+ return true;
15
+ if (data.kernel_complete === true)
16
+ return true;
17
+ return status === "completed";
18
+ }
19
+ /** Snap the last TE curve point to authoritative full-map TE when kernel training is done. */
20
+ export function snapTeCurvesToMapTe(data) {
21
+ const mapTe = data.map_topographic_error ??
22
+ (data.kernel_complete === true ? data.topographic_error : null);
23
+ if (mapTe == null || !Number.isFinite(Number(mapTe)))
24
+ return data;
25
+ const out = { ...data };
26
+ for (const key of ["ordering_topographic_errors", "convergence_topographic_errors"]) {
27
+ const arr = out[key];
28
+ if (Array.isArray(arr) && arr.length > 0) {
29
+ const copy = arr.slice();
30
+ copy[copy.length - 1] = Number(mapTe);
31
+ out[key] = copy;
32
+ }
33
+ }
34
+ return out;
35
+ }
36
+ export function teCurveLabel(base, kernelComplete) {
37
+ return kernelComplete ? `${base} (→ map TE)` : `${base} (sampled)`;
38
+ }
12
39
  export function formatCurveSourceNote(data) {
13
40
  const src = data.training_curve_source_batches;
14
- if (!src)
15
- return null;
16
41
  const parts = [];
17
- const ordTotal = typeof src.ordering === "number" ? src.ordering : null;
18
- const convTotal = typeof src.convergence === "number" ? src.convergence : null;
42
+ const ordTotal = src && typeof src.ordering === "number" ? src.ordering : null;
43
+ const convTotal = src && typeof src.convergence === "number" ? src.convergence : null;
19
44
  const ordShown = Array.isArray(data.ordering_errors) ? data.ordering_errors.length : 0;
20
45
  const convShown = Array.isArray(data.convergence_errors) ? data.convergence_errors.length : 0;
21
46
  if (ordTotal != null && ordTotal > ordShown) {
@@ -24,7 +49,52 @@ export function formatCurveSourceNote(data) {
24
49
  if (convTotal != null && convTotal > convShown) {
25
50
  parts.push(`${convShown} of ${convTotal.toLocaleString()} convergence batch samples`);
26
51
  }
52
+ const mapTe = data.map_topographic_error;
53
+ const epochTe = data.epoch_topographic_error;
54
+ if (mapTe != null &&
55
+ epochTe != null &&
56
+ Number.isFinite(Number(mapTe)) &&
57
+ Number.isFinite(Number(epochTe)) &&
58
+ Math.abs(Number(epochTe) - Number(mapTe)) > 0.0005) {
59
+ parts.push(`Live TE is a subsampled batch estimate during training; final point snaps to map TE ${Number(mapTe).toFixed(4)} (last sampled ${Number(epochTe).toFixed(4)})`);
60
+ }
61
+ else if (mapTe != null && Number.isFinite(Number(mapTe))) {
62
+ parts.push(`TE curve final point is full-map topographic error (${Number(mapTe).toFixed(4)}).`);
63
+ }
27
64
  if (parts.length === 0)
28
65
  return null;
29
- return `Displaying uniformly subsampled curves (≤1000 points/phase): ${parts.join("; ")}.`;
66
+ const subsample = ordTotal != null || convTotal != null
67
+ ? "Displaying uniformly subsampled QE+TE curves (≤1000 points/phase, joint indices): "
68
+ : "";
69
+ return `${subsample}${parts.join("; ")}.`.replace(/: ;/, ":");
70
+ }
71
+ /** Map TE onto QE batch axis; when batch-aligned (same length), use values directly. */
72
+ export function alignTeToQeAxis(te, qeLen) {
73
+ if (qeLen <= 0)
74
+ return [];
75
+ if (te.length === qeLen)
76
+ return te;
77
+ if (te.length === 0)
78
+ return Array(qeLen).fill(null);
79
+ if (te.length >= qeLen)
80
+ return te.slice(0, qeLen);
81
+ const pad = qeLen - te.length;
82
+ return [...Array(pad).fill(null), ...te];
83
+ }
84
+ /**
85
+ * Last sampled TE point from the (batch-aligned) live TE curve. This is the most
86
+ * recent per-epoch TE estimate, distinct from the final trained-map TE in the summary.
87
+ */
88
+ export function lastEpochTeFromCurves(data) {
89
+ const conv = data.convergence_topographic_errors;
90
+ if (Array.isArray(conv) && conv.length > 0) {
91
+ const v = Number(conv[conv.length - 1]);
92
+ return Number.isFinite(v) ? v : null;
93
+ }
94
+ const ord = data.ordering_topographic_errors;
95
+ if (Array.isArray(ord) && ord.length > 0) {
96
+ const v = Number(ord[ord.length - 1]);
97
+ return Number.isFinite(v) ? v : null;
98
+ }
99
+ return null;
30
100
  }