@barivia/barsom-mcp 0.20.3 → 0.21.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 +2 -1
- package/dist/audit.js +4 -1
- package/dist/shared.js +28 -1
- package/dist/tools/training_core.js +2 -0
- package/dist/tools/training_monitor.js +4 -4
- package/dist/training_monitor_curve.js +16 -31
- package/dist/views/src/views/training-monitor/index.html +13 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -162,7 +162,7 @@ All actions use a frozen trained map — no retraining. Derived columns use **`d
|
|
|
162
162
|
|
|
163
163
|
| Tool | Role |
|
|
164
164
|
|------|------|
|
|
165
|
-
| `training_monitor` | Live QE + topographic-error curves (ordering/convergence, dual y-axis)
|
|
165
|
+
| `training_monitor` | Live QE + **panel** topographic-error curves (ordering/convergence, dual y-axis); **Panel TE** and **Map TE** scalars at kernel end; FLooP step progress when applicable; optional — `jobs(action=status)` is enough |
|
|
166
166
|
| `results_explorer` | Browse metrics and figures after training completes |
|
|
167
167
|
| `_fetch_figure` | **Host / App only** — Results Explorer invokes this for one raster figure; not for agent chat |
|
|
168
168
|
|
|
@@ -180,6 +180,7 @@ The right viewer depends on **(MCP App support)** **and** **(can the human reach
|
|
|
180
180
|
|
|
181
181
|
### Migration notes
|
|
182
182
|
|
|
183
|
+
- **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.
|
|
183
184
|
- **Features (0.20.0, non-breaking):**
|
|
184
185
|
- **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.
|
|
185
186
|
- **`datasets(action=reduce_spectral)` column selectors.** New optional `columns_range` (`[first, last]`, the server expands the inclusive numeric block — preferred for wide spectra over sending thousands of names) and `columns_except` (drop non-frequency columns like id/label/target from the block, or use alone for "all numeric except these"). `columns_except` composes with `columns_block` or `columns_range`; `columns_block` and `columns_range` are mutually exclusive. Existing `columns_block` callers are unaffected.
|
package/dist/audit.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* MCP tool audit wrapper — tool name, action, latency, outcome; no secrets.
|
|
3
3
|
*/
|
|
4
4
|
import { logAudit } from "./logger.js";
|
|
5
|
+
import { runWithTrace } from "./shared.js";
|
|
5
6
|
/** Keys safe to include in audit param summaries (no paths, keys, or bodies). */
|
|
6
7
|
const AUDIT_PARAM_KEYS = new Set([
|
|
7
8
|
"action",
|
|
@@ -160,7 +161,9 @@ export function registerAuditedTool(server, name, description, schema, handler)
|
|
|
160
161
|
server.tool(name, description, schema, (async (args) => {
|
|
161
162
|
const rec = args;
|
|
162
163
|
const action = typeof rec.action === "string" && rec.action.length > 0 ? rec.action : "default";
|
|
163
|
-
|
|
164
|
+
// One trace per tool invocation: every apiCall inside this handler shares one
|
|
165
|
+
// trace_id, so the API + job chain reconstruct the whole logical action.
|
|
166
|
+
return runWithTrace(() => runMcpToolAudit(name, action, rec, () => handler(args)));
|
|
164
167
|
}));
|
|
165
168
|
}
|
|
166
169
|
/** Attach audit flags to a tool result (stripped before returning to MCP client). */
|
package/dist/shared.js
CHANGED
|
@@ -5,6 +5,7 @@ import fs from "node:fs/promises";
|
|
|
5
5
|
import { createReadStream, createWriteStream } from "node:fs";
|
|
6
6
|
import { createGzip } from "node:zlib";
|
|
7
7
|
import { createHash, randomUUID } from "node:crypto";
|
|
8
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
8
9
|
import { Readable } from "node:stream";
|
|
9
10
|
import { pipeline } from "node:stream/promises";
|
|
10
11
|
import os from "node:os";
|
|
@@ -26,7 +27,7 @@ export const RETRYABLE_STATUS = new Set([502, 503, 504]);
|
|
|
26
27
|
* X-Barsom-Client-Version so the server can annotate tool guidance with the
|
|
27
28
|
* wrapper version each action requires. Keep in sync with package.json on bump.
|
|
28
29
|
*/
|
|
29
|
-
export const CLIENT_VERSION = "0.
|
|
30
|
+
export const CLIENT_VERSION = "0.21.0";
|
|
30
31
|
/** User-facing links; keep aligned with barivia.se / api.barivia.se. */
|
|
31
32
|
export const PUBLIC_SITE_ORIGIN = "https://barivia.se";
|
|
32
33
|
/** Self-serve account dashboard (manage plan, billing, and API keys). */
|
|
@@ -342,6 +343,30 @@ function throwApiError(status, bodyText, requestId) {
|
|
|
342
343
|
/**
|
|
343
344
|
* @param requestTimeoutMs Optional per-request timeout (default `BARIVIA_FETCH_TIMEOUT_MS`).
|
|
344
345
|
*/
|
|
346
|
+
// ---- Distributed-trace context (W3C traceparent) ----
|
|
347
|
+
//
|
|
348
|
+
// One logical user action (a tool call that may make several API requests, e.g.
|
|
349
|
+
// upload + analyze + train + poll) shares ONE trace_id so the API and the whole job
|
|
350
|
+
// chain (stage -> prepare -> train -> finalize) can be reconstructed end-to-end. The
|
|
351
|
+
// trace id is scoped per tool invocation via AsyncLocalStorage (see registerAuditedTool);
|
|
352
|
+
// each API call mints a fresh span id. Falls back to a per-call trace if no scope is set.
|
|
353
|
+
const _traceStore = new AsyncLocalStorage();
|
|
354
|
+
function _newTraceId() {
|
|
355
|
+
return randomUUID().replace(/-/g, ""); // 32 hex
|
|
356
|
+
}
|
|
357
|
+
function _newSpanId() {
|
|
358
|
+
return randomUUID().replace(/-/g, "").slice(0, 16); // 16 hex
|
|
359
|
+
}
|
|
360
|
+
/** Run `fn` within a fresh trace scope so all apiCall()s inside share one trace_id. */
|
|
361
|
+
export function runWithTrace(fn) {
|
|
362
|
+
return _traceStore.run(_newTraceId(), fn);
|
|
363
|
+
}
|
|
364
|
+
function _currentTraceId() {
|
|
365
|
+
return _traceStore.getStore() ?? _newTraceId();
|
|
366
|
+
}
|
|
367
|
+
function _traceparentHeader() {
|
|
368
|
+
return `00-${_currentTraceId()}-${_newSpanId()}-01`;
|
|
369
|
+
}
|
|
345
370
|
export async function apiCall(method, path, body, extraHeaders, requestTimeoutMs) {
|
|
346
371
|
const url = `${API_URL}${path}`;
|
|
347
372
|
const contentType = extraHeaders?.["Content-Type"] ?? "application/json";
|
|
@@ -350,6 +375,7 @@ export async function apiCall(method, path, body, extraHeaders, requestTimeoutMs
|
|
|
350
375
|
Authorization: `Bearer ${API_KEY}`,
|
|
351
376
|
"Content-Type": contentType,
|
|
352
377
|
"X-Request-ID": requestId,
|
|
378
|
+
traceparent: _traceparentHeader(),
|
|
353
379
|
"X-Barsom-Client-Version": CLIENT_VERSION,
|
|
354
380
|
...extraHeaders,
|
|
355
381
|
};
|
|
@@ -424,6 +450,7 @@ export async function apiRawCall(path, requestTimeoutMs) {
|
|
|
424
450
|
headers: {
|
|
425
451
|
Authorization: `Bearer ${API_KEY}`,
|
|
426
452
|
"X-Request-ID": requestId,
|
|
453
|
+
traceparent: _traceparentHeader(),
|
|
427
454
|
},
|
|
428
455
|
}, effectiveTimeout);
|
|
429
456
|
if (!resp.ok) {
|
|
@@ -78,6 +78,8 @@ export const TRAINING_INPUT_SCHEMA = {
|
|
|
78
78
|
convergence: z.tuple([z.number(), z.number()]),
|
|
79
79
|
})]).optional()),
|
|
80
80
|
batch_size: z.number().int().optional(),
|
|
81
|
+
te_panel_size: z.number().int().optional()
|
|
82
|
+
.describe("Fixed evaluation-panel size for live topographic error during training (default clamp(grid_nodes*6, 500, 10000))"),
|
|
81
83
|
quality_metrics: z.union([z.enum(["fast", "standard", "full"]), z.array(z.string())]).optional(),
|
|
82
84
|
backend: z.enum(["auto", "cpu", "gpu", "gpu_graphs"]).optional().default("auto"),
|
|
83
85
|
output_format: z.enum(["png", "pdf", "svg"]).optional().default("png"),
|
|
@@ -2,7 +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
|
|
5
|
+
import { lastEpochTeFromCurves } from "../training_monitor_curve.js";
|
|
6
6
|
export const TRAINING_MONITOR_URI = "ui://barsom/training-monitor";
|
|
7
7
|
export const TRAINING_MONITOR_REFRESH_MS = 5000;
|
|
8
8
|
function hasCurveArrays(data) {
|
|
@@ -72,13 +72,13 @@ async function enrichWithTrainingLog(job_id, data) {
|
|
|
72
72
|
if (status === "completed" || status === "failed" || status === "cancelled") {
|
|
73
73
|
merged.topographic_error = mapTe;
|
|
74
74
|
}
|
|
75
|
-
return
|
|
75
|
+
return merged;
|
|
76
76
|
}
|
|
77
77
|
catch {
|
|
78
78
|
if (epochTe != null) {
|
|
79
|
-
return
|
|
79
|
+
return { ...data, epoch_topographic_error: epochTe };
|
|
80
80
|
}
|
|
81
|
-
return data
|
|
81
|
+
return data;
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
export function registerTrainingMonitorTool(server) {
|
|
@@ -16,25 +16,8 @@ export function isKernelTrainingComplete(data, status) {
|
|
|
16
16
|
return true;
|
|
17
17
|
return status === "completed";
|
|
18
18
|
}
|
|
19
|
-
|
|
20
|
-
|
|
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)`;
|
|
19
|
+
export function teCurveLabel(base, _kernelComplete = false) {
|
|
20
|
+
return `${base} (panel)`;
|
|
38
21
|
}
|
|
39
22
|
export function formatCurveSourceNote(data) {
|
|
40
23
|
const src = data.training_curve_source_batches;
|
|
@@ -49,17 +32,20 @@ export function formatCurveSourceNote(data) {
|
|
|
49
32
|
if (convTotal != null && convTotal > convShown) {
|
|
50
33
|
parts.push(`${convShown} of ${convTotal.toLocaleString()} convergence batch samples`);
|
|
51
34
|
}
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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)})`);
|
|
35
|
+
const teEval = data.te_evaluation;
|
|
36
|
+
const panelM = teEval?.te_panel_size;
|
|
37
|
+
const panelN = teEval?.te_panel_n_train;
|
|
38
|
+
if (typeof panelM === "number" && typeof panelN === "number" && panelN > 0) {
|
|
39
|
+
const strat = teEval?.te_panel_stratified === true ? ", stratified by mesh" : "";
|
|
40
|
+
parts.push(`TE curve uses a fixed panel of ${panelM.toLocaleString()} of ${panelN.toLocaleString()} training rows${strat}`);
|
|
60
41
|
}
|
|
61
|
-
|
|
62
|
-
|
|
42
|
+
const panelTe = data.panel_topographic_error;
|
|
43
|
+
const mapTe = data.map_topographic_error;
|
|
44
|
+
if (panelTe != null &&
|
|
45
|
+
mapTe != null &&
|
|
46
|
+
Number.isFinite(Number(panelTe)) &&
|
|
47
|
+
Number.isFinite(Number(mapTe))) {
|
|
48
|
+
parts.push(`Panel TE ${Number(panelTe).toFixed(4)} · Map TE ${Number(mapTe).toFixed(4)} (full training set)`);
|
|
63
49
|
}
|
|
64
50
|
if (parts.length === 0)
|
|
65
51
|
return null;
|
|
@@ -82,8 +68,7 @@ export function alignTeToQeAxis(te, qeLen) {
|
|
|
82
68
|
return [...Array(pad).fill(null), ...te];
|
|
83
69
|
}
|
|
84
70
|
/**
|
|
85
|
-
* Last
|
|
86
|
-
* recent per-epoch TE estimate, distinct from the final trained-map TE in the summary.
|
|
71
|
+
* Last panel TE point from the (batch-aligned) live TE curve.
|
|
87
72
|
*/
|
|
88
73
|
export function lastEpochTeFromCurves(data) {
|
|
89
74
|
const conv = data.convergence_topographic_errors;
|