@oliverames/mcp-server-for-wave 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +23 -0
- package/README.md +434 -0
- package/assets/icon.png +0 -0
- package/docs/hosted-oauth-connector.md +54 -0
- package/docs/privacy.md +50 -0
- package/index.js +5816 -0
- package/package.json +61 -0
- package/scripts/build-mcpb.mjs +121 -0
- package/scripts/check-release-consistency.mjs +127 -0
- package/scripts/smoke-list-tools.mjs +112 -0
- package/scripts/smoke-packed-install.mjs +139 -0
- package/scripts/smoke-validate-graphql.mjs +164 -0
- package/scripts/sync-plugin-metadata.mjs +94 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Oliver Ames
|
|
4
|
+
|
|
5
|
+
Portions derived from wave_mcp, Copyright (c) 2025 vinnividivicci.
|
|
6
|
+
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
in the Software without restriction, including without limitation the rights
|
|
10
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
furnished to do so, subject to the following conditions:
|
|
13
|
+
|
|
14
|
+
The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
copies or substantial portions of the Software.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/icon.png" width="80" height="80" alt="Wave MCP Server">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">MCP Server for Wave</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>Complete access to Wave Accounting from Claude, Codex, and any other MCP client</strong>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<code>74 tools</code> •
|
|
13
|
+
<code>42/42 mutations</code> •
|
|
14
|
+
<code>11/11 queries</code> •
|
|
15
|
+
<code>7 resources</code>
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
<p align="center">
|
|
19
|
+
<a href="https://www.npmjs.com/package/@oliverames/mcp-server-for-wave">
|
|
20
|
+
<img src="https://img.shields.io/npm/v/@oliverames/mcp-server-for-wave?style=flat-square&color=f5a542" alt="npm">
|
|
21
|
+
</a>
|
|
22
|
+
<a href="LICENSE">
|
|
23
|
+
<img src="https://img.shields.io/badge/license-MIT-f5a542?style=flat-square" alt="License">
|
|
24
|
+
</a>
|
|
25
|
+
<a href="https://www.buymeacoffee.com/oliverames">
|
|
26
|
+
<img src="https://img.shields.io/badge/Buy_Me_a_Coffee-support-f5a542?style=flat-square&logo=buy-me-a-coffee&logoColor=white" alt="Buy Me a Coffee">
|
|
27
|
+
</a>
|
|
28
|
+
</p>
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
Wave gives small businesses free accounting and invoicing, and a GraphQL API that
|
|
33
|
+
covers nearly all of it. This server puts that entire API in front of an AI
|
|
34
|
+
assistant: invoices and payments, estimates and deposits, customers, vendors,
|
|
35
|
+
products, sales taxes, the chart of accounts, and double-entry bookkeeping.
|
|
36
|
+
|
|
37
|
+
Every query is verified against Wave's live schema in CI, and the tools that
|
|
38
|
+
change or send anything stay hidden until you turn them on.
|
|
39
|
+
|
|
40
|
+
## Why This Exists
|
|
41
|
+
|
|
42
|
+
Bookkeeping is mostly translation. You have a receipt, a bank line, an email
|
|
43
|
+
promising to pay next week, and none of it is in the shape your books want. The
|
|
44
|
+
work is not hard, it is just constant, and it is exactly the kind of task worth
|
|
45
|
+
handing to an assistant that can hold the whole picture at once.
|
|
46
|
+
|
|
47
|
+
Doing that well needs more than a few convenience endpoints. An assistant that
|
|
48
|
+
can list invoices but not record the payment, or draft an estimate but not
|
|
49
|
+
convert it, forces you back into the web app halfway through every task. So this
|
|
50
|
+
server covers the API completely: all 42 mutations, all 11 root queries, every
|
|
51
|
+
sub-resource on a business. If Wave's API can do it, a tool here does it.
|
|
52
|
+
|
|
53
|
+
Two decisions shape the rest:
|
|
54
|
+
|
|
55
|
+
**Writes are off by default.** Wave has genuinely irreversible operations.
|
|
56
|
+
Sending an invoice emails a real customer. Deleting one is permanent. A default
|
|
57
|
+
install exposes 30 read-only tools; the other 44 appear only when you set
|
|
58
|
+
`WAVE_ALLOW_WRITES=1`. Reading your books should not require trusting a model
|
|
59
|
+
with your outbox.
|
|
60
|
+
|
|
61
|
+
**Errors explain themselves.** Wave rejects an unbalanced transaction without
|
|
62
|
+
telling you which figure is wrong. This server compares the anchor against the
|
|
63
|
+
line items first and reports the difference. A category word that matches no
|
|
64
|
+
account produces the list of real account names rather than a silent guess at
|
|
65
|
+
the first one.
|
|
66
|
+
|
|
67
|
+
## Quick Start
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"mcpServers": {
|
|
72
|
+
"wave-mcp-server": {
|
|
73
|
+
"command": "npx",
|
|
74
|
+
"args": ["-y", "@oliverames/mcp-server-for-wave@latest"],
|
|
75
|
+
"env": {
|
|
76
|
+
"WAVE_ACCESS_TOKEN": "your_token_here"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Then ask for your businesses and set one as the default:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
List my Wave businesses and set the first one as the default.
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Get a token
|
|
90
|
+
|
|
91
|
+
Create an application and generate an access token in the
|
|
92
|
+
[Wave developer portal](https://developer.waveapps.com/). Wave's tokens expire,
|
|
93
|
+
so expect to refresh it periodically, or use the
|
|
94
|
+
[hosted connector](#hosted-connector) which handles refresh for you.
|
|
95
|
+
|
|
96
|
+
### Install as a plugin
|
|
97
|
+
|
|
98
|
+
The repo doubles as a single-plugin marketplace for hosts that support them:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
/plugin marketplace add oliverames/wave-mcp-server
|
|
102
|
+
/plugin install wave-mcp-server
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Install in Codex
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
codex mcp add wave-mcp-server \
|
|
109
|
+
--env WAVE_ACCESS_TOKEN=your_token_here \
|
|
110
|
+
-- npx -y @oliverames/mcp-server-for-wave@latest
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Verify with `codex mcp list`. Startup takes about 0.2s, well inside Codex's
|
|
114
|
+
10-second `startup_timeout_sec`, and the retry budget is capped below its
|
|
115
|
+
60-second `tool_timeout_sec` so a slow API surfaces Wave's real error rather
|
|
116
|
+
than a client timeout.
|
|
117
|
+
|
|
118
|
+
### Enable write tools
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
"env": {
|
|
122
|
+
"WAVE_ACCESS_TOKEN": "your_token_here",
|
|
123
|
+
"WAVE_ALLOW_WRITES": "1"
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
This registers the 44 tools that create, change, delete, or email records.
|
|
128
|
+
Without it they are not advertised at all, so a model cannot call one by
|
|
129
|
+
guessing its name.
|
|
130
|
+
|
|
131
|
+
### Docker
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
docker build -t wave-mcp-server .
|
|
135
|
+
docker run --rm -i -e WAVE_ACCESS_TOKEN=your_token_here wave-mcp-server
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The `-i` matters: the server speaks JSON-RPC on stdin and stdout.
|
|
139
|
+
|
|
140
|
+
### 1Password token lookup
|
|
141
|
+
|
|
142
|
+
Rather than pasting a token into a config file, point the server at a secret
|
|
143
|
+
reference and it will shell out to the `op` CLI on startup:
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
"env": { "WAVE_OP_PATH": "op://Development/Wave/credential" }
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
`WAVE_ACCESS_TOKEN_FILE` works the same way for a file on disk.
|
|
150
|
+
|
|
151
|
+
## What You Can Do
|
|
152
|
+
|
|
153
|
+
**Bill a customer end to end**
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
Create an invoice for Acme Corp with 10 hours of consulting at $150/hour,
|
|
157
|
+
due in 30 days. Approve it and email it to billing@acme.com.
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Quote, then convert**
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
Create an estimate for the website redesign package with a 25% deposit,
|
|
164
|
+
send it, and convert it to an invoice once they accept.
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Record a receipt**
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
Log a $45.99 expense from Office Depot on 2026-03-15 for office supplies,
|
|
171
|
+
paid from Business Checking.
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**Split a transaction**
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
Record a $100 withdrawal from checking: $60 to fuel and $40 to meals.
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Reconcile a processor payout**
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
A Stripe payout of $97 landed in checking: $100 of consulting income
|
|
184
|
+
less a $3 processing fee.
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**Chase what is owed**
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
Show me every unpaid invoice over $500, sorted by amount due, and which
|
|
191
|
+
customers carry the largest overdue balances.
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Tools Reference
|
|
195
|
+
|
|
196
|
+
Names are prefixed `wave_` so they do not collide with other MCP servers.
|
|
197
|
+
Tools marked **W** require `WAVE_ALLOW_WRITES=1`.
|
|
198
|
+
|
|
199
|
+
### Businesses and reference data
|
|
200
|
+
|
|
201
|
+
| Tool | Purpose |
|
|
202
|
+
|------|---------|
|
|
203
|
+
| `wave_list_businesses` | List reachable businesses |
|
|
204
|
+
| `wave_get_business` | Full business detail |
|
|
205
|
+
| `wave_set_default_business` | Set the session default |
|
|
206
|
+
| `wave_get_invoice_estimate_settings` | Accent color and logo |
|
|
207
|
+
| `wave_auth_status` | How credentials resolved, and what is gated. Makes no API call |
|
|
208
|
+
| `wave_get_user` | Account the token belongs to |
|
|
209
|
+
| `wave_get_oauth_application` | Application that issued the token |
|
|
210
|
+
| `wave_list_currencies` / `wave_get_currency` | Supported currencies |
|
|
211
|
+
| `wave_list_countries` / `wave_get_country` | Countries and their provinces |
|
|
212
|
+
| `wave_get_province` | One province or state |
|
|
213
|
+
| `wave_list_account_types` | The five top-level account types |
|
|
214
|
+
| `wave_list_account_subtypes` | Subtypes, which `wave_create_account` requires |
|
|
215
|
+
|
|
216
|
+
### Chart of accounts
|
|
217
|
+
|
|
218
|
+
| Tool | Purpose |
|
|
219
|
+
|------|---------|
|
|
220
|
+
| `wave_list_accounts` / `wave_get_account` | Accounts with balances |
|
|
221
|
+
| `wave_create_account` **W** | Add an account |
|
|
222
|
+
| `wave_patch_account` **W** | Rename or renumber |
|
|
223
|
+
| `wave_archive_account` **W** | Hide from pickers, keep history |
|
|
224
|
+
|
|
225
|
+
### Customers, vendors, products, taxes
|
|
226
|
+
|
|
227
|
+
| Tool | Purpose |
|
|
228
|
+
|------|---------|
|
|
229
|
+
| `wave_list_customers` / `wave_get_customer` | Customers with balances |
|
|
230
|
+
| `wave_create_customer` / `wave_patch_customer` / `wave_delete_customer` **W** | Manage customers |
|
|
231
|
+
| `wave_list_vendors` / `wave_get_vendor` | Vendors (read-only in Wave's API) |
|
|
232
|
+
| `wave_list_products` / `wave_get_product` | Products and services |
|
|
233
|
+
| `wave_create_product` / `wave_patch_product` / `wave_archive_product` **W** | Manage products |
|
|
234
|
+
| `wave_list_sales_taxes` / `wave_get_sales_tax` | Taxes and rate history |
|
|
235
|
+
| `wave_create_sales_tax` / `wave_patch_sales_tax` / `wave_archive_sales_tax` **W** | Manage taxes |
|
|
236
|
+
|
|
237
|
+
### Invoices and payments
|
|
238
|
+
|
|
239
|
+
| Tool | Purpose |
|
|
240
|
+
|------|---------|
|
|
241
|
+
| `wave_list_invoices` / `wave_get_invoice` | Invoices with items and payments |
|
|
242
|
+
| `wave_create_invoice` / `wave_patch_invoice` / `wave_clone_invoice` **W** | Build invoices |
|
|
243
|
+
| `wave_approve_invoice` **W** | Move a draft into the books |
|
|
244
|
+
| `wave_send_invoice` **W** | Emails the customer |
|
|
245
|
+
| `wave_mark_invoice_sent` **W** | Record delivery made outside Wave |
|
|
246
|
+
| `wave_delete_invoice` **W** | Permanent |
|
|
247
|
+
| `wave_get_invoice_payment` | One payment |
|
|
248
|
+
| `wave_create_invoice_payment` / `wave_patch_invoice_payment` / `wave_delete_invoice_payment` **W** | Record payments |
|
|
249
|
+
| `wave_send_invoice_payment_receipt` **W** | Emails the customer |
|
|
250
|
+
|
|
251
|
+
### Estimates and deposits
|
|
252
|
+
|
|
253
|
+
| Tool | Purpose |
|
|
254
|
+
|------|---------|
|
|
255
|
+
| `wave_list_estimates` / `wave_get_estimate` | Estimates with history and deposits |
|
|
256
|
+
| `wave_create_estimate` / `wave_patch_estimate` / `wave_clone_estimate` **W** | Build estimates |
|
|
257
|
+
| `wave_approve_estimate` **W** | Approve a draft |
|
|
258
|
+
| `wave_send_estimate` **W** | Emails the customer |
|
|
259
|
+
| `wave_mark_estimate_sent` / `wave_mark_estimate_accepted` **W** | Record offline delivery and acceptance |
|
|
260
|
+
| `wave_reset_estimate_acceptance` **W** | Undo an acceptance |
|
|
261
|
+
| `wave_send_estimate_acceptance_email` **W** | Emails the customer |
|
|
262
|
+
| `wave_generate_estimate_pdf` **W** | Render a PDF |
|
|
263
|
+
| `wave_convert_estimate_to_invoice` **W** | Turn an accepted estimate into an invoice |
|
|
264
|
+
| `wave_delete_estimate` **W** | Permanent |
|
|
265
|
+
| `wave_get_estimate_payment` | One deposit payment |
|
|
266
|
+
| `wave_create_estimate_deposit_payment` / `wave_update_estimate_deposit_payment` / `wave_delete_estimate_payment` **W** | Record deposits |
|
|
267
|
+
| `wave_send_estimate_deposit_receipt` **W** | Emails the customer |
|
|
268
|
+
|
|
269
|
+
### Bookkeeping
|
|
270
|
+
|
|
271
|
+
| Tool | Purpose |
|
|
272
|
+
|------|---------|
|
|
273
|
+
| `wave_create_money_transaction` **W** | One expense, income, or transfer |
|
|
274
|
+
| `wave_create_money_transactions` **W** | Bulk import, applied atomically |
|
|
275
|
+
| `wave_create_deposit_transaction` **W** | A payout whose net differs from gross |
|
|
276
|
+
| `wave_create_expense_from_receipt` **W** | Expense, account matched from a category word |
|
|
277
|
+
| `wave_create_income_from_payment` **W** | Income, account matched from a category word |
|
|
278
|
+
|
|
279
|
+
## Resources
|
|
280
|
+
|
|
281
|
+
Read-only JSON views for grounding context. Everything here is also reachable
|
|
282
|
+
through a tool, so hosts that ignore resources lose nothing.
|
|
283
|
+
|
|
284
|
+
`wave://businesses` • `wave://accounts` • `wave://customers` •
|
|
285
|
+
`wave://vendors` • `wave://products` • `wave://sales-taxes` •
|
|
286
|
+
`wave://account-taxonomy`
|
|
287
|
+
|
|
288
|
+
## How Transactions Work
|
|
289
|
+
|
|
290
|
+
Wave is double-entry, so `wave_create_money_transaction` has two sides:
|
|
291
|
+
|
|
292
|
+
- The **anchor** is the account money physically moved through, a bank account
|
|
293
|
+
or credit card, with a direction of `DEPOSIT` or `WITHDRAWAL`.
|
|
294
|
+
- The **line items** are the categories it is attributed to. Their amounts must
|
|
295
|
+
total the anchor amount.
|
|
296
|
+
|
|
297
|
+
A $50 office-supplies expense paid from checking is one anchor (checking,
|
|
298
|
+
`WITHDRAWAL`, `50.00`) and one line item (Office Supplies, `50.00`). A split is
|
|
299
|
+
the same anchor with more line items.
|
|
300
|
+
|
|
301
|
+
Every transaction carries an `external_id`. Wave deduplicates on it, so passing
|
|
302
|
+
a stable value of your own makes retries safe; one is generated when you omit
|
|
303
|
+
it.
|
|
304
|
+
|
|
305
|
+
## Environment Variables
|
|
306
|
+
|
|
307
|
+
| Variable | Required | Default | Description |
|
|
308
|
+
|----------|----------|---------|-------------|
|
|
309
|
+
| `WAVE_ACCESS_TOKEN` | Yes | (none) | OAuth2 bearer token from the Wave developer portal |
|
|
310
|
+
| `WAVE_BUSINESS_ID` | No | (none) | Default business, so tools can omit `business_id` |
|
|
311
|
+
| `WAVE_ALLOW_WRITES` | No | off | Set to `1` to register the 44 tools that change or send data |
|
|
312
|
+
| `WAVE_ACCESS_TOKEN_FILE` | No | (none) | Read the token from a file instead |
|
|
313
|
+
| `WAVE_OP_PATH` | No | (none) | Read the token from 1Password, e.g. `op://Vault/Item/credential` |
|
|
314
|
+
| `WAVE_TIMEOUT_MS` | No | `20000` | Per-request timeout |
|
|
315
|
+
| `WAVE_TOTAL_BUDGET_MS` | No | `50000` | Total time for one call including retries |
|
|
316
|
+
| `WAVE_MAX_RESPONSE_BYTES` | No | `8388608` | Reject responses above this size |
|
|
317
|
+
| `WAVE_HTTP_RETRIES` | No | `2` | Retries on 429 and 5xx |
|
|
318
|
+
| `WAVE_DISABLE_AGENT_CONFIG_FALLBACK` | No | off | Read only the environment, not agent config files |
|
|
319
|
+
|
|
320
|
+
Credentials resolve in order: environment, then the host agent's own config
|
|
321
|
+
file, then `WAVE_ACCESS_TOKEN_FILE`, then 1Password. Reading the agent config
|
|
322
|
+
matters because MCP clients launch this server as a subprocess, so a value in
|
|
323
|
+
`claude_desktop_config.json` or `~/.codex/config.toml` reaches it only if the
|
|
324
|
+
user wired it through by hand.
|
|
325
|
+
|
|
326
|
+
## Amount Handling
|
|
327
|
+
|
|
328
|
+
Money is sent to Wave as strings, not floats, so `0.1 + 0.2` cannot become
|
|
329
|
+
`0.30000000000000004` on the way to your ledger. Balance checks compare minor
|
|
330
|
+
units as integers for the same reason.
|
|
331
|
+
|
|
332
|
+
One exception, and it is Wave's: `moneyDepositTransactionCreate` types its
|
|
333
|
+
amounts as `Float` rather than `Decimal`, so `wave_create_deposit_transaction`
|
|
334
|
+
sends numbers there because the API accepts nothing else.
|
|
335
|
+
|
|
336
|
+
## Wave API Limitations
|
|
337
|
+
|
|
338
|
+
These are constraints in Wave's API, not gaps here. Each was confirmed against
|
|
339
|
+
the live schema.
|
|
340
|
+
|
|
341
|
+
- **Transactions cannot be read back.** Wave creates money transactions but
|
|
342
|
+
exposes no query to list them; there is no `transactions` connection on
|
|
343
|
+
`Business`. Review them in the web app.
|
|
344
|
+
- **Vendors are read-only.** The schema has no `vendorCreate`, `vendorPatch`,
|
|
345
|
+
or `vendorDelete`.
|
|
346
|
+
- **Money transactions cannot reference a vendor.**
|
|
347
|
+
`wave_create_expense_from_receipt` records the name in the description.
|
|
348
|
+
- **`wave_patch_estimate` demands fields you are not changing.** Wave marks
|
|
349
|
+
seven of them required on the patch input; read the estimate first and pass
|
|
350
|
+
its current values back.
|
|
351
|
+
- **`wave_patch_account` needs the account's current `sequence`** as an
|
|
352
|
+
optimistic-concurrency check.
|
|
353
|
+
- **Line items must reference a product.** No free-text lines.
|
|
354
|
+
- **`wave_create_deposit_transaction` returns no ID.**
|
|
355
|
+
- **Bills, receipts, payroll, and reports have no API.**
|
|
356
|
+
- **No file attachments.** Receipt images cannot be uploaded.
|
|
357
|
+
- **Rate limits are tight**, roughly two concurrent requests.
|
|
358
|
+
- **Un-archiving is web-app only.**
|
|
359
|
+
|
|
360
|
+
## Hosted Connector
|
|
361
|
+
|
|
362
|
+
`worker/` holds a Cloudflare Worker serving the same tools at a URL, with
|
|
363
|
+
per-user OAuth instead of a shared token. Users authorize against their own
|
|
364
|
+
Wave account, tokens are encrypted before storage, and write access is chosen
|
|
365
|
+
at authorization time so a read-only connection cannot be escalated later.
|
|
366
|
+
|
|
367
|
+
See [worker/README.md](worker/README.md) for setup and the security model.
|
|
368
|
+
|
|
369
|
+
## Architecture
|
|
370
|
+
|
|
371
|
+
```
|
|
372
|
+
index.js Single-file server: client, 74 tools, 7 resources
|
|
373
|
+
createWaveServer() Factory over injected credentials, shared by
|
|
374
|
+
the stdio process and the hosted Worker
|
|
375
|
+
scripts/
|
|
376
|
+
smoke-validate-graphql.mjs Schema-check every query against live Wave
|
|
377
|
+
smoke-list-tools.mjs Start over stdio and enumerate what is advertised
|
|
378
|
+
sync-plugin-metadata.mjs Propagate the version to every host manifest
|
|
379
|
+
check-release-consistency Fail the build when anything disagrees
|
|
380
|
+
build-mcpb.mjs Desktop bundle
|
|
381
|
+
worker/ Hosted OAuth connector
|
|
382
|
+
test/unit.test.mjs 54 tests, no network
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
The tool layer lives in one file on purpose. It is imported unchanged by the
|
|
386
|
+
Worker, so the hosted and local servers cannot drift apart.
|
|
387
|
+
|
|
388
|
+
### Verification without credentials
|
|
389
|
+
|
|
390
|
+
Wave validates a GraphQL document and coerces its variables *before* it checks
|
|
391
|
+
authentication. An `UNAUTHENTICATED` response therefore means the query is
|
|
392
|
+
correct, while `GRAPHQL_VALIDATION_FAILED` means it is not.
|
|
393
|
+
|
|
394
|
+
CI exploits that to schema-check all 64 documents on every push with no token
|
|
395
|
+
at all, which catches a field Wave renames before a user does.
|
|
396
|
+
|
|
397
|
+
## Building
|
|
398
|
+
|
|
399
|
+
```bash
|
|
400
|
+
npm install
|
|
401
|
+
npm test # 54 unit tests, no network
|
|
402
|
+
npm run smoke:list-tools # start over stdio, enumerate tools
|
|
403
|
+
npm run smoke:schema # validate every query against live Wave
|
|
404
|
+
npm run release:check # version parity across 8 manifests
|
|
405
|
+
npm run build:mcpb # desktop bundle
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
Contributions welcome. See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
409
|
+
|
|
410
|
+
## Not Affiliated With Wave
|
|
411
|
+
|
|
412
|
+
An independent project, not affiliated with, endorsed by, or sponsored by Wave
|
|
413
|
+
Financial Inc. Wave Financial Inc. owns the Wave name, logo, and marks; the icon
|
|
414
|
+
above is theirs and is used only to identify the service this server connects
|
|
415
|
+
to. Originally forked from
|
|
416
|
+
[vinnividivicci/wave_mcp](https://github.com/vinnividivicci/wave_mcp), then
|
|
417
|
+
rewritten.
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
<p align="center">
|
|
422
|
+
<a href="https://www.buymeacoffee.com/oliverames">
|
|
423
|
+
<img src="https://img.shields.io/badge/Buy_Me_a_Coffee-support-f5a542?style=for-the-badge&logo=buy-me-a-coffee&logoColor=white" alt="Buy Me a Coffee">
|
|
424
|
+
</a>
|
|
425
|
+
</p>
|
|
426
|
+
|
|
427
|
+
<p align="center">
|
|
428
|
+
<sub>
|
|
429
|
+
Built by <a href="https://ames.consulting">Oliver Ames</a> in Vermont
|
|
430
|
+
• <a href="https://github.com/oliverames">GitHub</a>
|
|
431
|
+
• <a href="https://linkedin.com/in/oliverames">LinkedIn</a>
|
|
432
|
+
• <a href="https://bsky.app/profile/oliverames.bsky.social">Bluesky</a>
|
|
433
|
+
</sub>
|
|
434
|
+
</p>
|
package/assets/icon.png
ADDED
|
Binary file
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Hosted OAuth connector
|
|
2
|
+
|
|
3
|
+
Most MCP clients can connect to a remote server over HTTP instead of launching
|
|
4
|
+
a local process. That path suits Wave well: rather than each user minting an
|
|
5
|
+
access token in the developer portal and pasting it into a config file, they
|
|
6
|
+
authorize the connector against their own Wave account and it handles refresh.
|
|
7
|
+
|
|
8
|
+
The implementation lives in [`worker/`](../worker/README.md); this document
|
|
9
|
+
covers what it is for and how to reason about it.
|
|
10
|
+
|
|
11
|
+
## When to use which
|
|
12
|
+
|
|
13
|
+
| | Local stdio | Hosted connector |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| Setup | Paste a token into your client config | Click through an OAuth screen |
|
|
16
|
+
| Token refresh | You regenerate it when it expires | Automatic |
|
|
17
|
+
| Where credentials live | Your machine | Encrypted in Cloudflare KV |
|
|
18
|
+
| Works offline against Wave | No, Wave is remote either way | No |
|
|
19
|
+
| Multi-user | One token per install | One authorization per user |
|
|
20
|
+
| Best for | A single developer on one machine | Sharing with a team, or a client that only speaks HTTP |
|
|
21
|
+
|
|
22
|
+
If you are the only user and you are comfortable managing a token, the local
|
|
23
|
+
server is simpler and has a smaller surface. The hosted connector earns its
|
|
24
|
+
complexity when more than one person needs access, or when your client cannot
|
|
25
|
+
launch a subprocess.
|
|
26
|
+
|
|
27
|
+
## Access levels
|
|
28
|
+
|
|
29
|
+
Write access is chosen during authorization, not afterwards. A read-only
|
|
30
|
+
connection requests only Wave's read scopes, so it is not merely that the
|
|
31
|
+
server declines to offer write tools: Wave never granted the permission, and no
|
|
32
|
+
later prompt injection can escalate it.
|
|
33
|
+
|
|
34
|
+
Choose write access only for a client you trust to act on your behalf. Sending
|
|
35
|
+
an invoice emails a real customer.
|
|
36
|
+
|
|
37
|
+
## Endpoints
|
|
38
|
+
|
|
39
|
+
| Path | Purpose |
|
|
40
|
+
|------|---------|
|
|
41
|
+
| `/mcp` | Streamable HTTP transport, the current standard |
|
|
42
|
+
| `/sse` | Legacy SSE transport |
|
|
43
|
+
| `/authorize`, `/token`, `/register` | OAuth 2.1, S256 PKCE only |
|
|
44
|
+
| `/callback` | Where Wave returns after authorization |
|
|
45
|
+
| `/` | Landing page |
|
|
46
|
+
| `/privacy` | Privacy statement |
|
|
47
|
+
| `/delete` | Remove stored tokens for one user |
|
|
48
|
+
|
|
49
|
+
## Operating it
|
|
50
|
+
|
|
51
|
+
Setup, secrets, deployment, and the post-deploy verification probes are in
|
|
52
|
+
[`worker/README.md`](../worker/README.md). The short version: `wrangler deploy`
|
|
53
|
+
reporting success proves nothing, so the deployment is not considered live
|
|
54
|
+
until the three unauthenticated probes pass.
|
package/docs/privacy.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Privacy
|
|
2
|
+
|
|
3
|
+
## The local server
|
|
4
|
+
|
|
5
|
+
`@oliverames/mcp-server-for-wave` runs on your own machine as a subprocess of
|
|
6
|
+
your MCP client. It has no backend and sends nothing anywhere except to Wave's
|
|
7
|
+
own API at `https://gql.waveapps.com/graphql/public`.
|
|
8
|
+
|
|
9
|
+
**Stored:** nothing. The server keeps no database, cache, or log file. Your
|
|
10
|
+
access token is read from the environment, a file you point it at, or 1Password,
|
|
11
|
+
and lives only in process memory.
|
|
12
|
+
|
|
13
|
+
**Transmitted:** your requests to Wave, and nothing else. There is no analytics,
|
|
14
|
+
telemetry, or error reporting.
|
|
15
|
+
|
|
16
|
+
**Logged:** diagnostics go to stderr, which your MCP client may capture. Access
|
|
17
|
+
tokens are redacted from error text before it is emitted.
|
|
18
|
+
|
|
19
|
+
Your accounting data flows from Wave, through this process, to your MCP client.
|
|
20
|
+
What that client then does with it, including whether it sends the content to a
|
|
21
|
+
model provider, is governed by that client's own privacy policy, not this one.
|
|
22
|
+
|
|
23
|
+
## The hosted connector
|
|
24
|
+
|
|
25
|
+
The Cloudflare Worker in `worker/` is a different matter, because it stores
|
|
26
|
+
credentials on your behalf.
|
|
27
|
+
|
|
28
|
+
**Stored:** your Wave user ID, your Wave OAuth access and refresh tokens
|
|
29
|
+
(encrypted at the application layer before they reach storage, with a key
|
|
30
|
+
separate from the OAuth provider's), and whether you granted write access.
|
|
31
|
+
|
|
32
|
+
**Not stored:** your Wave password, which the connector never sees; and your
|
|
33
|
+
accounting data, which is fetched for a request and returned to your client
|
|
34
|
+
without being retained.
|
|
35
|
+
|
|
36
|
+
**Deleting your data:** use the connector's `/delete` page to remove the stored
|
|
37
|
+
tokens, then revoke the application in your Wave account settings.
|
|
38
|
+
|
|
39
|
+
## Third parties
|
|
40
|
+
|
|
41
|
+
Wave Financial Inc. operates the API this software calls. Their handling of
|
|
42
|
+
your data is governed by [Wave's privacy policy](https://www.waveapps.com/legal/privacy-policy).
|
|
43
|
+
|
|
44
|
+
This project is not affiliated with Wave Financial Inc.
|
|
45
|
+
|
|
46
|
+
## Questions
|
|
47
|
+
|
|
48
|
+
Open an issue at
|
|
49
|
+
<https://github.com/oliverames/wave-mcp-server/issues>, or email
|
|
50
|
+
oliverames@gmail.com.
|