MCP Server for Wave
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@MCP Server for WaveCreate an invoice for Acme Corp with 10 hours of consul"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Wave gives small businesses free accounting and invoicing, and a GraphQL API that covers nearly all of it. This server puts that entire API in front of an AI assistant: invoices and payments, estimates and deposits, customers, vendors, products, sales taxes, the chart of accounts, and double-entry bookkeeping.
Every query is verified against Wave's live schema in CI, and the tools that change or send anything stay hidden until you turn them on.
Why This Exists
Bookkeeping is mostly translation. You have a receipt, a bank line, an email promising to pay next week, and none of it is in the shape your books want. The work is not hard, it is just constant, and it is exactly the kind of task worth handing to an assistant that can hold the whole picture at once.
Doing that well needs more than a few convenience endpoints. An assistant that can list invoices but not record the payment, or draft an estimate but not convert it, forces you back into the web app halfway through every task. So this server covers the API completely: all 42 mutations, all 11 root queries, every sub-resource on a business. If Wave's API can do it, a tool here does it.
Two decisions shape the rest:
Writes are off by default. Wave has genuinely irreversible operations.
Sending an invoice emails a real customer. Deleting one is permanent. A default
install exposes 30 read-only tools; the other 44 appear only when you set
WAVE_ALLOW_WRITES=1. Reading your books should not require trusting a model
with your outbox.
Errors explain themselves. Wave rejects an unbalanced transaction without telling you which figure is wrong. This server compares the anchor against the line items first and reports the difference. A category word that matches no account produces the list of real account names rather than a silent guess at the first one.
Related MCP server: Wave MCP Server
Quick Start
{
"mcpServers": {
"wave-mcp-server": {
"command": "npx",
"args": ["-y", "@oliverames/mcp-server-for-wave@latest"],
"env": {
"WAVE_ACCESS_TOKEN": "your_token_here"
}
}
}
}Then ask for your businesses and set one as the default:
List my Wave businesses and set the first one as the default.Get a token
Create an application and generate an access token in the Wave developer portal. Wave's tokens expire, so expect to refresh it periodically, or use the hosted connector which handles refresh for you.
Install as a plugin
The repo doubles as a single-plugin marketplace for hosts that support them:
/plugin marketplace add oliverames/wave-mcp-server
/plugin install wave-mcp-serverInstall in Codex
codex mcp add wave-mcp-server \
--env WAVE_ACCESS_TOKEN=your_token_here \
-- npx -y @oliverames/mcp-server-for-wave@latestVerify with codex mcp list. Startup takes about 0.2s, well inside Codex's
10-second startup_timeout_sec, and the retry budget is capped below its
60-second tool_timeout_sec so a slow API surfaces Wave's real error rather
than a client timeout.
Enable write tools
"env": {
"WAVE_ACCESS_TOKEN": "your_token_here",
"WAVE_ALLOW_WRITES": "1"
}This registers the 44 tools that create, change, delete, or email records. Without it they are not advertised at all, so a model cannot call one by guessing its name.
Docker
docker build -t wave-mcp-server .
docker run --rm -i -e WAVE_ACCESS_TOKEN=your_token_here wave-mcp-serverThe -i matters: the server speaks JSON-RPC on stdin and stdout.
1Password token lookup
Rather than pasting a token into a config file, point the server at a secret
reference and it will shell out to the op CLI on startup:
"env": { "WAVE_OP_PATH": "op://Development/Wave/credential" }WAVE_ACCESS_TOKEN_FILE works the same way for a file on disk.
What You Can Do
Bill a customer end to end
Create an invoice for Acme Corp with 10 hours of consulting at $150/hour,
due in 30 days. Approve it and email it to billing@acme.com.Quote, then convert
Create an estimate for the website redesign package with a 25% deposit,
send it, and convert it to an invoice once they accept.Record a receipt
Log a $45.99 expense from Office Depot on 2026-03-15 for office supplies,
paid from Business Checking.Split a transaction
Record a $100 withdrawal from checking: $60 to fuel and $40 to meals.Reconcile a processor payout
A Stripe payout of $97 landed in checking: $100 of consulting income
less a $3 processing fee.Chase what is owed
Show me every unpaid invoice over $500, sorted by amount due, and which
customers carry the largest overdue balances.Tools Reference
Names are prefixed wave_ so they do not collide with other MCP servers.
Tools marked W require WAVE_ALLOW_WRITES=1.
Businesses and reference data
Tool | Purpose |
| List reachable businesses |
| Full business detail |
| Set the session default |
| Accent color and logo |
| How credentials resolved, and what is gated. Makes no API call |
| Account the token belongs to |
| Application that issued the token |
| Supported currencies |
| Countries and their provinces |
| One province or state |
| The five top-level account types |
| Subtypes, which |
Chart of accounts
Tool | Purpose |
| Accounts with balances |
| Add an account |
| Rename or renumber |
| Hide from pickers, keep history |
Customers, vendors, products, taxes
Tool | Purpose |
| Customers with balances |
| Manage customers |
| Vendors (read-only in Wave's API) |
| Products and services |
| Manage products |
| Taxes and rate history |
| Manage taxes |
Invoices and payments
Tool | Purpose |
| Invoices with items and payments |
| Build invoices |
| Move a draft into the books |
| Emails the customer |
| Record delivery made outside Wave |
| Permanent |
| One payment |
| Record payments |
| Emails the customer |
Estimates and deposits
Tool | Purpose |
| Estimates with history and deposits |
| Build estimates |
| Approve a draft |
| Emails the customer |
| Record offline delivery and acceptance |
| Undo an acceptance |
| Emails the customer |
| Render a PDF |
| Turn an accepted estimate into an invoice |
| Permanent |
| One deposit payment |
| Record deposits |
| Emails the customer |
Bookkeeping
Tool | Purpose |
| One expense, income, or transfer |
| Bulk import, applied atomically |
| A payout whose net differs from gross |
| Expense, account matched from a category word |
| Income, account matched from a category word |
Resources
Read-only JSON views for grounding context. Everything here is also reachable through a tool, so hosts that ignore resources lose nothing.
wave://businesses • wave://accounts • wave://customers •
wave://vendors • wave://products • wave://sales-taxes •
wave://account-taxonomy
How Transactions Work
Wave is double-entry, so wave_create_money_transaction has two sides:
The anchor is the account money physically moved through, a bank account or credit card, with a direction of
DEPOSITorWITHDRAWAL.The line items are the categories it is attributed to. Their amounts must total the anchor amount.
A $50 office-supplies expense paid from checking is one anchor (checking,
WITHDRAWAL, 50.00) and one line item (Office Supplies, 50.00). A split is
the same anchor with more line items.
Every transaction carries an external_id. Wave deduplicates on it, so passing
a stable value of your own makes retries safe; one is generated when you omit
it.
Environment Variables
Variable | Required | Default | Description |
| Yes | (none) | OAuth2 bearer token from the Wave developer portal |
| No | (none) | Default business, so tools can omit |
| No | off | Set to |
| No | (none) | Read the token from a file instead |
| No | (none) | Read the token from 1Password, e.g. |
| No |
| Per-request timeout |
| No |
| Total time for one call including retries |
| No |
| Reject responses above this size |
| No |
| Retries on 429 and 5xx |
| No | off | Read only the environment, not agent config files |
Credentials resolve in order: environment, then the host agent's own config
file, then WAVE_ACCESS_TOKEN_FILE, then 1Password. Reading the agent config
matters because MCP clients launch this server as a subprocess, so a value in
claude_desktop_config.json or ~/.codex/config.toml reaches it only if the
user wired it through by hand.
Amount Handling
Money is sent to Wave as strings, not floats, so 0.1 + 0.2 cannot become
0.30000000000000004 on the way to your ledger. Balance checks compare minor
units as integers for the same reason.
One exception, and it is Wave's: moneyDepositTransactionCreate types its
amounts as Float rather than Decimal, so wave_create_deposit_transaction
sends numbers there because the API accepts nothing else.
Wave API Limitations
These are constraints in Wave's API, not gaps here. Each was confirmed against the live schema.
Transactions cannot be read back. Wave creates money transactions but exposes no query to list them; there is no
transactionsconnection onBusiness. Review them in the web app.Vendors are read-only. The schema has no
vendorCreate,vendorPatch, orvendorDelete.Money transactions cannot reference a vendor.
wave_create_expense_from_receiptrecords the name in the description.wave_patch_estimatedemands fields you are not changing. Wave marks seven of them required on the patch input; read the estimate first and pass its current values back.wave_patch_accountneeds the account's currentsequenceas an optimistic-concurrency check.Line items must reference a product. No free-text lines.
wave_create_deposit_transactionreturns no ID.Bills, receipts, payroll, and reports have no API.
No file attachments. Receipt images cannot be uploaded.
Rate limits are tight, roughly two concurrent requests.
Un-archiving is web-app only.
Hosted Connector
A Cloudflare Worker serves the same tools over OAuth instead of a shared token. Users authorize against their own Wave account, tokens are encrypted before storage, and write access is chosen at authorization time so a read-only connection cannot be escalated later.
The deployment at https://wave.amesvt.com/mcp is private: an owner
allowlist restricts it to one Wave account, and any other account is refused
before a token is stored. Deploy your own copy from worker/ to use it.
See worker/README.md for setup and the security model.
Architecture
index.js Single-file server: client, 74 tools, 7 resources
createWaveServer() Factory over injected credentials, shared by
the stdio process and the hosted Worker
scripts/
smoke-validate-graphql.mjs Schema-check every query against live Wave
smoke-list-tools.mjs Start over stdio and enumerate what is advertised
smoke-packed-install.mjs Pack, install, and launch the way npx does
sync-plugin-metadata.mjs Propagate the version to every host manifest
check-release-consistency Fail the build when anything disagrees
build-mcpb.mjs Desktop bundle
worker/ Hosted OAuth connector
test/unit.test.mjs 58 tests, no networkThe tool layer lives in one file on purpose. It is imported unchanged by the Worker, so the hosted and local servers cannot drift apart.
Verification without credentials
Wave validates a GraphQL document and coerces its variables before it checks
authentication. An UNAUTHENTICATED response therefore means the query is
correct, while GRAPHQL_VALIDATION_FAILED means it is not.
CI exploits that to schema-check all 64 documents on every push with no token at all, which catches a field Wave renames before a user does.
Building
npm install
npm test # 58 unit tests, no network
npm run smoke:list-tools # start over stdio, enumerate tools
npm run smoke:packed # pack, install, and launch via the bin symlink
npm run smoke:schema # validate every query against live Wave
npm run release:check # version parity across 8 manifests
npm run build:mcpb # desktop bundleContributions welcome. See CONTRIBUTING.md.
Not Affiliated With Wave
An independent project, not affiliated with, endorsed by, or sponsored by Wave Financial Inc. Wave Financial Inc. owns the Wave name, logo, and marks; the icon above is theirs and is used only to identify the service this server connects to. Originally forked from vinnividivicci/wave_mcp, then rewritten.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/oliverames/wave-mcp-server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server