formlab-mcp 0.6.16 → 0.6.17
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/batch-analyze.js +2 -2
- package/tools/lab.js +15 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "formlab-mcp",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.17",
|
|
4
4
|
"mcpName": "io.github.juliu1980/formlab-mcp",
|
|
5
5
|
"description": "Read-only Model Context Protocol server for FormLab — 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.17",
|
|
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.17",
|
|
17
17
|
"runtimeHint": "npx",
|
|
18
18
|
"transport": {
|
|
19
19
|
"type": "stdio"
|
package/tools/batch-analyze.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// ============================================================
|
|
11
11
|
|
|
12
12
|
import { getStore, resolveById, flattenComposition } from '../data.js';
|
|
13
|
-
import { _batchActualMassKg, _massToKg } from './lab.js';
|
|
13
|
+
import { _batchActualMassKg, _massToKg, _batchUnitMassKg } from './lab.js';
|
|
14
14
|
import { _costPerKg } from './ingredients.js';
|
|
15
15
|
|
|
16
16
|
// ---- shared compute (mirrors _bcActualPctMap / _bcProposedPctMap / _bcSpreadLevel) ----
|
|
@@ -43,7 +43,7 @@ function _amountMap(b) {
|
|
|
43
43
|
return out;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
function _targetKg(b) { const q = parseFloat(b.targetQuantity);
|
|
46
|
+
function _targetKg(b) { const q = parseFloat(b.targetQuantity); if (!isFinite(q)) return null; const mk = _massToKg(q, b.unit); if (mk != null) return mk; const umk = _batchUnitMassKg(b); return umk != null ? q * umk : null; }
|
|
47
47
|
function _yieldPct(b) { const tk = _targetKg(b), mk = _batchActualMassKg(b); return (tk && tk > 0 && mk != null) ? +(mk / tk * 100).toFixed(1) : null; }
|
|
48
48
|
|
|
49
49
|
// Blend cost per kg = Σ ingredient costPerKg × weightFraction over the flattened
|
package/tools/lab.js
CHANGED
|
@@ -18,9 +18,19 @@ const _MASS_TO_KG = { mg: 1e-6, cg: 1e-5, dg: 1e-4, g: 1e-3, hg: 0.1, kg: 1, oz:
|
|
|
18
18
|
const _VOL_TO_L = { 'µl': 1e-6, ul: 1e-6, ml: 1e-3, cl: 1e-2, dl: 0.1, l: 1, gal: 3.78541, fl_oz: 0.0295735, floz: 0.0295735, pt: 0.473176, qt: 0.946353, tsp: 0.00492892, tbsp: 0.0147868, cup: 0.236588 };
|
|
19
19
|
function _massToKg(v, u) { const f = _MASS_TO_KG[String(u || '').trim().toLowerCase()]; return (f != null && isFinite(v)) ? v * f : null; }
|
|
20
20
|
function _volToL(v, u) { const f = _VOL_TO_L[String(u || '').trim().toLowerCase()]; return (f != null && isFinite(v)) ? v * f : null; }
|
|
21
|
+
// Mass (kg) of one count-unit (tablet/capsule/…) from b.unitMass {value, unit};
|
|
22
|
+
// null when unset. Lets dose-form batches derive a total mass (count × unit mass).
|
|
23
|
+
function _batchUnitMassKg(b) {
|
|
24
|
+
if (!b || !b.unitMass) return null;
|
|
25
|
+
const v = parseFloat(b.unitMass.value);
|
|
26
|
+
return (isFinite(v) && v > 0) ? _massToKg(v, b.unitMass.unit) : null;
|
|
27
|
+
}
|
|
21
28
|
function _batchActualMassKg(b) {
|
|
22
29
|
const y = parseFloat(b.actualYield);
|
|
23
|
-
if (isFinite(y)) {
|
|
30
|
+
if (isFinite(y)) {
|
|
31
|
+
const kg = _massToKg(y, b.unit); if (kg != null) return kg;
|
|
32
|
+
const umk = _batchUnitMassKg(b); if (umk != null) return y * umk; // count unit × per-unit mass
|
|
33
|
+
}
|
|
24
34
|
if (Array.isArray(b.actualComposition)) {
|
|
25
35
|
let t = 0, any = false;
|
|
26
36
|
for (const c of b.actualComposition) { const kg = _massToKg(parseFloat(c.amount), c.unit); if (kg != null) { t += kg; any = true; } }
|
|
@@ -68,6 +78,9 @@ function _trimBatch(b, formulationsById) {
|
|
|
68
78
|
status: b.status || '',
|
|
69
79
|
targetQuantity: b.targetQuantity ?? null,
|
|
70
80
|
unit: b.unit || '',
|
|
81
|
+
// Per-count-unit mass for dose-form batches (tablet/capsule) — lets a count
|
|
82
|
+
// batch derive a total mass (count × unit mass) for cost / yield.
|
|
83
|
+
unitMass: (b.unitMass && b.unitMass.value != null) ? { value: b.unitMass.value, unit: b.unitMass.unit || 'mg' } : null,
|
|
71
84
|
actualYield: b.actualYield ?? null,
|
|
72
85
|
// Finished-product density (g/mL === kg/L), measured on the blend, + the
|
|
73
86
|
// derived/measured actual VOLUME (mass is the additive quantity; volume is
|
|
@@ -303,4 +316,4 @@ const get_sample = {
|
|
|
303
316
|
export const tools = { list_batches, get_batch, list_samples, get_sample };
|
|
304
317
|
|
|
305
318
|
// Exported for the batch-analyze tools so they reuse the SAME mass logic.
|
|
306
|
-
export { _batchActualMassKg, _massToKg };
|
|
319
|
+
export { _batchActualMassKg, _massToKg, _batchUnitMassKg };
|