Wave MCP
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| WAVE_BUSINESS_ID | No | Default business. Otherwise the token's business, or the only non-personal one | auto |
| WAVE_MCP_DATA_DIR | No | Where imported ledgers are stored (mode 600) | ~/.local/share/wave-mcp |
| WAVE_MCP_TOOLSETS | No | Comma list: context,customers,vendors,products,accounts,sales_taxes,invoices,estimates,payments,transactions,reports,ledger,graphql | all |
| WAVE_MCP_HTTP_HOST | No | HTTP transport host | 127.0.0.1 |
| WAVE_MCP_HTTP_PATH | No | HTTP transport path | /mcp |
| WAVE_MCP_HTTP_PORT | No | HTTP transport port | 8787 |
| WAVE_MCP_LOG_LEVEL | No | debug, info, warn, error, silent (stderr only) | info |
| WAVE_MCP_READ_ONLY | No | Register only read tools | off |
| WAVE_MCP_HTTP_TOKEN | No | Bearer token for HTTP (credentials file); required off loopback | |
| WAVE_MCP_SCAN_LIMIT | No | Max records a report or search walks | 5000 |
| WAVE_MCP_TIMEOUT_MS | No | Per request timeout | 30000 |
| WAVE_MCP_MAX_RETRIES | No | Per request max retries | 3 |
| WAVE_CREDENTIALS_FILE | No | Override the credentials file path | ~/.config/wave-mcp/credentials.env |
| WAVE_MCP_CACHE_TTL_MS | No | Cache for scans and reference data; any write clears it | 120000 |
| WAVE_MCP_ALLOWED_HOSTS | No | Host/Origin allow-list for HTTP | loopback |
| WAVE_MCP_MAX_CONCURRENCY | No | Concurrent Wave requests (Wave's limits are tight) | 2 |
| WAVE_MCP_MAX_RESPONSE_CHARS | No | Hard cap per tool result (trailing list items are dropped, with a note) | 60000 |
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
Wave accounting (waveapps.com). Call wave_get_context first; if credentials are missing or expired the user signs in with node /app/bin/wave-mcp.js login in a terminal on f32f40788a38. Full guide: the wave://guide resource.
Uses Wave's public GraphQL API, plus Wave's web-app API for transactions.
Call wave_get_context first: it shows the businesses this credential reaches and the default one. Every other tool takes an optional businessId.
Lists are paginated (page/pageSize, or offset/limit for reports and payments). Ask for small pages, follow nextPage/nextOffset only when needed, and prefer detail="summary".
Transactions (live): wave_list_transactions, wave_get_transaction, wave_update_transaction and wave_delete_transaction use Wave's web-app API, which needs a browser session (wave_get_context shows auth.webSession). Sessions end when the user signs out of Wave or they time out; then ask the user to run node /app/bin/wave-mcp.js login in a terminal on f32f40788a38 and paste a request copied from the Wave web app (the command explains how). No restart is needed. Edits send the transaction's sequence, so they never overwrite a newer change.
Wave's public API has no reports. wave_run_report computes them from an imported Data Export (wave_import_ledger, a snapshot: check ledger.importedAt) or falls back to invoices and current balances; every report states its basis. wave_list_ledger_transactions searches that snapshot.
Never ask the user to paste access tokens, session tokens or curl commands into the chat. The user sets credentials in a terminal on f32f40788a38: node /app/bin/wave-mcp.js login (pastes a request copied from the browser) or node /app/bin/wave-mcp.js secrets set WAVE_ACCESS_TOKEN. You may run node /app/bin/wave-mcp.js check yourself to verify; it prints no secrets.
Writes change real books. wave_send_* tools email real customers; confirm recipients and amounts with the user first. wave_create_transaction supports dryRun.
Line items on invoices and estimates must reference a product (wave_list_products).
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {
"listChanged": true
} |
| prompts | {
"listChanged": true
} |
| resources | {
"listChanged": true
} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| wave_get_contextA | Start here. Returns the signed-in Wave user, how credentials are configured, the businesses this credential reaches, which one is the default, whether writes are enabled, and what the public API cannot do. Works without credentials and then explains how to set them up. |
| wave_list_businessesA | List the Wave businesses this credential can access (paginated). |
| wave_get_businessB | Full details of one business: address, contact info, currency, timezone, whether Wave can email invoices for it, and invoice/estimate branding. |
| wave_get_referenceA | Reference data needed to fill other tools' inputs: currencies, countries, one country with its provinces/states (kind=country, code=CA), a province by code, account types, and account subtypes (needed by wave_create_account). Lists are paginated. |
| wave_graphql_queryA | Escape hatch for reads the other tools do not shape the way you need: run one GraphQL query (never a mutation) against Wave's public API and get the raw data. Select only the fields you need, and always pass page/pageSize on connections. Schema: the wave://schema resource. Example: query($id: ID!){ business(id:$id){ customers(page:1,pageSize:5){ edges{ node{ id name } } } } } |
| wave_list_customersA | List customers with their outstanding and overdue balances. Filter by exact email, archive state or modification time; use search for a name substring. |
| wave_get_customerA | One customer with address, shipping details, notes and outstanding/overdue amounts. Deleted customers are still returned by ID, marked deleted: true. |
| wave_create_customerC | Create a customer. Only name is required. |
| wave_update_customerA | Change fields on a customer. Only the fields you pass are changed; nested address/shipping objects replace the existing ones. |
| wave_delete_customerA | Delete a customer. Wave removes it from lists, but the record stays readable by ID (wave_get_customer shows deleted: true); there is no undelete through the API. |
| wave_list_vendorsA | List vendors (suppliers). Wave's API cannot create, change or delete vendors, and bills are not exposed. |
| wave_get_vendorC | One vendor with address and contact details. |
| wave_list_productsA | List products/services (the items invoice and estimate lines must reference), with price, income/expense accounts and default sales taxes. Active only unless isArchived=true. |
| wave_get_productB | One product/service with its accounts and default sales taxes. |
| wave_create_productA | Create a product or service. Give it an incomeAccountId to sell it on invoices, an expenseAccountId to buy it, or both. |
| wave_update_productB | Change a product/service. defaultSalesTaxIds replaces the whole list. |
| wave_archive_productA | Archive a product/service so it can no longer be added to new invoices. Past invoices keep it. The API cannot un-archive (web app only). |
| wave_list_accountsA | List the chart of accounts with current balances. Find transaction accounts with filters: bank accounts are types=[ASSET] subtypes=[CASH_AND_BANK]; credit cards subtypes=[CREDIT_CARD]; expense categories types=[EXPENSE]; income categories types=[INCOME]. |
| wave_get_accountB | One account with type, subtype, normal balance, current balance (account and business currency) and its sequence number. |
| wave_create_accountA | Add an account to the chart of accounts. The subtype decides its type (see wave_get_reference kind=account_subtypes). |
| wave_update_accountA | Rename an account or change its description/number. Wave requires the account's current sequence (optimistic locking); it is read automatically when omitted. Pass null for description/displayId to clear them. |
| wave_archive_accountA | Archive an account (hide it while keeping its history). Wave refuses for system accounts and accounts with a balance. No un-archive through the API. |
| wave_list_sales_taxesA | List sales taxes with current rate (as a fraction: 0.13 = 13%). detail="full" adds the dated rate history. |
| wave_get_sales_taxC | One sales tax with its full dated rate history. |
| wave_create_sales_taxB | Create a sales tax. abbreviation must be unique in the business (max 10 characters). |
| wave_update_sales_taxA | Change a sales tax. To change the rate, pass rates with an effective date; rates replaces the dated rate list, so include existing entries you want to keep (see wave_get_sales_tax). |
| wave_archive_sales_taxA | Archive a sales tax so it can no longer be applied. No un-archive through the API. |
| wave_list_invoicesA | List invoices with totals, amount due and status. Filter by status, customer, invoice date range, modification time, number, currency or the estimate they came from (sourceId). detail="full" adds line items and payments (pages of at most 25). |
| wave_get_invoiceC | One invoice with line items, taxes, discounts, payments, reminders, PDF and customer view links. |
| wave_create_invoiceA | Create an invoice for a customer. Created as DRAFT unless status=SAVED (approved). Number, dates, title and notes default to the business's invoice settings. Taxes default per product; pass taxIds:[] for none. Nothing is emailed. |
| wave_update_invoiceB | Change an invoice. Passing items replaces every line; omit items to keep them. Wave may refuse changes to paid invoices. |
| wave_clone_invoiceA | Copy an invoice into a new draft. |
| wave_approve_invoiceA | Approve a draft invoice, which posts it to the books (status SAVED). Does not email it. |
| wave_send_invoiceA | Email an invoice through Wave to real recipients. Irreversible: confirm recipients first. Requires email sending enabled for the business (wave_get_business emailSendEnabled). |
| wave_mark_invoice_sentA | Record that an invoice was sent outside Wave (no email goes out). |
| wave_delete_invoiceB | Permanently delete an invoice. |
| wave_get_invoice_paymentC | One invoice payment with account, method, currency details and processor info. |
| wave_record_invoice_paymentA | Record a payment received outside Wave against an invoice (cash, cheque, bank transfer...). paymentAccountId is the account the money went into (usually types=[ASSET] subtypes=[CASH_AND_BANK]). The invoice must be approved, not a draft. |
| wave_update_invoice_paymentC | Change a manually recorded invoice payment. |
| wave_delete_invoice_paymentA | Delete a recorded invoice payment; the invoice becomes unpaid by that amount. |
| wave_send_invoice_payment_receiptB | Email a receipt for an invoice payment to real recipients. Irreversible. |
| wave_list_estimatesA | List estimates (quotes) with totals, deposit state and status. detail="full" adds line items, deposit payments and acceptance history (pages of at most 25). |
| wave_get_estimateB | One estimate with items, taxes, discounts, deposit settings and payments, acceptance history and attachments. |
| wave_create_estimateB | Create a draft estimate (quote). Each line needs a product and a unitPrice. Optionally ask for a deposit. Nothing is emailed. |
| wave_update_estimateA | Change an estimate. Wave's patch requires customer, status, title, dates, currency and exchange rate; any you omit are copied from the current estimate. Passing items replaces every line. |
| wave_clone_estimateB | Copy an estimate into a new draft. |
| wave_approve_estimateA | Approve a draft estimate so it can be sent. Does not email it. |
| wave_mark_estimate_acceptedC | Record that the customer accepted the estimate. |
| wave_reset_estimate_acceptanceB | Undo the customer's acceptance so the estimate can be changed or re-accepted. |
| wave_convert_estimate_to_invoiceA | Create an invoice from an estimate (deposits paid carry over). Returns the new invoiceId. |
| wave_generate_estimate_pdfB | Generate (or regenerate) the estimate PDF and return its URL. |
| wave_send_estimate_acceptance_emailA | Email the customer the confirmation that their estimate acceptance was recorded. Irreversible. |
| wave_delete_estimateB | Permanently delete an estimate. |
| wave_send_estimateA | Email an estimate through Wave to real recipients. Irreversible: confirm recipients first. The estimate must be approved. |
| wave_mark_estimate_sentA | Record that an estimate was sent outside Wave (no email goes out). |
| wave_get_estimate_paymentB | One estimate deposit payment (or refund/void) with its estimate. |
| wave_record_estimate_depositB | Record a deposit received outside Wave against an estimate. paymentAccountId is where the money went (a CASH_AND_BANK account). |
| wave_update_estimate_depositC | Change a manually recorded estimate deposit. |
| wave_delete_estimate_paymentC | Delete a recorded estimate deposit payment. |
| wave_send_estimate_deposit_receiptB | Email a receipt for an estimate deposit to real recipients. Irreversible. |
| wave_create_transactionA | Record a bank/credit-card transaction and categorize it, as if entered in Wave's Transactions page. Expense paid by card: anchor {accountId: card, direction: WITHDRAWAL}, line {accountId: expense category, balance: INCREASE}. Sale deposited to bank: anchor {bank, DEPOSIT}, line {income account, INCREASE}. Split across several lines; they must total the anchor. Keep the returned id: wave_get_transaction / wave_list_transactions read it back. Use dryRun=true to validate without writing. Wave does not document de-duplication by externalId: if a call timed out, check the Wave web app before retrying. |
| wave_create_transactionsA | Create up to 50 money transactions in one request (same shape as wave_create_transaction). Every transaction is checked first (amounts balance; debit and credit sides make sense for the accounts); nothing is sent if any fails. Wave applies the batch as one request and reports input errors by path (transactions.N...). |
| wave_list_transactionsA | List and search the business's transactions as Wave's Transactions page shows them: bank and card transactions, expenses, deposits, transfers, bill and invoice payments. Live data. Filter by date range, text in the description, bank/card account (accountId), category accounts (categoryIds) or uncategorizedOnly. Cursor pagination: pass nextCursor back as cursor. Each row has the amount, direction, bank/card account, category (or categories when split), vendor, sales tax and review status. IDs work with wave_get_transaction, wave_update_transaction and wave_delete_transaction. Uses Wave's web-app API (unofficial), which needs a web session: if it is missing or expired, the user runs node /app/bin/wave-mcp.js login in a terminal on f32f40788a38. |
| wave_get_transactionA | One transaction with every line: bank/card side (anchor), category lines with order numbers, amounts, sales tax, vendor/customer, tags, review status, its current sequence, and which fields Wave allows editing. Uses Wave's web-app API (unofficial), which needs a web session: if it is missing or expired, the user runs node /app/bin/wave-mcp.js login in a terminal on f32f40788a38. |
| wave_update_transactionA | Edit a transaction as in the Wave web app: date, description, notes, reviewed (verified), bank/card account, total amount, category, vendor or customer, or the full set of split lines. Journal entries (journalEntry: true) have no bank side: edit their lines, keeping debits equal to credits. Single-category transactions: use categoryId / vendorId / customerId / amount / salesTax directly. Split transactions: pass lines (every line to keep, with order from wave_get_transaction; omit order to add a line; lines left out are removed). Line amounts include sales tax and must add up to the amount. Reads the transaction first and sends its current sequence, so it never overwrites a newer edit. dryRun=true shows the change without saving. Confirm with the user before changing amounts or accounts. Uses Wave's web-app API (unofficial), which needs a web session: if it is missing or expired, the user runs node /app/bin/wave-mcp.js login in a terminal on f32f40788a38. |
| wave_delete_transactionA | Delete a transaction, as the web app's Delete does. Changes the books and account balances: confirm with the user first. Wave refuses for transactions that must be removed at their source (e.g. invoice payments, payroll). Uses Wave's web-app API (unofficial), which needs a web session: if it is missing or expired, the user runs node /app/bin/wave-mcp.js login in a terminal on f32f40788a38. |
| wave_list_paymentsA | List money received: invoice payments and estimate deposits, newest first, with totals. These are the only transaction records Wave's public API can read; bank/card transactions, expenses and money transactions created through the API cannot be listed. Filter by payment date range and customer. |
| wave_run_reportA | Financial reports computed by this server (Wave's API has no report endpoint). Rows are paginated with limit/offset; totals always cover every row. From the imported ledger (wave_import_ledger; any period, default year to date; balance-sheet style reports as of endDate): profit_and_loss, balance_sheet, cash_flow, trial_balance, account_balances, account_transactions/general_ledger (filter with account or accountId), sales_tax (sales and purchases), aged_payables, purchases_by_vendor. Without a ledger, profit_and_loss, balance_sheet, trial_balance and account_balances fall back to current account balances from the API (as of now only), and sales_tax to invoices. Live from invoices (invoice date range): income_by_customer, sales_by_product, invoice_summary; as of today: aged_receivables; customer_statement needs customerId. Not possible: tax_line, payroll_wage_tax, payroll_benefits_deductions. |
| wave_import_ledgerA | Load the full transaction journal from Wave's Data Export so transactions can be listed and date-range reports run. Wave's public API cannot read transactions; this is the supported way to get them. Get the file: Wave → Settings → Data Export → Accounting (CSV) → Export; Wave emails a download link (valid 24 hours). Pass the path of the downloaded .zip (or the accounting .csv inside it) on the machine running this server. Replaces any earlier import for the business. Nothing is sent to Wave; the ledger is stored locally (mode 600). |
| wave_list_ledger_transactionsA | List and search transactions from an imported Data Export snapshot (wave_import_ledger). For live data use wave_list_transactions; this works offline and includes invoices, bills and journal entries: bank and card transactions, expenses, invoices, bills, payments, transfers and journal entries. Filter by date range, account (ID or name), text, customer, vendor, amount, direction or uncategorized only. Newest first by default. Each row has the amount on the bank/card account (or the journal total), the categories used, and the transaction ID for wave_get_ledger_transaction. |
| wave_get_ledger_transactionA | One transaction from the imported ledger with every journal line (account, debit, credit, line description, sales tax). IDs come from wave_list_ledger_transactions. |
| wave_list_ledger_accountsA | Accounts that appear in the imported ledger, with their type, where the type came from (Wave chart of accounts or export labels) and line counts. Use the names or IDs to filter wave_list_ledger_transactions and account_transactions reports. |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
| month_end_review | Review a month: sales, payments received, receivables and tax collected |
| chase_overdue_invoices | Find overdue invoices and plan follow-ups |
| record_expense | Record a purchase as a categorized money transaction |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
| limitations | Gaps in Wave's public API and the reports this server cannot produce |
| guide | Usage guide for agents: first steps, the sign-in command the user runs, tool map and safety rules (the wave-accounting skill) |
| schema | Full public schema snapshot, for wave_graphql_query (about 135 KB) |
TDQS
Scored across 72 tools
Resources are generally separable (customer/product/account/tax/invoice/estimate), but the set contains many near-parallel or easily confused pairs: wave_list_transactions vs wave_list_ledger_transactions, wave_get_transaction vs wave_get_ledger_transaction, wave_create_transaction vs wave_create_transactions, wave_send_invoice vs wave_mark_invoice_sent, and wave_list_payments vs the transaction listers. The long descriptions do resolve most conflicts (live web-app data vs imported Data Export snapshot), but an agent must read carefully to avoid picking the wrong twin, and wave_graphql_query overlaps with most read tools.
Names follow a steady wave_verb_noun pattern with a shared verb vocabulary (list/get/create/update/delete/archive/approve/send/mark), and the invoice/estimate/transaction families mirror each other predictably. Minor deviations exist: wave_create_transactions (plural batch) next to wave_create_transaction, 'record' vs 'create' for payments/deposits, and one-off verbs like run_report, import_ledger, graphql_query.
72 tools is far beyond a comfortable surface for an agent, even for a broad accounting domain. Much of the bulk comes from duplicated CRUD families (live transaction API, imported-ledger reader, and parallel invoice/estimate payment lifecycles), which inflates the count without adding distinct capability.
Coverage is unusually thorough: customers, products, accounts, sales taxes, invoices, estimates, invoice payments, estimate deposits, transactions, ledger import, and a large report suite, each with read/create/update/delete-or-archive paths. Remaining gaps (vendor writes, bills, un-archive, payroll reports) are explicitly documented as Wave API limitations rather than omissions, and escape hatches (wave_graphql_query, ledger import) cover the rest.