Skip to main content
Glama
ninetails-io

gnucash-mcp

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault

No arguments

Instructions

Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.

This server publishes no instructions, or was last inspected before Glama recorded them.

Capabilities

Features and capabilities supported by this server

Protocol revision2025-11-25

CapabilityDetails
tools
{
  "listChanged": false
}
prompts
{
  "listChanged": false
}
resources
{
  "subscribe": false,
  "listChanged": false
}
experimental
{}

Tools

Functions exposed to the LLM to take actions

NameDescription
get_server_configA

Get the server's loaded configuration.

Returns loaded modules, tool count, book path, debug mode, and version. Use this to verify which tools are available in this session.

switch_bookA

Switch the active GnuCash book (multi-book sessions only).

All subsequent tool calls — and the audit/debug logs — operate on the newly-selected book until the next switch. Only present when GNUCASH_BOOK_PATH lists 2+ books.

get_account_slotsA

Read slots (custom metadata) from an account.

Slots are key-value pairs stored on accounts for metadata like APR, credit limit, reward rates, or any custom data.

Args: account: Account ref: full path (e.g., "Liabilities:Credit Cards:Capital One"), %short GUID, or full 32-char GUID. key: Specific slot key to retrieve. If omitted, returns all slots.

set_account_slotA

Set a custom metadata slot on an account.

Stores a key-value pair on the account. Values are stored as strings. Use for APR, credit limits, reward rates, or any per-account metadata.

Args: account: Account ref: full path (e.g., "Liabilities:Credit Cards:Capital One"), %short GUID, or full 32-char GUID. key: Slot key (e.g., "apr", "credit_limit"). value: Slot value (always stored as string).

delete_account_slotA

Remove one custom metadata slot from an account.

Permanent, and surgical: only the named key is deleted — other slots, the account, and its transactions are untouched. Errors, changing nothing, if the account ref or key doesn't exist, or if the key contains '/' (reserved for internal hierarchical slots; user slots are flat). get_account_slots lists the removable keys; set_account_slot re-creates one.

Args: account: Account ref: full path (e.g., "Liabilities:Credit Cards:Capital One"), %short GUID, or full 32-char GUID. key: Slot key to remove.

get_audit_logA

Read audit log entries for a date.

Returns the human-readable text audit log, led by a Showing X-Y of Z audit entries (date) indicator. Each write operation (CREATE, UPDATE, DELETE, VOID, RECONCILE, etc.) is one entry separated by a blank line. Reads are not logged.

Unlike the row-list tools, the window is anchored to the most recent entry: offset pages backward into history (offset=0 is the newest page), since "what happened lately" is the usual question. limit=0 returns the count only.

Args: log_date: Date to read (YYYY-MM-DD). Defaults to today. limit: Page size (default 50). 0 = count only. offset: Entries to skip back from the most recent (default 0).

create_backupA

Create an on-demand backup of the GnuCash book.

Uses SQLite's online backup API so the snapshot is safe even if the book is being read or written concurrently by GnuCash desktop. Verifies the copy with PRAGMA integrity_check before declaring success; a failed check deletes the bad backup and raises.

Manual backups are kept indefinitely — automatic retention (session / weekly / monthly stages) does not touch them. To review or remove backups, the user works with the files directly in the backup directory; no tool can list or delete them.

The response includes a restore_hint describing the filesystem command to restore from this backup. Restore is a human-run filesystem operation, not an MCP tool — see docs/RESTORE_FROM_BACKUP.md for details.

Args: label: Optional free-text label (sanitized to [A-Za-z0-9_-]) appended to the filename for human context, e.g. "pre-recategorization" or "pre-tax-filing".

list_budgetsA

List all budgets in the book.

Leads with a Showing X-Y of Z budgets line, then a compact one-line-per-budget format by default. Page with offset; limit=0 returns the count only. Use verbose=true for the full JSON envelope.

Args: verbose: If false (default), compact text output — optimized for reading and token efficiency. If true, structured JSON, for when you need machine-readable fields rather than a report. limit: Page size (default 50, max 250). 0 = count only. offset: 0-indexed first row to return (default 0).

get_budgetA

Get full details of a budget including all budget amounts.

Returns a compact text table by default — collapses uniform periods (e.g., "250/mo (all periods)") so the typical 12-cell repeat doesn't dominate the response. Use verbose=true for the full structured periods dict per account.

Args: name: Budget name. verbose: If false (default), compact text output — optimized for reading and token efficiency. If true, structured JSON, for when you need machine-readable fields rather than a report.

create_budgetA

Create a new budget.

Args: name: Budget name (e.g., "2026 Budget"). year: Budget year. Defaults to current year. Ignored when start_date is provided. num_periods: Number of periods. Default 12 (monthly for a year). period_type: Period length: - "monthly" (default) - "quarterly" - "weekly" description: Optional description. start_date: Optional ISO date (YYYY-MM-DD) when the budget's first period begins. When omitted, falls back to January 1 of year. Use this to author a historical budget for comparison against past actuals (e.g. start_date="2024-01-01") or to start mid-year.

set_budget_amountA

Set a budget target for an account.

Args: budget_name: Name of the budget. account: Account ref: full path (e.g., "Expenses:Groceries"), %short GUID, or full 32-char GUID. amount: Monthly budget amount as string (e.g., "500.00"). period: Which period(s) to set: - None or "all": Set same amount for all periods (default) - Integer 0-11: Set specific period (0 = January for yearly budget) - "q1", "q2", "q3", "q4": Set all periods in quarter

get_budget_reportA

Compare actual spending against budget.

Returns a compact text table by default with ⚠ markers on categories exceeding budget. Use verbose=true for the full structured dict.

Args: budget_name: Name of the budget. period: Which period to report: - None: Current period based on today's date (default) - Integer 0-11: Specific period - "ytd": Year to date (all periods up to current) - "all": All periods account: Optional filter to specific account or parent account. include_children: If True and account specified, include child accounts. verbose: If false (default), compact text output — optimized for reading and token efficiency. If true, structured JSON, for when you need machine-readable fields rather than a report.

delete_budgetA

Delete a budget and every per-period amount set on it.

Permanent — there is no undo and no archive state. Only the plan is removed: transactions and account balances are untouched (budgets are plans, not postings). Errors, changing nothing, if the name matches no budget — list_budgets shows what exists, exact and case-sensitive. To retire a budget while keeping its numbers readable, simply stop reporting on it instead of deleting.

Args: name: Budget name (exact, case-sensitive).

create_partyA

Create a customer, vendor, or employee.

Additive: each call creates a fresh party — names are NOT checked for duplicates, so list_parties first when unsure. Returns the assigned ID (e.g., "000001"), the handle every later call wants. ID counters are PER TYPE: customer 000001 and vendor 000001 are different parties, which is why party_type is required everywhere.

Args: party_type: "customer" (pays you), "vendor" (you pay), or "employee" (expense-voucher workflows). name: Party name (e.g., "Acme Corp", "Jane Smith"). currency: ISO currency code (e.g., "USD", "EUR"). Defaults to book's default currency. notes: Optional notes (max 4096 characters). Employees have no notes field — rejected, not ignored. address: Optional address with keys: name, addr1, addr2, addr3, addr4, phone, fax, email. Each sub-field capped at 1024 characters.

list_partiesA

List customers, vendors, and/or employees.

Leads with a Showing X-Y of Z line per type, then a compact one-line-per-party format by default. Page with offset; limit=0 returns the count only. Use verbose=true for full JSON with guid, address, notes, etc.

Args: party_type: "customer", "vendor", or "employee". Omit for all three (sections in that order). active_only: If True, only show active parties. Default True. verbose: If false (default), compact text output — optimized for reading and token efficiency. If true, structured JSON, for when you need machine-readable fields rather than a report. limit: Page size per type (default 50, max 250). 0 = count only. offset: 0-indexed first row to return (default 0).

get_partyA

Get details for one customer, vendor, or employee by ID.

Args: party_type: "customer", "vendor", or "employee" — required because ID counters collide across types (customer 000001 ≠ vendor 000001). id: Party ID (e.g., "000001"). This is the human-readable ID shown in GnuCash, not the internal GUID.

update_partyA

Update a customer's, vendor's, or employee's mutable fields.

None = no change on every parameter; pass an empty string to clear notes. Returns a diff-style response with the changed fields only.

Args: party_type: "customer", "vendor", or "employee" (ID counters collide across types — always required). id: Party ID (e.g., "000001"). name: New display name. currency: New ISO currency code (future documents only). notes: New notes; "" clears. Employees have no notes field — rejected, not ignored. active: Set active/inactive (inactive parties hide from default listings but keep their history). address: Full replacement address (see create_party).

delete_partyA

Delete a customer, vendor, or employee.

Blocked while the party has documents (invoices, bills, vouchers, credit notes) — the audit trail outranks tidiness. Prefer update_party(active=false) to retire a party while keeping its history.

Args: party_type: "customer", "vendor", or "employee" (ID counters collide across types — always required). id: Party ID (e.g., "000001").

create_billtermA

Create a new billing term.

Args: name: Billterm name (e.g., "Net 30"). due_days: Number of days until payment is due. Default 30. description: Optional description. discount_days: Days within which early discount applies. discount_percent: Early payment discount percentage (e.g., "2" for 2%).

list_billtermsA

List all billing terms.

Leads with a Showing X-Y of Z billterms line, then a compact one-line-per-term format by default. Page with offset; limit=0 returns the count only. Use verbose=true for full JSON with guid, discount details, etc.

Args: verbose: If false (default), compact text output — optimized for reading and token efficiency. If true, structured JSON, for when you need machine-readable fields rather than a report. limit: Page size (default 50, max 250). 0 = count only. offset: 0-indexed first row to return (default 0).

create_taxtableA

Create a new sales-tax table.

A taxtable holds one or more entries. Each entry contributes either a percentage rate or a flat-value surcharge routed to a specific GL account (ASSET for input-tax credit, LIABILITY for output sales tax payable). Multi-entry composites (e.g., GST 5% + PST 7%) produce multiple tax splits per line at posting time.

Args: name: Taxtable name, unique within the book (e.g., "CA Sales 7.25%", "BC GST+PST"). entries: List of {type, amount, account} dicts. type: "value" or "percentage". amount: positive decimal as string. Percentages are the rate ("5.00" = 5%, not "0.05"). account: account path, %short-guid, or full GUID. Must be ASSET or LIABILITY type. All entries on a single taxtable must reference accounts in the same commodity.

Example: create_taxtable( name="BC GST+PST", entries=[ {"type": "percentage", "amount": "5.00", "account": "Liabilities:GST Payable"}, {"type": "percentage", "amount": "7.00", "account": "Liabilities:PST Payable"}, ], )

list_taxtablesA

List all sales-tax tables, or get one by name.

Leads with a Showing X-Y of Z taxtables line. Compact format (default): one line per taxtable with name, entry count, and per-entry rate→account routing. Page with offset; limit=0 returns the count only. Verbose: structured JSON with resolved account paths and refcount.

Args: name: Tax table name for a single-table detail lookup (entries, rates, account routing, refcount). All other parameters are ignored. verbose: If false (default), compact text output — optimized for reading and token efficiency. If true, structured JSON, for when you need machine-readable fields rather than a report. limit: Page size (default 50, max 250). 0 = count only. offset: 0-indexed first row to return (default 0).

update_taxtableA

Update a sales-tax table's name and/or entries.

Diff-style response: only changed fields are returned.

Entry replacement on a taxtable that's already in use is destructive to FUTURE entries' tax math. Existing posted invoices retain their original splits (splits are stored, not derived), but new entries on any document will use the replacement entries' rates. When refcount > 0 and entries is given, force=True is required to proceed.

Args: name: Current taxtable name. new_name: New name (optional). entries: Replacement entry list (optional). Same shape and validation as create_taxtable. force: Required to replace entries when the taxtable is already referenced by document entries.

delete_taxtableA

Delete a sales-tax table.

Refuses when any Entry row references the taxtable (computed via SQL on the entries table). Voided invoices still pin their taxtables — voided entry rows persist for audit-trail purposes. Remove or re-assign referencing entries first.

Args: name: Taxtable name.

create_documentA

Create a customer invoice, vendor bill, employee expense voucher, or credit note.

The owner side derives from the document type — invoice → customer, bill → vendor, voucher → employee. Credit notes exist on both sides, so they alone require party_type ("customer" or "vendor"). After creating, add line items with add_document_entry, then post_document to put it on the books.

Args: document_type: "invoice", "bill", "voucher", or "credit_note". owner_id: The owning party's ID (customer ID for invoices, vendor ID for bills, employee ID for vouchers). ID counters are per type. party_type: Required for credit notes only ("customer" or "vendor" — which side the credit belongs to). Derived from document_type otherwise. date_opened: ISO date. Defaults to today (echoed in the response). notes: Optional notes (max 4096 characters). currency: ISO code. Defaults to the owner's currency, then the book default. term: Billterm name (e.g., "Net 30"). Optional. id: Custom document number; auto-generated when omitted. job_id: Optional Job to group under (invoices and bills; must belong to the same owner). applies_to_id: Credit notes only — the invoice/bill this credit note reverses. The link is PROVENANCE, not a constraint: apply_credit_note can net the credit against any open document from the same owner (its response notes the divergence when the applied target differs from this link).

add_document_entryA

Add a line item to a customer invoice, vendor bill, employee voucher, or credit note.

Only unposted documents accept entries — unpost_document first to amend a posted one. Amounts are decimal strings; the line total is quantity × price (plus tax when a taxtable is attached).

Args: document_type: "invoice", "bill", "voucher", or "credit_note". id: Document ID (e.g., "000001"). account: Income account for invoices / credit notes; expense account for bills and vouchers. Full path, %short GUID, or full GUID. description: Line item description. quantity: Quantity as a decimal string (e.g., "3"). price: Unit price as a decimal string (e.g., "125.00"). party_type: Credit notes only — disambiguates when a customer and vendor credit note share an ID. taxtable: Tax table name to apply. Optional. tax_included: Whether price already includes tax. notes: Optional entry notes. action: Optional entry action label (e.g., "Hours").

delete_documentA

Delete an UNPOSTED customer invoice, vendor bill, employee voucher, or credit note.

Posted documents are on the books — unpost_document first (payments block unposting; the audit trail outranks tidiness).

Args: document_type: "invoice", "bill", "voucher", or "credit_note". id: Document ID (e.g., "000001"). party_type: Owner side, credit notes only — pass it when a credit note's ID collides with a document of the same ID on the other side (ID counters are per type).

apply_credit_noteA

Net a posted credit note against a posted invoice or bill from the same owner. No cash moves — the credit balance transfers between lots on the same A/R or A/P account.

This is the most common credit-note settlement path: the bookkeeper issues a credit note against an overcharge, then nets it against the next invoice from that customer (or applies it to an outstanding bill on the vendor side). Use pay_document instead when the credit note will be settled by sending or receiving cash.

Args: credit_note_id: The credit note to apply (must be posted). applies_to_invoice_id: The target invoice/bill (must be posted, same owner, same currency, same A/R or A/P post account). Need not be the document the credit note was created against — that link is provenance, and the response notes the divergence when this target differs from it. amount: Decimal-string amount to apply, in the document currency. Defaults to min(credit_note_ remaining, target_remaining) — apply as much as possible. apply_date: ISO date for the netting transaction. Defaults to today. owner_type: Optional 'customer' or 'vendor' disambiguator for ID collisions.

list_documentsA

List customer invoices, vendor bills, employee vouchers, and credit notes.

Leads with a Showing X-Y of Z invoices (date range) line, then a compact one-line-per-invoice format by default. Page with offset; limit=0 returns the count only. Use verbose=true for structured JSON with GUIDs, dates, notes, etc.

Status vocabulary (shared by every invoice/bill tool): open = created and editable, not yet booked to A/R//A/P — not payable. posted = booked to A/R//A/P with a lot tracking its balance — payable. paid = posted with a zero remaining balance (lot closed). outstanding = posted with a remaining balance — the unpaid subset; get it directly from get_outstanding_documents rather than deriving it here. The status filter below covers document state (open/posted) only; settlement state lives on the lot.

Args: status: Filter by status: "posted" or "open", or omit for all. document_type: Filter to one document kind ("invoice", "bill", "voucher", "credit_note"). Omit for all. party_type: Filter by owner side ("customer", "vendor"). Omit for all. verbose: If false (default), compact text output — optimized for reading and token efficiency. If true, structured JSON, for when you need machine-readable fields rather than a report. limit: Page size (default 50, max 250). 0 = count only. job_id: Filter to invoices grouped under a specific job — useful for the "what's part of this engagement?" listing pattern. offset: 0-indexed first row to return (default 0).

get_documentA

Get full details for a customer invoice, vendor bill, employee voucher, or credit note, including line items.

Returns all entries with quantities, prices, and totals; the response's type field names the document kind.

Status vocabulary: open = editable, not yet booked; posted = on the books, payable; paid = remaining balance zero. The full definitions live on list_documents; the unpaid list is get_outstanding_documents.

Args: id: Document ID (e.g., "000001"). This is the human-readable ID, not the internal GUID. document_type: "invoice", "bill", "voucher", or "credit_note" — disambiguates when IDs collide across per-type counters. party_type: Owner side ("customer"/"vendor") — needed only for credit notes, which exist on both sides.

post_documentA

Post a customer invoice, vendor bill, employee voucher, or credit note to A/R or A/P.

Posting creates a transaction in the A/R or A/P account and makes the invoice official. Once posted, entries cannot be added.

For a foreign-currency document, the exchange rate is etched at posting and cannot be updated retroactively. If the latest price for the invoice currency is more than 7 days from the post date (the GNUCASH_FX_GUARD_DAYS window), posting is refused with a stale_fx_rate error — run create_price for a rate near the post date, then retry, or pass force=True to post with the stale rate (recorded in the response and audit log as fx_stale/"forced"). A rate beyond the 90-day staleness cap cannot be forced.

Args: id: Document ID (e.g., "000001"). post_account: A/R or A/P account path (e.g., "Assets:Accounts Receivable"). post_date: Date in ISO format (YYYY-MM-DD). Defaults to today. due_date: Payment due date (YYYY-MM-DD). Optional. description: Description for the posting transaction. Optional. document_type: "invoice", "bill", "voucher", or "credit_note" — disambiguates when IDs collide. party_type: Owner side, credit notes only. force: Override the stale-FX-rate guard and post with a 7–90 day stale rate. Default False.

unpost_documentA

Reverse a posted customer invoice, vendor bill, employee voucher, or credit note (each keeps its type through the round-trip).

Deletes the posting transaction and lot, and clears the invoice's posted-state metadata. The invoice returns to "open" state and can be edited or re-posted. Refuses if the invoice has any payments applied — void payments first, then unpost.

Args: id: Document ID (e.g., "000001"). document_type: "invoice", "bill", "voucher", or "credit_note" — disambiguates when IDs collide. party_type: Owner side, credit notes only.

pay_documentA

Record a payment against a posted customer invoice, vendor bill, employee voucher, or credit note.

Creates a payment transaction from the specified bank/cash account to the document's A/R or A/P account. Partial payments are supported.

dry_run=true rehearses the payment without booking it: the full validation, conversion, discount, and FX pipeline runs and the response shows the proposed splits, the remaining balance after, whether the invoice would settle in full, and any account the real call would auto-create. Same inputs, same code path — a rehearsal that succeeds is a payment that will book. Recommended before complex payments (cross-currency, discounts, credit-note refunds).

For cross-currency payments where the rate moved between post-date and pay-date, a realized FX gain/loss split is booked. Pass fx_account to control routing; otherwise the server picks the unique INCOME/EXPENSE account whose leaf name matches "fx", "forex", "foreign exchange", "currency gain/loss", "exchange gain/loss", or "currency translation". When zero or multiple match, the canonical Income:Foreign Exchange Gain/Loss is used (auto-created if absent), and an fx_notice is returned listing ambiguous candidates so you can pass fx_account explicitly next time.

For invoices with early-payment-discount terms (e.g., "2/10 Net 30" = 2% off if paid within 10 days), pass apply_discount=True to settle via discount. The tool validates that the invoice has discount terms, the payment date is within the discount window, and the shortfall matches the expected discount on pre-tax principal. Each failure mode rejects with a specific error rather than silently downgrading to a partial payment. discount_account controls routing the same way fx_account does (auto-resolves to Expenses:Sales Discounts for customer payments, Income:Purchase Discounts Taken for vendor bill payments).

Args: id: Document ID (e.g., "000001"). payment_account: Bank or cash account for payment (e.g., "Assets:Checking"). amount: Payment amount as decimal string (e.g., "500.00"). payment_date: Payment date (YYYY-MM-DD). Defaults to today. description: Description for the payment transaction. Optional. document_type: "invoice", "bill", "voucher", or "credit_note" — disambiguates when IDs collide. party_type: Owner side, credit notes only. fx_account: Optional INCOME or EXPENSE account to receive realized FX gain/loss (cross-currency payments only). Accepts a full path, %short GUID, or full 32-char GUID. apply_discount: When True, treat this payment as the final settlement and absorb the early-payment discount from the invoice's billterm. Default False — explicit opt-in. Hard-rejects on credit notes (refunds don't take discounts). discount_account: Optional INCOME or EXPENSE account to receive the discount split. Auto-resolves when omitted. Accepts full path, %short GUID, or full 32-char GUID. force: Override the stale-FX-rate guard. A cross-currency payment etches the rate at pay time; if the latest price is 7–90 days from the payment date the payment is refused with stale_fx_rate unless force=True (the override is recorded as fx_stale/"forced"). A rate beyond the 90-day cap cannot be forced. memo: Optional memo for the bank-account split (e.g., check number or wire reference). description names the whole transaction; memo annotates the cash movement. dry_run: When True, rehearse without writing — returns the proposed splits and projected outcome instead of booking. Default False.

Returns: status is "paid" when the document settles to zero, "partial" when a balance remains, and "would_pay" on dry runs — plus the amount paid, remaining balance, and transaction reference.

create_jobA

Create a job for a customer or vendor.

A job groups invoices (or bills) from one counterparty under a project-level container. Useful when a single customer has multiple distinct engagements (e.g., 'API Rewrite' and 'Q3 Maintenance') that should be reported on separately even though invoices flow to the same A/R.

Args: owner_id: Customer or vendor ID (e.g., "000001"). owner_type: "customer" or "vendor". Employees are not supported (no GnuCash desktop UI for employee jobs). name: Human-readable job name (e.g., "API Rewrite"). reference: Optional reference string (PO number, project code).

list_jobsA

List jobs, optionally filtered.

Leads with a Showing X-Y of Z jobs line; page with offset, or pass limit=0 for the count only.

Pass id for one job's full details (name, owner, active state, linked invoice/bill IDs) — the exact-lookup mode that replaced get_job.

Args: id: Job ID for a single-job detail lookup (e.g., "000001"). All other filters are ignored. owner_type: Filter by "customer" or "vendor". Omit for all. owner_id: Filter by specific customer or vendor ID (requires owner_type). active_only: If True (default), exclude inactive jobs. verbose: If false (default), compact text output — optimized for reading and token efficiency. If true, structured JSON, for when you need machine-readable fields rather than a report. limit: Page size (default 50, max 250). 0 = count only. offset: 0-indexed first row to return (default 0).

update_jobA

Update a job's name, reference, or active state.

Any subset of fields can be passed; unspecified fields are left unchanged. Returns a diff-style response.

Args: job_id: Job ID. name: New name (optional). reference: New reference (optional). active: New active flag — pass False to deactivate a completed job without deleting it (preserves history).

get_job_reportA

Per-job summary: billed / paid / outstanding totals across all linked invoices, plus the per-invoice breakdown.

Totals are returned as totals_by_currency (a dict keyed by ISO currency code) so the same shape works whether the job's invoices share a currency or span multiple. Both posted and unposted (draft) invoices are included — drafts contribute their face value as billed + outstanding with paid=0, so the report shows the full pipeline.

Args: job_id: Job ID (e.g., "000001").

delete_jobA

Delete a job.

Refuses by default when invoices/bills are linked to the job (data-loss prevention). force=True re-parents every linked invoice back to its underlying customer or vendor before deleting the job row, preserving invoice history. Use update_job(active=False) instead if you want to keep the job in place but mark the project done.

Args: job_id: Job ID. force: If True, re-parent linked invoices instead of refusing. Default False.

get_outstanding_documentsA

Get all posted customer invoices, vendor bills, employee vouchers, and credit notes with outstanding balances.

This is the authoritative unpaid list: outstanding = posted with a remaining balance > 0. One call answers "what is actually unpaid?" — no need to combine list_documents and get_document (full status vocabulary on list_documents).

Leads with a Showing X-Y of Z invoices (date range) line, then a compact one-line-per-doc format by default with action columns (due date, days past due, currency, BILL tag, owner). Sorted most-overdue-first so the bookkeeper sees the urgent items at the top. Page with offset; limit=0 returns the count only.

Use verbose=true for structured JSON with original_amount / amount_paid / amount_due breakdown — the shape pay_document workflows expect.

Args: party_type: Filter by "customer" or "vendor". Omit for all. customer_id: Filter by specific customer ID. vendor_id: Filter by specific vendor ID. verbose: If false (default), compact text output — optimized for reading and token efficiency. If true, structured JSON, for when you need machine-readable fields rather than a report. limit: Page size (default 50, max 250). 0 = count only. offset: 0-indexed first row to return (default 0).

vendor_spending_reportA

Get spending breakdown by vendor for a period.

Analyzes posted vendor bills to show total billed, total paid, and outstanding amounts per vendor.

Returns a compact aligned text table by default. Use verbose=true for the structured dict (programmatic consumers).

Args: start_date: Start of period (YYYY-MM-DD). end_date: End of period (YYYY-MM-DD). vendor_id: Optional filter to a specific vendor. verbose: If false (default), compact text output — optimized for reading and token efficiency. If true, structured JSON, for when you need machine-readable fields rather than a report. group_by: Optional "month", "quarter", or "year" — split the range into sub-period columns of total billed per vendor and return a multi-period TSV table. Overrides verbose.

get_book_summaryA

Get a compact overview of the entire GnuCash book.

Returns book path, currency, account structure, transaction counts, key balances, net worth, commodities, and scheduled transactions in a single text response. Use this first to orient yourself.

list_accountsA

List all accounts in the GnuCash chart of accounts.

Leads with a Showing X-Y of Z accounts line, then a compact one-line-per-account format by default. Page with offset; limit=0 returns the count only. Use verbose=true for full JSON with guid, type, commodity, etc.

To FIND an account without paging the whole chart, pass query — a case-insensitive substring matched against each account's full path and description (e.g. query="grocer" or query="4930" on a numbered chart). Results emit %short GUIDs that every account-taking tool accepts. For searching transactions by text or amount, use search_transactions.

Args: root: Filter to a subtree (e.g., "Expenses" for expense accounts only). verbose: If false (default), compact text output — optimized for reading and token efficiency. If true, structured JSON, for when you need machine-readable fields rather than a report. limit: Page size (default 50, max 250). 0 = count only. offset: 0-indexed first row to return (default 0). query: Case-insensitive substring filter on account path/description. Combines with root.

get_accountA

Get details for one account: type, commodity, description, placeholder flag, GUID, and hierarchy position.

Read-only. Returns {"error": "Account not found: ..."} when the ref matches nothing — nothing raises. Use list_accounts to discover refs in bulk, get_balance when you only need a number, get_account_slots for custom metadata (APR, credit_limit, ...).

Args: name: Account ref: full path (e.g. 'Assets:Bank:Checking'), %short GUID, or full 32-char GUID

get_balanceA

Get the balance of an account as of a specific date.

Defaults to today's date — future-dated transactions (scheduled payments, accrued interest) are excluded. To project a balance forward including future entries, pass an explicit as_of_date past today.

Args: account_name: Account ref: full path (e.g. 'Assets:Bank:Checking'), %short GUID, or full 32-char GUID as_of_date: Date in ISO format (YYYY-MM-DD). Defaults to today.

list_transactionsA

List transactions with optional filters.

Leads with a Showing X-Y of Z transactions (date range) line so a truncated view is never mistaken for the whole set. Page with offset; limit=0 returns the count only.

Compact format (default):

  • Unfiltered: DATE<TAB>guid<TAB>Description<TAB>splits

  • Filtered by account (register form): DATE<TAB>guid<TAB>±Amount<TAB>Description<TAB>other splits Column 3 is the signed impact on the filtered account; that account is dropped from the splits column.

Transactions with more than 4 splits collapse to the top 3 by |value| plus +N more — call get_transaction for the full breakdown.

Args: account: Filter by account name (switches output to register form) start_date: Start date in ISO format (YYYY-MM-DD) end_date: End date in ISO format (YYYY-MM-DD) limit: Page size (default 50, max 250). 0 = count only. offset: 0-indexed first row to return (default 0). verbose: If false (default), compact text output — optimized for reading and token efficiency. If true, structured JSON, for when you need machine-readable fields rather than a report.

get_transactionB

Get details for a specific transaction by GUID.

Args: guid: Transaction GUID (32-character hex string, or 8+ char prefix)

create_transactionsA

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.

enter_statementA

Enter a COMPLETE bank/card statement in one atomic call: create the new lines, claim the ones already in the book, and reconcile everything against the closing balance — all in one save, or nothing at all.

THE WORKFLOW (two calls around your judgment):

  1. dry_run=true (the DEFAULT) — transcribe the statement and get back a classification of every line: NEW (not in the book), MATCH (an existing unreconciled split corresponds), OVERLAP (already reconciled), AMBIGUOUS (several candidates). MATCH/AMBIGUOUS rows come with the candidate's full annotation (date, amount, description, notes, memo, short GUID) so you can adjudicate each one.

  2. Rule every MATCH/AMBIGUOUS row yourself, adapt annotations, confirm with the user.

  3. dry_run=false — NEW rows now carry interpreted description/notes and counter-splits; MATCH rows carry match=<split guid> claims. The server enters, claims, reconciles every statement-touched split at statement_date, and saves once.

TRANSCRIBE, DON'T INTERPRET (dry-run): amounts and balances go in EXACTLY as the statement prints them — for credit cards too (charges positive, balance as amount owed). The server applies the sign convention from the account's type; you never flip a sign. The gate opening + sum(lines) == closing must hold or the call rejects: transcribe every line.

INPUT — lines is a TSV block. Header: ref, date first, then any order of description, notes, raw, match, amount (required), then optional amt, acct, memo, qty counter-split groups (batch grammar). The statement account's own leg is SYNTHESIZED — never a column. Dry-run typically needs only::

ref<TAB>date<TAB>raw<TAB>amount
1<TAB>2026-07-03<TAB>POS DEBIT WHOLEFDS #123<TAB>-87.12
  • raw = the verbatim statement line; it lands on the bank leg's memo (provenance). description/notes are your interpretation (commit).

  • match = the split GUID this line claims instead of creating (from the dry-run candidates table). Claim rows may also carry raw (updates the claimed split's memo) and notes (updates the transaction's notes), and END at their last fixed column — they take no split cells. The claimed amount must equal the line amount exactly — fix the book first if they disagree.

  • A commit row with no counter-splits auto-fills from the most recent same-description 2-split transaction, adapted to the line amount (marked auto_filled_from:<guid>). The precedent must have exactly one leg on the statement account and no cross-commodity leg — anything else rejects with "supply explicit counter-splits". Explicit counter-splits must not name the statement account (its leg is synthesized).

SAFETY: the account's reconciled balance must tie to opening_balance (a prior unentered statement blocks commit), every created-vs-existing exact overlap must be explicitly claimed or forced, and the projected closing tie is verified BEFORE anything is written. The two force flags are INDEPENDENT: force_base=true lands onto an untied opening base (the consequent tie discrepancy is recorded, and duplicate detection STAYS ON); force_duplicates=true creates past exact twins you have adjudicated as distinct. Neither bypasses the statement's own self-check. After the save, the reconciled balance is read back and verified against the tie.

OUTPUT (dry-run): summary (class counts), lines (ref, class, cands, note — the note is the resolved disposition: the guard's refusal coaching verbatim, the auto-fill prediction, or "will claim …"), candidates — SELF-CONTAINED comparison rows sorted strongest-correspondence first (ref, candidate_guid, confidence, state, date_new/old + delta, amt_new/old + delta, cur, desc_new/old, notes_old, memo_old, cat_new/old, split_match, signals; _new = the statement line in book convention, _old = the existing split — never re-read your own input; cur is structurally blank on this surface), plus warnings (only when present; candidates likewise) and tie — the projected reconciled balance vs the closing, with a count of rows this exact payload would refuse at commit. The dry-run rehearses the SAME disposition procedure commit runs — force included. The tie is the only verdict; MATCH/AMBIGUOUS rows are yours to rule. OUTPUT (commit): results (ref, status, guid, note; status is created | claimed | skipped_duplicate, or on a refused statement rejected | statement_aborted — the note column carries the row's coaching and auto_filled_from:<guid> markers), plus, on success only, the new reconciled balance and the tie (a refusal returns just summary + results).

Args: account: Statement account ref (path, %short, or GUID). BANK/CASH/ASSET/CREDIT/LIABILITY only. statement_date: The statement's closing date (YYYY-MM-DD); every touched split reconciles at it. opening_balance: Opening balance, exactly as printed. closing_balance: Closing balance, exactly as printed. lines: The TSV block described above. dry_run: DEFAULT TRUE — the rehearsal is the workflow. force_base: Land onto an untied opening base; the tie discrepancy is recorded, twin detection stays on. force_duplicates: Create past exact unclaimed twins (you adjudicated them as distinct charges). show_all: Dry-run only. Lines with MEDIUM/HIGH candidates suppress their LOW amount-coincidences (the cands column notes "+N LOW suppressed"); show_all=true lists everything.

search_transactionsA

Search transactions by description, memo, notes, or amount.

Compact format (default): DATE<TAB>guid<TAB>Description<TAB>splits Transactions with more than 4 splits collapse to the top 3 by |value| plus +N more — call get_transaction for the full breakdown. Leads with a Showing X-Y of Z transactions line; page with offset, or pass limit=0 for the count.

Args: query: Search query string. For amount, supports: exact ("100"), greater (">100"), less ("<100"), range ("100-200") field: Field to search: 'description', 'memo', 'notes', or 'amount' limit: Page size (default 50, max 250). 0 = count only. offset: 0-indexed first row to return (default 0). verbose: If false (default), compact text output — optimized for reading and token efficiency. If true, structured JSON, for when you need machine-readable fields rather than a report.

create_accountA

Create a new account in the chart of accounts.

Args: name: Account name (e.g., "AI Subscriptions"). account_type: One of ASSET, BANK, CASH, CREDIT, EQUITY, EXPENSE, INCOME, LIABILITY, MUTUAL, STOCK, RECEIVABLE, PAYABLE. parent: Parent account ref (full path, %short GUID, or full 32-char GUID). Omit for top-level. description: Optional description. placeholder: Container-only account. Default False. commodity: ISO currency code ("USD") or stock/fund symbol ("VTSAX"). Defaults to book's default currency. commodity_namespace: "CURRENCY" (default), "FUND", or an exchange ("NASDAQ", "NYSE"). Required with non-currency commodities. notes: Optional free-text notes (max 4096 bytes). Shows in GnuCash desktop's account editor Notes field.

update_accountA

Update an existing account's properties.

Args: name: Account ref to update (full path e.g. "Expenses:Groceries", %short GUID, or full 32-char GUID) new_name: New name for the account (just the leaf name, not full path) description: New description placeholder: New placeholder status (true = container only) account_type: New account type (e.g., "CREDIT", "BANK"). Only changes within the same debit/credit polarity are allowed — e.g., LIABILITY to CREDIT, ASSET to BANK. Cross-polarity changes (e.g., ASSET to LIABILITY) are blocked. notes: New notes (max 4096 bytes; shared with GnuCash desktop's Notes field). Pass "" to clear.

move_accountA

Move an account — children and balances ride along — under a new parent in the hierarchy.

No transaction data changes: only the account's position (and therefore every descendant's full path) is rewritten. Errors, changing nothing, if either ref matches no account, if the move would create a cycle (new parent is the account itself or one of its descendants), or if the new parent already has a child of the same name. %short GUIDs survive the move; saved full paths do not. Use update_account to rename in place instead of moving.

Args: name: Account ref to move (full path e.g. "Expenses:Old:Account", %short GUID, or full 32-char GUID) new_parent: New parent account ref (full path, %short GUID, or full 32-char GUID)

delete_accountB

Delete an account from the chart of accounts.

Safeguards prevent deletion if the account has children or transactions.

Args: name: Account ref to delete (full path, %short GUID, or full 32-char GUID)

delete_transactionA

Delete one transaction by GUID — or several in one call.

Safeguards prevent deletion if a transaction has reconciled splits (force=true overrides) or is an invoice's posting record (unpost_document first).

Pass a LIST of GUIDs to delete several in one book open / one save. The batch is all-or-nothing: every guid is validated before anything is deleted, so a bad guid rejects the whole call with nothing removed. Response for a list is {status, count, transactions: [{guid, description}]}; a single guid returns the single-object shape as before.

Args: guid: Transaction GUID (32-char hex or 8+ char prefix), or a list of them. force: Allow deleting transactions with reconciled splits.

update_transactionsA

Update MANY transactions with per-row values (bulk edit).

INPUT — updates is a TSV block: header guid plus any of description, notes, date (at least one), then one row per transaction::

guid<TAB>description<TAB>notes
56926ac2<TAB>PayPal Credit Payment<TAB>Resolved — card payment
7f0fc117<TAB><TAB>Netflix subscription, $22.10/mo

An EMPTY cell leaves that field UNCHANGED. To blank a field, opt in with a clear column: its cell names the fields to clear on that row (notes or description,notes) — explicit per row, so a sparse batch can never mass-erase by accident. date is not clearable; a row that sets and clears the same field rejects. Splits and memos are not updatable here (replace_splits)::

guid<TAB>notes<TAB>clear
56926ac2<TAB>Verified subscription<TAB>
7f0fc117<TAB><TAB>notes

One book open, one save; on_error="abort" (default) sinks the batch on any bad row, "skip" keeps good rows. Date moves on transactions with reconciled splits are rejected per row unless force=true (they shift the transaction out of its reconciled statement period). Returns a results TSV keyed by your input guids. This is the canonical update tool for one transaction or many (the former update_transaction tool was removed; this replaces it — same value across many transactions = the same cells repeated per row).

replace_splitsA

Replace all splits in a transaction with a new set.

Replace all splits in a transaction with a completely new set. The transaction's currency, description, date, and notes are preserved. New splits must balance to zero.

A new split that reproduces an existing one (same account, amount, and quantity) is an UNCHANGED leg: it keeps the old split's memo (supply a memo to override) and its reconcile state. So recategorizing the expense leg of a reconciled bank transaction is safe — resubmit the bank leg as-is and only the changed leg resets.

Args: guid: Transaction GUID (32-character hex string, or 8+ char prefix) splits: Complete new set of splits. Each split needs: - 'account' (required): Account ref — full path, %short GUID, or full 32-char GUID - 'amount' (required): Value in transaction currency, as a decimal string - 'quantity' (optional): Amount in account's commodity, as a decimal string. Required if account commodity differs from transaction currency. - 'memo' (optional): Split memo force: Required only when the replacement would CHANGE a reconciled split (or remove splits from lots) — unchanged reconciled legs are preserved without it.

list_commoditiesA

List all commodities (currencies, stocks, etc.) in the book.

Leads with a Showing X-Y of Z commodities line, then a compact one-line-per-commodity format by default. Page with offset; limit=0 returns the count only. Use verbose=true for structured JSON with fraction, latest prices, etc.

THE PRICE-UPDATE WORK LIST: stale_days=30, held_only=true returns exactly the commodities needing fresh quotes, each marked Nd stale or no price on file. Look the quotes up, then record them all in one create_prices call.

Args: verbose: If false (default), compact text output — optimized for reading and token efficiency. If true, structured JSON, for when you need machine-readable fields rather than a report. limit: Page size (default 50, max 250). 0 = count only. offset: 0-indexed first row to return (default 0). stale_days: Only commodities whose latest market price is at least this many days old, including never-priced ones, excluding the book default currency. Omit for the unfiltered list. held_only: Only commodities some real account is denominated in. Filters AND-combine.

create_commodityA

Create a new commodity (stock, mutual fund, etc.).

Args: mnemonic: Symbol (e.g., "VTSAX"). Unique within namespace. fullname: Human-readable name. namespace: "FUND" (default) for mutual funds, "NASDAQ"/"NYSE"/ "AMEX" for stocks, or any custom string. fraction: Smallest fractional unit. 1 = whole units, 100 = 2 decimals, 10000 = 4 decimals (default, shares), 1000000 = 6 decimals (crypto). cusip: Optional CUSIP/ISIN identifier.

create_pricesA

Record MANY prices in one call (bulk quote entry).

INPUT — prices is a TSV block: a header row, then one row per price. Required columns ref, commodity, date, value; optional columns extend the header IN ORDER: ns, cur, source, type (stop anywhere; rows may end early; empty cells take defaults)::

ref<TAB>commodity<TAB>date<TAB>value<TAB>cur<TAB>source
1<TAB>VTSAX<TAB>2026-07-21<TAB>148.32<TAB><TAB>web:yahoo
2<TAB>EUR<TAB>2026-07-21<TAB>1.0845<TAB><TAB>web:ecb
  • ref: your correlation key, echoed in results.

  • ns: commodity namespace; empty auto-resolves when the symbol is unambiguous across namespaces.

  • cur: quote currency; empty = book default.

  • source: where the quote came from (provenance — default "user:price"); type: nav/last/bid/ask.

Per-row semantics are create_price's exactly: an existing price with the same commodity/currency/date/source is UPDATED in place (status: updated), never duplicated. One book open, one save, on_error="abort" (default) sinks the whole batch on any bad row; dry_run=true previews as would_create / would_update.

The companion work list: list_commodities(stale_days=30, held_only=true).

create_priceA

Record a price for a commodity (stock, NAV, exchange rate).

An existing price with the same commodity/currency/date/source is updated rather than duplicated.

Args: commodity: Symbol (e.g., "VTSAX"). namespace: Commodity namespace (e.g., "FUND", "NASDAQ"). value: Price per unit as decimal string (e.g., "250.45"). currency: ISO currency code. Defaults to the book's default currency — so create_price(commodity="USD", value="7.30") on a CNY-default book stores "1 USD = 7.30 CNY" (the natural reading). Pass explicitly for cross-currency pairs that don't involve the book default. date: ISO date (YYYY-MM-DD). Defaults to today. price_type: "nav" (default, mutual funds), "last", "bid", "ask", or "unknown". source: Source identifier. Default "user:price".

delete_priceA

Delete a single price entry.

Identifies the price by (commodity, namespace, date). Pass source to disambiguate when multiple prices exist on the same commodity+date (e.g. one user-entered and one fetched from a feed).

Args: commodity: Symbol (e.g., "VTSAX", "USD", "EUR"). namespace: Namespace (e.g., "FUND", "CURRENCY"). date: ISO date (YYYY-MM-DD) of the price to delete. source: Optional source tag (e.g., "user:price", "user:yfinance"). Required when multiple prices exist on the same commodity+date.

get_pricesA

Get price history for a commodity.

Leads with a Showing X-Y of Z prices (date range) line, then a compact aligned text table by default. Page with offset; limit=0 returns the count only. Use verbose=true for the full structured envelope (prices list, showing, total, offset, count).

Args: commodity: Symbol of the commodity (e.g., "VTSAX"). namespace: Namespace of the commodity (e.g., "FUND"). start_date: Optional start date filter (YYYY-MM-DD). end_date: Optional end date filter (YYYY-MM-DD). currency: Optional currency filter (e.g., "USD"). limit: Page size (default 50, max 250). 0 = count only. verbose: If false (default), compact text output — optimized for reading and token efficiency. If true, structured JSON, for when you need machine-readable fields rather than a report. offset: 0-indexed first row to return (default 0).

get_latest_priceA

Get the most recent price for a commodity.

Args: commodity: Symbol of the commodity (e.g., "VTSAX"). namespace: Namespace of the commodity (e.g., "FUND"). currency: Currency for the price. Defaults to the book's default currency. Pass explicitly to get a price quoted in a non-default currency.

Returns: JSON with date, value, type, and source of most recent price. Returns null if no price exists.

create_lotA

Create a new, empty lot for cost basis tracking.

Additive: the lot starts open with no splits attached, and nothing else in the book changes. A lot groups one purchase with its later sales so cost basis and capital gain compute per purchase. The full flow: create_lot → create_transactions (the buy, a one-row batch) → assign_split_to_lot → calculate_lot_gain; the lot auto-closes when its assigned splits net to zero shares. Errors if the account ref matches nothing. Skip this tool when you only want a valuation — get_book_summary and balance_sheet price holdings without lots.

Args: account: Account ref for the investment account: full path (e.g., "Assets:Investments:VTSAX"), %short GUID, or full 32-char GUID. title: Lot identifier (e.g., "VTSAX 2026-01-15 purchase"). notes: Optional notes.

list_lotsA

List all lots for an investment account.

Leads with a Showing X-Y of Z lots line, then a compact one-line-per-lot format by default. Page with offset; limit=0 returns the count only. Use verbose=true for full JSON with guid, title, notes, etc.

Args: account: Account ref (full path, %short GUID, or full 32-char GUID). include_closed: If True, include fully-sold lots. Default False. verbose: If false (default), compact text output — optimized for reading and token efficiency. If true, structured JSON, for when you need machine-readable fields rather than a report. limit: Page size (default 50, max 250). 0 = count only. offset: 0-indexed first row to return (default 0).

get_lotA

Get detailed information about a lot.

Args: guid: Lot GUID (or 8+ char prefix).

Returns: JSON with lot details including all splits: - title, notes, is_closed - splits: list of all splits with date, quantity, value - summary: total quantity, cost basis, cost per share

assign_split_to_lotA

Assign a transaction split to a lot.

Use after creating a buy/sell transaction to link the investment account split to its lot for cost basis tracking.

Args: split_guid: GUID of the split (from transaction's investment account). 8+ char prefix accepted. lot_guid: GUID of the lot (or 8+ char prefix).

Workflow: 1. create_lot("Assets:VTSAX", "VTSAX Jan 2026") 2. create_transactions(...one-row batch buying 10 shares, qty column on the investment leg...) 3. assign_split_to_lot(investment_split_guid, lot_guid)

calculate_lot_gainA

Calculate potential or actual capital gain for a lot.

If shares and sale_price provided, calculates hypothetical gain. Otherwise uses lot's current state and latest price.

Args: lot_guid: Lot GUID (or 8+ char prefix). shares: Optional number of shares to calculate for. Defaults to all remaining shares. sale_price: Optional sale price per share. Defaults to latest price for the commodity.

close_lotA

Mark a lot as closed.

Use when a lot is fully sold but wasn't automatically marked closed, or to manually close a lot with zero shares.

Args: guid: Lot GUID (or 8+ char prefix).

Note: Lots are automatically marked closed when their quantity reaches zero through assigned splits. This tool is for manual cleanup.

set_reconcile_stateA

Set the reconciliation state for a split.

Args: split_guid: GUID of the split to update (32-character hex string, or 8+ char prefix) state: New reconcile state: 'n' (new), 'c' (cleared), 'y' (reconciled) reconcile_date: Date in ISO format (YYYY-MM-DD). Required for 'y', defaults to today.

get_reconciliation_statusA

Per-account reconciliation table behind the dashboard's aggregate counts — answers "WHICH accounts are never reconciled / dormant / behind?"

One line per reconcilable account with activity, bucketed exactly as the dashboard classifies them: behind (most-behind first, with pending-split counts), never, current, dormant ($0, fully reconciled, idle), and excluded (opted out via the account's no_reconcile slot — the right setting for loans, escrow payables, and other statement-less accounts: set_account_slot(account, "no_reconcile", "1"). Reporting-only; reconcile tools still work on excluded accounts).

Args: verbose: If false (default), compact text output — optimized for reading and token efficiency. If true, structured JSON, for when you need machine-readable fields rather than a report. limit: Page size (default 50, max 250). 0 = count only. offset: 0-indexed first row to return.

get_unreconciled_splitsA

Get unreconciled splits for an account.

Leads with a Showing X-Y of Z splits indicator, then one line per split, then a summary footer reflecting the full unreconciled set (so the headline is honest even when individual lines are clipped). Page with offset; limit=0 returns the count only.

Use verbose=true for structured JSON with split GUIDs, amounts, totals, and the showing indicator as a structured field.

Args: account: Account ref: full path (e.g. 'Assets:Bank:Checking'), %short GUID, or full 32-char GUID as_of_date: Only include splits on or before this date (YYYY-MM-DD) verbose: If false (default), compact text output — optimized for reading and token efficiency. If true, structured JSON, for when you need machine-readable fields rather than a report. limit: Page size (default 50, max 250). 0 = count only. offset: 0-indexed first row to return (default 0).

reconcile_accountA

Reconcile splits against a statement balance.

SIGN CONVENTION: statement_balance is the ACCOUNT's balance in GnuCash's signs — for liability accounts (credit cards) a $5,000 owed balance is "-5000", not "5000".

Two modes:

  • Targeted (split_guids=[...]): reconcile exactly the listed splits (SPLIT guids from get_unreconciled_splits, not transaction guids). Use when statement and book disagree and you need to pick a subset.

  • Bulk (reconcile_all=true): reconcile every unreconciled split dated on or before through_date (default: statement_date — a statement reconciliation is bounded by the statement). One call, no GUID round-trip.

Both modes verify the resulting reconciled balance ties to statement_balance before mutating; mismatch rejects with the discrepancy amount.

TYPICAL STATEMENT FLOW (credit card or bank): batch-enter the statement's transactions (create_transactions), then reconcile_all with the statement's closing date and balance. Multi-month catch-up: enter all months in one batch, then one reconcile_all per statement, oldest first — the through_date default keeps each sweep inside its own statement.

Args: account: Account ref: full path (e.g. 'Assets:Bank:Checking'), %short GUID, or full 32-char GUID statement_date: Statement ending date (YYYY-MM-DD) statement_balance: Expected balance from statement (as string, e.g., '1234.56') split_guids: List of split GUIDs to reconcile (targeted mode). Omit for bulk mode. reconcile_all: When true, reconcile all unreconciled splits up to through_date. through_date: Date filter for bulk mode (YYYY-MM-DD); defaults to statement_date.

void_transactionA

Void a transaction (proper accounting void, not delete).

Voiding preserves the transaction for audit purposes but zeroes out all split values. Use this instead of delete when you need to maintain an audit trail.

Args: guid: Transaction GUID to void (32-character hex string, or 8+ char prefix) reason: Reason for voiding (required for audit trail)

unvoid_transactionA

Restore a voided transaction to its pre-void amounts.

The inverse of void_transaction: original split values come back from the slots the void stored, and the void markers and reason are cleared. All-or-nothing — if any split's stored void data is missing, the tool errors and restores nothing (no partial resurrection). Errors too if the transaction isn't found or isn't voided. Restored splits return as UNreconciled ('n'), so a previously reconciled transaction needs reconciling again afterward.

Args: guid: Transaction GUID to unvoid (32-character hex string, or 8+ char prefix)

spending_by_categoryA

Get spending breakdown by expense category for a period.

Returns a compact aligned text table by default. Use verbose=true for the structured dict (programmatic consumers, plotting).

Args: start_date: Start of period (YYYY-MM-DD), inclusive. No calendar snapping — for calendar-month figures pass full month boundaries (e.g. 2026-05-01 to 2026-07-31). end_date: End of period (YYYY-MM-DD), inclusive. depth: Hierarchy depth for grouping (1 = top-level categories, 2 = subcategories) verbose: If false (default), compact text output — optimized for reading and token efficiency. If true, structured JSON, for when you need machine-readable fields rather than a report. group_by: Optional "month", "quarter", or "year" — split the range into sub-period columns and return a multi-period TSV table (category rows, one column per period plus Total and Avg). Overrides verbose.

income_by_sourceA

Get income breakdown by source for a period.

Returns a compact aligned text table by default. Use verbose=true for the structured dict.

Args: start_date: Start of period (YYYY-MM-DD) end_date: End of period (YYYY-MM-DD) depth: Hierarchy depth for grouping (1 = top-level categories, 2 = subcategories) verbose: If false (default), compact text output — optimized for reading and token efficiency. If true, structured JSON, for when you need machine-readable fields rather than a report. group_by: Optional "month", "quarter", or "year" — split the range into sub-period columns and return a multi-period TSV table (source rows, one column per period plus Total and Avg). Overrides verbose.

balance_sheetA

Generate a balance sheet as of a specific date.

Shows assets, liabilities, and equity with account breakdowns. A = L + E holds by construction; non-zero unrealized P&L appears as a synthetic equity row.

Args: as_of_date: Date in ISO format (YYYY-MM-DD). Defaults to today — matching get_book_summary's implicit cutoff so cross-tool comparisons agree without threading the same date into both calls. Pass an explicit date for historical snapshots.

net_worthA

Calculate net worth (assets minus liabilities), as one value or a trajectory.

Read-only. Valuation is as-of: each date values holdings at the latest market rate on or before it, in the book's default currency; voided transactions are excluded. end_date alone gives one number; add start_date + interval for a series (one value per interval step from start_date, end_date always included as the last row). For the full A = L + E statement with per-account detail, use balance_sheet; this tool is the headline number and its trend. The dashboard's trajectory section is this same calculation.

Args: end_date: Calculate net worth as of this date (YYYY-MM-DD) start_date: Optional start date for time series (YYYY-MM-DD) interval: Optional interval for time series: 'month', 'quarter', or 'year'

cash_flowA

Calculate cash flow (inflows and outflows) for a period.

Scope is BANK and CASH accounts by default. Credit-card and investment movements are not cash flow (they're liability / asset changes — use balance_sheet). An explicit account= of any type works but the default scope is narrow.

Internal transfers (transactions with no INCOME or EXPENSE leg — transfer to savings, currency wallet shuffle, paying a credit card from checking) are filtered by default. The default totals answer "where did money come from and where did it go?" rather than "every debit and credit." Pass include_transfers=true for the gross flow (e.g. for reconciling against a bank statement).

Args: start_date: Start of period (YYYY-MM-DD) end_date: End of period (YYYY-MM-DD) account: Optional specific account to analyze (defaults to all cash/bank accounts) include_transfers: When False (default), filter internal transfers. When True, include every cash/bank movement regardless of category. group_by: Optional "month", "quarter", or "year" — split the range into sub-period columns and return an Inflows / Outflows / Net trend table (TSV).

debt_payoff_planA

Calculate an avalanche-method debt payoff schedule with YETI multiplier.

Auto-discovers CREDIT/LIABILITY accounts that have an 'apr' slot set. Set APRs via set_account_slot (e.g., set_account_slot("Liabilities:Visa", "apr", "23.49")).

Assumptions the schedule is computed under: interest compounds monthly at apr/12 on the running balance; one payment per debt per month starting this month; extra budget beyond the minimums goes to the highest-APR debt first (avalanche); balances are as of today in the book default currency; amounts round to 0.01. Debts the plan cannot include are CONFESSED in ⚠ lines, never silently dropped: foreign-currency debts with no FX rate, loans with no minimum_payment/loan_term_months to estimate a payment from, and balance-carrying debts with no 'apr' slot.

Returns a compact text summary by default — kill order with balances/APRs/payoff months, YETI line, totals, debt-free date. Use verbose=true for the structured dict (per-account interest_paid / credit_limit / minimum_payment, plus the structured yeti block) suitable for programmatic consumers.

YETI (Your Expense's True Impact) shows the true cost of a purchase when carrying debt: "A $1.00 purchase will cost you $1.68 by the time your debt is paid off."

Args: monthly_budget: Total monthly amount available for all debt payments combined additional_purchase: Dollar amount to calculate YETI for (default "1.00") verbose: If false (default), compact text output — optimized for reading and token efficiency. If true, structured JSON, for when you need machine-readable fields rather than a report.

create_scheduled_transactionA

Create a recurring transaction template.

Args: name: Scheduled transaction name (e.g., "Monthly Rent"). description: Transaction description at instantiation. splits: List of split dicts, e.g. [{"account": "Expenses:Rent", "amount": "1850.00"}, ...]. amount / quantity must be decimal strings; quantity is required when an account's commodity differs from the template's transaction currency. start_date: First occurrence (YYYY-MM-DD). frequency: "weekly", "biweekly" (2w), "monthly", "bimonthly" (2mo), "quarterly" (3mo), or "yearly". end_date: Optional last occurrence (YYYY-MM-DD). enabled: Active. Default True. notes: Transaction notes applied to every instantiated transaction (what the payment is — visible in GnuCash's double-line register view). currency: ISO code denominating every instantiated transaction; defaults to the book default. Use when no leg is in the book currency (a USD-to-USD card payment scheduled inside a CNY book) so amounts are the foreign currency's own numbers. Not updatable after creation — delete and recreate to change it.

list_scheduled_transactionsA

List all scheduled transactions.

Leads with a Showing X-Y of Z scheduled transactions line, then a compact one-line-per-schedule format by default. Page with offset; limit=0 returns the count only. Use verbose=true for structured JSON with GUIDs, splits, dates, etc.

Args: enabled_only: If True, only show enabled schedules. Default True. verbose: If false (default), compact text output — optimized for reading and token efficiency. If true, structured JSON, for when you need machine-readable fields rather than a report. limit: Page size (default 50, max 250). 0 = count only. offset: 0-indexed first row to return (default 0).

get_upcoming_transactionsA

Get scheduled transactions due within a time window.

This is the "what bills are coming up?" query. Leads with a Showing X-Y of Z upcoming transactions (date range) line, soonest first. Page with offset; limit=0 returns the count only.

Args: days: Look ahead window in days. Default 14. verbose: If false (default), compact text output — optimized for reading and token efficiency. If true, structured JSON, for when you need machine-readable fields rather than a report. limit: Page size (default 50, max 250). 0 = count only. offset: 0-indexed first row to return (default 0).

create_transaction_from_scheduledA

Create an actual transaction from a scheduled template.

Args: guid: Scheduled transaction GUID (or 8+ char prefix). transaction_date: Date for the transaction. Defaults to next occurrence.

update_scheduled_transactionA

Update a scheduled transaction.

Args: guid: Scheduled transaction GUID (or 8+ char prefix). enabled: Enable or disable. end_date: Three-state field for the schedule's end date.

    - Omit (or pass ``null``): leave unchanged.
    - Pass ``"YYYY-MM-DD"``: set to that date.
    - Pass ``""`` (empty string): clear the existing
      end date back to "no end" (open-ended schedule).

    The empty-string sentinel exists because MCP tool
    schemas don't easily express "set to null" as a
    distinct value from "no change supplied" — both
    arrive as Python ``None``. Empty-string is the
    explicit "clear it" signal.
notes: Instantiation notes applied to transactions
    created from this schedule going forward (existing
    transactions untouched). Same three-state
    convention as end_date: text to set, ``""`` to
    clear, omit to leave unchanged.
delete_scheduled_transactionA

Delete a scheduled transaction.

Does not affect transactions already created from this schedule.

Args: guid: Scheduled transaction GUID (or 8+ char prefix).

Prompts

Interactive templates invoked by user choice

NameDescription

No prompts

Resources

Contextual data attached and managed by the client

NameDescription
accounts_resourceFull chart of accounts from the GnuCash book. Resources are whole-snapshot reads, so this unwraps the paginated envelope and returns the bare account list (up to the server cap).

TDQS

A3.8/5.0

Scored across 87 tools

Disambiguation4/5

Most tools target a distinct resource+action (create_account vs update_account vs delete_account), and workflows like document status are carefully explained. The main ambiguities are the single-vs-bulk price pairs (create_price/create_prices, get_prices/get_latest_price) and the overlapping transaction entry tools (create_transactions vs enter_statement), though descriptions do disambiguate them.

Naming Consistency3/5

The core follows a consistent verb_noun pattern (list_accounts, create_transactions, delete_price, reconcile_account), but many report-style tools break it with noun phrases like balance_sheet, cash_flow, net_worth, spending_by_category, and vendor_spending_report. The mix is readable but not uniform.

Tool Count2/5

87 tools is far beyond a typical practical surface and will overwhelm agents even though GnuCash is a broad domain. Many bulk/single and report variants inflate the count; the server would be more usable consolidated to ~50 tools.

Completeness4/5

Coverage is impressively deep: accounts, transactions, reconciliation, documents, parties, jobs, budgets, commodities, prices, lots, scheduled transactions, and reporting all have lifecycle or workflow support. Notable gaps remain — billterms have no update/delete, documents have no header-edit or entry-removal tools, and backups cannot be listed or restored via the API.

Maintenance

ActivityActive
ResponsivenessSlow