# Emerald (npm · emerald-mcp)

The database under the chat: a local SQLite file a model asks for rows, not notes to re-read.

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

## Components

- npm · `emerald-mcp`: 71/100 (this document), [markdown](https://verifymcp.io/servers/kfireliyahu-emerald/emerald-mcp.md), [page](https://verifymcp.io/servers/kfireliyahu-emerald/emerald-mcp)

## Channel facts

- Registry: `npm`
- Package: `emerald-mcp`
- Version: `0.3.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-04.

- **Supply Chain Security**: 100/100
  - No malware found by supply-chain analysis.
  - No known CVEs affecting this package version or its production dependencies.
  - No install/post-install scripts declared.
  - No production dependencies, so there is no dependency health to assess.
- **Provenance & Transparency**: 48/100
  - Source repository is publicly reachable at the declared URL.
  - Provenance check failed: no build-provenance attestation is published.
  - Clear OSI-approved license (AGPL-3.0-only).
  - Actively maintained (last published 0 days ago).
  - Publishes a security disclosure policy (SECURITY.md).
- **Schema Quality & AI Usability**: 35/100
  - 0% of prompts and resources have a non-trivial description (not blank, and not just the item's name).
  - AI-judged instruction clarity (good).
  - Context-footprint check failed: tool/resource definitions use about 26594 tokens (~309/item across 86 items; 85 tools + 1 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 53/100
  - Stability observed for 16 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**: 100/100
  - No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.
  - All 3 tool(s) whose name or description implies an irreversible operation declare an MCP destructiveHint annotation.
  - An AI judge read all 87 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.
  - Supports UI / widget rendering.

## Install

### How do I install the Emerald MCP server?

Emerald runs locally as an npm package, launched with npx -y emerald-mcp. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.

### Claude

```bash
claude mcp add kfireliyahu-emerald -- npx -y emerald-mcp
```

### Cursor

```json
{
  "mcpServers": {
    "kfireliyahu-emerald": {
      "command": "npx",
      "args": [
        "-y",
        "emerald-mcp"
      ]
    }
  }
}
```

### VS Code

```json
{
  "servers": {
    "kfireliyahu-emerald": {
      "command": "npx",
      "args": [
        "-y",
        "emerald-mcp"
      ]
    }
  }
}
```

### Codex

```bash
codex mcp add kfireliyahu-emerald -- npx -y emerald-mcp
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "kfireliyahu-emerald": {
      "type": "local",
      "command": [
        "npx",
        "-y",
        "emerald-mcp"
      ],
      "enabled": true
    }
  }
}
```

### OpenClaw

```bash
openclaw mcp add kfireliyahu-emerald --command npx --arg -y --arg emerald-mcp
```

### Hermes

```yaml
mcp_servers:
  kfireliyahu-emerald:
    command: "npx"
    args: ["-y", "emerald-mcp"]
```

### Netclaw

```json
{
  "McpServers": {
    "kfireliyahu-emerald": {
      "Transport": "stdio",
      "Command": "npx",
      "Arguments": [
        "-y",
        "emerald-mcp"
      ]
    }
  }
}
```

### Vellum

```bash
assistant mcp add kfireliyahu-emerald -t stdio -c npx -a -y emerald-mcp
```

### Other

```json
{
  "mcpServers": {
    "kfireliyahu-emerald": {
      "command": "npx",
      "args": [
        "-y",
        "emerald-mcp"
      ]
    }
  }
}
```

## 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-04 (score 71, +26)

- [security improvement] Malware scan: unverified → pass
- [security improvement] Known CVEs: unverified → pass
- [functional improvement] Dependency health: unverified → pass

### 2026-11-03 (score 45, −25)

- [security regression] Malware scan: pass → unverified
- [security regression] Known CVEs: pass → unverified
- [functional regression] Dependency health: pass → unverified
- [functional] Package version: 0.2.0 → 0.3.0

### 2026-10-31 (score 70, +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-29 (score 69, +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-27 (score 68, +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-25 (score 67, +1)

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

### 2026-10-24 (score 66, +26)

- [security improvement] Malware scan: unverified → pass
- [security improvement] Known CVEs: unverified → pass
- [functional improvement] Dependency health: unverified → pass

### 2026-10-23 (score 40, −25)

- [security regression] Malware scan: pass → unverified
- [security regression] Known CVEs: pass → unverified
- [functional regression] Dependency health: pass → unverified
- [functional] Package version: 0.1.6 → 0.2.0

## MCP tools (85)

### `formats` (~139 tokens)

Declared formats

What kinds of thing this document holds — every format declared in it, with its columns, their types, units, bounds and enum members, and the prose the author wrote around them. The columns are declared per document: `start_here` ends with one line per format, enough to write a row, and this is the whole declaration. Name a `format` for its full column list; omit it for the list of what is declared. Reads the file; never modifies it.

Input parameters:

- `format` (string): One declared format by name, for its full column list.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `declare_format` (~2237 tokens)

Declare a new format

Bring a **new** kind of thing into a document: a format, compiled to a real table with real types and checks. Call `formats` first. Each column has a `snake_case` `name`, a `type` — one of `text`, `integer`, `real`, `date`, `boolean`, `enum`, `money`, `link`, `quantity`, `decimal`, `month`, `instant`, `links`, `percent`, `time`, `enums`, `elsewhere`, `file` — and optionally a `unit` (`g`, `kcal`), `required`, `unique` (no two rows share it — a name a row is known by; text, integer, decimal, date, month or link), `values` for an enum or enums, `minimum`/`maximum`, a `scale` for a `decimal` (6 for a rate) and a `note` saying what it means. **A format declares its claims** — `claims`, equations over its numeric columns with a tolerance, like `4*protein_g + 4*carbs_g + 9*fat_g = kcal` within 2%; they make a row provable and never refuse one. **Ask the user whether any figures relate** before answering (a total and its parts, a price and a quantity); a list — books, contacts — makes none, needs no claims, and still gets every refusal. A figure worked out rather than typed is a `computed` column. **`money` is two columns**, minor units beside a currency, so no figure is a float. **`quantity` is two as well**, a figure beside the unit *that row* is in — only when the unit varies by row; otherwise a `real` with a `unit`. Neither takes a `unit`. A **ratio** has a `unit` and a `per` — `density`, `g` per `ml` — the only way a figure crosses dimensions: a claim writes `amount in g via ingredient.density`. No free-text check, no expression. **Across two columns of one row** is a `constraint`, one of four sentences: `isbn is required when kind is BOOK` (`required only when` adds *and empty otherwise*), `at most one of book, article, video`, `score is at most 10 when kind is QUIZ` (or `at least`), and `one row per course, week` (`when status is DONE` limits it) — a row breaking one is refused. `notes` is prose kept word for word: where the figures come from and what was decided.…

Input parameters:

- `aggregates` (array): What this format works out from the rows of another that point at it. Each has a `name` in `snake_case`, an `of` — one of sum, count, average, minimum, maximum, latest, earliest — an `over_format` an…
- `claims` (array): What is claimed about one of these, proved or failed on every row. Each has a `name` in `snake_case`, an `expression` — one `=` with arithmetic over this format’s own numeric columns on either side —…
- `columns` (array): The columns, in the order they should read. Up to 64. `text` — Words. A name, a note, a code — anything not measured. `integer` — A whole number, with no fractional part at all. `real` — A number tha…
- `commit` (boolean): Write it. Omit or false for the dry run.
- `computed` (array): Computed columns, worked out rather than typed, each `{ name, works_out, note }`: a claim’s expression with no `=` — `price * (1 + vat)`, `amount in g`, `link.column`, an aggregate’s name, another co…
- `constraints` (array): Rules across the columns of one row, each `{ rule, note }` — `rule` is one of `x is required when kind is A, B`, `x is required only when kind is A, B` (and empty otherwise), `at most one of a, b, c`…
- `format` (string): The name of the format, `snake_case` — `ingredient`. It names the table.
- `formats` (array): Several new formats, each `{ format, title, columns, … }`.
- `notes` (string): Prose about the format. Kept verbatim, never parsed. Optional but wanted.
- `path` (string, required): Absolute path to a .emd file on this machine.
- `rollups` (array): Figures per month over this format’s rows, each `{ name, works_out, note }`: `sum of minutes by month of done_on or started_on and project where status is DONE` — sum, count, average, minimum or maxi…
- `rule_sets` (array): Rows of this format as rules setting a link of another, each `{ name, sets, note }`: `note.topic from target when title matches pattern by kind (EXACT equals, PART contains) first by priority, record…
- `title` (string): What a person calls one of these — `Ingredient`.

### `connect_formats` (~590 tokens)

Connect two formats

Join two formats this document **already** declares: a `link` column on `format`, so each of its rows belongs to one row of `target` — each `recipe_line` belongs to one `recipe` — and, optionally, a total on `target` along it (an `aggregate`): `recipe.total_kcal`, the sum of `amount_g / 100 * ingredient.kcal` over the lines that name it. This is how a format gets a total of another declared after it, which `declare_format` cannot do. Name an existing link as `column` to add only the total. **It only adds**: a link may be empty, so rows already there lose nothing; `required: true` on a format that has rows is refused. A connection is removed with `extend_format` `remove` (the link column once its cells are emptied, or the total); re-pointing one is done at `#/connections` in the app. Returns what it would add and writes nothing until `commit: true`.

Input parameters:

- `aggregate` (object): The total `target` keeps of the rows that name it: a `name` in `snake_case`, an `of` — one of sum, count, average, minimum, maximum — a `works_out` expression over each of those rows in their own voc…
- `column` (string): The link column’s name, `snake_case`. Defaults to the target’s name, suffixed if taken. Name an existing link to it to add only the total.
- `commit` (boolean): Write it. Omit or false for the dry run.
- `format` (string, required): The format that gets the link — the many side, `recipe_line`.
- `found_by` (string): How the link fills itself while empty: `date in period` (this date’s month) or `date on paid_on` (the same day). No row or two is refused by name; a link a person set stays.
- `note` (string): What the link means. Kept with the declaration, word for word.
- `path` (string, required): Absolute path to a .emd file on this machine.
- `required` (boolean): Every row must name one. Refused on a format that already has rows.
- `target` (string, required): The format each row belongs to one of — `recipe`, the one side.

### `extend_format` (~1086 tokens)

Change a format

Change a format this document **already** declares, as its table does: add `columns`, `claims`, `computed` columns, `unique`, `constraints`, `rollups` and `aggregates` as `declare_format` takes them; `rename`, `change` or `remove` what it has; `order` the columns. A rename keeps its values. Refused, naming the rows: a `required` column on rows, removing a column holding values, a list the rows do not fit, a type or unit change over values. Writes nothing until `commit: true`.

Input parameters:

- `aggregates` (array): New totals, as `declare_format` takes them. A `count` needs no `works_out`.
- `change` (array): Columns it has, named, with what changes: `{ name, type: "enum", values }`.
- `claims` (array): New claims, each with a `name`, an `expression` with one `=`, an optional `tolerance` and a `note` — as `declare_format` takes them.
- `columns` (array): The new columns, after the ones it has. Up to 64. `text` — Words. A name, a note, a code — anything not measured. `integer` — A whole number, with no fractional part at all. `real` — A number that ca…
- `commit` (boolean): Write it. Omit or false for the dry run.
- `computed` (array): New computed columns, as `declare_format` takes them.
- `constraints` (array): New row constraints, each `{ rule, note }` as `declare_format` takes them — refused, naming the rows, while any row already breaks one.
- `format` (string, required): The format to change, by name.
- `order` (array): Every column, new ones too, once each, in order.
- `path` (string, required): Absolute path to a .emd file on this machine.
- `remove` (array): Names of columns, claims, totals, computed columns, rollups or rule sets.
- `rename` (object): `{ old: new }`.
- `rollups` (array): New rollups, as `declare_format` takes them.
- `rule_sets` (array): New rule sets.
- `unique` (array): Columns it already has that should become unique — refused, naming the repeated values, while two rows share one.

### `delete_format` (~131 tokens)

Remove a format and every row in it

Remove a declared format: its declaration, its table and **every row in it**. Refused while another format links to it or folds its rows. The dry run says how many rows go; show that to the user, and call again with `commit: true` only on a plain yes. To remove some rows, use `delete_format_rows` instead.

Input parameters:

- `commit` (boolean): Remove it. Omit or false for the dry run.
- `format` (string, required): The format to remove, by name.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `format_rows` (~592 tokens)

Rows of a declared format

The rows this document holds of one declared format. Ask `formats` first for the column names — they belong to the document. `match` filters on exact values (text and enum members case-insensitively; a list column, the rows holding the one named), `search` a substring in every column as shown (a price as 12.90, a link by its row’s name) but yes/no, and `order` sorts by any column, computed too — by default the one a row is named by, which is its first text column. Narrow `columns` to get a table of just those — a computed column (a total, or one worked out from the row) by its name too; leave it out and each row comes back in full with its empty columns left out, **and with what its claims say about it** — proved, failed, or unchecked when the row has nothing in a column one of them names. Every row carries the id the other tools use to correct or remove it. Reads the file; never modifies it.

Input parameters:

- `columns` (array): Column names to show, as a table — stored or computed. Omit for every column.
- `descending` (boolean): Sort the other way. Defaults to false.
- `format` (string, required): The declared format, by name. `formats` lists them.
- `limit` (number): Rows to return. Defaults to 25, at most 200.
- `match` (object): Column name to the value it must equal. Text and enum columns match without regard to case; money or a quantity, a range: `10..50 ILS`. A list is any of them (`[planned, in_progress]`; a list column…
- `offset` (number): Rows to skip first — the `offset` a cut-short answer hands back for the rest.
- `order` (string): A declared column to sort by. Defaults to the column a row is named by — its first text column — which is what the screen sorts by too. Ordering by the generated id shuffles, since an id is generated…
- `path` (string, required): Absolute path to a .emd file on this machine.
- `search` (string): A substring, in every column as shown but yes/no.
- `values` (string): A column: each value it holds and how many rows, under the other filters.
- `view` (string): A saved view’s name: its match, search, order and columns, under any given here.

### `show_format` (~443 tokens)

A declared format’s rows, drawn as the app draws them

Shows the person one declared format as the app draws it — its table, its list (one line per row, a tick, grouped as `arrange_views view:list` says), or its board, roadmap or ready-to-start list when the format has one; or the agenda, every dated format’s rows from today; or a dashboard (no `format`: the document’s) — in the conversation, in a client that draws MCP Apps; the answer’s first line says whether this client said it draws them. Call it when they ask to see a format, or some of its rows: `match` and `search` filter exactly as `format_rows` does, and the view draws only those rows and says it is filtered. The text answer names every row the view shows, with its state or lane. The page is a read-only copy, drawn again as the file changes. Reads the file; never modifies it.

Input parameters:

- `columns` (object): Columns its lines draw, by format: `{unit:[title,state]}`. A roadmap’s `match` takes `unit.state`.
- `format` (string): The declared format, by name. `formats` lists them. The agenda takes none.
- `match` (object): Column name to the value it must equal. Text and enum columns match without regard to case; money or a quantity, a range: `10..50 ILS`. A list is any of them (`[planned, in_progress]`; a list column…
- `path` (string, required): Absolute path to a .emd file on this machine.
- `search` (string): A substring, in every column as shown but yes/no.
- `view` (string): table, board, list, roadmap, ready, agenda (default table), dashboard, or a saved view’s name.

### `format_problems` (~96 tokens)

What looks wrong in declared formats

What the engine can see is wrong in a format’s rows, without a rule anybody wrote: a link empty where most rows fill it, rows the same in every column, claims failed or unchecked. `read_onto_format` says it after every read. Reads only.

Input parameters:

- `formats` (array): By name. Omit for every one.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `search_document` (~175 tokens)

Search the whole document

Every row of every format, and the rest of the file, that holds any of `terms`: each hit by format, row and column, columns searched as shown. One call where `format_rows`’ `search` is one a format. Or `denylist`, a `--replace-text` file: the scan before a document is shared — hits only, a term named by its line, and the file’s bytes checked for a deleted value. Reads only.

Input parameters:

- `denylist` (string): Instead of `terms`: a denylist’s absolute path, `literal==>x` or `regex:pattern==>x` a line.
- `path` (string, required): Absolute path to a .emd file on this machine.
- `terms` (array): Words or phrases, any case, up to 50.

### `start_here` (~116 tokens)

What to start from

Call first. Answers the questions this document’s own views say a session starts from — what is ready to start, what is open, the latest entries — each with the `format_rows` call that lists it whole; then what a row of each format looks like, enough to write one; and where new work begins: `formats`, then `read_onto_format` (files or a folder of notes) or `declare_format`. Read-only.

Input parameters:

- `path` (string, required): Absolute path to a .emd file on this machine.

### `format_rollup` (~141 tokens)

A rollup of a declared format

A rollup a format declares, run: a figure per month, and per linked row. `formats` lists them. A month with a row that cannot answer is `unchecked`, naming it; money stays per currency. Reads only.

Input parameters:

- `format` (string, required): The declared format, by name.
- `from` (string): First month, `YYYY-MM`.
- `limit` (number): Groups. Default 25.
- `path` (string, required): Absolute path to a .emd file on this machine.
- `rollup` (string): By name; optional when there is one.
- `to` (string): Last month, `YYYY-MM`.

### `apply_rules` (~186 tokens)

Apply a rule set

Run a rule set over every row it sets — a write applies it by itself. A person’s value is never touched; a tie is named. With `link`, fill that link’s empty cells by its `found_by` instead; a set link is never moved, and a row with no match or two is named. Dry run until `commit: true`.

Input parameters:

- `commit` (boolean): Write it. Omit or false for the dry run.
- `format` (string, required): The format whose rows are the rules — or, with `link`, the one the link is on.
- `link` (string): A link column found by a date (`found_by`): fill the rows where it is empty.
- `path` (string, required): Absolute path to a .emd file on this machine.
- `rule_set` (string): By name; optional when there is one.

### `record_format_rows` (~348 tokens)

Record rows of a declared format

Write rows into a declared format — **a list of them in one call**. The columns belong to the document — `start_here` lists them, `formats` in full — and a key the format has not got refuses the batch rather than being dropped, because a figure you believe you stored and did not is worse than one you know is missing. A `money` column takes the amount in major units beside its own `<column>_currency` key, and a `quantity` column takes the figure beside a `<column>_unit` key naming a unit this document knows — `amount: 200` with `amount_unit: ml`. A `link` takes the row’s id, its name (first text column) when one row has it — `recipe: "Pancakes"` — or columns that find one: `{name: "Salt", brand: "Fine"}`, or one unique column alone: `course: {notion_id: "a1b2"}`; a row may name another of the same batch. Types, bounds and enum members are checked before anything is written and the whole batch applies or none of it does. Returns the diff and writes nothing until `commit: true`.

Input parameters:

- `commit` (boolean): Write it. Omit or false for the dry run.
- `format` (string, required): The declared format, by name.
- `path` (string, required): Absolute path to a .emd file on this machine.
- `rows` (array, required): One object per row, keyed by declared column name. Up to 200 in a call.
- `view` (string): An input view: its defaults fill left-out fields.

### `update_format_rows` (~285 tokens)

Correct rows of a declared format

Change columns of rows already stored — **a list of edits in one call**. Each names its row by `id` or by `where` (`{ref: "INV-0042"}`, which survives another writer; a text document renews ids on every write), and the `values` to change. `expect` (`{state: "planned"}`) refuses the batch unless the row still holds that, so two sessions cannot both take one row. Every column not named is left exactly as it was, which is the difference between this and the app: `#/formats/<name>` shows a person every column and writes every column, and this corrects three of thirty across a batch. A required column cannot be emptied. A `link` value may be the name of the row it points at, or a unique column of it (`course: {notion_id: "a1b2"}`), as in `record_format_rows`. Returns the before-and-after and writes nothing until `commit: true`.

Input parameters:

- `commit` (boolean): Write it. Omit or false for the dry run.
- `edits` (array, required): One entry per row. Up to 200 in a call.
- `format` (string, required): The declared format, by name.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `delete_format_rows` (~185 tokens)

Remove rows of a declared format

Remove rows from a declared format by id — **a list of them in one call**. **Refused while any other row still links to one of them** — the dry run names those rows; re-link or clear them first. The app removes one row at a time from `#/formats/<name>`; this is how thirty go at once, after printing every one of them in full. Writes nothing until `commit: true`; a copy of the document is taken first, and it is the only way back.

Input parameters:

- `commit` (boolean): Remove them. Omit or false for the dry run.
- `format` (string, required): The declared format, by name.
- `ids` (array, required): Row ids, as `format_rows` printed them. Up to 200.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `claim_units` (~247 tokens)

Claim units

In a document that keeps the `development` pack, take units by `ref`: `planned` to `in_progress` with `taken_by`, the git `branch` they are taken on and `started` (default today), the batch refused unless every one is still planned. With `take_over`, take units already `in_progress` from that taker — a session that stopped mid-unit — refused unless each is held by exactly that taker; `started` is reset and the taker replaced is kept on `taken_over_from`. Writes nothing until `commit: true`.

Input parameters:

- `branch` (string): The git branch they are taken on.
- `commit` (boolean): Write it. Omit or false for the dry run.
- `path` (string, required): Absolute path to a .emd file on this machine.
- `started` (string): `YYYY-MM-DD`; today if left out.
- `take_over` (string): The `taken_by` whose claim this replaces, for units already in progress.
- `taken_by` (string, required): Who takes them: the host’s name for its session.
- `units` (array, required): Refs, up to 50.

### `land_units` (~333 tokens)

Land units

In a document that keeps the `development` pack: a unit’s closing pass in one call, for a list of units by `ref`: their new `decisions` (date, milestone, scope and `state: live` default to the unit’s), the unit to `built` with `landed` (default today), `files` and `decision` (default its first new one), its `review_entries` (`state: open` default), `close_milestone`, its git `commit` (refused without one unless `same_commit: true`) and `branch` (another than the claim’s refused unless `other_branch`), and the `report` of the session that built it. Or `inbox`: ids of a child’s proposals — an inbox row, `state: new`, `refs` its unit, its `body` one ```json block of one unit’s landing (no `close_milestone`), written to the document path the hooks printed — each landed as written and moved to `applied`. Each step is `record_format_rows` or `update_format_rows`, judged as they are, run in that order; all of it applies or none. Diff by format; writes nothing until `commit: true`.

Input parameters:

- `commit` (boolean): Write it. Omit or false for the dry run.
- `inbox` (array): Inbox row ids of landing proposals, or `{id, other_branch}`.
- `path` (string, required): Absolute path to a .emd file on this machine.
- `units` (array): Up to 50.

### `dispatch_plan` (~137 tokens)

Dispatch plan

In a document that keeps the `development` pack, before handing units out: for unit refs (every ready unit if left out), which can run at once and which share a path in their `paths` — one per line — and so go one after another, beside any unit in progress; a unit with no paths is unknown, never safe. Then each one’s brief, the units it waits on and the decisions it names. Read-only.

Input parameters:

- `path` (string, required): Absolute path to a .emd file on this machine.
- `units` (array): Refs, up to 50. Omit for every ready unit.

### `record_units` (~291 tokens)

Add or correct a unit of measure, or set the currency

Teach this document a unit it does not know — `slice`, `IU`, a tablespoon that is 20 ml in this kitchen. A unit is what a declared column says its figures are in and what a claim's `in` converts into, so a conversion the document cannot make is a conversion this fixes. **The engine converts inside a dimension and never across one**: grams to kilograms is arithmetic, millilitres to grams needs a density, and a density is a column of the format rather than a unit. A symbol the document already has is corrected — its factor and its note, never what it measures. `currency` sets the document’s currency — what a new amount on a screen starts in; a row still names its own. `week_starts_on` is what `this week` counts from (Monday). `formats` lists them. Returns the diff and writes nothing until `commit: true`.

Input parameters:

- `commit` (boolean): Write it. Omit or false for the dry run.
- `currency` (string): The document’s currency, a three-letter code: `ILS`.
- `path` (string, required): Absolute path to a .emd file on this machine.
- `units` (array): One object per unit. Up to 64 in a call; none for a setting alone.
- `week_starts_on` (string): A day by name: `sunday`.

### `read_onto_format` (~1100 tokens)

Read a file onto a declared format

Read what the user already keeps onto a declared format: any .csv, .xlsx (or .xls) or .json of rows, a file row per table row; or **a folder of Markdown notes**, a note per row: `title`, `file`, each frontmatter key, `inline tags`, `body links` and `body`, with a link read from `[[name]]` or `[text](file.md)`; a body link naming no row is left out and named, the note still read; a hidden file is not read; links between notes of one folder are filled; `items` reads list items or table rows onto another format, each linked to its note. **Dry-runs by default**: the reader with each key’s header cell and first value, the rows as stored, the rows that would not type and why, and **what the format claims, checked before a row exists**. Writes on `commit: true`. Keys come from `formats`. **The user maps the columns; this only guesses** — show the reader, correct it with `columns`. `fixed` gives a key one value for the file (a currency, a unit, the row every line belongs to). A column fills a `link` only by the exact unique name of its row, and a line naming none is refused; a link is guessed only from a header that names it. `header` writes the file’s total, period or card as one row of another format that every line links to. **A source read again updates its rows** by note file name, and any file by a unique column (so a later export updates what an earlier one wrote): changed, added and gone named; gone kept unless `remove_gone`. `supersede_reading` takes a reading back. **Several files in one call**: `files`, each `{ file, format }` with its own columns — read in the order their links need, so a line names a row from a file above it; one dry run for the lot, written whole or not at all.

Input parameters:

- `columns` (object): Column number → key of the format, from what the user confirmed, or → null to read nothing from that column. Applied over the reader the dry run showed, so send only what changes; a column not named…
- `commit` (boolean): Write it. Omit or false for the dry run.
- `date_order` (string): How a written date is ordered: one of dmy, mdy, ymd. Inferred from the file when omitted.
- `file` (string): Absolute path to the file to read: one of .xlsx, .xls, .csv, a .json file of rows (an array of objects, or an object holding one), a Markdown note (.md), or **a folder of notes** — every .md file in…
- `files` (array): Instead of `format` and `file`: each `{ file, format }`, plus that file’s own columns.
- `fixed` (object): Key → one value for every row of this file. A currency for a `money` column, a unit for a `quantity` one, or the row a `link` points at, by id or unique name.
- `format` (string): The declared format to read onto, by name. Not with `files`.
- `header` (object): `{ format, link, cells, fixed }`: the one row of another format this file writes and every line links to through `link`. `cells` is key → [row, column], **a position, never a value**, from 0; a negat…
- `header_row` (number): Force the header row, from 0, when the guess is wrong — exports often have a preamble.
- `items` (string): "list" or "table": a folder’s list items or table rows as rows of `format`, each linked to its note’s row (read the notes in the same `files`, or before).
- `path` (string, required): Absolute path to a .emd file on this machine.
- `recursive` (boolean): A folder of notes: its subfolders’ notes too (not a hidden one), with a `folder` column, the path under it.
- `remove_gone` (boolean): Read again: remove rows whose line is gone (else named, kept). Per file in `files`.
- `separators` (object): Key → what splits one cell into several members, for a column holding several of a list (`enums`): `{ "tags": "," }` reads `vegan, quick` as two. Never guessed; without one a cell is one member, and…
- `sheet` (string): Which sheet, or key of a .json object; defaults to the first.
- `under` (string): With `items`: only those under this heading.

### `supersede_reading` (~203 tokens)

Take back what one file wrote

Take back the rows one file wrote onto a declared format — **exactly those rows and no others**. Called with no `reading`, it lists every file read onto the format with when, and how many rows each still has. **Dry-runs by default** and writes nothing until `commit: true`. **The reader is kept** (forgetting one is `readers`), so supersede → correct the file → read it again is one call each. This is how a file read twice stops being two copies; a file is matched by its bytes, never row by row.

Input parameters:

- `commit` (boolean): Do it. Omit or false for the dry run.
- `format` (string, required): The declared format, by name.
- `path` (string, required): Absolute path to a .emd file on this machine.
- `reading` (string): The reading to take back, by id. Omit to list what this format has read.

### `readers` (~124 tokens)

A format’s readers

List a declared format’s readers — one per kind of file it has read — or name and forget them. Dry-runs until `commit: true`. Forgetting takes back no row.

Input parameters:

- `commit` (boolean): Do it. Omit or false for the dry run.
- `forget` (array): Reader ids.
- `format` (string, required): The format, by name.
- `path` (string, required): Absolute path to a .emd file on this machine.
- `rename` (array): `{ reader, name }`, by id; an empty name restores the file’s.

### `keep_copy` (~124 tokens)

Back up a document now

Backs the document up now: a copy as it is, in Emerald’s folder, named for what it comes before (`work.before-20261006T120512-cleanup.emd`), and says where. A read or batch of 100+ rows keeps one by itself; ask before anything else worth undoing. The last 3 are kept, apart from the rolling copies.

Input parameters:

- `before` (string): What it comes before, in a few words: `the cleanup`.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `store_document_as` (~122 tokens)

Store a document as text or as SQLite bytes

Writes the document again as `text` (diffs in git) or as `sqlite` bytes, and every later write keeps that shape — the app’s Save as text. Refused when the text would not carry it — a document keeping the ledger, for one. Writes nothing until `commit: true`.

Input parameters:

- `as` (string, required): How the file is stored from now on.
- `commit` (boolean): Write it. Omit or false for the dry run.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `arrange_views` (~254 tokens)

Arrange a format’s views

How the app shows a format: on its entry, a column’s `show_as`, `header` line, `hidden`, `new_entry` and the `order`; a board’s lanes; a table column’s `width`; a column’s `setting` in a list, a roadmap, a ready list or a start-here question; `view: saved`, a `name` and what it keeps (`today` stays that phrase) — on one saved, only what is given changes; `match:{}`, `search:""`, `order:""`, `columns:{}` empty one — or `rename`, or `delete: true`; `kind: input` takes `fields`, `defaults`, `default`; `view: dashboard` `add`s, `size`s, `order`s and `remove`s blocks (no `format`: the document’s). `formats` with a `format` shows the arrangement and each column’s ways. Dry-runs until `commit: true`.

Input parameters:

- `commit` (boolean): Write it; omit for the dry run.
- `path` (string, required): Absolute path to a .emd file on this machine.
- `settings` (array, required): Up to 200.

### `create_document` (~187 tokens)

Create an Emerald document

Create a new .emd document at a path that does not exist yet; never overwrites. It keeps the `packs` named and no other, and **none when omitted**: a document starts empty and holds what the person declares. Name a pack only when the work is what it is for — `development` for a project’s memory, `ledger` for their finances. With no path the user may be shown a form; otherwise ask them.

Input parameters:

- `packs` (array): The packs the document starts with, by id; `[]` for none. One of: `development` (a project’s memory: milestones, units, decisions, open questions, an inbox), `ledger` (personal finance, from statemen…
- `path` (string): Full path for the new document, ending in .emd. Omit to ask the user.

### `keep_packs` (~223 tokens)

Keep packs in a document

Keep packs in an existing document (the Packs screen’s *Keep … here*): declares the pack’s formats it lacks, adds missing optional columns to those it holds, renames a column the pack renamed with its values, changes no row; a pack kept already is brought up to date this way, its formats’ and columns’ notes and views’ settings too wherever the document still holds an earlier build’s — one a person changed is kept, and the dry run names it. Refused, naming the column, where a format of the same name is not the pack’s. Dry run until `commit: true`.

Input parameters:

- `commit` (boolean): Keep them. Omit or false for the dry run.
- `packs` (array, required): The packs to keep, by id. One of: `development` (a project’s memory: milestones, units, decisions, open questions, an inbox), `ledger` (personal finance, from statements, paychecks and fund reports).
- `path` (string, required): Absolute path to a .emd file on this machine.

### `document_info` (~115 tokens)

Emerald document info

Describe an Emerald document (a .emd file): whether this plugin will serve it, the formats it declares with how many rows each holds, and — when it keeps the ledger — its accounts, transactions, categories and reporting currency. Call it when a tool refuses a document — every tool refuses one whose schema does not match, and this is the one that says so in a sentence the user can act on. Reads the file; never modifies it.

Input parameters:

- `path` (string, required): Absolute path to a .emd file on this machine.

### `category_tree` (~94 tokens)

Category tree

The document category taxonomy: every category full path (Living → Utilities → Electricity), its id, its kind and its Plan bucket. Read this before suggesting a category for anything — the ids here are what the write tools take, and two categories may share a short name.

Input parameters:

- `include_archived` (boolean): Include archived categories. Default false.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `uncategorized_merchants` (~242 tokens)

Uncategorized merchants

The uncategorized backlog, grouped by merchant: how many rows each merchant has, and what the user has already categorized other rows of the same merchant under. Ordered by the largest single-currency outflow, which is not printed. Start a "help me categorize" conversation here.

Input parameters:

- `account_id` (string): Restrict to one account. Ids come from `accounts` in `document_info`.
- `from` (string): Inclusive start of the charge-date range, as YYYY-MM-DD.
- `include_amounts` (boolean): Print what each merchant adds up to. Off by default: the backlog is already ordered by it, and naming a category needs the merchant rather than the sum. Ask for it only when the user wants the spendi…
- `limit` (number): Merchants to return. Default 50, maximum 500.
- `path` (string, required): Absolute path to a .emd file on this machine.
- `search` (string): Matched against the merchant as written, the normalized merchant, and the provider label.
- `to` (string): Inclusive end of the charge-date range, as YYYY-MM-DD.

### `transactions` (~372 tokens)

Transactions

A filtered page of the ledger, plus the totals over every matching row — not just the page. Filter by date range, account, category, uncategorized-only, merchant search and amount bounds; sort by date, charge date, merchant, category or amount. Use this to answer "what did I spend at X" and to show the evidence behind any figure.

Input parameters:

- `account_id` (string): Restrict to one account. Ids come from `accounts` in `document_info`.
- `category_id` (string): Restrict to one category. Ids come from `category_tree`.
- `direction` (string): ASC or DESC. Default DESC.
- `from` (string): Inclusive start of the charge-date range, as YYYY-MM-DD.
- `include_subcategories` (boolean): Read `category_id` as a parent and include its children. Default false, matching the app own picker, where choosing Utilities means Utilities.
- `limit` (number): Rows to return. Default 50, maximum 500.
- `max` (number): Largest amount, by magnitude, in major units of the document currency.
- `min` (number): Smallest amount, by magnitude, in major units of the document currency. 200 matches a 200 refund as well as a 200 charge.
- `path` (string, required): Absolute path to a .emd file on this machine.
- `search` (string): Matched against the merchant as written, the normalized merchant, and the provider label.
- `sort` (string): One of DATE, CHARGED, MERCHANT, CATEGORY, AMOUNT. Default DATE.
- `to` (string): Inclusive end of the charge-date range, as YYYY-MM-DD.
- `uncategorized` (boolean): Only rows with no category. Overrides `category_id`.

### `month_summary` (~123 tokens)

Month summary

One month already added up: income, expenses, balance, savings rate; how each compares with last month, the three-month average and the same month last year; the category breakdown rolled up to parents; the fixed-versus-variable split; and where the income came from. Defaults to the current month. Every figure comes from the same call the app own Insights screen makes, so they cannot disagree.

Input parameters:

- `month` (string): The month as YYYY-MM. Defaults to the current month.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `year_summary` (~110 tokens)

Year summary

One year already added up: income, expenses, balance and savings rate; averages over the complete months only; the best and worst month by balance; a month-by-month table where a month with no data reads as nothing rather than as zero; and what the paychecks say about gross, tax and payroll savings. Defaults to the current year.

Input parameters:

- `path` (string, required): Absolute path to a .emd file on this machine.
- `year` (number): Four-digit year. Defaults to the current year.

### `fixed_expenses` (~94 tokens)

Bills

The bills the document tracks: what each is expected to cost, its amortized monthly equivalent, its cadence, whether the amount is fixed or variable, what it is matched against in the ledger, and whether the user marked it cuttable. Active ones by default.

Input parameters:

- `include_inactive` (boolean): Include ended bills. Default false.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `debts` (~88 tokens)

Debts

Every debt with its principal, what has been paid, what is outstanding, how far along it is and when it projects to be paid off — plus the totals and what card installments still commit the user to. Open debts by default.

Input parameters:

- `include_closed` (boolean): Include closed debts in the table. Default false.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `savings` (~111 tokens)

Savings

Goals and where the money is: net worth — the one figure Overview shows, with its parts (in the bank, in accounts with holdings, locked, pension and hishtalmut funds, owed) — each goal balance broken into the four ways money got there, and the monthly expense figure the emergency-fund target is measured against. Open goals by default.

Input parameters:

- `include_closed` (boolean): Include closed goals. Default false.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `goal_projection` (~90 tokens)

Goal projection

What each open goal will hold in N months, by plain addition of the amount Plan puts toward it each month (no interest counted), and what a month would have to be to reach it on its date. The same view as the Goals screen.

Input parameters:

- `months` (number): How many months ahead. Default 12.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `debt_projection` (~91 tokens)

Debt projection

What each open debt will have left in N months, by plain addition of its monthly payment and the extra Plan puts toward it (no interest counted), and when it is paid off with and without the extra. The same view as the Debts screen.

Input parameters:

- `months` (number): How many months ahead. Default 12.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `fixed_expense_candidates` (~210 tokens)

Recurring charges the ledger suggests

What looks like a recurring bill in the imported transactions but has not been confirmed as one: the merchant, the typical charge and its amortized monthly share, the cadence, how many months it was seen in, how much the amount and the day move, and how confident the detector is. Merchants already tracked or previously dismissed are excluded. This is the question `fixed_expenses` answers the other half of — that one lists what is already committed. Reads the document; changes nothing.

Input parameters:

- `ending_at` (string): The newest month of the window, as YYYY-MM. Default this month.
- `include_dismissed` (boolean): Also list the merchants that were said no to.
- `path` (string, required): Absolute path to a .emd file on this machine.
- `window_months` (number): How many months of history to look at, 2–24. Default 3. A window cannot see a cadence longer than itself: use 12 to find yearly charges.

### `track_fixed_expenses` (~248 tokens)

Track recurring charges as bills

Turn recurring charges into tracked bills — **several in one call**. An entry either names a `merchant` from fixed_expense_candidates, in which case the charge, cadence and category come from the detector unless overridden, or gives a `name` and an `expected_amount` for a bill the ledger cannot see, like an annual premium paid by standing order elsewhere. **Dry-runs by default**: it returns what it would track and writes nothing until called again with commit true. Confirming the same merchant twice updates the row already covering it rather than adding a second. The document is copied to a timestamped .bak before anything is written.

Input parameters:

- `commit` (boolean): Default false. False previews the batch; true writes it.
- `ending_at` (string): The newest month of that window, as YYYY-MM. Default this month.
- `expenses` (array, required): One entry per bill. Up to 100 in a call.
- `path` (string, required): Absolute path to a .emd file on this machine.
- `window_months` (number): The lookback used to re-find the candidates, matching the one you listed them with. Default 3.

### `dismiss_fixed_expense_candidates` (~129 tokens)

Say no to suggested recurring charges

Record that these merchants are not recurring bills, so the detector stops offering them — to this conversation and to every later one. A dismissal lasts until it is cleared; pass restore to clear it and let the merchant be suggested again. Writes immediately: it changes no transaction, no category and no bill.

Input parameters:

- `merchants` (array, required): Normalized merchant names, exactly as `fixed_expense_candidates` printed them.
- `path` (string, required): Absolute path to a .emd file on this machine.
- `restore` (boolean): Default false. True clears these dismissals instead of making them.

### `plans` (~135 tokens)

Plans: bill choices and buckets

The Plan screen as rows: each plan with what every bill does to it (out of net, part of the split, or ignored) and its buckets — what each is for (a goal, a debt, a category, a fund, an account or nothing) and how it is sized. Ids are here for `set_plan`, `set_fixed_expense_roles` and `edit_fixed_expenses`. The worked-out figures for a month are `plan_month`.

Input parameters:

- `path` (string, required): Absolute path to a .emd file on this machine.
- `plan_id` (string): One plan. Default: all of them.

### `plan_month` (~338 tokens)

Work a plan out for a month, and use it

One plan worked out for one month: net pay, the bills out of net, what is left to split, each bucket’s amount and share, what it actually got lately, when a goal is reached at this rate, money with no job to do — and what *using* the plan for that month would write (a planned deposit into a goal, a planned payment on a debt). **Dry-runs by default**, which is also how you read it; `commit: true` writes those suggestions and gives the month this plan. `months` instead gives a list of months this plan and writes nothing else, and `back_to_default` puts months of this plan back on the default — the Plan screen’s month checkboxes. A month nobody gave a plan is on the default. `only_buckets` writes just those buckets’ suggestions. A fund or an account is pointed at and writes nothing. The document is copied to a timestamped .bak first.

Input parameters:

- `back_to_default` (array): Months as YYYY-MM of this plan to put back on the default plan.
- `commit` (boolean): Default false. False shows the month and writes nothing; true uses the plan.
- `month` (string): The month as YYYY-MM. Default this month.
- `months` (array): Months as YYYY-MM to give this plan. Records nothing.
- `only_buckets` (array): Bucket ids from `plans`. Default: all.
- `path` (string, required): Absolute path to a .emd file on this machine.
- `plan_id` (string): Ids come from `plans`. Default: the default plan.

### `set_fixed_expense_roles` (~197 tokens)

Say what each bill does to a plan

For each bill, per plan: `net` (comes out of net pay before the split), `split` (is one share of the split, drawn on the bar) or `ignore` (this plan leaves it out). **A list in one call.** A bill the paycheck already took (a pension deduction) is outside the choice and refused. An expense with no choice made is ignored. This is the Plan screen’s switch on every row. **Dry-runs by default**: it shows each change and writes nothing until `commit: true`; the document is copied to a timestamped .bak first. Ids come from `plans`.

Input parameters:

- `commit` (boolean): Default false. False previews; true writes.
- `path` (string, required): Absolute path to a .emd file on this machine.
- `roles` (array, required): One entry per bill and plan. Up to 100 in a call.

### `set_plan` (~373 tokens)

Create, change or delete plans

What the Plan screen does to a plan — **a list of entries in one call**. Per entry: `plan_id` for an existing plan, or `create: true` with `name` (and `copy_of`, a plan id, to start from its sizes and its choices for the bills); then any of `name`, `make_default`, `basis` ({kind: LAST_PAYCHECK | AVERAGE_LAST_N | SPECIFIC_PAYCHECK | CUSTOM_AMOUNT, amount, months, paycheck_id}) — a bonus month is a plan whose basis is an amount — and `buckets`, **the plan’s whole list of buckets** as the screen’s bar has it: each {bucket_id (existing) or name (new), points_at: {kind: goal | debt | category | fund | account | none, id}, mode: percent | amount | rest, percent (0-100), amount (major units), locked}. A bucket is for one thing, and a bucket sized “rest” takes what is left after the others and the bills that are part of the split; at most one. Or, alone, `delete: true` (the default plan too; another becomes the default), or `remove_buckets` to take buckets out and hand each share to its right. What each bill does to a plan is `set_fixed_expense_roles`. **Dry-runs by default**; `commit: true` writes. The document is copied to a timestamped .bak first. Ids come from `plans`.

Input parameters:

- `commit` (boolean): Default false. False previews; true writes.
- `path` (string, required): Absolute path to a .emd file on this machine.
- `plans` (array, required): One entry per plan. Up to 100 in a call.

### `edit_fixed_expenses` (~252 tokens)

Edit, end or stop tracking bills

Change bills that exist — **a list in one call**. Per entry: `name`, `expected_amount` (the whole charge, major units — a yearly premium is the year’s figure), `cadence`, `category_id` (null clears), `is_discretionary`, `day_of_month` (null clears), `ended_on` (YYYY-MM-DD or "today": it keeps its history and drops out of every plan); or, alone, `stop_tracking: true`, which removes it and what every plan said about it — its charges stay on Transactions. How it is recognised in the ledger is not changed here. Adding is `track_fixed_expenses`; what it does to a plan is `set_fixed_expense_roles`. **Dry-runs by default**; `commit: true` writes. The document is copied to a timestamped .bak first. Ids come from `plans` or `fixed_expenses`.

Input parameters:

- `commit` (boolean): Default false. False previews; true writes.
- `edits` (array, required): One entry per bill. Up to 100 in a call.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `paychecks` (~169 tokens)

Paychecks

Every paycheck recorded in the document — gross, net, the employer’s own contributions, total compensation, the effective tax rate, what payroll put away, and whether each itemization reconciles to its net. Plus the salary months with no paycheck at all, per-year totals, and **the deduction-type vocabulary with its ids**, which is what a line of a new paycheck has to name. Reads the document; changes nothing.

Input parameters:

- `include_lines` (boolean): Also print each listed paycheck’s itemization, line by line. Default false.
- `limit` (number): How many paychecks to list, newest salary month first. Default 24, maximum 500. The yearly totals and the vocabulary are never capped.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `record_paychecks` (~214 tokens)

Record paychecks

Write paychecks into the document — **several in one call**, one entry per paycheck. Each needs the salary month, the payment date, gross and net; an itemization is optional, and each of its lines names a deduction type by id from `paychecks`. Read a paycheck PDF or photo yourself and this is where what it says goes. **Dry-runs by default**: it returns what it would record, including whether each paycheck’s itemization reconciles to its net, and writes nothing until called again with commit true. Nothing here deduplicates — a second paycheck for a salary month is allowed, because a 13th salary is exactly that. The document is copied to a timestamped .bak before anything is written.

Input parameters:

- `commit` (boolean): Default false. False previews the batch; true writes it.
- `path` (string, required): Absolute path to a .emd file on this machine.
- `paychecks` (array, required): One entry per paycheck. Up to 60 in a call.

### `set_deduction_types` (~477 tokens)

Make, rename, order and remove deduction types, and say what each counts as and pays into

Everything the deduction types screen does, for **several types in one call**: **make** a type (its name, what the paycheck calls it, its kind), **rename** one, change its kind, **remove** one, put the list in **order**, and say what each type **counts as** (out of net, saving — the two flags every paycheck figure turns on) and what it **pays into** — a fund or a goal for a saving line, a debt (a payroll loan) for a loan repayment. **This link is how a fund finds its paycheck lines**: a pension line is a deposit into a fund only because its type names the fund, and `funds` checks those deposits against the fund’s own report. A new type starts as its kind’s guess, and a kind change carries the flags along while they are still the guess; one that differs is shown as set by hand, and `guess` puts it back. One destination per type — naming one clears the others, and `pays_into: null` links nothing. Refused by name: a target of the wrong kind (a fund or a goal takes a line that counts as saving, a debt a loan repayment), a name another type has, and removing a built-in type, a type a stored paycheck line names, or one another row links to. Types and their ids are listed by `paychecks`; funds by `funds`, goals by `savings`, debts by `debts`. **Dry-runs by default**; the batch applies whole or not at all, and the document is copied to a timestamped .bak first.

Input parameters:

- `commit` (boolean): Default false. False previews; true writes.
- `order` (array): Types by id or by name — the names they will have after this call, new ones included. These come first in the list, in this order, and the rest follow as they are.
- `path` (string, required): Absolute path to a .emd file on this machine.
- `types` (array): One entry per deduction type, up to 60 in a call. With `type` it changes or removes that type; without it, it makes a new one, which needs `name` and `kind`.

### `funds` (~233 tokens)

Pension and hishtalmut funds

Every pension, study fund, provident fund and managers’ insurance the document tracks: the latest transcribed balance and how old it is, the management fee as stated and as measured against what was actually charged, what the dormant ones cost a year, and — the point of the feature — **whether the paychecks and the fund’s own report agree about what was deposited**. A fund finds its paycheck lines through the deduction types that pay into it — `set_deduction_types` sets that link. Name one with fund_id to also get its readings, its return window by window, and the working behind that check. Reads the document; changes nothing.

Input parameters:

- `fund_id` (string): Narrow to one fund and add its working — readings, returns, and the expected-versus-actual table behind the deposit verdict. Ids come from this same tool.
- `include_archived` (boolean): Include archived funds. Default false. A fund can be archived with a gap still open in it, so ask for them when chasing one.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `record_fund_readings` (~234 tokens)

Record fund balances from a report

Transcribe what a pension, hishtalmut, provident or managers’-insurance report says into the document — **several readings in one call**. Each entry names a fund by fund_id or by name, and carries one reading: the balance and its date, plus whatever else the report printed. A name that matches no fund creates it, once per batch, so four quarterly reports for one fund are four entries naming the same fund. **Leave out anything the report did not state** — an empty field means "not stated", and a 0 would be read as a stated zero. **Dry-runs by default**: it returns what it would write and writes nothing until called again with commit true. The document is copied to a timestamped .bak first. Editing a fund or correcting a reading already written is app-only.

Input parameters:

- `commit` (boolean): Default false. False previews the batch; true writes it.
- `path` (string, required): Absolute path to a .emd file on this machine.
- `readings` (array, required): One entry per reading. Up to 60 in a call.

### `create_goals` (~257 tokens)

Create goals

Add goals — **several in one call**. A goal has a name; a target amount and a target date are optional, and an open-ended goal is a real answer. Its kind is optional and drives nothing (EARMARKED when left out, as the goal form makes it); how many months of expenses the bank should hold is the cushion (`set_cushion`). **Dry-runs by default**; the document is copied to a timestamped .bak before anything is written. **The goal form’s one question is `default_holding`**: a bank account’s Cash, a place kept by hand or a new one takes `already_there`; a holding a broker’s rows buy is followed by its rows, with nothing to type; a bank account’s Cash can count the transfers into it (`from_transfers`). Editing, closing and deleting a goal are app-only, except its default holding (`set_goal_deposit_categories`).

Input parameters:

- `commit` (boolean): Default false. False previews the batch; true writes it.
- `goals` (array, required): One entry per goal. Up to 60 in a call.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `record_deposits` (~231 tokens)

Record savings movements

Record money moving in a goal — **several in one call**. Six modes: DEPOSIT, WITHDRAWAL, TRANSFER between holdings, OPENING for what was already there when tracking started, and GAIN / LOSS for what the market did. **The mode carries the direction; the amount is always positive.** OPENING, GAIN and LOSS are in the balance but are not money the user set aside, so they are kept out of the savings rate. **Dry-runs by default**; the document is copied to a timestamped .bak first. Editing and deleting a movement are app-only. A DEPOSIT here is money **set aside** by hand, still inside the bank; deposits **read from the bank** come from the Money moved category a goal reads (`set_goal_deposit_categories`).

Input parameters:

- `commit` (boolean): Default false. False previews the batch; true writes it.
- `deposits` (array, required): One entry per movement. Up to 60 in a call.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `set_place_accounts` (~296 tokens)

Name the account a place’s money is shown by

Say which account each place on Net worth is — the bank account, the broker, the savings account whose statements show its money — **several in one call**. A place *links* to an account and is not merged with it: a deposit or cash at home has no statements and names none (`account: null` takes a link away). A card cannot be named, and an account is named by one place at most. The account gets its own cash holding in the place, made with the link — or `cash_holding` picks one already there, such as the holding goal deposits already land in. Unlinking keeps the holding. A bank account or cash has a place named as it and its own cash from the moment it is made; naming it from another place moves it there while its own place holds nothing. A place that names one account is not re-pointed to another in one call: set it to `null` first. Nothing here moves a figure. Places and holdings are listed by `savings`; accounts by `document_info`. **Dry-runs by default**; the document is copied to a timestamped .bak first.

Input parameters:

- `commit` (boolean): Default false. False previews; true writes.
- `links` (array, required): One entry per place. Up to 60 in a call.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `record_holding_readings` (~299 tokens)

Record what a statement says a holding is worth

Write what a statement printed each holding was worth on a day — **a list in one call**. Transcribed, never fetched: there is no price feed. `value` is the worth as printed, in major units; `quantity`, `cost_basis` and `price` are optional and each is a check: the cost against what the rows that name the holding put in by that day, the quantity against their quantities, quantity × `price` against the value (a price printed in agorot is divided by 100 first — the check catches it when it is not). An account’s own cash holding takes a value only, checked against the account’s running balance. A goal fed by the holding is then worth its latest reading, with what went in beside it, and the difference is growth. The same figures for the same day again change nothing; other figures for a day that has a reading are refused unless `correct: true`; `remove: true` takes a day’s reading away. Holdings and their ids are listed by `savings`. **Dry-runs by default**; the document is copied to a timestamped .bak first.

Input parameters:

- `commit` (boolean): Default false. False previews; true writes.
- `path` (string, required): Absolute path to a .emd file on this machine.
- `readings` (array, required): One entry per holding and day. Up to 200 in a call.

### `set_goal_deposit_categories` (~429 tokens)

Point goals at their deposits

Say what feeds each goal, **several in one call**. **One feed per goal, never both**, so nothing counts twice: either a **Money moved category** — a transaction categorized under it is a deposit into the goal, read from the bank — or, with `from_holding: true`, **the rows that name its default holding**: every buy or sale of that holding inside an account that holds things (`set_transaction_holdings`) is a deposit, and the bank transfer that brought the cash there feeds nothing — or, with `from_transfers: true`, **the confirmed transfers into the bank or cash account whose own Cash is its default holding**: each transfer pair (`answer_transfers`) arriving there is a deposit and one leaving it a withdrawal, after its Was already there; off unless asked, since a main account takes transfers that are not for a goal. A broker’s cash is no such feed. Categorizing is the only step, and a row re-categorized elsewhere takes its deposit with it. One goal per category, per holding and per account; the category must be a Money moved one (a transfer or a saving, not a spending category), and the goal needs a default holding for the money to sit in: `default_holding` sets it, in the same entry or on its own, as the Goals form’s "Where is the money?" does. `category: null` stops either feed, and removes the deposits it had derived. Money you set aside by hand (`record_deposits`) and what was already there are never touched. `savings` shows what a holding’s rows put in, against the cost basis a reading printed. **Dry-runs by default**; the document is copied to a timestamped .bak first.

Input parameters:

- `commit` (boolean): Default false. False previews the batch; true writes it.
- `goals` (array, required): One entry per goal. Up to 60 in a call.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `set_cushion` (~304 tokens)

Set the cushion

Set the cushion. **What is in the bank** is the latest statement balance of the bank accounts that count — **every bank account and cash, until `accounts` unticks it**; **each goal saved in one of them holds its part** (its deposits into that account’s own cash holding), and **the cushion is what is left**, against a target of `months` of bills plus the Plan `buckets` (Groceries, say) counted beside the bills. No goal is the cushion and setting it makes none. Goals holding more than an account, or than the whole, is a failed check, never shrunk to fit. Every answer ends with the split as it stands. Wealth › Net worth and Plan both show it. **Dry-runs by default**; the document is copied to a timestamped .bak first.

Input parameters:

- `accounts` (array): Bank accounts whose balance counts toward what is in the bank, or stops counting. A card or an account with holdings never counts.
- `buckets` (array): Plan buckets to count in the cushion, or to stop counting. Ids or names from `plans`.
- `commit` (boolean): Default false. False previews; true writes.
- `months` (number): How many months of bills, above 0 and at most 24, to two places. e.g. 3.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `pause_goals` (~163 tokens)

Pause goals

Pause goals for a number of months, or start them again, **several in one call**. A paused goal is skipped by the projection and gives its monthly amount back to what is left in Plan for those months; it is not deleted and keeps its history. `months` counts this month: 1 skips this month only. 0 starts the goal again. Plan's own bucket is left alone. **Dry-runs by default**; the document is copied to a timestamped .bak first.

Input parameters:

- `commit` (boolean): Default false. False previews; true writes.
- `goals` (array, required): One entry per goal. Up to 60 in a call.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `settle_set_aside` (~191 tokens)

Is it still set aside?

Answer, for a goal, whether money **set aside** (promised to it but still inside the bank) is still there. A set-aside amount nobody has confirmed for more than 90 days is the one that is asked about; `goal_projection` lists them. `still_there: true` records today against them so they are not asked about again for 90 days. `false` withdraws them from the goal, with the reason written, so the balance stops counting money that is gone. Several goals in one call. **Dry-runs by default**; the document is copied to a timestamped .bak first.

Input parameters:

- `commit` (boolean): Default false. False previews; true writes.
- `goals` (array, required): One entry per goal. Up to 60 in a call.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `suggest_goal_amounts` (~174 tokens)

Suggest a split

Suggest each goal's monthly amount: goals by nearest date (no kind of goal goes first), each taking what gets it there on its date until what Plan has for goals runs out (what they take now plus what is left after every bucket). A goal with no date is sized as a year of what is missing; a reached, target-less or paused goal gets nothing. The preview is the suggestion; `commit: true` writes it through the same rule a typed amount on Goals uses, so the rest of the split moves with it. **Dry-runs by default**; the document is copied to a timestamped .bak first.

Input parameters:

- `commit` (boolean): Default false. False shows the suggestion; true writes it.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `set_goal_amounts` (~143 tokens)

Set what goes toward goals and debts each month

Set the monthly amount toward a goal, or the extra on a debt: the Plan bucket for it, in the default plan. The difference comes from (or goes back to) Plan's Everything else, as on the Goals and Debts screens; a goal or debt with no bucket gets one. **Dry-runs by default**; the document is copied to a timestamped .bak first.

Input parameters:

- `amounts` (array, required): One entry per goal or debt. Up to 60 in a call.
- `commit` (boolean): Default false. False previews; true writes.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `set_opening_balances` (~180 tokens)

Set accounts' opening balances

Say what each bank or cash account **held before its first statement**, and the day that stands for everything before — **several in one call**. A document cannot read this off a file. The answer shows the check it buys: opening, plus what the statements moved since, has to equal the balance the last statement ended on, and the row says by how much when it does not. `clear: true` forgets one. A credit card has none. **Dry-runs by default**; the document is copied to a timestamped .bak first.

Input parameters:

- `accounts` (array, required): One entry per account. Up to 30 in a call.
- `commit` (boolean): Default false. False previews the batch; true writes it.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `create_debts` (~164 tokens)

Create debts

Add debts — **several in one call**. A debt is the original principal plus a stream of payments; **the balance is derived, never stored**, so there is nothing here that sets one. Optionally set how payments are discovered: a merchant pattern for a standing order, or a category. **Dry-runs by default**; the document is copied to a timestamped .bak first. Editing, closing and deleting a debt are app-only, as is promoting a card installment group.

Input parameters:

- `commit` (boolean): Default false. False previews the batch; true writes it.
- `debts` (array, required): One entry per debt. Up to 60 in a call.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `record_debt_payments` (~161 tokens)

Record debt payments

Record payments against a debt — **several in one call**. The amount is a magnitude; the direction is known. A payment is **all principal** unless it names an interest portion, because v1 has no amortization schedule. Paying more than is outstanding is allowed and is flagged. **Dry-runs by default**; the document is copied to a timestamped .bak first. Payments the app discovered at import are a separate thing and are confirmed in the app.

Input parameters:

- `commit` (boolean): Default false. False previews the batch; true writes it.
- `path` (string, required): Absolute path to a .emd file on this machine.
- `payments` (array, required): One entry per payment. Up to 60 in a call.

### `guide_status` (~133 tokens)

Guide: what to set up, and what is done

In a document that keeps the ledger: the places worth setting up, in order — reading statements and paychecks, making the list of categories yours, giving each merchant a category, the bills, the plan, the opening balance and the cushion — with which are done and what the document shows for each, and the tool that does each one. The same list and the same checks as the app’s Guide me. Call it after create_document, or when the user asks what is left to set up. Reads the document; changes nothing.

Input parameters:

- `path` (string, required): Absolute path to a .emd file on this machine.

### `update_account` (~191 tokens)

Rename an account or set its last 4 digits

Give an account a real name and the last 4 digits of its card or number. The digits are what matches an imported statement to the right account, and what identifies a card charge on a bank statement, so they are worth asking for. Account ids come from document_info.

Input parameters:

- `account_id` (string, required): The account to change.
- `kind` (string): One of CREDIT_CARD, BANK, CASH, HOLDINGS, if it was created as the wrong one. HOLDINGS is an account with holdings — a broker, a savings account; `set_place_accounts` then names it from the place its…
- `last4` (string): The last four digits, as digits.
- `name` (string): A name the user will recognise, e.g. "Cal – Visa 1234".
- `path` (string, required): Absolute path to a .emd file on this machine.

### `choose_categories` (~222 tokens)

Archive or restore categories

Put categories away, or bring them back. Give `archive_ids` and `restore_ids` to say exactly which — these work at any time. On a document that holds no account and no transaction yet, `keep_ids` is also accepted and archives everything not listed, which is the fast way to trim the shipped list; it is refused afterwards, when one forgotten id would archive a category out from under a year of transactions. Archiving is not deleting — the history stays and comes back with it — and system categories are never touched. Ids come from category_tree. On a document that already holds work it takes a timestamped .bak first.

Input parameters:

- `archive_ids` (array): Categories to put away. Their transactions keep pointing at them.
- `keep_ids` (array): Empty document only: every id to keep. Everything else is archived. Refused once the document holds an account or a transaction.
- `path` (string, required): Absolute path to a .emd file on this machine.
- `restore_ids` (array): Archived categories to bring back.

### `create_category` (~228 tokens)

Create categories

Add categories the document does not have — **a list of them in one call**. `choose_categories` only keeps or archives the ones already there, so this is what answers "none of these fit". Give a `parent_id` or `parent_name` to put it under another, in which case it takes the parent’s kind and `kind` is ignored. Categories nest three levels at most (Living → Utilities → Electricity): a top-level category is a heading — Living, Lifestyle, Income — and a Plan bucket for it counts everything under it. Entries are applied in order, so a parent and its children go in the same call. If a category of the same name already sits in the same place nothing is created: a live one is reported back with its id, an archived one is brought back rather than twinned. Writes immediately — the change is additive, so there is nothing to preview.

Input parameters:

- `categories` (array): One entry per category. Up to 100 in a call.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `edit_categories` (~348 tokens)

Rename, colour, move, merge, archive or delete categories

Change categories that exist — **a list of edits in one call**. Per entry: `name`, `parent_id` (any category of its kind with room — three levels at most — or null for the top level), `move` (up or down), `archived`, `monthly_target` (major units, null clears — the category’s Plan bucket in the plan in use: one figure, shared with Plan), `color` (category-1 … category-11, null for automatic), `icon`, or, alone in the entry, `merge_into` (a category id) or `delete: true` — with `move_to` (a category id of the same kind) when rows, rules, subcategories, Plan buckets, bills, debts or goals still use it. This is the Categories screen’s rename, colour, move, merge, archive and delete, and its Plan bucket. A heading (Living, Income…) is a top-level category, renamed and moved like any other. Adding categories is `create_category`. **Dry-runs by default**: it says what each entry would do and which it refuses (a taken name, a fourth level, a delete with nowhere to move what uses it) and writes nothing until you call again with `commit: true`. The document is copied to a timestamped .bak first. Ids come from `category_tree`.

Input parameters:

- `commit` (boolean): Default false. False previews and writes nothing; true makes the changes.
- `edits` (array): One entry per category. Up to 100 in a call.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `set_excluded_from_totals` (~225 tokens)

Leave rows out of the totals, or count them again

Leave transactions out of every total — **a list of ids in one call** — or count them again with `excluded: false`. A row left out stays in the ledger and is shown marked; it is simply not summed. This is what the Transactions screen’s row menu does: a card bill the bank statement shows as one charge is the same money as the card statement’s rows, and counting both doubles the month. **Dry-runs by default** and says how much it would take off the totals; commit with `commit: true` after the user has said yes. The document is copied to a timestamped .bak first. Ids come from `transactions`.

Input parameters:

- `commit` (boolean): Default false. False previews and writes nothing; true makes the change.
- `excluded` (boolean): Default true: leave them out. False counts them in the totals again.
- `path` (string, required): Absolute path to a .emd file on this machine.
- `transaction_ids` (array, required): Up to 2000 transaction ids, from `transactions`.

### `set_transaction_notes` (~140 tokens)

Add, edit or clear notes on transactions

Write the note on a transaction — **a list in one call**. An empty `note` clears it. This is the row menu’s *Add a note*: a line about one charge that is worth remembering. **Dry-runs by default**; commit with `commit: true` after the user has seen the table. Ids come from `transactions`.

Input parameters:

- `commit` (boolean): Default false. False previews and writes nothing; true makes the change.
- `notes` (array, required): One entry per transaction. Up to 2000 in a call.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `set_transaction_holdings` (~237 tokens)

Say what a transaction’s cash turned into

Name the holding a transaction bought or sold, and how much of it — **a list in one call**. Such a row is cash turning into a holding inside one account (a broker buying a fund, a savings account opening a deposit): it counts in neither came in, went out nor moved, and the holding’s quantity is the sum of its rows’ quantities. `quantity` is a count of the thing, never money — bought is positive, sold negative, up to six places. `holding: null` clears both. The holding must sit at a place that names the row’s own account, or at one that names none, and is never an account’s own cash. Holdings and their ids are listed by `savings`; transaction ids by `transactions`. **Dry-runs by default**; the document is copied to a timestamped .bak first.

Input parameters:

- `commit` (boolean): Default false. False previews; true writes.
- `path` (string, required): Absolute path to a .emd file on this machine.
- `rows` (array, required): One entry per transaction. Up to 500 in a call.

### `transfers` (~109 tokens)

Transfers between the person’s own accounts

Lists the transfers between two of the person’s own accounts that imports have found: a row leaving one account and the same amount arriving in another within a few days. Undecided ones by default; `answer` reads the transfers or the rows that are not one. An answered transfer is neither income nor spending. `answer_transfers` answers them.

Input parameters:

- `answer` (string): Which to list. Default undecided.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `answer_transfers` (~175 tokens)

Answer transfers between the person’s own accounts

Answers the pairs `transfers` lists — **a list in one call**. `transfer`: one transfer between two of the person’s accounts, so neither row is income or spending (the end on an ordinary account counts as money moved, the end on an account with holdings is in no total). `not_a_transfer`: two rows that only looked like one; both stay what they were and are never suggested again. `undecided` takes an answer back. **Dry-runs by default**; the document is copied to a timestamped .bak first.

Input parameters:

- `commit` (boolean): Default false. False previews; true writes.
- `pairs` (array, required): One entry per pair. Up to 500 in a call.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `count_card_charges` (~167 tokens)

Count a card payment with no statement as spending

The card payments in a bank statement for a card this document has no statement of — that month of card spending is otherwise missing. Without `cards`, lists them. With `cards` (the last four digits, or `unknown`), counts each card’s payments as the spending they stand for, so Home stops asking; the rows already count, so no figure moves. Reading the card’s statement later still makes them card payments to answer. **Dry-runs by default**; the document is copied to a timestamped .bak first.

Input parameters:

- `cards` (array): Last four digits per card, or `unknown`.
- `commit` (boolean): Default false. False previews; true writes.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `set_no_pay_months` (~153 tokens)

Say a month had no pay

Says months had no pay — between jobs, unpaid leave — so no paycheck is missing for them: Home stops asking "No paycheck for …" and `paychecks` stops listing them as gaps. `no_pay: false` takes it back. Stores the answer only; no paycheck and no figure. **Dry-runs by default**; the document is copied to a timestamped .bak first.

Input parameters:

- `commit` (boolean): Default false. False previews; true writes.
- `months` (array, required): Salary months, as YYYY-MM.
- `no_pay` (boolean): Default true. False takes the answer back.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `set_card_payers` (~190 tokens)

Say which bank rows pay a card

Say that a bank merchant pays a credit card — **a list in one call**. Give the bank `transaction_id` and the `card_account_id` it pays (ids from `document_info`); omit the card to take the answer back. This is *This pays a card* on a Transactions row and on a By merchant row. Optionally `counts` says whether those bank rows count in the totals: false leaves them out, because the card’s own rows already hold that money. **Dry-runs by default**; commit with `commit: true` once the user has said yes.

Input parameters:

- `commit` (boolean): Default false. False previews and writes nothing; true makes the change.
- `path` (string, required): Absolute path to a .emd file on this machine.
- `payments` (array, required): One entry per bank merchant. Up to 200 in a call.

### `skip_merchants` (~174 tokens)

Skip merchants for now, or put them back

Set merchants aside in the By merchant queue — **a list in one call** — or put them back with `skip: false`. "I do not know what this is" is a real answer to a cryptic bank transfer; a skip writes nothing to any transaction. Names are the normalized ones `uncategorized_merchants` prints. **Dry-runs by default**; commit with `commit: true`.

Input parameters:

- `commit` (boolean): Default false. False previews and writes nothing; true makes the change.
- `merchants` (array, required): Normalized merchant names, exactly as `uncategorized_merchants` printed them.
- `path` (string, required): Absolute path to a .emd file on this machine.
- `skip` (boolean): Default true: set them aside. False puts them back in the queue.

### `assign_category` (~214 tokens)

Assign categories to transactions

Categorize the transactions with no category — **many merchants in one call**. Send every merchant-to-category decision you have worked out as one `assignments` list rather than calling this once per merchant. **Dry-runs by default**: it returns what the whole batch would change and writes nothing until you call it again with commit true, so show the user the table and let them answer once. Rows categorized by hand are never touched, rows assigned by a rule are left to the rule, and every row it does change is marked AI so the batch stays findable. The document is copied to a timestamped .bak before anything is written. A row covered by two assignments refuses the batch rather than picking one.

Input parameters:

- `assignments` (array, required): One entry per category-and-scope. Up to 200 in a call.
- `commit` (boolean): Default false. False previews the whole batch and writes nothing; true makes it.
- `path` (string, required): Absolute path to a .emd file on this machine.

### `note_merchant_suggestions` (~123 tokens)

Note what was suggested about merchants

Remember what this conversation suggested for merchants that were **not** categorized — a category the user declined, or a name nobody could identify — so the next conversation does not ask about the same names again. Shown in the Asked about column of uncategorized_merchants, cleared automatically when a merchant is categorized. Writes immediately: it changes no transaction, no category and no rule.

Input parameters:

- `path` (string, required): Absolute path to a .emd file on this machine.
- `suggestions` (array, required): One entry per merchant. Up to 200 in a call.

### `create_rule` (~348 tokens)

Create a categorization rule

Create a standing rule that categorizes matching transactions into a category, now and on every future import. **This is the widest write here** — it reaches the whole history — so it needs two calls: the first returns the transactions the rule would match plus a confirmation token, and the second must quote that token back. Show the user the matches before you commit; the token expires the moment the match set changes. Rows categorized by hand are never touched.

Input parameters:

- `account_id` (string): Restrict the rule to one account.
- `amount_max` (number): Restrict by amount, as a magnitude in major units.
- `amount_min` (number): Restrict by amount, as a magnitude in major units.
- `apply_to_existing` (boolean): Default true: also categorize the transactions it already matches. False creates the rule for future imports only.
- `category_id` (string, required): The category the rule assigns. Ids come from `category_tree`.
- `commit` (boolean): Default false. True requires `confirmation` from the preview.
- `confirmation` (string): The token the preview printed. Required to commit.
- `kind` (string): One of EXACT_MERCHANT, CONTAINS, REGEX, PROVIDER_CATEGORY, AMOUNT_RANGE, ACCOUNT. Default CONTAINS, which survives the branch numbers and spacing that vary between two statements of the same shop.
- `name` (string): Optional. Defaults to the pattern, which is the name of most rules.
- `path` (string, required): Absolute path to a .emd file on this machine.
- `pattern` (string): What the rule matches on. Required for every kind but ACCOUNT and AMOUNT_RANGE.

### `statement_preview` (~236 tokens)

Look at a statement file

Read a card or bank statement (.xlsx, .xls, .csv) and report what is in it: which sheet, where the header row is, what each column looks like and what the app guesses it is, the first rows verbatim, and whether a reader already recognises these columns. Changes nothing — call this before import_statement so the user can confirm the columns. **The user maps the columns; the app only guesses**, so show them this and ask.

Input parameters:

- `amount_convention` (string): One of SIGNED_SINGLE, POSITIVE_IS_CHARGE, SEPARATE_DEBIT_CREDIT. Read from the roles when omitted.
- `columns` (object): Try columns without importing: column number → role.
- `file` (string, required): Absolute path to the statement file. One of .xlsx, .xls, .csv.
- `header_row` (number): Force the header row number, when the guess is wrong.
- `path` (string, required): Absolute path to a .emd file on this machine.
- `sheet` (string): Which sheet, when the file has more than one. Defaults to the first.

### `import_statement` (~463 tokens)

Import a statement

Import a card or bank statement into an account, through the app’s own pipeline: the same parse, the same dedupe by natural key, the same rules applied on commit. **Dry-runs by default** — it reports what it would import and how the parse compares with the total the file prints for itself, and writes nothing until commit is true. **A file whose printed total does not reconcile is refused**, because importing over it puts numbers in the ledger the file itself says are wrong. Re-importing the same statement is safe: duplicates are matched and skipped.

Input parameters:

- `account_id` (string): The account to import into. Ids come from `document_info`. Or `new_account`.
- `amount_convention` (string): One of SIGNED_SINGLE, POSITIVE_IS_CHARGE, SEPARATE_DEBIT_CREDIT. Usually read from the roles you map — state it only when a single amount column needs saying which sign is a charge.
- `columns` (object): Column number → role, from what the user confirmed in `statement_preview`. Omit when a reader already recognises the file.
- `commit` (boolean): Default false. False previews the import and writes nothing.
- `file` (string, required): Absolute path to the statement file. One of .xlsx, .xls, .csv.
- `header_row` (number): Force the header row number, when the guess is wrong.
- `ignore_printed_total` (boolean): Import even though the parse disagrees with the total the file prints. Default false, and it should stay false unless the user is certain the file’s own figure is wrong.
- `new_account` (object): Instead of `account_id`, on a document that has no account for this file yet: the account the statement is for, made on commit — or found, when one of that kind already has these last 4 digits, as th…
- `path` (string, required): Absolute path to a .emd file on this machine.
- `save_mapping_as` (string): Keep these columns as a reader under this name, so the next statement of the same shape imports without asking.
- `sheet` (string): Which sheet, when the file has more than one.

### `record_holdings_statement` (~653 tokens)

Record a statement of an account with holdings

Write what a broker’s (or any account with holdings’) statement printed — **the whole file in one call**: its movements onto the account, and its holdings table as dated readings. **You read the file** — a PDF, usually — and send what it printed; nothing here parses it. Each row is the **value date**, the description as printed, the signed `amount` (a buy negative, a deposit or a sale positive; a fee printed in its own column is already inside the amount), and `cash_after`, the cash balance the statement printed after it. A buy or a sale also names the `holding` and the `quantity` (bought positive, sold negative). **The balances are a check**: each must be the one before it plus the row, and the batch is refused at the first that is not — a misread figure. **They are also each row’s key** (date, amount, balance after), so an overlapping file — a half-year export repeating the months — adds only what is new, and the same file again adds nothing. A row of amount 0 naming no holding (the broker’s tax bookkeeping) is left out. `holdings` is the holdings table: per holding its `value`, and when printed its `quantity`, `cost_basis` and `price` (one unit, in major units — a price printed in agorot is divided by 100); `cash` is the cash line. Each is checked: the cost against what the holding’s rows put in by `as_of`, the quantity against theirs, quantity × price against the value, and the cash against the account’s rows. Holdings are named by id or name (`savings` lists them); a name the place does not have is made there. **Dry-runs by default**; the document is copied to a timestamped .bak first.

Input parameters:

- `account_id` (string): The account with holdings. Or `new_account`.
- `as_of` (string, required): The day the holdings table is for, YYYY-MM-DD.
- `cash` (number): The cash line of the holdings table, in major units.
- `commit` (boolean): Default false. False previews; true writes.
- `currency` (string): Defaults to the document’s currency.
- `file` (string, required): Absolute path to the statement file you read. Hashed, so the same file is known again.
- `from` (string): The first day of the period the statement printed, when it printed one.
- `holdings` (array): The holdings table, one entry per holding but the cash. Up to 100.
- `new_account` (string): Instead of `account_id`: the name of the account to make, e.g. the broker’s name.
- `path` (string, required): Absolute path to a .emd file on this machine.
- `place` (string): The place on Net worth that names the account, when it names none yet — by id or name; made when new. Defaults to one with the account’s name.
- `rows` (array): Every movement, in the order printed. Up to 400.

### `frame_copy` (~98 tokens)

The document, for a view drawn in the conversation

Not for a model: a view drawn in the conversation calls it with its own key to fetch its document, and any other call is refused. Read rows with `format_rows`.

Input parameters:

- `key` (string, required): The drawn view’s own key.
- `path` (string, required): Absolute path to a .emd file on this machine.
- `since` (string): The stamp of the copy the view holds; no copy comes back while it still holds.

## Diagnostics

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

## Score history

- 2026-11-04: 71
- 2026-11-03: 45
- 2026-11-02: 70
- 2026-11-01: 70
- 2026-10-31: 70
- 2026-10-30: 69
- 2026-10-29: 69
- 2026-10-28: 68
- 2026-10-27: 68
- 2026-10-26: 67
- 2026-10-25: 67
- 2026-10-24: 66
- 2026-10-23: 40
- 2026-10-22: 65
- 2026-10-21: 65
- 2026-10-20: 38
- 2026-10-19: 53

## Common questions

### What is the Emerald MCP server?

Emerald is an MCP server listed in the public MCP registry as io.github.kfireliyahu/emerald. The database under the chat: a local SQLite file a model asks for rows, not notes to re-read. This page covers its npm package (emerald-mcp).

### Is the Emerald MCP server safe to use?

Emerald scores 71 out of 100 on VerifyMCP. We found no known CVEs affecting it as of 4 November 2026. It declares no install or post-install scripts. 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 Emerald MCP server expose?

Emerald exposes 85 tools: formats, declare_format, connect_formats, extend_format, delete_format, and 80 more. Their descriptions and schemas cost roughly 22,653 tokens of context every time the server is loaded.

### Is the Emerald MCP server still maintained?

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

### What licence is the Emerald MCP server under?

Emerald declares the AGPL-3.0-only licence, which is OSI-approved. That covers the source only, and says nothing about the cost of any service it calls.

## Links

- npm package: https://www.npmjs.com/package/emerald-mcp
- Socket report: https://socket.dev/npm/package/emerald-mcp
- Repository: https://github.com/kfireliyahu/emerald-emd
- Website: https://getemerald.dev/
- Changelog RSS feed: https://verifymcp.io/servers/kfireliyahu-emerald/emerald-mcp.xml
- Changelog JSON feed: https://verifymcp.io/servers/kfireliyahu-emerald/emerald-mcp.json
- HTML version of this page: https://verifymcp.io/servers/kfireliyahu-emerald/emerald-mcp
