From 7d41b99707aa51c79c26553ec71d5c3b42eba9f1 Mon Sep 17 00:00:00 2001 From: Stephen Waits <1+swaits@noreply.git.swaits.com> Date: Sun, 11 Oct 2026 01:37:31 +0000 Subject: [PATCH] First slice: ledger-store (journal+blobs+sqlite) and ledger-mcp (18 tools, receipt policy) --- crates/ledger-mcp/src/types.rs | 568 +++++++++++++++++++++++++++++++++ 1 file changed, 568 insertions(+) create mode 100644 crates/ledger-mcp/src/types.rs diff --git a/crates/ledger-mcp/src/types.rs b/crates/ledger-mcp/src/types.rs new file mode 100644 index 0000000..ad9ea91 --- /dev/null +++ b/crates/ledger-mcp/src/types.rs @@ -0,0 +1,568 @@ +//! Tool parameter and output types. Amounts cross MCP as decimal strings, +//! never JSON floats. + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +// --------------------------------------------------------------------------- +// Chart and accounts +// --------------------------------------------------------------------------- + +/// Parameters for `books.add_account`. +#[derive(Debug, Deserialize, JsonSchema)] +pub struct AddAccountParams { + /// Account code, e.g. "1000". Must be unique. + pub code: String, + /// Human name, e.g. "Business Checking". + pub name: String, + /// One of: asset, liability, equity, revenue, expense. + pub account_type: String, + /// Parent account id for hierarchy. Omit for top-level. + pub parent: Option, + /// True for header accounts that never take postings. + pub placeholder: Option, +} + +/// Result of `books.add_account`. +#[derive(Debug, Serialize, JsonSchema)] +pub struct AddAccountOut { + /// The new account's id. + pub account_id: u64, +} + +/// One account in the chart. +#[derive(Debug, Serialize, JsonSchema)] +pub struct ChartAccount { + /// Account id. + pub id: u64, + /// Account code. + pub code: String, + /// Human name. + pub name: String, + /// Account type. + pub account_type: String, + /// Parent account id, if any. + pub parent: Option, + /// True for header accounts. + pub placeholder: bool, + /// Current balance in the account's normal side, as a decimal string. + /// Positive means a normal balance. + pub balance: String, +} + +/// Result of `books.chart`. +#[derive(Debug, Serialize, JsonSchema)] +pub struct ChartOut { + /// Accounts in code order. + pub accounts: Vec, + /// Base currency code. + pub base_currency: String, +} + +// --------------------------------------------------------------------------- +// Reports +// --------------------------------------------------------------------------- + +/// One trial-balance line. +#[derive(Debug, Serialize, JsonSchema)] +pub struct TrialBalanceLineOut { + /// Account code. + pub code: String, + /// Account name. + pub name: String, + /// Debit balance as a decimal string, or "0". + pub debit: String, + /// Credit balance as a decimal string, or "0". + pub credit: String, +} + +/// Result of `books.trial_balance`. +#[derive(Debug, Serialize, JsonSchema)] +pub struct TrialBalanceOut { + /// Lines in code order. + pub lines: Vec, + /// Sum of the debit column, decimal string. + pub total_debits: String, + /// Sum of the credit column, decimal string. + pub total_credits: String, + /// True when debits equal credits exactly. + pub balanced: bool, +} + +/// Parameters for `books.profit_and_loss`. +#[derive(Debug, Deserialize, JsonSchema)] +pub struct ProfitAndLossParams { + /// Period start, YYYY-MM-DD. Defaults to the beginning of time. + pub start: Option, + /// Period end, YYYY-MM-DD. Defaults to today. + pub end: Option, +} + +/// Result of `books.profit_and_loss`. +#[derive(Debug, Serialize, JsonSchema)] +pub struct ProfitAndLossOut { + /// Period start, YYYY-MM-DD. + pub start: String, + /// Period end, YYYY-MM-DD. + pub end: String, + /// Total revenue, decimal string. + pub revenue: String, + /// Total expenses, decimal string. + pub expenses: String, + /// Revenue minus expenses, decimal string. Positive is profit. + pub net_income: String, +} + +/// Parameters for `books.balance_sheet`. +#[derive(Debug, Deserialize, JsonSchema)] +pub struct BalanceSheetParams { + /// As of this date, YYYY-MM-DD. Defaults to today. + pub as_of: Option, +} + +/// Result of `books.balance_sheet`. +#[derive(Debug, Serialize, JsonSchema)] +pub struct BalanceSheetOut { + /// As of this date, YYYY-MM-DD. + pub as_of: String, + /// Total assets, decimal string. + pub assets: String, + /// Total liabilities, decimal string. + pub liabilities: String, + /// Total equity including current earnings, decimal string. + pub equity: String, + /// Net income to date, folded into equity, decimal string. + pub current_earnings: String, + /// True when assets equal liabilities plus equity exactly. + pub balanced: bool, +} + +// --------------------------------------------------------------------------- +// Entries +// --------------------------------------------------------------------------- + +/// Parameters for `books.draft_entry`. +#[derive(Debug, Deserialize, JsonSchema)] +pub struct DraftEntryParams { + /// Entry date, YYYY-MM-DD. + pub date: String, + /// Description of what happened. + pub description: String, +} + +/// Result of `books.draft_entry`. +#[derive(Debug, Serialize, JsonSchema)] +pub struct DraftEntryOut { + /// The draft's entry id. Use it with `books.add_line`. + pub entry_id: u64, + /// Always "draft" — drafts are not journaled until posted. + pub status: String, +} + +/// Parameters for `books.add_line`. +#[derive(Debug, Deserialize, JsonSchema)] +pub struct AddLineParams { + /// Draft entry id from `books.draft_entry`. + pub entry_id: u64, + /// Account id, or account code (e.g. "1000"). + pub account: String, + /// "debit" or "credit". + pub side: String, + /// Exact decimal amount as a string, e.g. "123.45". Never a float. + pub amount: String, +} + +/// Result of `books.add_line`. +#[derive(Debug, Serialize, JsonSchema)] +pub struct AddLineOut { + /// True when the line was added. + pub ok: bool, +} + +/// One line of a posting preview. +#[derive(Debug, Serialize, JsonSchema)] +pub struct PreviewLine { + /// Account id. + pub account_id: u64, + /// Account code. + pub code: String, + /// Account name. + pub name: String, + /// "debit" or "credit". + pub side: String, + /// Line amount, decimal string. + pub amount: String, + /// Account balance before posting, decimal string. + pub balance_before: String, + /// Account balance after posting, decimal string. + pub balance_after: String, +} + +/// One attached receipt in a preview. +#[derive(Debug, Serialize, JsonSchema)] +pub struct PreviewReceipt { + /// Attachment id. + pub attachment_id: u64, + /// Original file name. + pub file_name: String, + /// SHA-256 of the bytes. + pub sha256: String, +} + +/// Parameters for `books.post_entry`. +#[derive(Debug, Deserialize, JsonSchema)] +pub struct PostEntryParams { + /// Draft entry id. + pub entry_id: u64, + /// Pass true to post after reviewing the preview. Default false: returns + /// the preview without posting. + pub confirmed: Option, + /// Optional safety rail: abort if the entry's total debits differ from + /// this decimal string. + pub expected_total: Option, +} + +/// Result of `books.post_entry`: either a preview or a posted confirmation. +#[derive(Debug, Serialize, JsonSchema)] +pub struct PostEntryOut { + /// "preview" or "posted". + pub status: String, + /// The entry id. + pub entry_id: u64, + /// Entry date, YYYY-MM-DD. + pub date: String, + /// Entry description. + pub description: String, + /// Total debits, decimal string. + pub total: String, + /// Full posting lines with resulting balances. + pub lines: Vec, + /// Receipts currently attached. + pub receipts: Vec, + /// True when the receipt policy requires a receipt for this entry. + pub receipt_required: bool, + /// True when a receipt is required but none is attached. Posting with + /// confirmed=true fails in this state. + pub receipt_missing: bool, +} + +/// Parameters for `books.reverse_entry`. +#[derive(Debug, Deserialize, JsonSchema)] +pub struct ReverseEntryParams { + /// Id of the posted entry to reverse. + pub entry_id: u64, + /// Date of the reversing entry, YYYY-MM-DD. + pub date: String, + /// Description of the reversal. + pub description: String, + /// Pass true to post after reviewing the preview. Default false. + pub confirmed: Option, +} + +/// Result of `books.reverse_entry`. +#[derive(Debug, Serialize, JsonSchema)] +pub struct ReverseEntryOut { + /// "preview" or "posted". + pub status: String, + /// The new reversing entry's id. + pub reversal_id: u64, + /// Id of the entry being reversed. + pub reverses: u64, + /// Reversed lines with resulting balances. + pub lines: Vec, +} + +// --------------------------------------------------------------------------- +// Receipts and documents +// --------------------------------------------------------------------------- + +/// Parameters for `books.attach_receipt`. +#[derive(Debug, Deserialize, JsonSchema)] +pub struct AttachReceiptParams { + /// Entry id to attach to. Omit to file unlinked, then link later with + /// `books.link_receipt`. + pub entry_id: Option, + /// Original file name, e.g. "receipt.png". + pub file_name: String, + /// MIME type, e.g. "image/png". + pub mime_type: String, + /// File bytes, base64-encoded. Max 25 MB. + pub content_base64: String, + /// Optional description. + pub description: Option, +} + +/// Result of `books.attach_receipt`. +#[derive(Debug, Serialize, JsonSchema)] +pub struct AttachReceiptOut { + /// Attachment id. + pub attachment_id: u64, + /// SHA-256 of the stored bytes. + pub sha256: String, + /// True when identical bytes were already stored (deduplicated). + pub deduplicated: bool, +} + +/// Parameters for `books.file_document`. +#[derive(Debug, Deserialize, JsonSchema)] +pub struct FileDocumentParams { + /// One of: formation, legal, tax, accounting, other. + pub category: String, + /// Original file name. + pub file_name: String, + /// MIME type. + pub mime_type: String, + /// File bytes, base64-encoded. Max 25 MB. + pub content_base64: String, + /// Optional description. + pub description: Option, +} + +/// Parameters for `books.link_receipt`. +#[derive(Debug, Deserialize, JsonSchema)] +pub struct LinkReceiptParams { + /// Attachment id from `books.attach_receipt`. + pub attachment_id: u64, + /// Entry id to link it to. + pub entry_id: u64, +} + +/// Result of `books.link_receipt`. +#[derive(Debug, Serialize, JsonSchema)] +pub struct LinkReceiptOut { + /// True when linked. + pub ok: bool, +} + +/// Parameters for `books.list_receipts`. +#[derive(Debug, Deserialize, JsonSchema)] +pub struct ListReceiptsParams { + /// Filter to one entry. Omit for all. + pub entry_id: Option, +} + +/// One receipt or document. +#[derive(Debug, Serialize, JsonSchema)] +pub struct ReceiptOut { + /// Attachment id. + pub attachment_id: u64, + /// SHA-256 of the bytes. + pub sha256: String, + /// Linked entry id, if any. + pub entry_id: Option, + /// Category: receipts, formation, legal, tax, accounting, other. + pub category: String, + /// Original file name. + pub file_name: String, + /// MIME type. + pub mime_type: String, + /// Size in bytes. + pub size_bytes: u64, + /// Description, if any. + pub description: Option, +} + +/// Result of `books.list_receipts`. +#[derive(Debug, Serialize, JsonSchema)] +pub struct ListReceiptsOut { + /// Attachments. + pub receipts: Vec, +} + +/// Parameters for `books.get_receipt`. +#[derive(Debug, Deserialize, JsonSchema)] +pub struct GetReceiptParams { + /// Attachment id. + pub attachment_id: u64, +} + +/// Result of `books.get_receipt`: metadata plus base64 bytes. The bytes are +/// hash-verified on read. +#[derive(Debug, Serialize, JsonSchema)] +pub struct GetReceiptOut { + /// Attachment id. + pub attachment_id: u64, + /// SHA-256 of the bytes. + pub sha256: String, + /// Linked entry id, if any. + pub entry_id: Option, + /// Category. + pub category: String, + /// Original file name. + pub file_name: String, + /// MIME type. + pub mime_type: String, + /// Size in bytes. + pub size_bytes: u64, + /// File bytes, base64-encoded. + pub content_base64: String, +} + +// --------------------------------------------------------------------------- +// Search, digest, export +// --------------------------------------------------------------------------- + +/// Parameters for `books.get_entry`. +#[derive(Debug, Deserialize, JsonSchema)] +pub struct GetEntryParams { + /// Entry id. + pub entry_id: u64, +} + +/// One line of a full entry. +#[derive(Debug, Serialize, JsonSchema)] +pub struct EntryLineOut { + /// Account id. + pub account_id: u64, + /// Account code. + pub code: String, + /// Account name. + pub name: String, + /// "debit" or "credit". + pub side: String, + /// Amount, decimal string. + pub amount: String, +} + +/// Result of `books.get_entry`: the full entry with lines and receipts. +#[derive(Debug, Serialize, JsonSchema)] +pub struct GetEntryOut { + /// Entry id. + pub entry_id: u64, + /// Entry date, YYYY-MM-DD. + pub date: String, + /// Description. + pub description: String, + /// "draft" or "posted". + pub status: String, + /// Id of the reversed entry, for reversals. + pub reversal_of: Option, + /// Full lines. + pub lines: Vec, + /// Total debits, decimal string. + pub total: String, + /// Attached receipts. + pub receipts: Vec, +} + +// --------------------------------------------------------------------------- +// Search, digest, export +// --------------------------------------------------------------------------- + +/// Parameters for `books.search_entries`. +#[derive(Debug, Deserialize, JsonSchema)] +pub struct SearchEntriesParams { + /// Filter to an account id. + pub account_id: Option, + /// Filter to entries on or after this date, YYYY-MM-DD. + pub date_from: Option, + /// Filter to entries on or before this date, YYYY-MM-DD. + pub date_to: Option, + /// Free-text match on the description. + pub text: Option, + /// Max rows. Default 50. + pub limit: Option, + /// Rows to skip. Default 0. + pub offset: Option, +} + +/// One entry in search results. +#[derive(Debug, Serialize, JsonSchema)] +pub struct EntrySummary { + /// Entry id. + pub entry_id: u64, + /// Entry date, YYYY-MM-DD. + pub date: String, + /// Description. + pub description: String, + /// Total debits, decimal string. + pub total: String, + /// Id of the reversed entry, for reversals. + pub reversal_of: Option, + /// Number of receipts attached. + pub receipt_count: u64, +} + +/// Result of `books.search_entries`. +#[derive(Debug, Serialize, JsonSchema)] +pub struct SearchEntriesOut { + /// Matching entries, newest first. + pub entries: Vec, +} + +/// Parameters for `books.digest`. +#[derive(Debug, Deserialize, JsonSchema)] +pub struct DigestParams { + /// Include entries posted on or after this date, YYYY-MM-DD. + pub since: String, +} + +/// One entry in the digest. +#[derive(Debug, Serialize, JsonSchema)] +pub struct DigestEntry { + /// Entry id. + pub entry_id: u64, + /// Entry date, YYYY-MM-DD. + pub date: String, + /// Description. + pub description: String, + /// Total debits, decimal string. + pub total: String, + /// Id of the reversed entry, for reversals. + pub reversal_of: Option, + /// Number of receipts attached. + pub receipt_count: u64, + /// True when the receipt policy required a receipt. + pub receipt_required: bool, + /// True when required but missing (should not happen for posted + /// entries; the server rejects such posts). + pub receipt_missing: bool, +} + +/// Net movement of one account over the digest period. +#[derive(Debug, Serialize, JsonSchema)] +pub struct DigestTotal { + /// Account code. + pub code: String, + /// Account name. + pub name: String, + /// Net movement in the account's normal side, decimal string. + pub movement: String, +} + +/// Result of `books.digest`: what changed, for human review. +#[derive(Debug, Serialize, JsonSchema)] +pub struct DigestOut { + /// The since date, YYYY-MM-DD. + pub since: String, + /// Entries posted in the period, newest first. + pub entries: Vec, + /// Net movement per account over the period. + pub totals_by_account: Vec, +} + +/// Parameters for `books.export`. +#[derive(Debug, Deserialize, JsonSchema)] +pub struct ExportParams { + /// "csv" or "json". + pub format: String, + /// "journal", "trial_balance", "profit_and_loss", or "balance_sheet". + pub report: String, + /// Period start for profit_and_loss, YYYY-MM-DD. + pub start: Option, + /// Period end for profit_and_loss, YYYY-MM-DD. + pub end: Option, + /// As-of date for balance_sheet, YYYY-MM-DD. + pub as_of: Option, +} + +/// Result of `books.export`. +#[derive(Debug, Serialize, JsonSchema)] +pub struct ExportOut { + /// The requested format. + pub format: String, + /// The requested report. + pub report: String, + /// The exported content. + pub content: String, +}