formlab-mcp 0.6.41 → 0.6.42
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 +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/tools/analytics.js +21 -5
package/README.md
CHANGED
|
@@ -46,7 +46,7 @@ Ask Claude (or another MCP client) things like:
|
|
|
46
46
|
| `get_sample` | Full record + canonical variant + test reports + blend lineage |
|
|
47
47
|
| `list_test_results` | Filtered list of test reports (by sample, parameter, **measured-value range** (`value_min`/`value_max`), date, or lab) |
|
|
48
48
|
| `get_test_result` | Full report: every measurement's value, spec, any per-measurement run **conditions**, and the **resolved instrument** (`instrumentSource`: row / method / run). Complex types carry a `representative` (time-series → final, distribution → D50); a time series adds `aggregated` (per-timestamp mean ± sd) and `pointReplicates` (repeat readings at one `t`). Report-level `seriesReplicates` lists parameters measured 2+ times as separate rows — point vs series replicates are distinct. Each measurement also gives its **effective spec** (`effectiveSpec`, `specSource`: override / template / parameter / none) and `passFail` — panel and Test Method specs apply exactly as in the app, and every pass/fail count across the tools uses the same rule |
|
|
49
|
-
| `get_doe_matrix` | Pivot matrix (CSV by default) — rows × ingredients × parameters. A parameter measured under two or more **run conditions** (storage 25 °C vs 40 °C) is one column per condition (`pH`, `pH · 40 °C`; the bare name is the base arm), never averaged together — same columns as the app's DOE Matrix |
|
|
49
|
+
| `get_doe_matrix` | Pivot matrix (CSV by default) — rows × ingredients × parameters. A parameter measured under two or more **run conditions** (storage 25 °C vs 40 °C) is one column per condition (`pH`, `pH · 40 °C`; the bare name is the base arm), never averaged together — same columns, in the same order (grouped by measurement type, the test panel's order within a group), as the app's DOE Matrix |
|
|
50
50
|
| `find_failures` | Parameters ranked by failure **rate** (failing ÷ all readings; replicates in one report count once), `lowN` flag under 5 readings, plus formulations ranked by share of failing reports — same numbers as the app's Test Analytics → Failures |
|
|
51
51
|
| `get_coverage_matrix` | TEST coverage by formulation, batch or sample (`grain`) × parameter. Each cell is the **latest** reading by test date (same-day reports averaged, any fail → fail) with its date and how many reports measured it; one column per **run condition** when a parameter was measured under two or more — same rollup as the app's Test Analytics → Coverage |
|
|
52
52
|
| `compare_test_results` | **Test results** side by side across 2+ formulas, batches, samples or reports — the app's Compare Results table. Parameters grouped by measurement type (Numbers, Curves, Distributions, Ranked scales, Text results) and, within a group, in the test panel's order (`order: method`, default) or `az`. One cell per item × parameter: `aggregation` `latest` (default; one value per test date, replicates averaged ± sd), `mean` (± sd between dates), `median` (min–max) or `trend` (change from the first date). Curves compare at their final point, distributions at D50; ranked scales and text show the latest reading (with its rank); a parameter measured under 2+ run conditions is split into `arms`, never averaged. `differs` flags a >5% spread across items, as the app highlights |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "formlab-mcp",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.42",
|
|
4
4
|
"mcpName": "io.github.juliu1980/formlab-mcp",
|
|
5
5
|
"description": "Read-only Model Context Protocol server for FormLab \u2014 lets Claude (and other MCP clients) read and analyze your FormLab data, from a local export file OR your live cloud workspace.",
|
|
6
6
|
"type": "module",
|
package/server.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
3
|
"name": "io.github.juliu1980/formlab-mcp",
|
|
4
4
|
"description": "Read-only MCP for FormLab — let Claude query your formulation lab. Free reads a local export; Pro connects to your live cloud workspace with a dedicated read-only token.",
|
|
5
|
-
"version": "0.6.
|
|
5
|
+
"version": "0.6.42",
|
|
6
6
|
"websiteUrl": "https://formvix.com/mcp",
|
|
7
7
|
"repository": {
|
|
8
8
|
"url": "https://github.com/juliu1980/FormLab",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
{
|
|
14
14
|
"registryType": "npm",
|
|
15
15
|
"identifier": "formlab-mcp",
|
|
16
|
-
"version": "0.6.
|
|
16
|
+
"version": "0.6.42",
|
|
17
17
|
"runtimeHint": "npx",
|
|
18
18
|
"transport": {
|
|
19
19
|
"type": "stdio"
|
package/tools/analytics.js
CHANGED
|
@@ -226,7 +226,7 @@ const get_test_result = {
|
|
|
226
226
|
const get_doe_matrix = {
|
|
227
227
|
definition: {
|
|
228
228
|
name: 'get_doe_matrix',
|
|
229
|
-
description: 'Build a Design-of-Experiments style pivot matrix: rows = formulations (or samples / batches / tests), columns = composition wt-% per ingredient + aggregated test-parameter values. Same shape as the FormLab DOE Matrix view. A parameter measured under two or more RUN CONDITIONS (e.g. storage 25 °C vs 40 °C) is one column per condition — "pH · 40 °C" — and the bare parameter column is the base arm (readings with no condition, else the condition with the most readings, on a tie the one declared as the default on the Test Method / Test Panel); they are different responses, never averaged together. Returns CSV when format=csv (default) for compact LLM consumption, or structured JSON when format=json.',
|
|
229
|
+
description: 'Build a Design-of-Experiments style pivot matrix: rows = formulations (or samples / batches / tests), columns = composition wt-% per ingredient + aggregated test-parameter values. Same shape as the FormLab DOE Matrix view. A parameter measured under two or more RUN CONDITIONS (e.g. storage 25 °C vs 40 °C) is one column per condition — "pH · 40 °C" — and the bare parameter column is the base arm (readings with no condition, else the condition with the most readings, on a tie the one declared as the default on the Test Method / Test Panel); they are different responses, never averaged together. Parameter columns come in the same order as the app: grouped by measurement type (numbers, curves, distributions, ranked scales, text) and in the test panel order within a group. Returns CSV when format=csv (default) for compact LLM consumption, or structured JSON when format=json.',
|
|
230
230
|
inputSchema: {
|
|
231
231
|
type: 'object',
|
|
232
232
|
properties: {
|
|
@@ -340,8 +340,18 @@ const get_doe_matrix = {
|
|
|
340
340
|
const armNames = _mcpArmNames(rows.flatMap(r => r.measurements
|
|
341
341
|
.filter(m => m && m.parameter && parameters.has(m.parameter))
|
|
342
342
|
.map(m => ({ parameter: m.parameter, m }))));
|
|
343
|
+
// Parameter columns in the app's order (13h): grouped by measurement type
|
|
344
|
+
// (numbers, curves, distributions, ranked scales, text), the test panel's
|
|
345
|
+
// order within a group — the same order as compare_test_results.
|
|
346
|
+
const testOf = new Map();
|
|
347
|
+
(db.testResults || []).forEach(t => (t.measurements || []).forEach(x => testOf.set(x, t)));
|
|
348
|
+
const ordered = _mcpParamGroupsOf(rows.flatMap(r => r.measurements.filter(m => m && m.parameter && parameters.has(m.parameter)).map(m => ({ m, t: testOf.get(m) || null }))), 'method', db)
|
|
349
|
+
.flatMap(g => g.parameters);
|
|
350
|
+
const byTrim = new Map([...parameters].map(k => [String(k).trim(), k]));
|
|
351
|
+
const paramOrder = ordered.map(p => byTrim.get(p)).filter(k => k != null);
|
|
352
|
+
[...parameters].forEach(k => { if (!paramOrder.includes(k)) paramOrder.push(k); });
|
|
343
353
|
const paramCols = [];
|
|
344
|
-
|
|
354
|
+
paramOrder.forEach(p => {
|
|
345
355
|
const arms = armNames.get(p);
|
|
346
356
|
if (!arms) { paramCols.push({ id: p, cond: null, label: p }); return; }
|
|
347
357
|
arms.forEach((label, sig) => paramCols.push({ id: p, cond: sig, label }));
|
|
@@ -1394,15 +1404,21 @@ const _MCP_PARAM_GROUPS = [
|
|
|
1394
1404
|
];
|
|
1395
1405
|
const _mcpCmp = (a, b) => String(a).localeCompare(String(b), undefined, { numeric: true, sensitivity: 'base' }) || (a < b ? -1 : a > b ? 1 : 0);
|
|
1396
1406
|
function _mcpParamGroups(items, sort, db) {
|
|
1407
|
+
const pairs = [];
|
|
1408
|
+
(items || []).forEach(it => (it.tests || []).forEach(t => (t.measurements || []).forEach(m => pairs.push({ m, t }))));
|
|
1409
|
+
return _mcpParamGroupsOf(pairs, sort, db);
|
|
1410
|
+
}
|
|
1411
|
+
// From [{ m, t }] pairs (t may be null) — get_doe_matrix holds flattened measurements (13h).
|
|
1412
|
+
function _mcpParamGroupsOf(pairs, sort, db) {
|
|
1397
1413
|
const sortAz = sort === 'az';
|
|
1398
1414
|
const rank = new Map(_MCP_PARAM_GROUPS.map(([t], i) => [t, i]));
|
|
1399
1415
|
const best = new Map(), panels = new Map(), onPanel = new Map();
|
|
1400
|
-
(
|
|
1416
|
+
(pairs || []).forEach(({ m, t }) => {
|
|
1401
1417
|
const p = String((m && m.parameter) || '').trim();
|
|
1402
1418
|
if (!p) return;
|
|
1403
1419
|
const k = rank.get(_mcpMeasType(m)) ?? 0;
|
|
1404
1420
|
if (!best.has(p) || k < best.get(p)) best.set(p, k);
|
|
1405
|
-
if (sortAz || !t.templateId) return;
|
|
1421
|
+
if (sortAz || !t || !t.templateId) return;
|
|
1406
1422
|
if (!panels.has(t.templateId)) panels.set(t.templateId, (db.templates || []).find(x => x && x.id === t.templateId) || null);
|
|
1407
1423
|
const panel = panels.get(t.templateId);
|
|
1408
1424
|
const params = (panel && panel.parameters) || [];
|
|
@@ -1411,7 +1427,7 @@ function _mcpParamGroups(items, sort, db) {
|
|
|
1411
1427
|
const at = [String(panel.name || ''), i];
|
|
1412
1428
|
const cur = onPanel.get(p);
|
|
1413
1429
|
if (!cur || _mcpCmp(at[0], cur[0]) < 0 || (at[0] === cur[0] && i < cur[1])) onPanel.set(p, at);
|
|
1414
|
-
})
|
|
1430
|
+
});
|
|
1415
1431
|
const byMethod = (a, b) => {
|
|
1416
1432
|
const x = onPanel.get(a), y = onPanel.get(b);
|
|
1417
1433
|
if (x && y) return _mcpCmp(x[0], y[0]) || (x[1] - y[1]) || _mcpCmp(a, b);
|