npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

formlab-mcp

v0.6.48

Published

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.

Readme

formlab-mcp

npm license FormLab

A read-only Model Context Protocol server for FormLab. Lets Claude (or any MCP-compatible AI assistant) read and analyze your local FormLab database — your formulations, ingredients, batches, samples and test reports — without your data ever leaving your machine.

Local-first + AI-native. Your proprietary recipes stay on your laptop; only the LLM's answer to your question travels.

Listed in the official MCP Registry as io.github.juliu1980/formlab-mcp.

What it does

Ask Claude (or another MCP client) things like:

  • "Which of my formulations use silicone fluid at 5% or more?"
  • "Compare the composition of Formula FORM-001 and FORM-004 side by side."
  • "Compare the test results of Coating A and Coating B — which parameters differ, and does either fail?"
  • "What parameters fail most often in Q2 testing?"
  • "Show me the DOE matrix at sample grain for all 'Anti-aging serum' family formulas."
  • "What's untested? Which of my approved formulas have no measurements yet?"
  • "Map the design space of my WallPaint project — which ingredients vary, and what combination haven't I tried yet?"
  • "List all peptide ingredients with purity above 95%."
  • "Which botanicals do I source from Madagascar?"
  • "Find ingredients where the sequence contains KTTKS."

Tools exposed

| Tool | Purpose | |---|---| | list_formulations | Filtered list of recipes | | get_formulation | Full record + flattened wt-% composition (sub-formulas expanded), the ordered step procedure, user custom fields, any process-factor values (DOE inputs — cure temp, mix time, RPM, pH…), and the declared finishedDensity (g/mL of the mixed product; volume-targeted batches scale by it) | | find_similar_formulations | Find formulas using a given ingredient ≥ threshold % | | compare_formulations | Pairwise side-by-side composition diff | | 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) | | 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 | | 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). Each lot also reports its Certificate of Analysis (coa: linked, or matched by lot number) and coaMissing for an in-stock lot without one. Filters: ingredient_id, expiring_within_days, status (e.g. hold to find lots held for a recall), missing_coa | | list_suppliers | Supplier / manufacturer records with roll-ups — ingredients approved, lots received / on hand (+ value at paid cost), qualified / trial / disqualified source counts, last receipt; lots count by supplier and by manufacturer | | get_supplier | One supplier in full: record, every ingredient it's an approved source for, every lot from it, and the recall trace (batches that consumed those lots → products, shipments, samples, sub-batches; lots on hold flagged) | | list_documents | The document register (same as the app's Documents page): ingredient SDS / PDS / CoA / other with lot, version, source and expiry; test and notebook attachments; per-record uploads and links (formulas, batches, samples, suppliers, equipment…). File metadata only, never the text inside a file, with who added each file (addedBy) and when. Filters: type, record, linked, query, lot, format, expiry, expiring_within_days, without_expiry, uploaded_within_days, added_by. A summary block carries the page's tile counts: expired, expiring in 30 days, SDS/CoA without expiry, ingredients missing an SDS (+ coverage %), lots in stock without a CoA, added last 7 days | | 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 | | list_batches | Filtered list of production / lab-prep events | | 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 as-prepared inputs — mL rows through that ingredient's density — else measured volume × density), estimatedVolume (mass ÷ the formula's finished density when no actual exists, flagged estimate: true), plannedMass for volume-target batches (target × finished density) and formulaFinishedDensity, 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) | | list_samples | Filtered list of physical specimens | | get_sample | Full record + canonical variant + test reports + blend lineage | | list_test_results | Filtered list of test reports (by sample, parameter, measured-value range (value_min/value_max — a curve at its final point, a distribution at its spec's statistic or D50), grade (min_grade/max_grade on a ranked scale), text value (value_equals), run condition (condition), date, or lab) | | 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 distribution also its bins and, when its spec names a statistic, judged at it); a ranked grade carries its scale and rank; 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 | | 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 | | 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 | | 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. A ranked scale shows its median grade (with its rank) when several readings share the date, a text result its most frequent value, a distribution with no D50 its bins; columns in the app's order (type groups, the test panel's order) | | 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 | | compare_batches | Reproducibility of 2+ runs (ideally of one formula): each run's yield %, actual produced mass, cost/kg; per-ingredient drift across the runs vs the formula's proposed wt-%; and the biggest outlier run. basis: wt_percent (default) or amount | | get_batch_pivot | Production analytics — aggregate batches by group_by (formula / status / month / prepared_by / project) × metric (count, avg_yield_pct, total_produced_kg, avg_cost_per_batch, total_samples, total_tests), with share + total for additive metrics. Optional batch_uids scope + status filter | | get_project_pivot | Portfolio analytics — aggregate projects by group_by (status / phase / priority / business_unit / site / customer / lead, or cf:<custom field>) × metric (count, total_formulas / batches / samples, total_cost, avg_formulas / batches / cost_per_batch), rolling child activity + cost up by any project attribute. Optional project_uids scope | | get_stability | Grounded stability / shelf-life analysis for a formulation, sample or batch × parameter (auto-detected): time-series, drift (per-month slope + R²), an I-chart (mean ±3σ + out-of-control points), spec status, and an ICH-Q1E-flavored projected shelf life (point + 95%-CI crossing), all on one point per test date per run condition (replicates averaged, never separate time points), split by storage condition. A ranked-scale (ordinal) or text parameter returns its step / pass-fail series instead: per-condition points, firstFail, latest, scaleShift | | get_design_space | COMPOSITION design space for one project, several (projects) or any formula list (formulas, across projects — like the app's Scope → DOE Matrix): which ingredients vary and over what observed wt-% ranges (ranked), an occupied-region summary ("where you've been"), the biggest untested interior gap as ready-to-seed DOE ranges (a combination you could have made but skipped), and an optional standardised 2-component PCA of all varying ingredients — loadings, variance-explained, scree, and a full-dimensional gap. Optional axes (choose the 2–3 axes), by_role (map ingredients summed by primary Function, e.g. Pigment / Solvent / Film Former — no gap, roles aren't DOE factors) and property (highest / lowest formula plus the ingredients whose level tracks it, Spearman ρ). Reports formulas that aren't on the map and the spread on it. Matches the in-app Design Space Viewer exactly. Answers "where haven't I explored?" / "what should I formulate next?" | | list_doe_designs | Saved DOE designs — the recipe behind each batch of runs: design type, factors + ranges, constraints, run count, D-efficiency. Filters: project, design_type, constrained_only, name_contains | | get_doe_design | One design in full: constraints in plain language, the model it was optimised for, generation settings (run budget, replicates, centre points, seed) and the complete run matrix with the formulation each run became | | find_by_smarts | SMARTS-pattern substructure search across every ingredient with a SMILES; peptides that only store a sequence are searched with the structure derived from it, as the app draws them (smilesDerived: true). Requires @rdkit/rdkit (optional dependency — install with npm install @rdkit/rdkit in mcp/ if you get a "not installed" error). Examples: c1ccccc1 (any aromatic 6-ring), [OX2H1] (any hydroxyl), C(=O)O (carboxylic acid), [F,Cl,Br,I] (any halogen). | | list_equipment | Filtered list of the Equipment registry (mixers, ovens, viscometers, balances…). Filters: category, status, manufacturer, name_contains | | get_equipment | Full equipment record + usedIn (panels, step presets, test methods, formula steps, batches, testReports resolved to it), usage (reports / formulas / batches / samples / pass rate / top formulas) and measured (per parameter: mean here vs mean on other instruments, biasPct) | | list_test_methods | Filtered list of Test Methods (the parameter library). Filter by analyte (the property measured, e.g. Viscosity — finds every method for it), category, name_contains, include_retired | | get_test_method | One Test Method: full definition, run conditions, analyte siblings, using panels, plus usage, valueSummary (mean / sd / min / max) and byInstrument | | list_step_presets | Filtered list of Step Presets (reusable procedure steps: duration / temp / RPM / equipment). Filters: industry, name_contains | | get_step_preset | Full step-preset record incl. the multi-value equipment list ({name, equipmentId}) | | list_test_panels | Filtered list of Test Panels (reusable column-sets — the parameters measured together on a sample). Filters: industry, name_contains | | get_test_panel | One Test Panel: ordered parameters with resolved method UIDs + specs, plus usage (reports created from it) and performanceByParameter | | list_projects | Filtered list of projects (the buckets formulations are filed under) + per-project formulation count. Filters: status, name_contains | | get_project | Full project record + the formulations filed under it | | list_notebook_entries | ELN feed — dated authored notes attached to records. Append-only: a withdrawn note is kept with retracted = {at, by, reason}. Filters: entity_type, entity_id, author, text_contains, since, until, retracted | | get_notebook_entry | One notebook entry — full body, author, attachment metadata, resolved linked record, and retracted when withdrawn |

Install

# Run directly without installing — recommended:
npx formlab-mcp /path/to/formlab-export.json

# Or install globally for repeated use:
npm install -g formlab-mcp
formlab-mcp /path/to/formlab-export.json

For local development from this repo:

cd mcp
npm install
node index.js /path/to/formlab-export.json

Requires Node 18+.

Get your FormLab export

  1. Open FormLab
  2. Sidebar → ⇅ Import / Export
  3. Click Export — saves formlab-export-YYYY-MM-DD.json to your downloads folder
  4. Point this MCP at the downloaded file

The MCP server watches the file — re-export from FormLab and the next tool call sees the fresh data without restarting the server.

Or: LIVE cloud mode (no export needed)

Instead of an export file, point the server at your live cloud workspace so it's always current.

  1. In FormLab (Pro): Settings → Account → AI tools (MCP) → Connect…
  2. Click Create token, copy the generated config, and paste it into Claude Desktop.

The config sets a single env var, which switches the server into cloud mode:

| Env var | Value | |---|---| | FORMLAB_MCP_TOKEN | a dedicated, read-only, revocable token (flmcp_…) | | FORMLAB_SUPABASE_URL | (optional) override the backend URL — defaults to production | | FORMLAB_SUPABASE_ANON_KEY | (optional) override the publishable key — defaults to production | | FORMLAB_REFRESH_SECONDS | (optional) poll interval, default 60 |

When FORMLAB_MCP_TOKEN is present, the server POSTs it to FormLab's mcp-data Edge Function, which returns your workspace scoped to you by row-level security and re-fetches every FORMLAB_REFRESH_SECONDS. It's read-only — enforced at the database (a dedicated mcp_readonly Postgres role with SELECT-only grants), not by trust.

Security. The token grants read-only access to one workspace and holds no account session — only a SHA-256 hash is stored server-side, and it never rotates. Treat the config like a password (don't share or commit it). Revoke or re-mint any time from Settings → Account → AI tools (MCP) → Connect.

Cloud mode has no extra dependency — it's a plain fetch.

Wire it up to Claude Desktop

Add this to your claude_desktop_config.json (on macOS: ~/Library/Application Support/Claude/claude_desktop_config.json):

{
  "mcpServers": {
    "formlab": {
      "command": "npx",
      "args": ["formlab-mcp", "/Users/you/Downloads/formlab-export-2026-05-30.json"]
    }
  }
}

Or for local-dev (from the repo):

{
  "mcpServers": {
    "formlab": {
      "command": "node",
      "args": [
        "/Users/you/formlab/mcp/index.js",
        "/Users/you/Downloads/formlab-export-2026-05-30.json"
      ]
    }
  }
}

Restart Claude Desktop. You should see a hammer icon indicating tools are available, and FormLab's 40 tools become callable in any conversation.

Wire it up to Claude Code

claude mcp add formlab npx formlab-mcp /Users/you/Downloads/formlab-export.json

Or with a stored env var:

export FORMLAB_EXPORT=/Users/you/Downloads/formlab-export-2026-05-30.json
claude mcp add formlab npx formlab-mcp

Privacy and architecture

  • No network calls. The server runs on stdio between Claude and your local Node process. Your data file never leaves your disk.
  • No FormLab cloud. FormLab itself is a local-first app; the MCP follows the same model.
  • Hot-reload on file change. Re-export from FormLab while the server is running — the next tool call picks up fresh data.
  • Read-only. Tier 1 cannot mutate your FormLab data. (Write tools are planned for a future paid "Pro" tier — register / batch / log-test mutations with cascade-safe confirmation.)

Data shape

The server accepts both FormLab export shapes:

  • Wrapped FAIR export: { "fair": {...metadata...}, "data": {...db...} }
  • Legacy bare-db JSON: {...db...} directly

The wrapped form is the default since 2026; the legacy form is supported for older exports.

Tier 2 (planned, paid)

Live file-sync with write tools: create_formulation, update_formulation, log_test_result, create_batch, etc. Mutations from Claude write back to the same JSON FormLab reads. Conflict detection, cascade-safe deletes, schema versioning.

Not yet shipped — see the FormLab roadmap.

License

MIT