@oliverames/ynab-mcp-server 1.7.1 → 2.1.1
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/README.md +136 -33
- package/assets/icon.png +0 -0
- package/docs/hosted-oauth-connector.md +85 -0
- package/index.js +620 -75
- package/package.json +14 -4
- package/scripts/build-mcpb.mjs +133 -0
- package/scripts/check-release-consistency.mjs +100 -0
- package/scripts/lib/smoke-client.mjs +133 -0
- package/scripts/smoke-batch-verify.mjs +81 -0
- package/scripts/smoke-list-tools.mjs +33 -0
- package/scripts/smoke-review-unapproved.mjs +27 -0
- package/scripts/test-safety-model.mjs +140 -0
package/README.md
CHANGED
|
@@ -6,18 +6,18 @@
|
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
8
|
<strong>The complete Model Context Protocol server for YNAB</strong><br>
|
|
9
|
-
<em>Give your AI assistant
|
|
9
|
+
<em>Give your AI assistant read-only budget access by default, with explicit write opt-in</em>
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
13
|
-
<code>
|
|
13
|
+
<code>47 tools with writes enabled</code> •
|
|
14
14
|
<code>100% API coverage</code> •
|
|
15
15
|
<code>YNAB API v1.83</code>
|
|
16
16
|
</p>
|
|
17
17
|
|
|
18
18
|
<p align="center">
|
|
19
19
|
<a href="https://www.npmjs.com/package/@oliverames/ynab-mcp-server"><img src="https://img.shields.io/npm/v/%40oliverames%2Fynab-mcp-server?style=flat-square&color=f5a542" alt="npm"></a>
|
|
20
|
-
<a href="https://github.com/oliverames/ynab-mcp-server/releases/tag/
|
|
20
|
+
<a href="https://github.com/oliverames/ynab-mcp-server/releases/tag/v2.1.1"><img src="https://img.shields.io/github/v/release/oliverames/ynab-mcp-server?style=flat-square&color=f5a542&label=MCPB" alt="MCPB release"></a>
|
|
21
21
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-f5a542?style=flat-square" alt="License"></a>
|
|
22
22
|
<a href="https://www.buymeacoffee.com/oliverames"><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"></a>
|
|
23
23
|
</p>
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
<a href="#quick-start">Quick Start</a> •
|
|
27
27
|
<a href="#install-with-mcpb">MCPB Download</a> •
|
|
28
28
|
<a href="#what-you-can-do">What You Can Do</a> •
|
|
29
|
-
<a href="#tools-reference">
|
|
29
|
+
<a href="#tools-reference">Tools Reference</a> •
|
|
30
30
|
<a href="#environment-variables">Configuration</a>
|
|
31
31
|
</p>
|
|
32
32
|
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
|
|
37
37
|
YNAB's budgeting philosophy works best when you interact with your budget frequently - but the app interface isn't designed for quick queries or bulk operations. "How much did I spend on groceries this month?" shouldn't require navigating three screens. "Categorize all my Amazon orders from this week" shouldn't be a manual, one-by-one process.
|
|
38
38
|
|
|
39
|
-
This server gives your AI assistant
|
|
39
|
+
This server gives your AI assistant a safe interface to YNAB's API, turning natural language into budget review and, when explicitly enabled, budget operations. All monetary values are automatically converted between dollars and YNAB's internal milliunits format so the AI never has to think about it. Built on the [official YNAB JavaScript SDK](https://github.com/ynab/ynab-sdk-js) with direct API calls for the newest endpoints (category creation, category groups, money movements) that the SDK hasn't caught up with yet.
|
|
40
40
|
|
|
41
41
|
---
|
|
42
42
|
|
|
@@ -44,11 +44,11 @@ This server gives your AI assistant full access to YNAB's API, turning natural l
|
|
|
44
44
|
|
|
45
45
|
### Install with MCPB
|
|
46
46
|
|
|
47
|
-
For Claude Desktop and other MCPB-compatible clients, download the local bundle from the [
|
|
47
|
+
For Claude Desktop and other MCPB-compatible clients, download the local bundle from the [v2.1.1 release](https://github.com/oliverames/ynab-mcp-server/releases/tag/v2.1.1):
|
|
48
48
|
|
|
49
|
-
[Download `ynab-mcp-server-1.
|
|
49
|
+
[Download `ynab-mcp-server-2.1.1.mcpb`](https://github.com/oliverames/ynab-mcp-server/releases/download/v2.1.1/ynab-mcp-server-2.1.1.mcpb)
|
|
50
50
|
|
|
51
|
-
The bundle includes the YNAB favicon, production runtime dependencies, and setup prompts for your personal access token
|
|
51
|
+
The bundle includes the YNAB favicon, production runtime dependencies, and setup prompts for your personal access token, optional default budget ID, and optional write-tool opt-in.
|
|
52
52
|
|
|
53
53
|
### 1. Get a YNAB Personal Access Token
|
|
54
54
|
|
|
@@ -110,6 +110,23 @@ npm install -g @oliverames/ynab-mcp-server
|
|
|
110
110
|
|
|
111
111
|
That's it. Your AI can now talk to YNAB.
|
|
112
112
|
|
|
113
|
+
By default, the server registers read-only tools only. To expose tools that create, update, import, or delete YNAB data, add `YNAB_ALLOW_WRITES=1` to the MCP server environment:
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"mcpServers": {
|
|
118
|
+
"ynab": {
|
|
119
|
+
"command": "npx",
|
|
120
|
+
"args": ["-y", "@oliverames/ynab-mcp-server"],
|
|
121
|
+
"env": {
|
|
122
|
+
"YNAB_API_TOKEN": "your-token-here",
|
|
123
|
+
"YNAB_ALLOW_WRITES": "1"
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
113
130
|
---
|
|
114
131
|
|
|
115
132
|
## What You Can Do
|
|
@@ -131,7 +148,7 @@ That's it. Your AI can now talk to YNAB.
|
|
|
131
148
|
|
|
132
149
|
## Features
|
|
133
150
|
|
|
134
|
-
**Complete YNAB API v1.83 coverage** with
|
|
151
|
+
**Complete YNAB API v1.83 coverage** with 47 tools when writes are enabled:
|
|
135
152
|
|
|
136
153
|
| Resource | Tools | Capabilities |
|
|
137
154
|
|----------|-------|-------------|
|
|
@@ -144,14 +161,18 @@ That's it. Your AI can now talk to YNAB.
|
|
|
144
161
|
| **Money Movements** | 4 | Budget re-allocation tracking |
|
|
145
162
|
| **Transactions** | 8 | Full CRUD, bulk ops, split transactions, multi-filter |
|
|
146
163
|
| **Scheduled Transactions** | 5 | Full CRUD for recurring transactions |
|
|
147
|
-
| **Convenience** |
|
|
164
|
+
| **Convenience** | 2 | Unapproved transaction review and overspending checks |
|
|
148
165
|
|
|
149
166
|
### Design Decisions
|
|
150
167
|
|
|
168
|
+
- **Read-only by default** - write tools are not registered unless `YNAB_ALLOW_WRITES=1` is set. Read tools are annotated with `readOnlyHint: true`; write tools are annotated with `readOnlyHint: false`, idempotency hints, and destructive hints for delete operations.
|
|
151
169
|
- **Dollar amounts everywhere** - inputs and outputs are in dollars (`-12.34`), never milliunits (`-12340`). Conversion is automatic and transparent.
|
|
152
170
|
- **Smart budget resolution** - set `YNAB_BUDGET_ID` for a default, or omit it to auto-resolve to your last-used budget. Every tool accepts an optional `budgetId` override.
|
|
171
|
+
- **Pinned YNAB host** - all HTTP requests are restricted to `https://api.ynab.com`, redirects are not followed, and API tokens are redacted from surfaced errors.
|
|
172
|
+
- **Token fallback options** - use `YNAB_API_TOKEN`, a small token file via `YNAB_API_TOKEN_FILE`, or a 1Password CLI reference via `YNAB_OP_PATH`.
|
|
153
173
|
- **Split transactions** - first-class support for subtransactions in create, read, and format operations.
|
|
154
174
|
- **Bulk operations** - `create_transactions` and `update_transactions` handle arrays in a single API call.
|
|
175
|
+
- **Verified batch updates** - `update_transactions` refetches every requested transaction after the bulk API call, retries mismatched fields once through `update_transaction`, and returns a `verification` block so approval counts cannot hide failed category writes.
|
|
155
176
|
- **Fetch-then-merge updates** - scheduled transaction updates (which use PUT semantics) automatically fetch the current state and merge your changes, so you only specify what changed.
|
|
156
177
|
- **Fuzzy search** - `search_categories` and `search_payees` do case-insensitive partial matching across all entries.
|
|
157
178
|
- **Approval workflow with anomaly flags** - `review_unapproved` groups transactions into "ready to approve" (categorized, split, or transfer) and "needs attention" (uncategorized), and attaches a `flags` array to each transaction surfacing anomalies: `manually_entered` (not bank-imported), `match_broken` (stale match reference), `scheduled_transaction_realized`, `new_payee`, `no_prior_amount_match` (novel amount for this payee), and `category_drift:was_X` (payee categorized differently in the prior 60 days). Group-level flags aggregate the union of all transaction flags.
|
|
@@ -164,6 +185,8 @@ That's it. Your AI can now talk to YNAB.
|
|
|
164
185
|
|
|
165
186
|
## Tools Reference
|
|
166
187
|
|
|
188
|
+
Read tools are available by default. Tools that create, update, import, or delete YNAB data are marked as write tools and are registered only when `YNAB_ALLOW_WRITES=1`.
|
|
189
|
+
|
|
167
190
|
### User & Budgets
|
|
168
191
|
|
|
169
192
|
| Tool | Description |
|
|
@@ -179,7 +202,7 @@ That's it. Your AI can now talk to YNAB.
|
|
|
179
202
|
|------|-------------|
|
|
180
203
|
| `list_accounts` | List all accounts with balances, debt details, and import status |
|
|
181
204
|
| `get_account` | Get full account details including notes and debt fields |
|
|
182
|
-
| `create_account` |
|
|
205
|
+
| `create_account` | Write tool: create a new account (checking, savings, creditCard, mortgage, etc.) |
|
|
183
206
|
|
|
184
207
|
**Supported account types:** `checking`, `savings`, `cash`, `creditCard`, `lineOfCredit`, `otherAsset`, `otherLiability`, `mortgage`, `autoLoan`, `studentLoan`, `personalLoan`, `medicalDebt`, `otherDebt`
|
|
185
208
|
|
|
@@ -190,11 +213,11 @@ That's it. Your AI can now talk to YNAB.
|
|
|
190
213
|
| `list_categories` | List all category groups and their categories with budgeted/activity/balance |
|
|
191
214
|
| `get_category` | Get full category details including goal progress and cadence |
|
|
192
215
|
| `get_month_category` | Get category budget for a specific month |
|
|
193
|
-
| `update_month_category` |
|
|
194
|
-
| `update_category` |
|
|
195
|
-
| `create_category` |
|
|
196
|
-
| `create_category_group` |
|
|
197
|
-
| `update_category_group` |
|
|
216
|
+
| `update_month_category` | Write tool: set the budgeted amount for a category in a month |
|
|
217
|
+
| `update_category` | Write tool: update name, note, goal target, goal target date, or move to a different group |
|
|
218
|
+
| `create_category` | Write tool: create a new category in an existing group (with optional goal) |
|
|
219
|
+
| `create_category_group` | Write tool: create a new category group |
|
|
220
|
+
| `update_category_group` | Write tool: rename a category group |
|
|
198
221
|
| `search_categories` | Case-insensitive partial name search (e.g., "groc" finds "Groceries") |
|
|
199
222
|
|
|
200
223
|
### Payees
|
|
@@ -203,8 +226,8 @@ That's it. Your AI can now talk to YNAB.
|
|
|
203
226
|
|------|-------------|
|
|
204
227
|
| `list_payees` | List all payees with transfer account mappings |
|
|
205
228
|
| `get_payee` | Get payee details |
|
|
206
|
-
| `create_payee` |
|
|
207
|
-
| `update_payee` |
|
|
229
|
+
| `create_payee` | Write tool: create a new payee |
|
|
230
|
+
| `update_payee` | Write tool: rename a payee |
|
|
208
231
|
| `search_payees` | Case-insensitive partial name search |
|
|
209
232
|
|
|
210
233
|
### Payee Locations
|
|
@@ -236,13 +259,15 @@ That's it. Your AI can now talk to YNAB.
|
|
|
236
259
|
| Tool | Description |
|
|
237
260
|
|------|-------------|
|
|
238
261
|
| `get_transactions` | Get transactions with filters: by account, category, payee, month, or status (`unapproved`/`uncategorized`) |
|
|
239
|
-
| `get_transaction` | Get a single transaction by ID (includes subtransactions). Auto-handles composite scheduled-transaction IDs like `uuid_YYYY-MM-DD`. |
|
|
240
|
-
| `create_transaction` |
|
|
241
|
-
| `create_transactions` |
|
|
242
|
-
| `update_transaction` |
|
|
243
|
-
| `update_transactions` |
|
|
244
|
-
| `
|
|
245
|
-
| `
|
|
262
|
+
| `get_transaction` | Get a single transaction by ID (includes subtransactions). Auto-handles composite scheduled-transaction IDs like `uuid_YYYY-MM-DD`; if the underlying matched transaction has been deleted, falls back to returning the active scheduled template wrapped as `{ resource_type: "scheduled_transaction", ... }`. |
|
|
263
|
+
| `create_transaction` | Write tool: create a transaction with optional split (subtransactions must sum to total) |
|
|
264
|
+
| `create_transactions` | Write tool: bulk create multiple transactions in a single API call (supports split transactions) |
|
|
265
|
+
| `update_transaction` | Write tool: partial update - only specified fields change |
|
|
266
|
+
| `update_transactions` | Write tool: batch update multiple transactions at once, then refetch and verify requested fields persisted. Pass `returnSummary: true` for compact counts instead of full objects on large batches (avoids overflowing the tool-result size limit). |
|
|
267
|
+
| `approve_transactions` | Write tool: approve unapproved transactions in bulk by filter (`payeeId` / `categoryId` / `accountId`) without hand-listing IDs. Skips uncategorized transactions by default; returns a compact summary. |
|
|
268
|
+
| `reassign_payee_transactions` | Write tool: move all transactions from one payee to another — the merge workaround, since the YNAB API has no payee delete/merge endpoint. |
|
|
269
|
+
| `delete_transaction` | Write tool: delete a transaction |
|
|
270
|
+
| `import_transactions` | Write tool: trigger import from linked bank accounts |
|
|
246
271
|
|
|
247
272
|
### Scheduled Transactions
|
|
248
273
|
|
|
@@ -250,9 +275,9 @@ That's it. Your AI can now talk to YNAB.
|
|
|
250
275
|
|------|-------------|
|
|
251
276
|
| `list_scheduled_transactions` | List all recurring transactions |
|
|
252
277
|
| `get_scheduled_transaction` | Get a specific scheduled transaction |
|
|
253
|
-
| `create_scheduled_transaction` |
|
|
254
|
-
| `update_scheduled_transaction` |
|
|
255
|
-
| `delete_scheduled_transaction` |
|
|
278
|
+
| `create_scheduled_transaction` | Write tool: create a recurring transaction with frequency |
|
|
279
|
+
| `update_scheduled_transaction` | Write tool: update (fetch-then-merge preserves unchanged fields) |
|
|
280
|
+
| `delete_scheduled_transaction` | Write tool: delete a scheduled transaction |
|
|
256
281
|
|
|
257
282
|
**Supported frequencies:** `never`, `daily`, `weekly`, `everyOtherWeek`, `twiceAMonth`, `every4Weeks`, `monthly`, `everyOtherMonth`, `every3Months`, `every4Months`, `twiceAYear`, `yearly`, `everyOtherYear`
|
|
258
283
|
|
|
@@ -260,7 +285,42 @@ That's it. Your AI can now talk to YNAB.
|
|
|
260
285
|
|
|
261
286
|
| Tool | Description |
|
|
262
287
|
|------|-------------|
|
|
263
|
-
| `review_unapproved` | Get unapproved transactions grouped by readiness: "ready to approve" (categorized, split, or transfer) vs. "needs category first" (uncategorized). Each transaction includes a `flags` array highlighting anomalies (manually_entered, match_broken, no_prior_amount_match, category_drift, new_payee, scheduled_transaction_realized) computed against 60 days of payee history. Includes a warning against blind approval. |
|
|
288
|
+
| `review_unapproved` | Get unapproved transactions grouped by readiness: "ready to approve" (categorized, split, or transfer) vs. "needs category first" (uncategorized). Each transaction includes a `flags` array highlighting anomalies (manually_entered, match_broken, no_prior_amount_match, category_drift, new_payee, scheduled_transaction_realized) computed against 60 days of payee history. Includes a warning against blind approval. Pass `summary: true` for counts + by-payee aggregates only, or `compact: true` to keep per-transaction rows (with IDs) while dropping bulky fields so the response fits inline. |
|
|
289
|
+
| `get_overspent_categories` | Get categories with negative balances for a month, useful for finding prior-month overspending that reduces the current month's Ready to Assign. |
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## Workflow Safety Notes
|
|
294
|
+
|
|
295
|
+
### Write Tool Opt-In
|
|
296
|
+
|
|
297
|
+
The server starts in read-only mode. Write tools are not merely discouraged; they are absent from `listTools` unless `YNAB_ALLOW_WRITES=1` is present when the MCP process starts. This mirrors the safer hosted-connector pattern: the default permission set can inspect budgets, transactions, categories, payees, months, and scheduled transactions, but it cannot mutate financial data.
|
|
298
|
+
|
|
299
|
+
If a client already has the process running, changing the environment is not enough. Restart the MCP server after setting or clearing `YNAB_ALLOW_WRITES`.
|
|
300
|
+
|
|
301
|
+
### Batch Updates
|
|
302
|
+
|
|
303
|
+
When a batch operation categorizes and approves transactions at the same time, do not use `review_unapproved` counts as the only success check. Approved transactions leave the review queue even if a category write failed, so queue counts can hide approved-but-still-uncategorized transactions.
|
|
304
|
+
|
|
305
|
+
`update_transactions` now protects this path by refetching every requested transaction after the bulk API call and comparing the persisted fields with the requested fields. If anything differs, it retries that transaction once through `update_transaction`. The response includes:
|
|
306
|
+
|
|
307
|
+
```json
|
|
308
|
+
{
|
|
309
|
+
"verification": {
|
|
310
|
+
"checked": 1,
|
|
311
|
+
"retried": [],
|
|
312
|
+
"failed": []
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Treat any `failed` entry as a real write failure and inspect the named transaction with `get_transaction`.
|
|
318
|
+
|
|
319
|
+
### Credit Card Payment Transfers
|
|
320
|
+
|
|
321
|
+
If two unapproved transactions are clearly a credit card payment plus the matching checking-account outflow, convert them into a transfer before approval. Approving both sides as ordinary categorized transactions preserves the wrong structure and creates cleanup work.
|
|
322
|
+
|
|
323
|
+
Manual YNAB transfer fixes can replace one side of the pair with a new transaction ID. Read-only verification should not assume both original IDs survive. If one old ID returns `resource_not_found`, inspect recent activity in both involved accounts and verify the pair by `transfer_transaction_id` cross-links.
|
|
264
324
|
|
|
265
325
|
---
|
|
266
326
|
|
|
@@ -268,11 +328,17 @@ That's it. Your AI can now talk to YNAB.
|
|
|
268
328
|
|
|
269
329
|
| Variable | Required | Description |
|
|
270
330
|
|----------|----------|-------------|
|
|
271
|
-
| `YNAB_API_TOKEN` | Yes* | [Personal access token](https://app.ynab.com/settings/developer) from YNAB Developer Settings |
|
|
331
|
+
| `YNAB_API_TOKEN` | Yes* | [Personal access token](https://app.ynab.com/settings/developer) from YNAB Developer Settings. |
|
|
332
|
+
| `YNAB_API_TOKEN_FILE` | No | Path to a file containing the token. The file must be 4 KB or smaller. Used only when `YNAB_API_TOKEN` is unset. |
|
|
272
333
|
| `YNAB_BUDGET_ID` | No | Default budget ID. If omitted, uses `"last-used"` (your most recently accessed budget). Run `list_budgets` to find IDs. |
|
|
334
|
+
| `YNAB_ALLOW_WRITES` | No | Set to `1` to register write tools. Any other value keeps the server read-only. |
|
|
273
335
|
| `YNAB_OP_PATH` | No | 1Password secret reference for your API token (see below). Required only if using the 1Password fallback instead of `YNAB_API_TOKEN`. |
|
|
336
|
+
| `YNAB_RATE_LIMIT_PER_HOUR` | No | Client-side rate limiter. Defaults to `190`; set to `0` to disable for controlled tests. |
|
|
337
|
+
| `YNAB_RATE_LIMIT_BURST` | No | Maximum burst size before rate limiting pauses requests. Defaults to `10`. |
|
|
338
|
+
| `YNAB_HTTP_TIMEOUT_MS` | No | Per-request timeout. Defaults to `30000`. |
|
|
339
|
+
| `YNAB_MAX_RESPONSE_BYTES` | No | Maximum direct-fetch response size for newer endpoints. Defaults to `8388608`. |
|
|
274
340
|
|
|
275
|
-
*`YNAB_API_TOKEN` is required unless `YNAB_OP_PATH` is set.
|
|
341
|
+
*`YNAB_API_TOKEN` is required unless `YNAB_API_TOKEN_FILE` or `YNAB_OP_PATH` is set.
|
|
276
342
|
|
|
277
343
|
### 1Password Integration
|
|
278
344
|
|
|
@@ -311,7 +377,9 @@ All amounts in tool inputs and outputs are in **dollars** (e.g., `-12.34` for a
|
|
|
311
377
|
|
|
312
378
|
## Rate Limiting
|
|
313
379
|
|
|
314
|
-
The YNAB API allows **200 requests per hour** per access token, enforced on a rolling window.
|
|
380
|
+
The YNAB API allows **200 requests per hour** per access token, enforced on a rolling window. This server applies a client-side limiter at 190 requests per hour with a burst of 10 by default. Each tool call typically uses one API request, except tools that deliberately verify or merge writes (`update_transactions`, `update_scheduled_transaction`) which perform additional reads.
|
|
381
|
+
|
|
382
|
+
Set `YNAB_RATE_LIMIT_PER_HOUR=0` only for controlled local tests or smoke checks where you know you will stay under YNAB's API limit.
|
|
315
383
|
|
|
316
384
|
---
|
|
317
385
|
|
|
@@ -326,11 +394,14 @@ The YNAB API allows **200 requests per hour** per access token, enforced on a ro
|
|
|
326
394
|
```
|
|
327
395
|
|
|
328
396
|
- **Transport:** stdio (standard MCP server pattern)
|
|
329
|
-
- **Auth:** Bearer token via `YNAB_API_TOKEN`
|
|
397
|
+
- **Auth:** Bearer token via `YNAB_API_TOKEN`, `YNAB_API_TOKEN_FILE`, or `YNAB_OP_PATH`
|
|
330
398
|
- **SDK:** Official [`ynab`](https://www.npmjs.com/package/ynab) v2.5+ for core endpoints, direct `fetch` for newer API features
|
|
399
|
+
- **Safety:** read-only default, explicit write opt-in, host-pinned HTTPS requests to `api.ynab.com`, no redirect following, redacted token errors
|
|
331
400
|
- **Validation:** All parameters validated with [Zod](https://zod.dev) schemas
|
|
332
401
|
- **Error handling:** API errors are caught, formatted, and returned as MCP error responses with detail messages
|
|
333
402
|
|
|
403
|
+
For a hosted OAuth connector design, see [docs/hosted-oauth-connector.md](docs/hosted-oauth-connector.md).
|
|
404
|
+
|
|
334
405
|
---
|
|
335
406
|
|
|
336
407
|
## Testing
|
|
@@ -345,6 +416,26 @@ Use `YNAB_TEST_BUDGET_ID` to target a dedicated test budget without changing you
|
|
|
345
416
|
|
|
346
417
|
Tests cover all tool categories: reads, reversible writes, bulk operations, search, split transactions, scheduled transaction CRUD with fetch-then-merge verification, money movements, and payee locations.
|
|
347
418
|
|
|
419
|
+
### MCP Smoke Tests
|
|
420
|
+
|
|
421
|
+
Use the smoke tests when you need to prove the server is reachable over stdio without reconstructing a custom MCP client. These commands use the official MCP SDK client, the same transport shape used by normal MCP hosts, and require `YNAB_API_TOKEN` because this server validates credentials at startup.
|
|
422
|
+
|
|
423
|
+
```bash
|
|
424
|
+
YNAB_API_TOKEN=your-token YNAB_BUDGET_ID=your-budget-id npm run smoke:list-tools
|
|
425
|
+
YNAB_API_TOKEN=your-token YNAB_BUDGET_ID=your-budget-id npm run smoke:review-unapproved
|
|
426
|
+
YNAB_API_TOKEN=your-token YNAB_BUDGET_ID=your-budget-id YNAB_ALLOW_WRITES=1 npm run smoke:batch-verify
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
To test the package currently published to npm instead of the local checkout:
|
|
430
|
+
|
|
431
|
+
```bash
|
|
432
|
+
YNAB_API_TOKEN=your-token YNAB_BUDGET_ID=your-budget-id npm run smoke:list-tools -- --published
|
|
433
|
+
YNAB_API_TOKEN=your-token YNAB_BUDGET_ID=your-budget-id npm run smoke:review-unapproved -- --published
|
|
434
|
+
YNAB_API_TOKEN=your-token YNAB_BUDGET_ID=your-budget-id YNAB_ALLOW_WRITES=1 npm run smoke:batch-verify -- --published
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
`smoke:list-tools` verifies that high-value read tools such as `review_unapproved`, `get_transactions`, `search_categories`, and `search_payees` are present. When `YNAB_ALLOW_WRITES=1` is set, it also verifies `update_transactions`. `smoke:review-unapproved` calls `review_unapproved` with `summary: true` and prints only aggregate counts. `smoke:batch-verify` creates a temporary transaction, uses `update_transactions` to categorize and approve it in one call, refetches it through the MCP server, and deletes it afterward.
|
|
438
|
+
|
|
348
439
|
---
|
|
349
440
|
|
|
350
441
|
## Development
|
|
@@ -363,6 +454,18 @@ YNAB_API_TOKEN=your-token npm start
|
|
|
363
454
|
|
|
364
455
|
Zero additional dependencies. No build step. Pure ESM.
|
|
365
456
|
|
|
457
|
+
### Release Checks
|
|
458
|
+
|
|
459
|
+
Before publishing, run:
|
|
460
|
+
|
|
461
|
+
```bash
|
|
462
|
+
npm run release:check
|
|
463
|
+
npm run build:mcpb
|
|
464
|
+
npm pack --dry-run
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
After publishing, run `npm run release:check:registry` to verify the npm `latest` dist-tag, repo metadata, README release links, and MCPB artifact references all agree on the same version.
|
|
468
|
+
|
|
366
469
|
---
|
|
367
470
|
|
|
368
471
|
## License
|
package/assets/icon.png
ADDED
|
Binary file
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Hosted OAuth Connector Pattern
|
|
2
|
+
|
|
3
|
+
This package is a local stdio MCP server. A hosted connector should not ask users for a YNAB personal access token. It should follow an OAuth authorization-code flow, store per-user YNAB tokens server-side, and expose MCP over HTTPS.
|
|
4
|
+
|
|
5
|
+
## Target Architecture
|
|
6
|
+
|
|
7
|
+
Use a Cloudflare Worker or equivalent edge service with these routes:
|
|
8
|
+
|
|
9
|
+
| Route | Purpose |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `/mcp` | MCP endpoint served by the hosted connector. Requires a valid connector grant. |
|
|
12
|
+
| `/authorize` | Starts the connector OAuth flow and renders consent. |
|
|
13
|
+
| `/callback` | Receives the YNAB authorization code and completes the connector authorization. |
|
|
14
|
+
| `/token` | Issues connector access tokens to MCP hosts. |
|
|
15
|
+
| `/register` | Dynamic client registration, if the host supports it. |
|
|
16
|
+
| `/privacy` | Public privacy policy. |
|
|
17
|
+
| `/delete` | User-facing data deletion and grant revocation flow. |
|
|
18
|
+
|
|
19
|
+
The Smirnovlabs-style Cloudflare pattern is a good fit: wrap the MCP handler in an OAuth provider, use a stateful MCP agent for authenticated sessions, and keep the YNAB OAuth token refresh path separate from tool registration. Composio-style hosted connectors use the same core shape: hosted OAuth, per-user token vaulting, and a remote MCP endpoint instead of user-supplied PATs.
|
|
20
|
+
|
|
21
|
+
## YNAB OAuth Requirements
|
|
22
|
+
|
|
23
|
+
Use YNAB OAuth instead of personal access tokens:
|
|
24
|
+
|
|
25
|
+
- Authorization URL: `https://app.ynab.com/oauth/authorize`
|
|
26
|
+
- Token URL: `https://app.ynab.com/oauth/token`
|
|
27
|
+
- Response type: `code`
|
|
28
|
+
- PKCE: `S256` code challenge and verifier
|
|
29
|
+
- Scope: use the minimum viable scope. Prefer read-only unless the hosted connector explicitly offers write tools.
|
|
30
|
+
|
|
31
|
+
Required hosted environment values:
|
|
32
|
+
|
|
33
|
+
| Variable | Purpose |
|
|
34
|
+
|---|---|
|
|
35
|
+
| `YNAB_OAUTH_CLIENT_ID` | YNAB OAuth application client ID. |
|
|
36
|
+
| `YNAB_OAUTH_CLIENT_SECRET` | YNAB OAuth application secret. Store only in the host secret manager. |
|
|
37
|
+
| `YNAB_OAUTH_SCOPE` | Requested YNAB OAuth scope. |
|
|
38
|
+
| `CONNECTOR_BASE_URL` | Public connector origin, for redirect URL construction. |
|
|
39
|
+
| `TOKEN_KV` | Durable KV/DB binding for encrypted YNAB access and refresh tokens. |
|
|
40
|
+
| `OAUTH_STATE_KV` | Short-lived state storage for authorization requests. |
|
|
41
|
+
|
|
42
|
+
## Safety Model
|
|
43
|
+
|
|
44
|
+
Carry the local safety model into the hosted connector:
|
|
45
|
+
|
|
46
|
+
1. Register read tools by default.
|
|
47
|
+
2. Register write tools only when the connector grant, deployment config, and requested YNAB scope all allow writes.
|
|
48
|
+
3. Annotate read tools with `readOnlyHint: true`.
|
|
49
|
+
4. Annotate write tools with `readOnlyHint: false`, and use `destructiveHint: true` for delete tools.
|
|
50
|
+
5. Pin outbound YNAB HTTP traffic to `https://api.ynab.com`; reject redirects and non-YNAB hosts.
|
|
51
|
+
6. Redact `Authorization`, bearer tokens, access tokens, and refresh tokens from logs and MCP errors.
|
|
52
|
+
7. Add a delete-data flow that revokes connector grants and removes stored YNAB tokens.
|
|
53
|
+
|
|
54
|
+
## OAuth State Handling
|
|
55
|
+
|
|
56
|
+
The authorization flow should bind state to both server-side storage and a browser cookie:
|
|
57
|
+
|
|
58
|
+
1. Generate a random OAuth `state`.
|
|
59
|
+
2. Generate a PKCE verifier and `S256` challenge.
|
|
60
|
+
3. Store `{ state, verifier, redirect_uri, client_id, scope }` with a 10-minute TTL.
|
|
61
|
+
4. Set an HttpOnly, Secure, SameSite=Lax cookie containing a SHA-256 hash of `state`.
|
|
62
|
+
5. On `/callback`, require the query `state`, the stored state record, and the cookie hash to match.
|
|
63
|
+
6. Delete the state record after first use.
|
|
64
|
+
|
|
65
|
+
This prevents replay and cross-tab confusion while keeping the YNAB token exchange server-side.
|
|
66
|
+
|
|
67
|
+
## Token Lifecycle
|
|
68
|
+
|
|
69
|
+
The hosted connector should store tokens by connector user or YNAB user ID:
|
|
70
|
+
|
|
71
|
+
- On callback, exchange the code for YNAB access and refresh tokens.
|
|
72
|
+
- Fetch the YNAB user profile immediately and persist the YNAB user ID with the token record.
|
|
73
|
+
- Refresh tokens before expiry, with a small safety window such as 60 seconds.
|
|
74
|
+
- If refresh fails, delete the stale token record and require reauthorization.
|
|
75
|
+
- Never expose YNAB access or refresh tokens to the MCP host or model context.
|
|
76
|
+
|
|
77
|
+
## Deployment Checklist
|
|
78
|
+
|
|
79
|
+
- Create and verify a YNAB OAuth application with the production redirect URI.
|
|
80
|
+
- Decide whether the hosted connector offers read-only only, read/write, or separate read-only and write-enabled variants.
|
|
81
|
+
- Publish privacy and deletion pages before public distribution.
|
|
82
|
+
- Add non-affiliation language; this connector is not an official YNAB product.
|
|
83
|
+
- Run read-only smoke tests through `/mcp`.
|
|
84
|
+
- If writes are enabled, run the batch category+approval smoke against a dedicated test budget and assert post-write refetch verification.
|
|
85
|
+
|