# io.github.fieldcure/zstudio (nuget · FieldCure.Mcp.ZStudio)

ZStudio EIS MCP server: data validation, DRT, equivalent-circuit fitting, and plotting.

- Trust score: 65/100 (medium)
- Change this week: +4
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-11-05

## Components

- nuget · `FieldCure.Mcp.ZStudio`: 65/100 (this document), [markdown](https://verifymcp.io/servers/fieldcure-zstudio/fieldcure-mcp-zstudio.md), [page](https://verifymcp.io/servers/fieldcure-zstudio/fieldcure-mcp-zstudio)

## Channel facts

- Registry: `nuget`
- Package: `FieldCure.Mcp.ZStudio`
- Version: `1.0.0`
- Transport: `stdio`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-11-05.

- **Supply Chain Security**: 88/100
  - No malware found by supply-chain analysis.
  - No known CVEs affecting this package version or its production dependencies.
  - Install-script risk not yet assessed.
  - No production dependencies, so there is no dependency health to assess.
- **Provenance & Transparency**: 6/100
  - Repository check failed: no source repository is declared.
  - Build provenance not yet verified.
  - Licence could not be checked: the licence is shipped as a file inside the package, not declared as a standard (SPDX) licence identifier, so we cannot tell whether it is OSI-approved.
  - Actively maintained (last published 21 days ago).
  - Security-disclosure policy not yet verified: we couldn't inspect the source repository.
- **Schema Quality & AI Usability**: 60/100
  - AI-judged instruction clarity (excellent).
  - Context-footprint check failed: tool/resource definitions use about 19685 tokens (~410/item across 48 items; 48 tools + 0 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 70/100
  - Stability observed for 21 of 30 days with no destabilising changes; credit accrues until the full window elapses.
- **Tool Coverage**: 100/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 100% of tool parameters carry a description.
- **Tool Safety**: 75/100
  - No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.
  - 0 of 2 tool(s) whose name or description implies an irreversible operation declare an MCP destructiveHint annotation; "remove_item" implies "remove" and declares no destructiveHint at all, which the MCP spec reads as destructive by default.
  - An AI judge read all 49 captured unit(s) of tool text and found none that tries to manipulate the model reading it.
- **Capabilities**: 100/100
  - Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.

## Install

### How do I install the io.github.fieldcure/zstudio MCP server?

io.github.fieldcure/zstudio runs locally as a NuGet package, launched with dnx FieldCure.Mcp.ZStudio@1.0.0 --yes. Ready-made configuration for Claude, Cursor, VS Code, Codex and 3 more is on this page, copied from each client's own documentation.

### Claude

```bash
claude mcp add fieldcure-zstudio -- dnx FieldCure.Mcp.ZStudio@1.0.0 --yes
```

### Cursor

```json
{
  "mcpServers": {
    "fieldcure-zstudio": {
      "command": "dnx",
      "args": [
        "FieldCure.Mcp.ZStudio@1.0.0",
        "--yes"
      ]
    }
  }
}
```

### VS Code

```json
{
  "servers": {
    "fieldcure-zstudio": {
      "command": "dnx",
      "args": [
        "FieldCure.Mcp.ZStudio@1.0.0",
        "--yes"
      ]
    }
  }
}
```

### Codex

```bash
codex mcp add fieldcure-zstudio -- dnx FieldCure.Mcp.ZStudio@1.0.0 --yes
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "fieldcure-zstudio": {
      "type": "local",
      "command": [
        "dnx",
        "FieldCure.Mcp.ZStudio@1.0.0",
        "--yes"
      ],
      "enabled": true
    }
  }
}
```

### Hermes

```yaml
mcp_servers:
  fieldcure-zstudio:
    command: "dnx"
    args: ["FieldCure.Mcp.ZStudio@1.0.0", "--yes"]
```

### Netclaw

```json
{
  "McpServers": {
    "fieldcure-zstudio": {
      "Transport": "stdio",
      "Command": "dnx",
      "Arguments": [
        "FieldCure.Mcp.ZStudio@1.0.0",
        "--yes"
      ]
    }
  }
}
```

### Other

```json
{
  "mcpServers": {
    "fieldcure-zstudio": {
      "command": "dnx",
      "args": [
        "FieldCure.Mcp.ZStudio@1.0.0",
        "--yes"
      ]
    }
  }
}
```

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-11-05 (score 65, +1)

No change was recorded against any check on this day. Stability & Change Management went from 67 to 70. That category is still filling its 30-day observation window: 20 days of observed history at the previous scan, 21 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-11-03 (score 64, +1)

No change was recorded against any check on this day. Stability & Change Management went from 60 to 63. That category is still filling its 30-day observation window: 18 days of observed history at the previous scan, 19 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-11-01 (score 63, +1)

No change was recorded against any check on this day. Stability & Change Management went from 53 to 57. That category is still filling its 30-day observation window: 16 days of observed history at the previous scan, 17 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-10-30 (score 62, +1)

No change was recorded against any check on this day. Stability & Change Management went from 47 to 50. That category is still filling its 30-day observation window: 14 days of observed history at the previous scan, 15 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-10-27 (score 61, +1)

No change was recorded against any check on this day. Stability & Change Management went from 37 to 40. That category is still filling its 30-day observation window: 11 days of observed history at the previous scan, 12 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-10-25 (score 60, +1)

No change was recorded against any check on this day. Stability & Change Management went from 30 to 33. That category is still filling its 30-day observation window: 9 days of observed history at the previous scan, 10 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-10-23 (score 59, +1)

No change was recorded against any check on this day. Stability & Change Management went from 23 to 27. That category is still filling its 30-day observation window: 7 days of observed history at the previous scan, 8 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-10-22 (score 58, +3)

- [functional improvement] Stability: unverified → 0.23

## MCP tools (48)

### `set_analysis_source` (~144 tokens)

Selects which data a dataset's downstream analyses (kk/drt/fit_ecm) and
plots use: 'original' (raw measured) or 'zhit_corrected' (the Z-HIT
reconstruction from a prior zhit call). 'zhit_corrected' requires that
an applied Z-HIT correction exists on the dataset (zhit with apply=true,
the default). Auto-saves the study.

Input parameters:

- `dataset_id` (string, required): The dataset id-handle (ds_…).
- `source` (string, required): 'original' (raw) or 'zhit_corrected'.
- `study_id` (string, required): The owning study id.

### `get_study_info` (~50 tokens)

Returns study-level metadata: name, description, author, timestamps,
sample fields (id/name/note/type), measurement environment, and dataset count.

Input parameters:

- `study_id` (string, required): The study id.

### `zhit` (~908 tokens)

Runs Z-HIT on a dataset's raw spectrum — the entry point of the analysis
pipeline. Z-HIT reconstructs the impedance modulus from the phase via the
Hilbert relation, giving a drift/artifact-corrected curve and a
data-quality metric.

Returns a token-light summary plus a chart overlaying the measured data
(markers) with the Z-HIT reconstruction (dashed line). The summary
describes how much the reconstruction changed the modulus — the size of the
drift/artifact it removed:
\- residualMaxPct / residualMeanPct: |Z−Ẑ|/|Z| over the spectrum (%).
\- maxResidualAtHz: where the correction is largest (drift usually shows at
  low frequency, where the slow sweep is most time-variant).
\- driftBands: frequency ranges where the correction exceeds ~1%.
\- significant: true when the correction is large enough that downstream
  analysis should use the corrected data rather than the raw spectrum.
When significant is true, call set_analysis_source('zhit_corrected') before
kk / drt / fit_ecm so they run on the de-drifted curve; otherwise the raw
data is fine.

apply (default true): store the reconstruction as the dataset's corrected
data — analyze + apply in one step, like the app's Apply button; a re-run
REPLACES the stored correction (last run wins) and the study auto-saves.
Pass apply=false to tune options non-destructively: the analysis result and
summary are produced but the stored corrected data is left untouched —
compare several option sets that way, then re-run the best one with
apply=true.

Tuning (all optional; defaults reproduce the app's auto behavior): the
smoothing/interpolation/window methods default to 'auto' (the engine tries
all and picks the best). Override only when auto misbehaves — e.g. pin
smoothing_type='savitzkyGolay' with number_of_points / polynomial_order, or
narrow the reconstruction window via window_center / window_width (log-f
decades). Enable include_uncertainty for confidence bands.

Requires a study_id (open_study) and…

Input parameters:

- `apply` (boolean): Store the reconstruction as the dataset's corrected data (default true; a re-run replaces it). false = analyze only — tune options without touching the stored correction.
- `confidence_level` (number): Confidence level for the uncertainty bands (e.g. 0.95, 0.85). Default 0.95.
- `dataset_id` (string, required): The dataset id-handle (ds_…).
- `include_uncertainty` (boolean): Include uncertainty (confidence-band) estimation. Default false.
- `interpolation_type` (string): Interpolation method: 'auto' (default), 'akima', 'makima', 'pchip', 'cubic'.
- `kind` (string): Overlay chart kind: 'nyquist' (default), 'bode', or 'admittance'.
- `number_of_iterations` (integer): Iterations for LOWESS smoothing (1–10). Default 3.
- `number_of_points` (integer): Points used for smoothing (1–20). Default 3.
- `polynomial_order` (integer): Polynomial order for Savitzky-Golay / Whittaker-Henderson smoothing (1–10). Default 2.
- `smoothing_type` (string): Smoothing method: 'auto' (default), 'none', 'lowess', 'modifiedSinc', 'savitzkyGolay', 'whittakerHenderson'.
- `study_id` (string, required): The study id returned by open_study.
- `theme` (string|null): Optional theme hint: 'light', 'dark', or 'auto' (default).
- `use_bootstrap` (boolean): Use bootstrap for uncertainty (else residual-based). Default true.
- `window_center` (number): Reconstruction window center on the log-frequency axis. Default 1.5.
- `window_type` (string): Window function: 'auto' (default), or e.g. 'hann', 'hamming', 'blackman', 'boxcar', 'flatTop'.
- `window_width` (number): Reconstruction window width in log-frequency decades (0.5–10). Default 3.0.

### `create_from_file` (~501 tokens)

Imports a raw vendor EIS data file and writes a ZStudio .zstudy document.
Supported formats are auto-detected: BioLogic (.mpr/.mpt), Gamry (.DTA),
Scribner/ZView (.z), Ivium (.ids/.idf), EcoChemie (.dfr), Elchemea (.i2b),
MinTech, and generic text (.csv/.tsv/.txt/.dat).

USE WHEN: the user wants to convert/import a measurement file into a .zstudy,
or start a study from raw data. When the user wants to ANALYZE the file in
the same conversation, pass open_session=true — the created study is loaded
into the session and the result also carries study_id, the tree, and
dataset_id handles, so no separate open_study call is needed.
Returns a token-light summary (per-dataset name, point count, frequency range,
detected vendor format) — never the raw spectrum.

If output_path is omitted, the .zstudy is written (atomically) to the working
directory — the ZSTUDIO_WORKDIR environment variable, or a 'ZStudio' folder
under the user's Documents — named after the study; the actual path is
returned.

Headerless data files (a bare numeric CSV/TSV with no header row) are
supported; use headerless_format to say how the columns are laid out.

Input parameters:

- `author` (string|null): Optional study author.
- `headerless_format` (string|null): Optional. Column layout to assume ONLY for a headerless file (ignored when the file has a header row): 'cartesian' (Freq, Z', Z"; default), 'cartesian_neg_z' (Freq, Z', -Z"), or 'polar_degrees' (Freq…
- `input_path` (string, required): Absolute path to the raw vendor EIS file to import.
- `open_session` (boolean): When true, also load the created .zstudy into the session and return study_id + dataset handles (skips the separate open_study call). Default false.
- `output_path` (string|null): Optional absolute path for the .zstudy to create. If omitted, written to the working directory (ZSTUDIO_WORKDIR or ~/Documents/ZStudio).
- `study_name` (string|null): Optional study name. Defaults to the input file's base name.

### `render_circuit` (~168 tokens)

Renders a ZStudio circuit (CDC) in one representation:
\- infix: canonical CDC (e.g. "Rs-Rct|Cdl")
\- formula: impedance as a nested expression (1/(1/Rct + 1/Cdl) …)
\- symbolic: impedance with element transforms (1/(s*Cdl), 1/(Qy1*pow(s,Qa1)) …)
\- latex: LaTeX of the impedance formula
\- svg: schematic diagram as an SVG string
Pure notation — no dataset needed.

Input parameters:

- `cdc` (string, required): Circuit description code, e.g. 'Rs-Rct|Cdl'.
- `kind` (string): 'infix' (default), 'formula', 'symbolic', 'latex', or 'svg'.

### `set_point_exclusions` (~243 tokens)

Masks (excludes) or unmasks individual measured points by zero-based index —
for picking off outliers. Get the indices from get_curve(include_excluded=
true, source='original'), whose Index column matches these indices.
Out-of-range and duplicate indices are ignored.

Excluded points stay in the dataset (reversible). Analyses use only
non-excluded points; stored analyses are NOT recomputed automatically —
rerun kk / drt_* / guess_ecm / fit_ecm afterward. The study auto-saves
unless ZSTUDIO_AUTOSAVE=off. For trimming a frequency tail prefer
set_frequency_exclusions. Requires a study_id and dataset_id.

Input parameters:

- `dataset_id` (string, required): The dataset id (ds_…).
- `indices` (string, required): Zero-based point indices as a comma- or space-separated list (e.g. "0, 5, 6"). A string, not a JSON array — some hosts cannot pass array arguments.
- `is_excluded` (boolean): True (default) to exclude the points, false to include them.
- `study_id` (string, required): The owning study id.

### `evaluate_ecm` (~383 tokens)

Evaluates an equivalent circuit at the CURRENT parameter values without
estimating or fitting - the middle step of guess -> adjust -> fit, and
the same thing the app does when a value is edited in the ECM panel:
recompute pseudo chi-squared, R-squared and every parameter's standard
error at the given state.

Starts from the dataset's stored modeling state (the result of
guess_ecm, a previous evaluate_ecm, or a fit) when it belongs to the
same circuit; parameters="Rct=120,..." overrides only the named values,
so consecutive calls compose exactly like consecutive edits in the app.
The evaluated state is persisted back to the modeling store, and
fit_ecm with seed_from='stored' then fits FROM this state.

Use it to test "what if this parameter were X" hypotheses cheaply, to
quantify how much a manual adjustment improves the start point before
fitting, or to read uncertainty numbers at a hand-tuned state.

Input parameters:

- `cdc` (string, required): Circuit description code, e.g. 'Rs-(Rct-W1)|Q1'.
- `dataset_id` (string, required): The dataset id-handle (ds_…).
- `kind` (string): Overlay chart kind: 'nyquist' (default), 'bode', or 'admittance'.
- `parameters` (string|null): Parameter overrides as a comma-separated 'Name=value' list, e.g. "Rct=120" (a string, not a JSON object — some hosts cannot pass object arguments). Unspecified parameters keep the dataset's stored mo…
- `study_id` (string, required): The study id returned by open_study.
- `theme` (string|null): Optional theme hint: 'light', 'dark', or 'auto' (default).

### `close_dc` (~50 tokens)

Releases a DC series read with read_dc_file from the session (its dc_id stops
working). Files already exported are not touched.

Input parameters:

- `dc_id` (string, required): The dc_… handle to release.

### `guess_ecm` (~371 tokens)

Estimates initial parameter values for an equivalent circuit from the
dataset's spectrum WITHOUT fitting - the app's "estimate initial values"
button as a tool, and the first step of the guess -> adjust -> fit
workflow. The structural analysis derives the circuit's RC pairs from
the topology and reads their values off the measured spectrum; the
guessed model is then evaluated as-is (zero iterations) so the summary
carries honest goodness-of-fit numbers for the START POINT, and the
guess is stored on the dataset's modeling store (visible in the app).

The estimator is deterministic and state-independent: it depends only on
the data and the circuit, never on values the model carried before. That
makes the workflow composable - to adjust the guess, call fit_ecm with
ONLY the parameters you want to change in initial={...}; every untouched
parameter is re-derived to exactly the value this tool returned.

parameters_estimated=false means the estimator declined (it could not
handle the topology) and the returned values are the factory defaults -
then supply your own initial values to fit_ecm instead.

Input parameters:

- `cdc` (string, required): Circuit description code, e.g. 'Rs-(Rct-W1)|Q1'.
- `dataset_id` (string, required): The dataset id-handle (ds_…).
- `include_schematic` (boolean): Also return the circuit's SVG schematic (same as render_circuit svg). Default false.
- `kind` (string): Overlay chart kind: 'nyquist' (default), 'bode', or 'admittance'.
- `study_id` (string, required): The study id returned by open_study.
- `theme` (string|null): Optional theme hint: 'light', 'dark', or 'auto' (default).

### `export_dc_data` (~359 tokens)

Writes a DC series (from read_dc_file) at full resolution for an external model:
a CSV with every row (no decimation) plus <name>.meta.json describing each column
(meaning, unit, origin, source column) and the source file (path, SHA-256,
technique). Returns the paths, the row count (always the full series) and SHA-256
of the CSV.

\- CSV: header "name [unit]"; first columns "row" (source row number) and "time [s]"
  (instrument time axis, not rebased); invariant-culture, round-trip numbers
  (single-precision source values keep their stored value); empty cell = no value.
\- columns: comma/space-separated channel/marker names (default all).
\- include_interval_current: also writes <name>_interval_current.csv (one row per
  interval between consecutive rows: start/end row and time, current [mA], status),
  a derived value -- not a measured current.
\- output_path: default <working directory>/<source name>_dc.csv. Existing files are
  not replaced unless overwrite=true.

Input parameters:

- `columns` (string|null): Optional comma/space-separated channel or marker names (default: all).
- `dc_id` (string, required): The dc_… handle from read_dc_file.
- `include_interval_current` (boolean): Also write <name>_interval_current.csv with the derived interval-averaged current. Default false.
- `output_path` (string|null): Optional absolute CSV path. Default: <working directory>/<source name>_dc.csv.
- `overwrite` (boolean): Replace existing files. Default false (an existing file is an error).

### `list_models` (~379 tokens)

Lists ZStudio's built-in equivalent-circuit model catalog (the app's Model
Workshop): ~45 vetted models across the categories Simple, Diffusion,
Porous, Battery, Corrosion, and Dielectric. Each entry carries the CDC
circuit, typical parameter values (physically plausible magnitudes), and
the recommended frequency window / points-per-decade for simulation.

HOW TO PICK A MODEL for a measured spectrum:
1\. Run drt_tikhonov (or drt_loewner) first — the number of resolved
   peaks/poles bounds how many time constants the data supports.
2\. Use the sample context (battery cell, corroding metal, porous
   electrode, dielectric, ...) to choose the category, then shortlist 2-4
   circuits whose structure matches the evidence (diffusion tail ->
   lad(...) line, depressed arc -> Q, blocking low-frequency rise ->
   series C, ...).
3\. Screen the shortlist with guess_ecm (estimate-only): keep candidates
   whose data-derived guess already reproduces the spectrum shape.
4\. Decide with fit_ecm: compare R²/χ² across candidates and reject
   over-parameterized ones via parameter dependencies, undetermined (null)
   statistics, and std errors.

The typical values are catalog defaults for simulate_circuit, teaching,
or eyeballing magnitudes — guess_ecm/fit_ecm derive initial values from
the data instead. For the CDC grammar see circuit_reference; for one
circuit's exact fittable parameters see parse_circuit. User-defined
Workshop models are not exposed. Pure catalog — no dataset needed.

Input parameters:

- `category` (string|null): Optional category filter: 'Simple', 'Diffusion', 'Porous', 'Battery', 'Corrosion', or 'Dielectric'.

### `set_study_info` (~161 tokens)

Updates study-level metadata. Only the fields you provide change; the rest
are left as-is. Returns the updated study info. Auto-saves the study.

Input parameters:

- `author` (string|null): Optional study author.
- `description` (string|null): Optional study description.
- `environment` (string|null): Optional measurement environment (e.g. temperature/humidity).
- `name` (string|null): Optional study name.
- `sample_id` (string|null): Optional sample id.
- `sample_name` (string|null): Optional sample name.
- `sample_note` (string|null): Optional sample note.
- `sample_type` (string|null): Optional sample type (e.g. Li-ion, NMC).
- `study_id` (string, required): The study id.

### `list_studies` (~168 tokens)

Lists the .zstudy files saved on disk in the working directory — the
ZSTUDIO_WORKDIR environment variable, or a 'ZStudio' folder under the user's
Documents — so you can discover existing studies to open_study. Pass
directory to list a different folder instead.

Returns the resolved directory and, per file, its name, full path, size,
last-modified time (newest first), and whether it is already open in this
session. Reads the directory only — opens nothing.

USE WHEN: the user refers to a saved study without giving a path, or you
need to find what studies exist before opening one.

Input parameters:

- `directory` (string|null): Optional folder to list instead of the working directory (ZSTUDIO_WORKDIR or ~/Documents/ZStudio).

### `batch_run` (~653 tokens)

Runs one analysis across many datasets in parallel — the fan-out companion to the single-
dataset analysis tools. Use it to apply the same step to a whole study (or a chosen subset)
in one call: a Kramers-Kronig quality gate over every dataset, a DRT pass, or an
equivalent-circuit guess / fit.

kind selects the analysis:
\- 'kk'           — Kramers-Kronig validation (data-quality gate).
\- 'drt_tikhonov' — Tikhonov DRT (γ(τ) distribution).
\- 'guess_ecm'    — ECM initial-value estimation only (no optimization).
\- 'fit_ecm'      — ECM non-linear fit (auto-seeds per dataset).

For 'guess_ecm' / 'fit_ecm': OMIT cdc to use EACH dataset's OWN stored circuit (the model
already on it — the right choice for a mixed study whose datasets have different circuits);
PASS a cdc to apply the same circuit to every dataset (a homogeneous study). A dataset with
neither is reported as an error and skipped. fit_ecm seeds from the structural estimator by
default (seed_from='stored' uses the dataset's stored model). cdc / seed_from are ignored for
kk / drt_tikhonov.

Operations on different datasets run concurrently (bounded by max_parallel) and are isolated:
each result is written only to its own dataset, the study is saved once at the end, and one
dataset's failure never stops the others. Returns a token-light per-dataset summary
(datasetId, name, ok, pseudoChiSquared / rSquared / circuit, or error) — for a chart or full
detail, call the single-dataset tool on a specific dataset afterwards.

Requires a study_id. Omit dataset_ids to run on every dataset in the study.

Input parameters:

- `cdc` (string|null): Circuit (CDC) for guess_ecm/fit_ecm. Omit to use each dataset's own stored circuit; pass one to apply the same circuit to all. Ignored for kk/drt_tikhonov.
- `dataset_ids` (string|null): Dataset id-handles (ds_…) to run on, as a comma- or space-separated list (e.g. "ds_1, ds_2" or "ds_1 ds_2"). Omit or leave empty to run on every dataset in the study. (A string, not a JSON array — so…
- `kind` (string, required): Analysis to run on each dataset: 'kk', 'drt_tikhonov', 'guess_ecm', or 'fit_ecm'.
- `max_parallel` (integer): Max datasets analyzed at once. 0 (default) = min(processor count, dataset count). Lower it to cap CPU/memory on a large fan-out.
- `seed_from` (string): fit_ecm seeding: 'estimate' (default) or 'stored' (the dataset's stored modeling state). Ignored for other kinds.
- `study_id` (string, required): The study id returned by open_study.

### `set_active_dataset` (~65 tokens)

Sets which dataset is the active one for the study (the default target the
ZStudio app opens). Auto-saves the study.

Input parameters:

- `dataset_id` (string, required): The dataset id (ds_…) to activate.
- `study_id` (string, required): The owning study id.

### `add_folder` (~91 tokens)

Adds a folder to a loaded study, under the given parent folder or at the
top level. Returns the new folder's id-handle (fd_…). Auto-saves the study.

Input parameters:

- `name` (string, required): The new folder name.
- `parent_node` (string|null): Optional parent folder id (fd_…). Defaults to the top level.
- `study_id` (string, required): The owning study id.

### `clear_exclusions` (~105 tokens)

Clears every mask on a dataset, restoring all measured points to included.
Stored analyses are NOT recomputed automatically — rerun kk / drt_* /
guess_ecm / fit_ecm to use the full spectrum again. The study auto-saves
unless ZSTUDIO_AUTOSAVE=off. Requires a study_id and dataset_id.

Input parameters:

- `dataset_id` (string, required): The dataset id (ds_…).
- `study_id` (string, required): The owning study id.

### `read_dc_file` (~292 tokens)

Reads a DC time-series file (charge/discharge, e.g. a BioLogic GCPL .mpr) into the
session and returns a dc_id handle with a summary: source path and SHA-256,
technique, acquisition start, row count, time and voltage ranges, every channel and
marker (meaning, unit, origin, source column), quantities the file does not record,
half cycles (rows, modes, direction, signed charge, voltage range), loops and steps.
No row arrays are returned here -- use get_dc_curve or export_dc_data.

USE WHEN: the user has charge/discharge (galvanostatic cycling) data, often next to
EIS files. EIS files go through create_from_file / import_data instead.

Supported: BioLogic .mpr GCPL (files with no frequency column, or only zero
frequencies). Data is held in memory for this session only; it is not saved into a
.zstudy. Voltages are exactly as recorded (never divided by a series-cell count).
control/V/mA is a setpoint (current in CC rows, voltage in CV rows), split into
current_setpoint / voltage_setpoint -- it is never a measured current. Reading the
same unchanged file again returns the existing handle.

Input parameters:

- `input_path` (string, required): Absolute path to the DC time-series file (BioLogic .mpr GCPL).

### `open_study` (~127 tokens)

Loads a ZStudio .zstudy document into the server session and returns a
study_id handle, the folder/dataset tree, and a token-light summary of
every dataset (frequency range, Nyquist geometry, analysis flags) — never
the raw spectrum.

USE WHEN: the user wants to inspect, analyze, or continue working with an
existing .zstudy file. Keep the returned study_id and dataset_id handles
for follow-up calls (list_tree, get_dataset, save_study).

Input parameters:

- `path` (string, required): Absolute path to the .zstudy file to open.

### `simulate_circuit` (~391 tokens)

Forward-simulates an EIS spectrum FROM an equivalent circuit (the inverse of
fit_ecm): evaluates the circuit (CDC + parameter values) over a log-spaced
frequency grid and adds the synthetic spectrum to the study as a new
dataset. Useful for "what does this circuit look like?", teaching, building
reference/test data, or seeding a fit.

Provide parameter values by name (see parse_circuit for the names/units);
any omitted parameter uses its typical value. Returns the new dataset_id,
the parameter values actually used, a token-light summary, and a Nyquist
chart. The dataset behaves like any imported one — analyze it like measured
data. Requires a study_id (open_study or new_study). Auto-saves the study.

Input parameters:

- `cdc` (string, required): Circuit description code, e.g. 'Rs-(Rct-W1)|Q1'.
- `freq_max_hz` (number): Highest frequency in Hz (default 1e6).
- `freq_min_hz` (number): Lowest frequency in Hz (default 0.1).
- `into_folder` (string|null): Optional destination folder id (fd_…); defaults to the Inbox folder.
- `name` (string|null): Optional dataset name; defaults to 'sim: <cdc>'.
- `parameters` (string|null): Parameter values as a comma-separated 'Name=value' list, e.g. "Rs=10,Rct=100,Cdl=1e-5" (a string, not a JSON object — some hosts cannot pass object arguments). Omitted params use typical values.
- `points_per_decade` (number): Points per decade (default 10).
- `study_id` (string, required): The study id (open_study/new_study).
- `theme` (string|null): Optional theme hint: 'light', 'dark', or 'auto'.

### `list_session` (~73 tokens)

Lists everything loaded in this session: every study (study_id, name, dataset
count, bound file path) and every DC time series read with read_dc_file (dc_id,
source file, technique, row count).

USE WHEN: you need to recover study_id / dc_id handles or check what is loaded.

### `export_study` (~281 tokens)

Exports a loaded study to an .xlsx workbook — a re-analyzable data container
with a stable English schema: README, Study, Datasets, Tree, the raw and
Z-HIT-corrected point tables, per-dataset summaries, an analysis dashboard,
and ECM results / parameters / options. Numeric cells stay numeric (SI base
units, with units in the column headers); point data is long-form, one row
per (dataset, point), joined on a workbook-local DatasetId. Exports every
dataset in study tree order.

Path resolution: an explicit .xlsx path if given, else the study's bound
path with a .xlsx extension, else a new file in the working directory
(ZSTUDIO_WORKDIR or ~/Documents/ZStudio). Read-only — it changes nothing in
the study or on disk besides writing the workbook.

USE WHEN: the user wants the study's data and results as a spreadsheet for
external analysis or sharing. Requires a study_id from open_study /
new_study / create_from_file.

Input parameters:

- `path` (string|null): Optional destination .xlsx path. If omitted, derives from the study's bound path or the working directory (ZSTUDIO_WORKDIR or ~/Documents/ZStudio).
- `study_id` (string, required): The study id returned by open_study.

### `get_curve` (~530 tokens)

Returns the actual measured points of a dataset, column-oriented and
token-budgeted — the drill-down for when the token-light summary/shape (or a
DRT curveShape) isn't enough but you don't want to render an image. Real
measured points only (no interpolation), thinned to a budget and optionally
restricted to a frequency window.

\- max_points (default 24, capped at 200): the curve is decimated to at most
  this many log-spaced points. Raise it for a closer look at a feature.
\- freq_min / freq_max: restrict to a frequency window — page through a
  spectrum by narrowing the window across calls. nTotalInWindow reports how
  many measured points the window actually holds, so you know whether more
  detail exists than was returned (decimated = true).
\- representation: 'nyquist' returns fHz + zReOhm + zImOhm (complete — derive
  \|Z|, phase, admittance yourself); 'bode' returns fHz + zMagOhm + phaseDeg.
\- source: 'selected' (the dataset's active source), 'original', or
  'zhit_corrected'.
\- include_excluded: default false drops masked points. Set true (with
  source='original') to see masked points plus per-point Index + Excluded
  columns, then feed Index to set_point_exclusions to curate exclusions.

Use this only after the shape description; prefer plot for a visual on a
chart-capable host. Requires a study_id and dataset_id.

Input parameters:

- `dataset_id` (string, required): The dataset id-handle (ds_…).
- `freq_max` (number|null): Optional high-frequency window bound (Hz).
- `freq_min` (number|null): Optional low-frequency window bound (Hz).
- `include_excluded` (boolean): Include excluded (masked) points and add per-point Index + Excluded columns, for curating exclusions. Default false. Use source='original' so Index matches set_point_exclusions.
- `max_points` (integer): Maximum points to return (log-spaced decimation), 2–200. Default 24.
- `representation` (string): Columns: 'nyquist' (Z′/Z″, default) or 'bode' (|Z|/phase).
- `source` (string): Data source: 'selected' (default), 'original', or 'zhit_corrected'.
- `study_id` (string, required): The study id returned by open_study.

### `set_dataset_properties` (~84 tokens)

Updates a dataset's editable properties — name and/or description. Only
provided fields change. Auto-saves the study.

Input parameters:

- `dataset_id` (string, required): The dataset id (ds_…).
- `description` (string|null): Optional new dataset description.
- `name` (string|null): Optional new dataset name.
- `study_id` (string, required): The owning study id.

### `remove_item` (~89 tokens)

Removes a folder or dataset from a loaded study. Removing a folder removes
everything inside it. The root and the default Inbox folder cannot be
removed. Auto-saves the study — the removal persists to the .zstudy.

Input parameters:

- `item_id` (string, required): The folder (fd_…) or dataset (ds_…) id to remove.
- `study_id` (string, required): The owning study id.

### `rename_item` (~75 tokens)

Renames a folder or dataset. The study root cannot be renamed. Auto-saves
the study.

Input parameters:

- `item_id` (string, required): The folder (fd_…) or dataset (ds_…) id to rename.
- `new_name` (string, required): The new name.
- `study_id` (string, required): The owning study id.

### `import_data` (~238 tokens)

Imports a raw vendor EIS file into an already-loaded study, adding one
dataset per measurement under the chosen folder (defaults to Inbox).
Returns the new dataset id-handles and token-light summaries — never the
raw spectrum. Auto-saves the study.

Headerless data files (a bare numeric CSV/TSV with no header row) are
supported; use headerless_format to say how the columns are laid out.

Input parameters:

- `headerless_format` (string|null): Optional. Column layout to assume ONLY for a headerless file (ignored when the file has a header row): 'cartesian' (Freq, Z', Z"; default), 'cartesian_neg_z' (Freq, Z', -Z"), or 'polar_degrees' (Freq…
- `input_path` (string, required): Absolute path to the raw vendor EIS file to import.
- `into_folder` (string|null): Optional destination folder id (fd_…) or the study id for the top level. Defaults to the Inbox folder.
- `study_id` (string, required): The study id returned by open_study/new_study.

### `set_frequency_exclusions` (~305 tokens)

Masks (excludes) or unmasks measured points by frequency band, so analyses
skip them — the EIS "trim the bad ends" operation. Give an inclusive band:
"exclude below 0.1 Hz" is (freq_max_hz: 0.1), "exclude above 100 kHz" is
(freq_min_hz: 100000), a band is both. At least one bound is required.

Excluded points stay in the dataset (reversible — use clear_exclusions or
is_excluded=false). Analyses use only non-excluded points, but stored
analyses are NOT recomputed automatically: rerun kk / drt_tikhonov /
drt_loewner / guess_ecm / fit_ecm after changing exclusions. The study
auto-saves unless ZSTUDIO_AUTOSAVE=off.

Requires a study_id and dataset_id.

Input parameters:

- `dataset_id` (string, required): The dataset id (ds_…).
- `freq_max_hz` (number|null): Inclusive upper frequency bound (Hz). Omit for open-ended (exclude everything from freq_min_hz up).
- `freq_min_hz` (number|null): Inclusive lower frequency bound (Hz). Omit for open-ended (exclude everything up to freq_max_hz).
- `is_excluded` (boolean): True (default) to exclude the band, false to include it.
- `study_id` (string, required): The owning study id.

### `fit_ecm` (~1616 tokens)

Fits an equivalent-circuit model to a dataset's measured spectrum — the
inverse of simulate_circuit (data + circuit → fitted parameters). Give the
circuit as a CDC string (validate it and learn its parameter names first
with parse_circuit); the fitter seeds initial values automatically, so
initial/bounds/fixed are all optional.

Runs a weighted non-linear least-squares fit on the dataset's selected data
source (raw, or Z-HIT corrected if you called set_analysis_source). Returns
a token-light summary — each parameter with its fitted value, units, and
uncertainty (std error %, t-statistic, p-value, dependency), plus
goodness-of-fit (pseudo χ², R², adjusted R², RMSE), iteration count, exit
condition, and bound-hit flags — and a chart overlaying the measured data
(markers) with the fitted model (dashed line). The result is stored on the
dataset and the study auto-saves.

Defaults: modulus weighting (1/|Z|², the EIS standard — far more robust on
real data than equal weighting) and Levenberg-Marquardt. LM is a LOCAL
optimizer: from a poor start it can stall or land in a local minimum. So
READ THE RESULT and iterate:
\- exit_condition != "Converged", low R², or a huge residual → first retry
  from a better start (pass initial=…); then switch algorithm to a
  trust-region variant ('trust_region_dogleg' or 'trust_region_newton').
  LM and the trust-region methods are the two workhorses — alternate
  between them; the variants behave differently per problem, so if one
  gives a poor result, try another or go back to LM with a different start.
  'trust_region_exact' is the most thorough but is sensitive to parameter
  scaling — reach for it last, not first.
\- parameters_estimated=false → auto-seeding failed; supply initial values.
\- a parameter pinned at a bound (bound_hit), or two parameters with
  dependency≈1, → the circuit is over-specified; drop/fix an element or
  widen bounds.
\- a free parameter with UNDETERMINED statistics (null std error…

Input parameters:

- `algorithm` (string): Optimizer: 'levenberg_marquardt' (default) or a trust-region variant 'trust_region_dogleg' / 'trust_region_newton' / 'trust_region_exact' (most thorough but scale-sensitive — try last), or 'nelder_me…
- `cdc` (string, required): Circuit description code, e.g. 'Rs-(Rct-W1)|Q1'.
- `data_format` (string): Data format for the fit residual: 'rectangular' (default), 'polar', or 'logPolar'.
- `dataset_id` (string, required): The dataset id-handle (ds_…).
- `fit_mode` (string): Fit mode: 'single' (default, one local optimization) or 'global' (basin-hopping — repeats random restarts + local fits to escape local minima; much slower).
- `fixed_parameters` (string|null): Optional parameter names to hold fixed during fitting, as a comma- or space-separated list (e.g. "Rs" or "Rs, Ls"). A string, not a JSON array — some hosts cannot pass array arguments.
- `global_search_options` (string|null): Basin-hopping tuning, applied only when fit_mode='global'. A comma-separated 'key=value' string (NOT a JSON object — some hosts cannot pass objects), e.g. "temperature_mode=relative,step_size_decades…
- `immittance_type` (string): Fit plane: 'impedance' (default) or 'admittance'.
- `include_schematic` (boolean): Also return the fitted circuit's SVG schematic (same as render_circuit svg). Default false.
- `initial` (string|null): Optional initial parameter values as a comma-separated 'Name=value' list, e.g. "Rs=10,Rct=100" (a string, not a JSON object — some hosts cannot pass object arguments). The rest are seeded automatical…
- `initial_mu` (number): Levenberg-Marquardt initial damping μ — raise it when the initial guess is poor. Default 100.
- `kind` (string): Overlay chart kind: 'nyquist' (default), 'bode', or 'admittance'.
- `lower_bounds` (string|null): Optional lower fit bounds as a comma-separated 'Name=value' list, e.g. "Rs=1,Rct=10".
- `seed_from` (string): Where unspecified parameters start from: 'estimate' (default, automatic estimation) or 'stored' (the dataset's current modeling state from guess_ecm / evaluate_ecm / a previous fit).
- `study_id` (string, required): The study id returned by open_study.
- `theme` (string|null): Optional theme hint: 'light', 'dark', or 'auto' (default).
- `upper_bounds` (string|null): Optional upper fit bounds as a comma-separated 'Name=value' list, e.g. "Rs=100,Rct=1000".
- `weight_source` (string): Weight source: 'experimentalData' (default) or 'fittedModelData'.
- `weighting` (string): Residual weighting: 'modulus' (default, 1/|Z|²), 'proportional', or 'unity'.

### `license_status` (~134 tokens)

Reports this MCP server's licensing state: 'Licensed' (a ZStudio license
with the AI/MCP entitlement is active), 'Trial' (no key — the time-limited
alpha trial is running), or 'Unlicensed' (trial expired or the key lacks
the AI entitlement; tool calls are refused). Includes the trial days
remaining when on trial and a human-readable message.

Always callable, even when tool execution is otherwise blocked — use it to
see why calls are refused and how to activate (set ZSTUDIO_LICENSE_KEY to a
license that includes the AI (MCP) add-on).

### `open_in_app` (~228 tokens)

Opens a .zstudy in the ZStudio desktop application (Windows) so the user can
view and work with it in the full GUI — interactive Nyquist/Bode plots, the
model workshop, etc. Launches via the .zstudy file association, so ZStudio
must be installed on this machine.

Give a study_id (from open_study/new_study/create_from_file) to open the
current session study — it is saved first (atomically, to its bound path or
the working directory) so the app opens exactly the current state, including
analysis results. Or give an explicit path to open an existing .zstudy file.

USE WHEN: the user wants to "open this in ZStudio", inspect a study visually,
or hand off to the desktop app. Returns the path opened.

Input parameters:

- `path` (string|null): Or an explicit .zstudy path to open instead of a session study.
- `study_id` (string|null): A session study id to save and open in the app (from open_study/new_study/create_from_file).

### `save_study` (~331 tokens)

Saves a loaded study to a .zstudy file (atomically), including any analysis
results stored on its datasets. Path resolution: an explicit path if given,
else the path the study was opened from / last saved to, else a new file in
the working directory (the ZSTUDIO_WORKDIR environment variable, or a
'ZStudio' folder under the user's Documents) named after the study. The
written path is returned.

Note: studies auto-save to their bound path after each mutating tool call,
so this is rarely needed for durability — use it mainly to "save as" to a
new path, or to force a write when auto-save is disabled
(ZSTUDIO_AUTOSAVE=off). Requires a study_id from open_study / new_study /
create_from_file.

If the bound file changed on disk outside this session (e.g. the user
saved it from the ZStudio app), the save is refused so those changes are
not silently overwritten — re-open the study to take the disk version, or
pass force=true (ideally after confirming with the user) to overwrite.

Input parameters:

- `force` (boolean): Overwrite the bound file even if it changed on disk outside this session (default false). Confirm with the user before forcing.
- `path` (string|null): Optional destination .zstudy path. If omitted, uses the study's bound path, or a new file in the working directory (ZSTUDIO_WORKDIR or ~/Documents/ZStudio).
- `study_id` (string, required): The study id returned by open_study.

### `list_tree` (~86 tokens)

Returns the folder/dataset hierarchy of a loaded study, with id-handles
for every node (fd_… folders, ds_… datasets) and dataset summaries.

USE WHEN: you need the current structure or have lost track of the
dataset_id handles. Requires a study_id from open_study.

Input parameters:

- `study_id` (string, required): The study id returned by open_study.

### `get_dataset` (~284 tokens)

Returns the token-light summary of one dataset: point count, frequency
range, Nyquist geometry (Rs, Rp, R_total, |Z| and phase ranges), shape
flags, and which analyses are already stored — never the raw spectrum.
Read the stored results themselves (fitted ECM parameters, Loewner poles,
K-K / DRT / Z-HIT) with get_analysis — no re-running required.

Set include_shape to also attach the geometric shape description (§4) — the
same −Z″ peaks / arc CPE-n / |Z| slope bands / LF-termination narration the
plot tool returns — so you can reason about the curve's form without plotting
it. (Structural counts like the number of RC processes still come from a DRT
run, not the shape.)

USE WHEN: you need a closer look at a specific dataset. Requires a
study_id (open_study) and dataset_id (open_study/list_tree).

Input parameters:

- `dataset_id` (string, required): The dataset id-handle (ds_…) from open_study or list_tree.
- `include_shape` (boolean): Also return the geometric shape description (§4: −Z″ peaks, arc CPE-n, |Z| slope bands, LF termination). Default false.
- `study_id` (string, required): The study id returned by open_study.

### `kk` (~823 tokens)

Runs a Kramers-Kronig (K-K) validation — the data-quality gate. K-K fits a
K-K-compliant model and reports whether the spectrum is internally
consistent (causal/linear/stable); large residuals flag drift, nonlinearity,
or instrument artifacts.

Runs on the dataset's currently selected data source (raw, or Z-HIT
corrected if you called set_analysis_source). Returns a token-light summary
plus an R/C-vs-τ chart — the resistance and capacitance of each fitted Voigt
RC element vs its time constant (dual axis). For the measured-vs-recon
residual *plot*, call plot(kind=residual, analysis=kk). The summary still
carries the residual description so you can read fit quality without a plot:
\- pass, pseudoChiSquared, timeConstantCount.
\- residuals.maxAbsPct / meanAbsPct + maxAtHz: residual size and where.
\- residuals.exceedBands: frequency ranges where |residual| exceeds ~1%.
\- residuals.signRunZ / systematic: a runs test on the residual sign sequence.
  \|z| > 2 ⇒ systematic (residuals cluster by sign — model misfit, drift, or
  non-stationarity), not random scatter.
\- residuals.verdict: 'ok' (small, random), 'noisy' (large but random), or
  'systematic' (structured deviation). A 'systematic' verdict means the
  data — not just the noise — violates K-K; consider Z-HIT or excluding
  artifact points before trusting downstream DRT/ECM fits.
The result is stored on the dataset and the study auto-saves.

test_type is one of the two single-component tests: 'real' fits the real
part and predicts the imaginary (real→imaginary); 'imaginary' does the
reverse. The engine's combined 'complex' test is intentionally not offered —
it is much slower. For a complex-style check, run kk twice (test_type='real'
then 'imaginary') and compare the two residual descriptions: if both pass
with small, random residuals the spectrum is K-K consistent; a component that
fails one direction localizes the inconsistency.

Other tuning (optional; defaults reproduce the app): impl…

Input parameters:

- `auto_optimize_rc_count` (boolean): Auto-select the number of RC (Voigt) elements. Default true.
- `data_representation` (string): Fit representation: 'auto' (default), 'impedance', or 'admittance'.
- `dataset_id` (string, required): The dataset id-handle (ds_…).
- `implementation` (string): Calculation method: 'leastSquares' (default), 'matrixInversion', 'nonLinearLeastSquares'.
- `include_capacitance` (boolean): Include a series capacitance in the K-K model. Default true.
- `include_inductance` (boolean): Include a series inductance in the K-K model. Default true.
- `rc_element_count` (integer): Manual RC element count (2–50), used only when auto_optimize_rc_count is false. Default 0 (unused).
- `study_id` (string, required): The study id returned by open_study.
- `test_type` (string): K-K test: 'real' (default, real→imaginary) or 'imaginary' (imaginary→real). For a complex-style check, run both and compare (see description).
- `theme` (string|null): Optional theme hint: 'light', 'dark', or 'auto' (default).

### `estimate_inductance` (~936 tokens)

DIAGNOSTIC / ALGORITHM-DEVELOPMENT tool (work in progress). Estimates the
series/stray impedance — series Rs + jωLs plus up to two parallel R|L
(Rp|Lp, Rx|Lx) — for the Loewner DRT de-embedding. Loewner requires the
IMPROPER part removed (Rs constant + jωLs unbounded; Z→0 as f→∞); a parallel
R|L is bounded (→ R as f→∞) and is KEPT as a native pole, not removed. So only
Rs+Ls are de-embedded for the analysis basis; the R|L are reported and drive
the inductive-free deliverable.

Unlike the other analysis tools this is intentionally verbose and
side-effect-free: it runs the preliminary Loewner pass and EXPOSES its
internals — the top |λ|-sorted eigenvalues (each with its residue
and γ=−Re(r/λ): γ>0 ⇒ capacitive R|C pole, γ<0 ⇒ inductive R|L stray pole),
\|λ1|/|λ2| and |λ3|/|λ4| gap
ratios, Taylor coefficients c0…c3, the detected case (ls_pair /
rplp_triplet / none), the Rp strategy (pole_id / c2_fallback), the SVD
singular-value spectrum (model order), the estimated Rs/Ls/Rp/Lp/Rx/Lx, and —
when it cannot estimate — WHY: rsSource (eigen / poles / imz_crossing /
re_at_fmax), loewnerFailed, and a terminal verdict (ok /
no_inductive_element / loewner_failed / rs_fallback), plus a step-by-step
notes trace. These come straight from the production estimator
(EISAnalyzer.EstimateSeriesAndStrayImpedance) — same numbers the DRT/Loewner
path uses — so the algorithm can be reviewed and refined together. It does
NOT modify the dataset. Returns a Nyquist overlay of the measured vs
inductance-removed spectrum. (Intended to feed K-K eventually; not yet wired.)

Also returns an 'overlap' block — the newer residue-sign classifier
(EISAnalyzer.DiagnoseHfStrayOverlap, diagnostic-only, does not change the
de-embedding): classification (None / FastRcResolved / FastRcOverlap /
NonIdentifiable), the removable stray Rs/Ls/Rp/Lp (and the second R|L Rx/Lx)
read from the eigenvalue Taylor coefficients (c0=Rs, c1=Ls/Lp, c2=−Lp²/Rp — so
Rp/Lp are recovered even when t…

Input parameters:

- `dataset_id` (string, required): The dataset id-handle (ds_…).
- `gap_threshold` (number): Rp|Lp triplet gap threshold |λ3|/|λ4| (default 3.0; lower = more sensitive).
- `study_id` (string, required): The study id returned by open_study.
- `theme` (string|null): Optional theme hint: 'light', 'dark', or 'auto' (default).

### `parse_circuit` (~508 tokens)

Validates a ZStudio equivalent-circuit string (CDC) and expands it into its
canonical form, impedance formula, element list, and fittable parameters.
Use it to check a circuit is valid and to see exactly which parameters
fit_ecm will fit — before calling fit_ecm.

CDC GRAMMAR (operator precedence, high → low):
  1\. transmission line: 'Z1 ... Z2' (W), 'Z1 .-. Z2' (T), 'Z1 .|. Z2' (O)
     — or the function form lad(Z1, Z2, OPT), OPT ∈ W O T K I S X B P.
  2\. parallel: '|'  '||'  '‖'        (function: par(Z1, Z2, …))
  3\. series:   '+'  '-'              (function: ser(Z1, Z2, …))
PARALLEL BINDS TIGHTER THAN SERIES: "R1-R2|C1" == "R1-(R2|C1)".
Brackets () [] {} are equivalent and override precedence. Short = 0,
open = 'oo' or '∞'. Wheatstone bridge: bridge(Z1,Z2,Z3,Z4,Z5).

ELEMENTS — name is "<Symbol><identifier>", Symbol a single uppercase letter
(R, C, L, Q, W, T, O, K, I, S, X, N, G, H, D). Parameters auto-expand per
element (e.g. Q1 → Qy1, Qa1). Common: R (Ω), C (F), L (H), Q=CPE (Qy, Qa),
W=Warburg, T/O=finite diffusion, G=Gerischer, H/D=empirical.
(s = jω = j·2π·f.) Examples: "Rs-Rct|Cdl", "Rs-(Rct-W)|Cdl" (Randles),
"Rs-(Rct-W1)|Q1", "lad(R,Q,W)".

For the full element catalog (every element's impedance + parameters,
transmission-line options, bridge), call circuit_reference. On an invalid
string, returns a clear parser error. Pure notation — no dataset needed.

Input parameters:

- `cdc` (string, required): Circuit description code, e.g. 'Rs-Rct|Cdl'.

### `close_study` (~88 tokens)

Removes a loaded study from the session, freeing its memory. Analysis
results are auto-saved to the study's .zstudy after each tool call, so
closing does not lose work (unless auto-save was disabled with
ZSTUDIO_AUTOSAVE=off, in which case save_study first).

Input parameters:

- `study_id` (string, required): The study id returned by open_study.

### `get_dataset_metadata` (~88 tokens)

Returns descriptive, non-numeric properties of a dataset: provenance and
acquisition metadata (source format, instrument, technique, DC/AC bias,
timestamps), point counts, and which analyses are stored. For the numeric
Nyquist geometry use get_dataset.

Input parameters:

- `dataset_id` (string, required): The dataset id (ds_…).
- `study_id` (string, required): The owning study id.

### `get_analysis` (~441 tokens)

Returns the analysis results ALREADY STORED on a dataset — WITHOUT re-running
anything. This is how you read back a prior fit_ecm / drt_loewner / kk /
drt_tikhonov / zhit result (e.g. after reopening a .zstudy, or to inspect a
dataset analyzed earlier in the session) instead of recomputing it. The analyses
flags from get_dataset / open_study tell you which results are present first.

Reports, for the dataset's currently selected data source, whichever results
exist (the rest are null):
\- ecm: the fitted circuit, goodness-of-fit (pseudo χ², R², adjusted R², RMSE),
  and every fitted parameter with its value, units, and uncertainty — the same
  parameter detail fit_ecm returns (minus run-time-only metadata such as
  iteration count, which is not persisted).
\- loewner: the series/stray impedance (Rs/Ls + the parallel R|L pairs Rp/Lp and
  Rx/Lx; Rs+Ls de-embedded, the R|L kept as native poles), the model order, and the
  significant relaxation poles (τ, f_peak, R; R<0 = RL) — the same pole list
  drt_loewner returns.
\- kk / drtTikhonov / zhit: the goodness-of-fit headline plus the options that were
  used and the analysis date (kk also reports validity and the time-constant count).

Every result carries the options it was produced with (read from the result's own
stored state, exactly like the app's Results-panel "copy"). Read-only: no chart, no
recomputation, and the study is not modified. For the
chart, full residual description, or to change options, call the matching analysis
tool (which recomputes). Requires a study_id and dataset_id.

Input parameters:

- `dataset_id` (string, required): The dataset id-handle (ds_…).
- `study_id` (string, required): The study id returned by open_study.

### `new_study` (~87 tokens)

Creates a new, empty study in the session (with a default Inbox folder)
and returns its study_id. Use import_data to add measurements. The study
auto-saves into the working directory on its first change; use save_study
only to choose a different path.

Input parameters:

- `author` (string|null): Optional study author.
- `name` (string|null): Optional study name.

### `export_inductive_free` (~411 tokens)

Exports the INDUCTIVE-FREE spectrum — the measured impedance with the inductive
stray removed (series jωLs and the parallel R|L elements Rp|Lp and Rx|Lx) but the
physical series resistance Rs KEPT. This is the HF-cleaned deliverable:
raw − jωLs − Z_RpLp − Z_RxLx, where Z_R|L(s) = s·R·L / (R + s·L). Note this is NOT the
Loewner DRT de-embedding, which additionally removes Rs (and keeps the R|L as native
poles) — here Rs stays.

Provenance: when a Loewner result is stored on the dataset, its already-materialized
inductive-free spectrum is served as-is (source='stored_loewner') using that run's
stray values — no recompute. Otherwise the stray (Ls, Rp, Lp) is estimated on demand
via the same production estimator (source='on_demand_estimate') and the spectrum is
computed from it; this is fast.

Returns the inductive-free spectrum points (freqHz, zReOhm, zImOhm), the stray values
used (rsOhm KEPT; lsHenry / rpOhm / lpHenry removed), the source, and a measured-vs-
inductive-free Nyquist overlay. Requires a study_id and dataset_id. Runs on the
selected data source. Side-effect-free.

Input parameters:

- `dataset_id` (string, required): The dataset id-handle (ds_…).
- `gap_threshold` (number): Rp|Lp detection gap threshold |λ3|/|λ4| for the on-demand estimation path (default 3.0).
- `study_id` (string, required): The study id returned by open_study.
- `theme` (string|null): Optional theme hint: 'light', 'dark', or 'auto' (default).

### `get_dc_curve` (~545 tokens)

Returns original rows of a DC series (from read_dc_file) at full resolution: every
point, in acquisition order, never decimated or interpolated. Large results are
paged -- request the next page with offset = nextOffset until nextOffset is null, and
all matching rows will have been returned.

\- Filters (optional, combined): time_min_s / time_max_s (instrument time axis,
  inclusive), half_cycle, step, loop.
\- columns: comma/space-separated channel/marker names from read_dc_file (default
  all). "row" (source row number) and "time [s]" are always the first two columns.
\- limit: rows per page (default 200 -- a page size, not a cap on the data; no fixed
  upper limit); offset: first matching row. nTotal is the number of matching rows.
\- include_interval_current: adds interval_current [mA] = charge increment / elapsed
  time, attributed to the END row of each interval (row i covers rows i-1..i; row 0
  of the file has none), plus interval_current_exclusion
  (0 ok, 1 non-positive duration, 2 crosses a mode/direction/step/half-cycle/
  cycle/loop switch, 3 missing data). It is a derived value, not a measured current.

Rows are arrays aligned with columns; null means the row has no value (e.g. a
voltage setpoint in a CC row). For a file to hand to a model, prefer export_dc_data.

Input parameters:

- `columns` (string|null): Optional comma/space-separated channel or marker names (default: all).
- `dc_id` (string, required): The dc_… handle from read_dc_file.
- `half_cycle` (integer|null): Optional half-cycle number to restrict to.
- `include_interval_current` (boolean): Add the derived interval-averaged current (mA) and its exclusion code. Default false.
- `limit` (integer): Rows per page (default 200). A page size, not a cap: follow nextOffset to get every row. No fixed upper limit.
- `loop` (integer|null): Optional loop number to restrict to.
- `offset` (integer): Index of the first matching row to return (default 0). Use nextOffset from the previous page.
- `step` (integer|null): Optional step number to restrict to.
- `time_max_s` (number|null): Optional upper time bound in seconds (inclusive).
- `time_min_s` (number|null): Optional lower time bound in seconds (instrument time axis, inclusive).

### `drt_tikhonov` (~1110 tokens)

Runs Tikhonov-regularized DRT — the continuous γ(τ) distribution. Resolves
the impedance into relaxation processes along the time-constant (τ) axis; the
peak count and positions guide equivalent-circuit choice (each peak ≈ one RC
time constant). This is the regularized method — λ trades smoothness against
fit. For the regularization-free, data-driven discrete-pole alternative use
drt_loewner.

The peak count is only a trustworthy time-constant count once series/stray
impedance is removed. If the dataset summary shows an inductive tail
(HasInductiveTail), enable de-embedding: include_series_inductance /
include_series_capacitance add Ls/Cs terms, and inductance_handling selects the
high-frequency inductive treatment as a single either/or choice — 'stray' de-embeds
the full inductive stray (series Ls + parallel Rp|Lp, and Rx|Lx when present; subtract
before the DRT analysis, add back — Ls is handled here, so include_series_inductance is
ignored), 'gdrt' runs Generalized DRT (an RL distribution for in-band distributed
inductance; Rs is pinned to the Löwner estimate because a free Rs is unidentifiable
in the joint RC+RL fit — series cable inductance still needs
include_series_inductance), and 'none' leaves it to the RC fit. For a precise
Rs/Ls/Rp/Lp/Rx/Lx diagnostic without running DRT, use estimate_inductance.

Returns a token-light summary — peaks (τ in s, f_peak in Hz, R in Ω), pseudo
χ², λ — a fit-residual description (measured vs DRT reconstruction:
maxAbs/meanPct, exceedBands, runs-test verdict), and a γ-vs-log-τ curve.

It also carries curveShape — a geometric reading of the γ(τ) plot itself
(the way the plot tool's `shape` reads a Bode/Nyquist plot). Narrate the
distribution from it, not from the peak count alone: `segments` walks the
curve high→low frequency labeling each region (baseline / rise / peak /
shoulder / fall / valley); a peak/shoulder segment carries its apex f, γ
height, fwhmDecades (broad vs sharp), areaOhm (≈ its R),…

Input parameters:

- `dataset_id` (string, required): The dataset id-handle (ds_…).
- `discretization_basis` (string): RBF basis: 'gaussian' (default), 'maternC0/C2/C4/C6', 'inverseQuadratic', 'inverseQuadric', 'cauchy'.
- `include_series_capacitance` (boolean): Include a series capacitance (Cs) term. Default false.
- `include_series_inductance` (boolean): Include a series inductance (Ls) term. Default false.
- `inductance_handling` (string): High-frequency inductance handling, a single mutually-exclusive choice: 'none' (default; RC-only fit), 'stray' (Löwner de-embedding of the inductive stray — series Ls + parallel Rp|Lp, and a second R…
- `lambda` (number): Regularization parameter λ (1e-6…1). Default 1e-3.
- `max_peaks` (integer): Maximum number of peaks to detect (1–10). Default 8.
- `model_criterion` (string): Peak model-selection criterion: 'bic' (default) or 'aic'.
- `regularization_method` (string): Penalty: 'firstOrderDerivative' (default) or 'secondOrderDerivative'.
- `shape_coeff` (number): RBF shape factor (0.1–2.0). Default 0.5.
- `shape_control` (string): Shape control: 'coefficientToFWHM' (default) or 'shapefactor'.
- `stray_gap_threshold` (number): Rp|Lp detection gap threshold |λ3|/|λ4| (default 3.0; lower = more sensitive). Only used when inductance_handling='stray'.
- `study_id` (string, required): The study id returned by open_study.
- `theme` (string|null): Optional theme hint: 'light', 'dark', or 'auto' (default).

### `move_item` (~117 tokens)

Moves a folder or dataset into another folder (use the study id as the
target for the top level). The root and the default Inbox folder cannot be
moved, and a folder cannot move into its own subtree. Auto-saves the study.

Input parameters:

- `item_id` (string, required): The folder (fd_…) or dataset (ds_…) id to move.
- `study_id` (string, required): The owning study id.
- `target_folder` (string, required): Destination folder id (fd_…), or the study id for the top level.

### `circuit_reference` (~86 tokens)

Returns the complete ZStudio equivalent-circuit notation reference as
Markdown: operator precedence, naming, every circuit element (impedance +
parameters), the lad() transmission-line options, and the Wheatstone
bridge. Call this once to learn the full CDC vocabulary before constructing
or fitting a circuit; use parse_circuit to validate a specific circuit and
get its exact fittable parameters.

### `drt_loewner` (~1151 tokens)

Runs Loewner-method DRT — the data-driven, regularization-free alternative to
drt_tikhonov. It extracts discrete RC/RL poles directly from the spectrum (no
λ to tune); each pole is one relaxation. Order selection (model_order_method /
singular_value_threshold / model_order) controls how many poles are kept.

The Loewner framework requires the IMPROPER part of the response removed — the
series terms Rs (constant) and jωLs (unbounded) — which otherwise create spurious
poles. The parallel stray Rp|Lp is PROPER (bounded: → Rp as f→∞), so it is NOT
removed: it stays in the data and is captured as a native negative-γ (RL) pole.
What is de-embedded is governed by stray_mode:
  • 'auto' (DEFAULT) — auto-detect the stray via a preliminary Loewner pass and
    de-embed the improper series terms (Rs + jωLs). A parallel Rp|Lp, when present,
    is detected and reported but LEFT in the data as a pole. On a clean strictly-
    proper spectrum nothing is detected, so it is safe to leave on.
  • 'manual' — use the exact Rs/Ls/Rp/Lp/Rx/Lx you pass via estimated_rs/ls/rp/lp/rx/lx:
    Rs+Ls are de-embedded; the parallel R|L pairs Rp|Lp and Rx|Lx are reported and drive
    the inductive-free deliverable.
  • 'none' — LEGACY / no de-embedding. Use only to reproduce the raw, un-de-embedded
    behavior; the pole count is then NOT a trustworthy time-constant count if any
    inductive tail or DC offset is present.
In 'auto' and 'manual' the improper series impedance (Rs + jωLs) is removed from BOTH
the measured and reconstructed spectra (never added back), so poles, χ², and residuals
share one basis; a parallel Rp|Lp stray appears as an RL pole. estimate_inductance gives
the same Rs/Ls/Rp/Lp diagnostic in detail, and export_inductive_free returns the spectrum
with the inductive stray removed but Rs kept.

Returns a token-light summary — poles (τ in s, f_peak in Hz, R in Ω; R<0 is an
RL pole) and the resolved model order k, plus de-embedding provenance the mode…

Input parameters:

- `dataset_id` (string, required): The dataset id-handle (ds_…).
- `estimated_lp` (number): Manual stray parallel inductance Lp (H) to de-embed when stray_mode='manual'. Default 0.
- `estimated_ls` (number): Manual series inductance Ls (H) to de-embed when stray_mode='manual'. Default 0.
- `estimated_lx` (number): Manual extra (second) stray parallel inductance Lx (H) to de-embed when stray_mode='manual'. Default 0.
- `estimated_rp` (number): Manual stray parallel resistance Rp (Ω) to de-embed when stray_mode='manual'. Default 0.
- `estimated_rs` (number): Manual series resistance Rs (Ω) to de-embed when stray_mode='manual'. Default 0.
- `estimated_rx` (number): Manual extra (second) stray parallel resistance Rx (Ω) to de-embed when stray_mode='manual'. Default 0.
- `model_order` (integer): Manual model order k (1–200); 0 = auto via model_order_method. Default 0.
- `model_order_method` (string): Order selection: 'matrixRank' (default), 'singularValueThreshold', or 'manual'.
- `singular_value_threshold` (number): σ[k]/σ[0] cutoff for the singularValueThreshold method. Default 1e-6.
- `stray_gap_threshold` (number): Rp|Lp detection gap threshold |λ3|/|λ4| (default 3.0; lower = more sensitive).
- `stray_mode` (string): Series/stray de-embedding mode: 'auto' (default — auto-detect and de-embed Rs/Ls/Rp|Lp/Rx|Lx), 'manual' (use estimated_rs/ls/rp/lp/rx/lx), or 'none' (LEGACY: no de-embedding; pole count untrustworthy…
- `study_id` (string, required): The study id returned by open_study.
- `theme` (string|null): Optional theme hint: 'light', 'dark', or 'auto' (default).

### `plot` (~948 tokens)

Renders one or more EIS datasets as a chart and returns it for inline
display on chart-capable hosts (Claude Desktop, Cowork, VS Code,
AssistStudio, …), plus a token-light text summary per dataset.

Pass a single id to plot one dataset, or several ids to overlay them as a
comparison (each dataset gets its own color and legend entry) — e.g. to
compare the datasets the user selected.

kind:
\- nyquist (default): −Z″ vs Z′, equal aspect — read semicircles, Rs/Rp.
\- bode: |Z| and phase vs log frequency.
\- admittance: Y″ vs Y′ where Y = 1/Z.
\- residual: relative residual (Δreal/Δimag %) vs log frequency — a
  dataset-level fit-quality view; requires `analysis` and exactly one
  dataset.

analysis: overlay a stored analysis reconstruction on the measured data
(the same comparison the app's Data-Explorer offers). One of 'none'
(default — measured only), 'zhit', 'kk', 'drt_tikhonov', 'drt_loewner',
'inductivefree' (the Loewner inductive-stray-removed spectrum vs the raw
measured), or 'ecm'. For nyquist/bode/admittance each dataset that has that
analysis gets its reconstruction drawn as a dashed line over its markers; run
the analysis first (it is read from the dataset, not recomputed). 'residual'
requires an analysis and renders (reconstructed − measured)/|Z|.

In a comparison mode the measured side is the SNAPSHOT the analysis captured
when it ran (for drt_loewner/inductivefree the de-embedded polarization / raw
basis its fit lives on), not the live spectrum — so masking (raw-only) does
not move it until you re-run the analysis. 'none' shows the live raw measured
with masked points marked.

The Plotly chart spec travels in structuredContent (zero model-token
cost); the host renders it. On a text-only host the chart is omitted and
only the summaries are returned — do not claim a chart was drawn in that
case (check _meta.render_status). Requires a study_id (open_study) and
dataset ids (open_study/list_tree). Charts reflect each dataset's se…

Input parameters:

- `analysis` (string): Overlay a stored analysis reconstruction: 'none' (default), 'zhit', 'kk', 'drt_tikhonov', 'drt_loewner', 'inductivefree' (Loewner inductive-stray-removed spectrum), or 'ecm'.
- `dataset_ids` (string, required): Dataset id-handle(s) (ds_…) as a comma- or space-separated list (e.g. "ds_1" or "ds_1, ds_2"). Multiple ids overlay as a comparison. A string, not a JSON array — some hosts cannot pass array argument…
- `kind` (string): Chart kind: 'nyquist' (default), 'bode', 'admittance', or 'residual'.
- `study_id` (string, required): The study id returned by open_study.
- `theme` (string|null): Optional theme hint: 'light', 'dark', or 'auto' (default).

## Diagnostics

Captured diagnostic sections: Provenance, Dependencies. The full working is on the page: https://verifymcp.io/servers/fieldcure-zstudio/fieldcure-mcp-zstudio#diagnostics

## Score history

- 2026-11-05: 65
- 2026-11-04: 64
- 2026-11-03: 64
- 2026-11-02: 63
- 2026-11-01: 63
- 2026-10-31: 62
- 2026-10-30: 62
- 2026-10-29: 61
- 2026-10-28: 61
- 2026-10-27: 61
- 2026-10-26: 60
- 2026-10-25: 60
- 2026-10-24: 59
- 2026-10-23: 59
- 2026-10-22: 58
- 2026-10-21: 55
- 2026-10-20: 55
- 2026-10-19: 55
- 2026-10-18: 55
- 2026-10-17: 55
- 2026-10-16: 55
- 2026-10-15: 55

## Common questions

### What is the io.github.fieldcure/zstudio MCP server?

io.github.fieldcure/zstudio is an MCP server listed in the public MCP registry as io.github.fieldcure/zstudio. ZStudio EIS MCP server: data validation, DRT, equivalent-circuit fitting, and plotting. This page covers its NuGet package (FieldCure.Mcp.ZStudio).

### Is the io.github.fieldcure/zstudio MCP server safe to use?

io.github.fieldcure/zstudio scores 65 out of 100 on VerifyMCP. We found no known CVEs affecting it as of 5 November 2026. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.

### What tools does the io.github.fieldcure/zstudio MCP server expose?

io.github.fieldcure/zstudio exposes 48 tools: set_analysis_source, get_study_info, zhit, create_from_file, render_circuit, and 43 more. Their descriptions and schemas cost roughly 17,302 tokens of context every time the server is loaded.

### Is the io.github.fieldcure/zstudio MCP server still maintained?

io.github.fieldcure/zstudio is still listed as active in the MCP registry. We last reached this channel on 5 November 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.

## Links

- NuGet package: https://www.nuget.org/packages/FieldCure.Mcp.ZStudio
- Socket report: https://socket.dev/nuget/package/FieldCure.Mcp.ZStudio
- Changelog RSS feed: https://verifymcp.io/servers/fieldcure-zstudio/fieldcure-mcp-zstudio.xml
- Changelog JSON feed: https://verifymcp.io/servers/fieldcure-zstudio/fieldcure-mcp-zstudio.json
- HTML version of this page: https://verifymcp.io/servers/fieldcure-zstudio/fieldcure-mcp-zstudio
