@barivia/barmesh-mcp 0.8.3 → 0.8.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/dist/shared.js CHANGED
@@ -35,7 +35,7 @@ function newRequestId() {
35
35
  return randomUUID();
36
36
  }
37
37
  /** Single source of truth for the proxy version. Keep in sync with package.json on bump. */
38
- export const CLIENT_VERSION = "0.8.3";
38
+ export const CLIENT_VERSION = "0.8.4";
39
39
  export const PUBLIC_SITE_ORIGIN = "https://barivia.se";
40
40
  /** Large per-cell CSV uploads may exceed the default fetch timeout. */
41
41
  export const UPLOAD_DATASET_TIMEOUT_MS = 180_000;
@@ -21,6 +21,18 @@ function formatMeshResultsSummary(jobId, data, summary) {
21
21
  const curveNote = (ord?.length ?? 0) > 0 || (conv?.length ?? 0) > 0
22
22
  ? `Training curves: ${ord?.length ?? 0} ordering + ${conv?.length ?? 0} convergence batch samples (≤1000 each in API/MCP responses).`
23
23
  : "";
24
+ const topDivs = summary.top_divergences;
25
+ const klComp = summary.feature_kl_complexity;
26
+ const topDivLine = topDivs && topDivs.length > 0
27
+ ? `Top feature-plane symmetric KL pairs (component association — not mesh SKL/EMD): ${topDivs
28
+ .map((t) => `${t.f1}&${t.f2}=${typeof t.symmetric_kl === "number" ? Number(t.symmetric_kl).toFixed(3) : t.symmetric_kl}`)
29
+ .join(", ")}`
30
+ : "";
31
+ const klComplexityLine = klComp && klComp.length > 0
32
+ ? `Feature KL complexity (mean outgoing): ${klComp
33
+ .map((t) => `${t.feature}=${typeof t.mean_outgoing_kl === "number" ? Number(t.mean_outgoing_kl).toFixed(3) : t.mean_outgoing_kl}`)
34
+ .join(", ")}`
35
+ : "";
24
36
  return [
25
37
  header,
26
38
  `Preset: ${String(summary.preset ?? "generic")} | Grid: ${grid.length >= 2 ? `${grid[0]}×${grid[1]}` : "N/A"} | Reference: ${String(summary.reference_mesh ?? "N/A")}`,
@@ -28,8 +40,13 @@ function formatMeshResultsSummary(jobId, data, summary) {
28
40
  meshes.length > 0 ? `\nDistances vs reference:\n${meshLines.join("\n")}` : "",
29
41
  stepLines.length > 0 ? `\nStepwise:\n${stepLines.join("\n")}` : "",
30
42
  readingLine ? `\nConvergence: ${readingLine}` : "",
43
+ topDivLine,
44
+ klComplexityLine,
31
45
  curveNote,
32
46
  "\nAdvisory: SOM distances complement, not replace, numerical uncertainty analysis (use barmesh_richardson for classical GCI).",
47
+ topDivLine || klComplexityLine
48
+ ? "Feature-plane KL (divergence_kl / feature_divergences.json) measures association between SOM component planes; mesh SKL/EMD measure fingerprint distance between meshes. Refresh or add W1 via barmesh_results(action=feature_divergences, metrics=[\"wasserstein\"])."
49
+ : "",
33
50
  "For every figure: barmesh_results_explorer(job_id) or barmesh_results(action=download). Use figures=\"none\" to skip inline images.",
34
51
  ].filter(Boolean).join("\n");
35
52
  }
@@ -40,6 +57,8 @@ const TEXT_ARTIFACTS = [
40
57
  "distances_to_ref.txt",
41
58
  "emd_stepwise.txt",
42
59
  "cfd_metrics.json",
60
+ "feature_divergences.json",
61
+ "weights.json",
43
62
  "richardson_gci.csv",
44
63
  ];
45
64
  export function registerResultsTool(server) {
@@ -47,18 +66,22 @@ export function registerResultsTool(server) {
47
66
 
48
67
  | Action | Use when |
49
68
  |--------|----------|
50
- | get | Read the summary. mesh_convergence: distances + inline figures. richardson: triplet p/GCI table + topology_warning; see richardson_gci.csv. |
51
- | image | Download one figure by filename (e.g. KL_ref.png, combined.pdf). |
69
+ | get | Read the summary. mesh_convergence: distances + inline figures + feature-plane KL tops when present. richardson: triplet p/GCI table + topology_warning; see richardson_gci.csv. |
70
+ | image | Download one figure by filename (e.g. KL_ref.png, combined.pdf, divergence_kl.png). |
52
71
  | render | Re-render the figures as publication PDFs (or SVG) on demand — PDFs are NOT generated by default. |
53
72
  | download | Save figures and metrics to a local folder (headless / agent path). |
73
+ | feature_divergences | Refresh feature-plane KL matrices / optional Wasserstein on SOM component planes (async; not mesh fingerprint KL). |
54
74
 
55
75
  BEST FOR: After barmesh_jobs(action=status) shows completed (and finalize finished if defer_figures was used).
56
- FIGURES: Default bundle is combined.png, overview_distances.png (all KL/EMD panels), plot_vol_all_meshes.png, plot_vol_steps.png, and learning_curve.png — not redundant per-feature PNGs. action=get inlines the headline set; pass figures="all" for every artifact listed in summary.files, figures="none" for metrics only (recommended for agents).
76
+ FIGURES: Default bundle is combined.png, overview_distances.png (all KL/EMD panels), plot_vol_all_meshes.png, plot_vol_steps.png, and learning_curve.png — not redundant per-feature PNGs or divergence_kl. action=get inlines the headline set; pass figures="all" for every artifact listed in summary.files, figures="none" for metrics only (recommended for agents).
77
+ FEATURE-PLANE KL: mesh SKL/EMD (KL_ref, EMD_*) compare meshes; divergence_kl / feature_divergences.json compare SOM component planes (barsom-parallel). Finalize already writes symmetric KL; use action=feature_divergences for W1 or a refresh.
57
78
  PDFs ON DEMAND: vector PDFs are not produced by default. Use action=render (format=pdf) once, then action=image or action=download.
58
79
  If GET /v1/results returns 404 shortly after status=completed, cfd_finalize may still be rendering — poll barmesh_jobs(status) until finalize_job_id completes.
59
80
  UNAVAILABLE: If the mesh analysis API is temporarily unavailable, say so plainly and suggest retry later (~retry_after_sec). Figures already downloaded stay usable.
60
81
  NOT FOR: Submitting jobs.`, {
61
- action: z.enum(["get", "image", "render", "download"]).describe("get: summary + figures; image: one file; render: lazy PDF/SVG; download: save to disk"),
82
+ action: z
83
+ .enum(["get", "image", "render", "download", "feature_divergences"])
84
+ .describe("get: summary + figures; image: one file; render: lazy PDF/SVG; download: save to disk; feature_divergences: feature-plane KL/W1 (async)."),
62
85
  job_id: z.string().describe("Job ID"),
63
86
  filename: z.string().optional().describe("For action=image: the figure filename (e.g. KL_ref.png or, after render, KL_ref.pdf)"),
64
87
  format: z.enum(["pdf", "png", "svg"]).optional().describe("For action=render: output format to generate (default pdf). PNG previews already exist by default."),
@@ -68,8 +91,60 @@ NOT FOR: Submitting jobs.`, {
68
91
  .describe("For action=get/download: which figures (default headline panels; 'all'; 'none'; or a list of base names)"),
69
92
  folder: z.string().optional().describe("For action=download: directory to save into (relative to workspace). Files land in a per-job subfolder."),
70
93
  include_json: z.boolean().optional().describe("For action=download: also save summary.json and text/CSV artifacts"),
94
+ metrics: z
95
+ .array(z.enum(["symmetric_kl", "directed_kl", "wasserstein"]))
96
+ .optional()
97
+ .describe("action=feature_divergences: which matrices (default symmetric_kl+directed_kl). Add wasserstein for W1."),
98
+ wasserstein_method: z
99
+ .enum(["sinkhorn", "exact"])
100
+ .optional()
101
+ .describe("action=feature_divergences: OT solver when metrics includes wasserstein (default sinkhorn)."),
102
+ output_format: z.enum(["png", "pdf", "svg"]).optional().describe("action=feature_divergences: heatmap format (default png)."),
103
+ output_dpi: z.number().int().min(1).max(4).optional().describe("action=feature_divergences: PNG scale factor (default 2)."),
71
104
  }, async (args) => {
72
- const { action, job_id, filename, format, figures, folder, include_json } = args;
105
+ const { action, job_id, filename, format, figures, folder, include_json, metrics: divergenceMetrics, wasserstein_method: wassersteinMethod, output_format, output_dpi, } = args;
106
+ if (action === "feature_divergences") {
107
+ const body = {
108
+ metrics: divergenceMetrics ?? ["symmetric_kl", "directed_kl"],
109
+ output_format: output_format ?? "png",
110
+ output_dpi: output_dpi ?? 2,
111
+ };
112
+ if (wassersteinMethod != null)
113
+ body.wasserstein_method = wassersteinMethod;
114
+ const data = (await apiCall("POST", `/v1/results/${job_id}/feature_divergences`, body));
115
+ const newJobId = data.id;
116
+ const poll = await pollUntilComplete(newJobId, POLL_STAGE_MAX_MS);
117
+ if (poll.status !== "completed") {
118
+ return textResult(`Feature divergences job ${newJobId} submitted (parent mesh job ${job_id}). Poll with barmesh_jobs(action=status, job_id="${newJobId}"), then barmesh_results(action=get, job_id="${job_id}").`);
119
+ }
120
+ const res = (await apiCall("GET", `/v1/results/${newJobId}`));
121
+ const sum = (res.summary ?? {});
122
+ const topDivs = sum.top_divergences ?? [];
123
+ const klComp = sum.feature_kl_complexity ?? [];
124
+ const topW1 = sum.top_wasserstein ?? null;
125
+ const lines = [
126
+ `Feature-plane divergences complete — job_id: ${newJobId} (parent mesh job: ${job_id})`,
127
+ `Method: ${sum.divergence_method ?? "symmetric_kl_min_shift_sum_norm_denormalized_component_planes"}`,
128
+ `Weight space: ${sum.weight_space ?? "denormalized"} (physical component planes)`,
129
+ `Metrics: ${JSON.stringify(sum.metrics ?? body.metrics)}`,
130
+ topDivs.length
131
+ ? `Top symmetric KL pairs: ${topDivs.map((t) => `${t.f1}&${t.f2}=${typeof t.symmetric_kl === "number" ? Number(t.symmetric_kl).toFixed(3) : t.symmetric_kl}`).join(", ")}`
132
+ : "",
133
+ klComp.length
134
+ ? `KL complexity ranking: ${klComp.map((t) => `${t.feature}=${typeof t.mean_outgoing_kl === "number" ? Number(t.mean_outgoing_kl).toFixed(3) : t.mean_outgoing_kl}`).join(", ")}`
135
+ : "",
136
+ topW1 && topW1.length
137
+ ? `Top Wasserstein pairs: ${topW1.map((t) => `${t.f1}&${t.f2}=${typeof t.wasserstein === "number" ? Number(t.wasserstein).toFixed(3) : t.wasserstein}`).join(", ")}`
138
+ : "",
139
+ `Artifacts refreshed on parent result (divergence_kl / feature_divergences.json). This is feature-plane association — distinct from mesh fingerprint SKL/EMD (KL_ref, EMD_*).`,
140
+ ].filter(Boolean);
141
+ const content = [{ type: "text", text: lines.join("\n") }];
142
+ const files = sum.files ?? [];
143
+ for (const f of files.filter((x) => /\.(png|svg)$/i.test(x))) {
144
+ await tryAttachImage(content, job_id, f);
145
+ }
146
+ return { content };
147
+ }
73
148
  if (action === "render") {
74
149
  const fmt = format ?? "pdf";
75
150
  const submit = (await apiCall("POST", "/v1/cfd/render", { job_id, format: fmt }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barivia/barmesh-mcp",
3
- "version": "0.8.3",
3
+ "version": "0.8.4",
4
4
  "description": "barmesh MCP proxy — SOM-based CFD mesh-convergence and Richardson/GCI analysis on the Barivia cloud API",
5
5
  "keywords": [
6
6
  "mcp",