formlab-mcp 0.6.43 → 0.6.45
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/package.json +1 -1
- package/server.json +2 -2
- package/tools/analytics.js +19 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "formlab-mcp",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.45",
|
|
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.45",
|
|
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.45",
|
|
17
17
|
"runtimeHint": "npx",
|
|
18
18
|
"transport": {
|
|
19
19
|
"type": "stdio"
|
package/tools/analytics.js
CHANGED
|
@@ -695,9 +695,19 @@ const find_failures = {
|
|
|
695
695
|
testDate: r.test.testDate || '',
|
|
696
696
|
sample: r.sample ? (r.sample.uid || r.sample.id) : '',
|
|
697
697
|
formulation: r.formulation ? (r.formulation.name || r.formulation.uid) : '',
|
|
698
|
-
value:
|
|
698
|
+
// The value it was judged on (13p): a curve's final point, a
|
|
699
|
+
// distribution at its spec's statistic (D90) or D50 — not "12 points".
|
|
700
|
+
...(() => {
|
|
701
|
+
const sp = _resolveSpec(r.m, r.test.templateId).spec || null;
|
|
702
|
+
const ty = _mcpMeasType(r.m);
|
|
703
|
+
if (ty === 'time_series' || ty === 'distribution') {
|
|
704
|
+
const stat = ty === 'distribution' ? String((sp && sp.stat) || 'D50') : 'final';
|
|
705
|
+
const v = ty === 'distribution' ? _mcpRepNumber(r.m, { dist: stat }) : _mcpRepNumber(r.m, { ts: 'final' });
|
|
706
|
+
return { value: v != null ? _round(v, 6) : _mcpCellValue(r), basis: stat, spec: sp };
|
|
707
|
+
}
|
|
708
|
+
return { value: r.replicates && r.numeric != null ? r.numeric : r.value, spec: sp };
|
|
709
|
+
})(),
|
|
699
710
|
unit: r.unit,
|
|
700
|
-
spec: _resolveSpec(r.m, r.test.templateId).spec || null,
|
|
701
711
|
...(r.replicates ? { replicatesAveraged: r.replicates } : {}),
|
|
702
712
|
});
|
|
703
713
|
}
|
|
@@ -1310,10 +1320,12 @@ const get_stability = {
|
|
|
1310
1320
|
}
|
|
1311
1321
|
return;
|
|
1312
1322
|
}
|
|
1313
|
-
|
|
1323
|
+
// A distribution whose spec names a statistic ("D90 <=30") is trended
|
|
1324
|
+
// and judged at that statistic, as the app does (13p); D50 otherwise.
|
|
1325
|
+
const rs = _resolveSpec(m, t.templateId), ac = rs.spec;
|
|
1326
|
+
const value = (ac && ac.stat && _mcpMeasType(m) === 'distribution') ? _mcpRepNumber(m, { dist: String(ac.stat) }) : _mcpRepNumber(m);
|
|
1314
1327
|
if (value == null) return;
|
|
1315
1328
|
if (m.unit && !unit) unit = m.unit;
|
|
1316
|
-
const rs = _resolveSpec(m, t.templateId), ac = rs.spec;
|
|
1317
1329
|
const sp = (ac && (Number.isFinite(ac.min) || Number.isFinite(ac.max))) ? { min: Number.isFinite(ac.min) ? ac.min : null, max: Number.isFinite(ac.max) ? ac.max : null } : null;
|
|
1318
1330
|
if (sp) {
|
|
1319
1331
|
const k = `${sp.min}|${sp.max}`;
|
|
@@ -1521,8 +1533,8 @@ function _mcpCompareCell(item, parameter, agg, db) {
|
|
|
1521
1533
|
});
|
|
1522
1534
|
return { sig: a.sig, m: a.m, t: points[points.length - 1].t, latestM: points[points.length - 1].m, points, readings: a.rows.length };
|
|
1523
1535
|
});
|
|
1524
|
-
// Primary first: the longest record,
|
|
1525
|
-
series.sort((a, b) => (b.points.length - a.points.length)
|
|
1536
|
+
// Primary first: no run condition when there is one (13n), then the longest record, the declared default condition, the most recent, then by name.
|
|
1537
|
+
series.sort((a, b) => ((a.sig ? 1 : 0) - (b.sig ? 1 : 0)) || (b.points.length - a.points.length)
|
|
1526
1538
|
|| (_mcpCondDefaultScore(b.latestM, b.t, db) - _mcpCondDefaultScore(a.latestM, a.t, db))
|
|
1527
1539
|
|| (b.points[b.points.length - 1].date - a.points[a.points.length - 1].date) || _mcpCmp(a.sig, b.sig));
|
|
1528
1540
|
const basis = type === 'time_series' ? 'final' : type === 'distribution' ? 'D50' : undefined;
|
|
@@ -1550,7 +1562,7 @@ function _mcpCompareCell(item, parameter, agg, db) {
|
|
|
1550
1562
|
const tags = _mcpCondTags(series.map(s => s.sig ? _mcpCondLabel(s.m) : ''));
|
|
1551
1563
|
return { type, ...cells[0], splitByCondition: true,
|
|
1552
1564
|
arms: series.map((s, i) => ({ condition: s.sig ? _mcpCondLabel(s.m) : null, tag: tags[i], ...cells[i] })),
|
|
1553
|
-
note: 'measured under more than one run condition: one arm per condition, never averaged; the top-level value is the first arm (the longest record)' };
|
|
1565
|
+
note: 'measured under more than one run condition: one arm per condition, never averaged; the top-level value is the first arm (no run condition when there is one, else the longest record)' };
|
|
1554
1566
|
}
|
|
1555
1567
|
|
|
1556
1568
|
// The app's difference highlight (_tcKeysDiffer): numbers differ when their
|