formlab-mcp 0.6.19 → 0.6.20
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 -2
- package/package.json +1 -1
- package/server.json +2 -2
- package/tools/ingredients.js +25 -2
package/README.md
CHANGED
|
@@ -33,8 +33,8 @@ Ask Claude (or another MCP client) things like:
|
|
|
33
33
|
| `find_similar_formulations` | Find formulas using a given ingredient ≥ threshold % |
|
|
34
34
|
| `compare_formulations` | Pairwise side-by-side composition diff |
|
|
35
35
|
| `list_ingredients` | Filtered list of raw materials. Filters: `family`, `supplier`, `name_contains`, `in_stock_only`, `ingredient_class` (small-molecule / surfactant / polymer / extract / fragrance / pigment / sequence / mixture), `sequence_contains` (e.g. `KTTKS` → Matrixyl), `taxon_contains` (e.g. `Centella`) |
|
|
36
|
-
| `get_ingredient` | Full record + supplier / **cost ($/kg)** / stock / chemical name / molecular weight / storage / formulations using it, plus **GHS safety** (pictograms, H/P codes, signal word), **per-jurisdiction regulatory status**, and **inventory lots** (balances + expiry). Returns every class-specific sub-object when present: `sequence` (peptide / oligo), `taxon` (NCBI ID + scientific name), `ingredientClass`, `extractDetails`, `sequenceDetails`, `polymerDetails`, `surfactantDetails`, `pigmentDetails`, `fragranceDetails` |
|
|
37
|
-
| `list_lots` | Inventory lots across ingredients (each a received batch with its own remaining balance, supplier, expiry, **unit cost**,
|
|
36
|
+
| `get_ingredient` | Full record + supplier / **cost ($/kg)** / stock / chemical name / molecular weight / storage / formulations using it, plus **GHS safety** (pictograms, H/P codes, signal word), **per-jurisdiction regulatory status**, **approved sources** (the suppliers / manufacturers qualified to supply this material — ASL/AML), and **inventory lots** (balances + expiry + the source each was received from). Returns every class-specific sub-object when present: `sequence` (peptide / oligo), `taxon` (NCBI ID + scientific name), `ingredientClass`, `extractDetails`, `sequenceDetails`, `polymerDetails`, `surfactantDetails`, `pigmentDetails`, `fragranceDetails` |
|
|
37
|
+
| `list_lots` | Inventory lots across ingredients (each a received batch with its own remaining balance, supplier, expiry, **unit cost**, **status** — `active` / `hold` / `quarantine` / `rejected`; held lots are blocked from consumption, with a `holdReason` — and the **approved source** it was received from: `source.supplier` + `source.manufacturer`, where recalls hinge on the manufacturer). Filters: `ingredient_id`, `expiring_within_days`, `status` (e.g. `hold` to find lots held for a recall) |
|
|
38
38
|
| `list_inventory` | Portfolio stock rollup — one row per ingredient with on-hand qty, summed lot balance, nearest expiry, and a low-stock flag (on-hand ≤ `reorderThreshold`, or zero). Filters: `low_stock_only`, `expiring_within_days`, `family` |
|
|
39
39
|
| `list_batches` | Filtered list of production / lab-prep events |
|
|
40
40
|
| `get_batch` | Full record + actual composition, measured/derived **actual volume** (mass ÷ density, never a sum of per-ingredient volumes) and **actual mass** (yield, else summed inputs, else measured volume × density), the **process** method + any **process-factor values** (DOE inputs), samples + blend lineage, and — as a finished-goods lot — its storage **`location`** + finished-goods **`stockLedger`** (shipments out + adjustments; `shippedQty` total) |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "formlab-mcp",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.20",
|
|
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.20",
|
|
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.20",
|
|
17
17
|
"runtimeHint": "npx",
|
|
18
18
|
"transport": {
|
|
19
19
|
"type": "stdio"
|
package/tools/ingredients.js
CHANGED
|
@@ -74,9 +74,27 @@ function _trimIngredient(i) {
|
|
|
74
74
|
...(i.surfactantDetails ? { surfactantDetails: i.surfactantDetails } : {}),
|
|
75
75
|
...(i.pigmentDetails ? { pigmentDetails: i.pigmentDetails } : {}),
|
|
76
76
|
...(i.fragranceDetails ? { fragranceDetails: i.fragranceDetails } : {}),
|
|
77
|
+
// Approved sources (ASL/AML): the suppliers/manufacturers qualified to supply
|
|
78
|
+
// this material. A received lot names one via lot.sourceId. Manufacturer is
|
|
79
|
+
// the recall/equivalence-relevant party; supplier is who you buy from.
|
|
80
|
+
...(Array.isArray(i.sources) && i.sources.length ? { sources: i.sources.map(s => ({
|
|
81
|
+
id: s.id, supplier: s.supplier || '', manufacturer: s.manufacturer || '',
|
|
82
|
+
partNo: s.partNo || '', grade: s.grade || '', status: s.status || 'qualified',
|
|
83
|
+
primary: !!s.primary,
|
|
84
|
+
})) } : {}),
|
|
77
85
|
};
|
|
78
86
|
}
|
|
79
87
|
|
|
88
|
+
// Resolve the approved source a lot was received from (lot.sourceId → the
|
|
89
|
+
// ingredient's ing.sources[]). Null when the lot predates sources or is unlinked.
|
|
90
|
+
function _lotSourceInfo(l) {
|
|
91
|
+
if (!l || !l.sourceId) return null;
|
|
92
|
+
const ing = (db.ingredients || []).find(i => i && i.id === l.ingredientId);
|
|
93
|
+
const s = (ing && Array.isArray(ing.sources)) ? ing.sources.find(x => x.id === l.sourceId) : null;
|
|
94
|
+
if (!s) return null;
|
|
95
|
+
return { sourceId: s.id, supplier: s.supplier || '', manufacturer: s.manufacturer || '', status: s.status || 'qualified' };
|
|
96
|
+
}
|
|
97
|
+
|
|
80
98
|
const list_ingredients = {
|
|
81
99
|
definition: {
|
|
82
100
|
name: 'list_ingredients',
|
|
@@ -130,7 +148,7 @@ const list_ingredients = {
|
|
|
130
148
|
const get_ingredient = {
|
|
131
149
|
definition: {
|
|
132
150
|
name: 'get_ingredient',
|
|
133
|
-
description: 'Get full details for a single ingredient: cost ($/kg), GHS safety (pictograms / H- / P-codes / signal word), per-jurisdiction regulatory status, inventory lots (balances + expiry), recent stock movements, and the formulations using it. Accepts internal id or UID (e.g. ING-001).',
|
|
151
|
+
description: 'Get full details for a single ingredient: cost ($/kg), GHS safety (pictograms / H- / P-codes / signal word), per-jurisdiction regulatory status, approved sources (the suppliers/manufacturers qualified to supply this material — ASL/AML), inventory lots (balances + expiry + the source each was received from), recent stock movements, and the formulations using it. Accepts internal id or UID (e.g. ING-001).',
|
|
134
152
|
inputSchema: {
|
|
135
153
|
type: 'object',
|
|
136
154
|
properties: {
|
|
@@ -176,6 +194,7 @@ const get_ingredient = {
|
|
|
176
194
|
qtyRemaining: l.qtyRemaining, qtyReceived: l.qtyReceived, unit: l.unit || '',
|
|
177
195
|
receivedDate: l.receivedDate || null, expiryDate: l.expiryDate || null,
|
|
178
196
|
supplier: l.supplier || '', status: l.status || 'active',
|
|
197
|
+
sourceId: l.sourceId || null, source: _lotSourceInfo(l),
|
|
179
198
|
}));
|
|
180
199
|
return {
|
|
181
200
|
..._trimIngredient(ing),
|
|
@@ -295,7 +314,7 @@ const find_by_smarts = {
|
|
|
295
314
|
const list_lots = {
|
|
296
315
|
definition: {
|
|
297
316
|
name: 'list_lots',
|
|
298
|
-
description: 'List inventory lots (received batches of an ingredient, each with its own remaining balance, supplier, expiry, and unit cost). Optionally filter by ingredient, expiring-within-days, or status. A lot\'s status can be "active", "hold" (a manual QC / recall hold — see holdReason), "quarantine", or "rejected"; the last three are blocked from auto-consumption. For one ingredient\'s full lots + traceability, use get_ingredient.',
|
|
317
|
+
description: 'List inventory lots (received batches of an ingredient, each with its own remaining balance, supplier, expiry, and unit cost). Each lot also carries the approved source it was received from (source.supplier + source.manufacturer; recalls hinge on the manufacturer), or null for lots predating sources. Optionally filter by ingredient, expiring-within-days, or status. A lot\'s status can be "active", "hold" (a manual QC / recall hold — see holdReason), "quarantine", or "rejected"; the last three are blocked from auto-consumption. For one ingredient\'s full lots + traceability, use get_ingredient.',
|
|
299
318
|
inputSchema: {
|
|
300
319
|
type: 'object',
|
|
301
320
|
properties: {
|
|
@@ -332,6 +351,10 @@ const list_lots = {
|
|
|
332
351
|
qtyRemaining: l.qtyRemaining, qtyReceived: l.qtyReceived, unit: l.unit || '',
|
|
333
352
|
receivedDate: l.receivedDate || null, expiryDate: l.expiryDate || null,
|
|
334
353
|
supplier: l.supplier || '', status: l.status || 'active',
|
|
354
|
+
// Approved source this lot was received from (supplier + manufacturer);
|
|
355
|
+
// null for lots received before sources existed. Recalls hinge on the
|
|
356
|
+
// manufacturer, so it is surfaced explicitly here.
|
|
357
|
+
sourceId: l.sourceId || null, source: _lotSourceInfo(l),
|
|
335
358
|
// 'hold' / 'quarantine' / 'rejected' lots are blocked from auto-consumption;
|
|
336
359
|
// holdReason explains a manual hold (QC / recall).
|
|
337
360
|
holdReason: l.holdReason || '',
|