Skip to main content
Glama
ninetails-io

gnucash-mcp

create_transactions

Create single or bulk GnuCash transactions atomically, with validation, duplicate detection, and optional dry-run to screen before committing.

Instructions

Create transactions in one atomic command (bulk entry) — the canonical entry tool for one transaction or many. A single transaction is a one-row batch (the former create_transaction tool was removed; this replaces it).

INPUT — transactions is a TSV block: a header row, then one row per transaction. The HEADER DECLARES THE LAYOUT. Base form: splits are (amount, account) column PAIRS, repeated as wide as a transaction needs::

ref<TAB>date<TAB>description<TAB>amt1<TAB>acct1<TAB>amt2<TAB>acct2...
1<TAB>2026-05-21<TAB>Gas<TAB>-54.19<TAB>Assets:Checking<TAB>54.19<TAB>Expenses:Auto:Fuel

Two opt-in extensions, each activated by naming it in the header (legacy headers parse exactly as before):

  • PER-SPLIT MEMOS — declare memo split columns; splits become (amount, account, memo) TRIPLES::

    ref<TAB>date<TAB>description<TAB>amt1<TAB>acct1<TAB>memo1<TAB>amt2<TAB>acct2<TAB>memo2
    1<TAB>2026-05-21<TAB>Gas<TAB>-54.19<TAB>Assets:Checking<TAB>card #4471<TAB>54.19<TAB>Expenses:Auto:Fuel

    Empty memo cells mid-row keep their tabs; a row may simply END once its last split's amount and account are present (trailing memo/qty cells are read as empty — no placeholder tabs needed, as above).

  • PER-TRANSACTION NOTES — declare a notes column directly after description::

    ref<TAB>date<TAB>description<TAB>notes<TAB>amt1<TAB>acct1...

    FIELD TARGETING for statement entry: description is the clean name; notes is what the purchase WAS — interpreted, not transcribed — and is what humans see in GnuCash's double-line register; the bank leg's memo is where the RAW statement line goes (provenance, visible only in expanded split view). Prefer filling notes whenever the description alone doesn't tell the story.

  • PER-TRANSACTION CURRENCY — declare a cur column after description (before or after notes); an ISO code cell sets THAT ROW's transaction currency, an empty cell keeps the book default::

    ref<TAB>date<TAB>description<TAB>cur<TAB>amt1<TAB>acct1<TAB>amt2<TAB>acct2
    1<TAB>2026-07-15<TAB>USD Card Payment<TAB>USD<TAB>-500<TAB>Assets:USD Checking<TAB>500<TAB>Liabilities:USD Card

    With cur, the row's amt cells are in that currency and must balance in it. Use it when NO leg is in the book's default currency (a USD-to-USD transfer inside a CNY book needs no invented CNY values and no qty). Splits on accounts of any OTHER commodity still need qty. The currency must already exist in the book, and cur cannot combine with an auto-fill row.

  • PER-SPLIT QUANTITY — declare qty split columns for splits whose ACCOUNT commodity differs from the book default (investment shares, foreign-currency accounts)::

    ref<TAB>date<TAB>description<TAB>amt1<TAB>acct1<TAB>qty1<TAB>amt2<TAB>acct2<TAB>qty2
    1<TAB>2026-07-01<TAB>VFIFX Purchase<TAB>-505.17<TAB>Assets:Checking<TAB><TAB>505.17<TAB>Assets:401k:VFIFX<TAB>7.7936

    amount stays in the book's default currency (the transaction currency — batch never changes that); qty is the amount in the account's own commodity. An EMPTY qty cell means the account uses the default currency (quantity == amount). A non-default-commodity account with an empty qty rejects that row.

  • PER-SPLIT ACTION — declare act split columns for GnuCash's typed movement tag ("Buy"/"Sell"/"Dividend" on investment legs — desktop convention; "Wire"/"ATM" on bank legs). Same group mechanics as memo/qty; empty cells skip it. Rarely needed for plain spending.

All extensions combine; when several split fields are declared, the header's FIRST group fixes their order (e.g. amt, acct, memo, qty).

AUTO-FILL — a row with NO split cells at all (ends right after description/notes) reproduces the most recent transaction with the same description — splits, memos, and quantities included::

1<TAB>2026-07-01<TAB>Rent
2<TAB>2026-07-01<TAB>Netflix

Auto-filled rows are marked auto_filled_from:<guid> in the results reason column; a row whose description matches nothing rejects ("no matching transaction to auto-fill from"). Use dry_run=true to preview what a batch of auto-fills would book. Perfect for recurring monthly entries. Transaction notes are NOT copied from the source (notes are often time-bound — "first appearance, investigate" must not replicate); supply a notes cell when the new instance needs one.

  • ref: YOUR correlation key per row (e.g. 1, 2, 3), unique within the batch. It is echoed back so you can match results to what you sent; the server never reuses or interprets it.

  • date: ISO YYYY-MM-DD. amount/qty: decimal STRINGS (never raw JSON numbers). Each transaction needs

    =2 splits balancing to zero in the default currency. Rows may differ in width (2 splits vs 3).

  • The transaction currency is the book default unless the row declares one via the cur column (see PER-TRANSACTION CURRENCY above).

BEHAVIOR — one book-open, one atomic save:

  • A STRUCTURAL error (unbalanced, unknown account, bad pairs) aborts the WHOLE batch by default; nothing is written. Pass on_error="skip" to write the good rows and reject only the bad ones.

  • A duplicate rejects ONLY its row; force=True overrides all blocking duplicates. dry_run=True validates + screens without writing.

OUTPUT — a JSON envelope of two TSV tables joined by ref:

  • results (always): ref, status, txn_guid, dup_count, max_confidence, reason. status is created | rejected | would_create (dry_run, candidate-free rows only) | review_required (dry_run rows with >=1 duplicate candidate — rule each against the duplicates table before committing); reason is a code like duplicate_detected or the validation message. max_confidence (HIGH/MEDIUM/blank) is the row's top duplicate candidate — enough for the common keep/drop call without the join.

  • duplicates (only when matches exist): SELF-CONTAINED comparison rows, sorted strongest-correspondence first — ref, candidate_guid, confidence, state, date_new, date_old, date_delta_days, amt_new, amt_old, amt_delta, cur, desc_new, desc_old, notes_old, memo_old, cat_new, cat_old, split_match, signals. _new = your proposed row, _old = the existing transaction; cat_* are the category (non-payment) legs as account=amount|...; split_match (exact/partial/none) compares them — MEDIUM on date+amount but none on category is usually a distinct purchase. Amounts are SIGNED (direction matters: a deposit is not a payment's twin). amt_delta is blank on cross-currency candidates (cur names the candidate's currency exactly when the frames differ); memo_old and state blanks mean this surface can't fill them. Never re-read your own input — both sides are in the row. Σ(dup_count) equals the duplicates row count.

  • Dry runs additionally lead with summary (would-create/ review-required/rejected counts + the homework line) and close with effects — the projected per-account balance deltas of the rows that would land.

Args: transactions: The TSV block described above. force: Override ALL blocking (HIGH) duplicates this batch. dry_run: Validate + screen, write nothing. on_error: "abort" (default) or "skip" for structural errors.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
forceNo
dry_runNo
on_errorNoabort
transactionsYes

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Schema Changelog

Changes observed during successful MCP inspections.

  1. First observedv1.4.2

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With annotations carrying only readOnlyHint=false, destructiveHint=false, and idempotentHint=false, the description shoulders the behavioral burden and does so exhaustively. It discloses atomic save semantics, whole-batch abort on structural errors, per-row duplicate rejection, force override, dry_run validation without writing, auto-filled row marking (auto_filled_from), and that notes are intentionally not copied from source. None of this contradicts the annotations — readOnlyHint=false is consistent with a tool that creates transactions.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long, but the tool is genuinely complex (TSV format, five opt-in extensions, auto-fill, error modes, dual output tables), and nearly every block earns its place. It is well-structured with capitalised headers, ASCII code examples, and a clear front-loaded purpose statement followed by progressively deeper detail. It loses one point only for sheer length — much of the extension detail could arguably be deferred until an agent actually tries to use an extension.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Despite the tool's high complexity (4 parameters, intricate TSV input grammar, multiple behavior modes, and a non-trivial output envelope), nothing an agent needs to call it correctly is missing. Input format, extensions, balancing rules, currency handling, error semantics, and the full output structure (results and duplicates tables, dry-run summary/effects) are all documented, so the lack of additional context in the schema/output_schema is fully compensated.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate fully — and it does. Every parameter is explained beyond the schema type: 'transactions' is fully specified as a TSV block with header-driven layout and extensions; 'force' (override blocking HIGH duplicates), 'dry_run' (validate + write nothing), and 'on_error' ('abort' vs 'skip') each get concrete semantic meaning. The description completely replaces the absent schema documentation.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb-resource pair ('Create transactions in one atomic command (bulk entry)') and explicitly declares itself 'the canonical entry tool for one transaction or many.' It even names the removed predecessor (create_transaction) to prevent confusion, and the scoping ('one transaction or many') distinguishes it from related siblings like update_transactions, replace_splits, and enter_statement without needing their schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description clearly establishes when to use the tool ('the canonical entry tool for one transaction or many') and gives rich usage scenarios (recurring monthly entries via auto-fill, statement entry via field targeting). However, it never explicitly states when NOT to use it or names alternatives for adjacent operations — e.g., it doesn't say 'use update_transactions to edit' or 'use enter_statement for statement workflows,' despite such siblings existing. Usage context is strong; explicit exclusions are the only gap.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.