@warmio/mcp 5.0.1 → 6.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/README.md CHANGED
@@ -51,7 +51,7 @@ Optional auth overrides:
51
51
  - `WARM_API_KEY`
52
52
  - `WARM_API_KEY_FILE`
53
53
 
54
- ## Universal copy/paste setup prompt
54
+ ## Universal Copy/Paste Setup Prompt
55
55
 
56
56
  ```text
57
57
  I want to connect Warm to this AI app using MCP.
@@ -73,12 +73,23 @@ Do not ask me to paste my Warm API key into this chat. Help me store it locally
73
73
  flow, then test the connection by calling Warm's verify_key tool.
74
74
 
75
75
  After setup, I should be able to ask:
76
- - What’s my net worth?
76
+ - What is my net worth?
77
77
  - Summarize my recent transactions.
78
78
  - What changed recently?
79
79
  - What recurring charges do I have?
80
80
  ```
81
81
 
82
+ ## CLI
83
+
84
+ ```bash
85
+ npx -y @warmio/mcp@latest context get
86
+ npx -y @warmio/mcp@latest context meta
87
+ npx -y @warmio/mcp@latest transactions get --month 2026-07
88
+ npx -y @warmio/mcp@latest transactions get --latest
89
+ ```
90
+
91
+ All CLI read commands print JSON.
92
+
82
93
  ## Optional Streamable HTTP
83
94
 
84
95
  If your MCP client supports Streamable HTTP, you can run:
@@ -96,26 +107,31 @@ Relevant optional environment variables:
96
107
 
97
108
  ## Core Tools
98
109
 
99
- Warm's published/documented MCP surface is the following four-tool core:
110
+ Warm's v6 MCP surface is exactly three tools:
100
111
 
101
- | Tool | Description |
102
- | --------------------- | ----------------------------------------------- |
103
- | `get_accounts` | List connected accounts with current balances |
104
- | `get_transactions` | Page through transactions with an opaque cursor |
105
- | `get_financial_state` | Return the current typed financial state bundle |
106
- | `verify_key` | Validate the configured API key |
112
+ | Tool | Description |
113
+ | ----------------------- | ------------------------------------------------ |
114
+ | `get_financial_context` | Return compact `FinancialContext` JSON |
115
+ | `get_transactions` | Return one month page or the fixed latest window |
116
+ | `verify_key` | Validate the configured API key |
107
117
 
108
- ## Strict Contract
118
+ ## Parameter Contract
109
119
 
110
120
  - Every tool takes a JSON object input and returns a JSON object output.
111
- - Treat the contracts as closed and typed. Do not depend on undocumented fields.
112
- - Calendar dates use `YYYY-MM-DD`. Incremental sync timestamps use ISO 8601 datetimes.
113
- - Amounts are numbers, never formatted strings.
114
- - Transaction amounts follow the Plaid sign convention:
115
- positive = expense/debit, negative = income/credit.
116
- - Pagination cursors are opaque strings. Do not parse them or mix them with changed filters.
121
+ - `get_financial_context` takes `{}` and returns compact context JSON without transaction items.
122
+ - `get_transactions` takes either `{ "month": "YYYY-MM" }`, `{ "latest": true }`, or `{}`.
123
+ - A bare `get_transactions` call with no arguments defaults to `{ "latest": true }`.
124
+ - `month` must use `YYYY-MM` format, for example `2026-07`.
125
+ - `latest` is a fixed 10-day window anchored to the current artifact; the caller cannot configure the window.
126
+ - `month` and `latest` are mutually exclusive.
127
+ - A malformed month returns an error.
128
+ - A well-formed month outside the covered range returns an error.
129
+ - A well-formed month inside the covered range with no transactions returns `count: 0` and `items: []`.
130
+ - Transaction amounts follow the Plaid sign convention: positive = expense/debit, negative = income/credit.
131
+
132
+ ## Shapes
117
133
 
118
- ### `get_accounts`
134
+ ### `get_financial_context`
119
135
 
120
136
  Input:
121
137
 
@@ -123,168 +139,89 @@ Input:
123
139
  {}
124
140
  ```
125
141
 
126
- Returns:
142
+ Returns compact `FinancialContext`:
127
143
 
128
144
  ```json
129
145
  {
130
- "accounts": [
131
- {
132
- "name": "Primary Checking",
133
- "type": "depository",
134
- "subtype": "checking",
135
- "balance": 2450.12,
136
- "institution": "Chase",
137
- "mask": "1234"
138
- }
139
- ]
146
+ "version": "v1",
147
+ "updated_at": "2026-07-06T11:58:41.000Z",
148
+ "currency": "USD",
149
+ "status": {
150
+ "position": {
151
+ "date": "2026-07-06",
152
+ "net_worth": 84500,
153
+ "cash": 18200,
154
+ "debt": 6400,
155
+ "investments": 72700,
156
+ "other_assets": 0,
157
+ "total_assets": 90900
158
+ },
159
+ "accounts": []
160
+ },
161
+ "transactions": {
162
+ "total": 2,
163
+ "months": [{ "month": "2026-07", "count": 2 }]
164
+ },
165
+ "recurring": [],
166
+ "budgets": [],
167
+ "goals": [],
168
+ "snapshots": [],
169
+ "liabilities": [],
170
+ "holdings": [],
171
+ "health": null
140
172
  }
141
173
  ```
142
174
 
143
175
  ### `get_transactions`
144
176
 
145
- Input:
177
+ Month input:
146
178
 
147
179
  ```json
148
- {
149
- "limit": 100,
150
- "cursor": "opaque-cursor-from-a-prior-page",
151
- "last_knowledge": "2026-03-11T00:00:00.000Z",
152
- "search": "coffee"
153
- }
180
+ { "month": "2026-07" }
154
181
  ```
155
182
 
156
- Returns:
183
+ Month response:
157
184
 
158
185
  ```json
159
186
  {
160
- "generated_at": "2026-03-11T12:00:00.000Z",
161
- "next_knowledge": "2026-03-11T12:00:00.000Z",
162
- "txns": [
187
+ "month": "2026-07",
188
+ "start_date": "2026-07-01",
189
+ "end_date": "2026-07-31",
190
+ "count": 1,
191
+ "items": [
163
192
  {
164
- "id": "txn_123",
165
- "date": "2026-01-15",
166
- "amount": 12.34,
167
- "merchant": "Coffee Shop",
168
- "description": "COFFEE SHOP",
193
+ "id": "txn_1",
194
+ "account_id": "acct_1",
195
+ "date": "2026-07-05",
196
+ "amount": 42.18,
197
+ "merchant": "Whole Foods",
198
+ "name": "WHOLE FOODS",
169
199
  "category": "FOOD_AND_DRINK",
170
- "detailed_category": "FOOD_AND_DRINK_COFFEE"
200
+ "subcategory": "FOOD_AND_DRINK_GROCERIES",
201
+ "pending": false,
202
+ "currency": "USD"
171
203
  }
172
- ],
173
- "pagination": {
174
- "limit": 100,
175
- "next_cursor": "opaque-next-cursor",
176
- "has_more": true
177
- }
204
+ ]
178
205
  }
179
206
  ```
180
207
 
181
- Cursor model:
182
-
183
- 1. Omit `cursor` on the first call.
184
- 2. Keep `limit` and any filters such as `search` fixed while following a cursor chain.
185
- 3. If `pagination.next_cursor` is non-null, pass it unchanged to fetch the next page.
186
- 4. Stop when `next_cursor` is `null`.
187
- 5. Do not combine `cursor` with `last_knowledge`.
188
-
189
- ### `get_financial_state`
190
-
191
- Input:
208
+ Latest input:
192
209
 
193
210
  ```json
194
- {}
211
+ { "latest": true }
195
212
  ```
196
213
 
197
- Returns:
214
+ Latest response:
198
215
 
199
216
  ```json
200
217
  {
201
- "generated_at": "2026-03-11T12:00:00.000Z",
202
- "snapshots": [
203
- {
204
- "date": "2026-03-11",
205
- "net_worth": 125430.55,
206
- "total_assets": 168210.77,
207
- "total_liabilities": 42780.22
208
- }
209
- ],
210
- "recurring": [
211
- {
212
- "merchant": "Netflix",
213
- "amount": 15.49,
214
- "frequency": "MONTHLY",
215
- "next_date": "2026-03-18",
216
- "type": "subscription",
217
- "active": true
218
- }
219
- ],
220
- "budgets": [
221
- {
222
- "name": "Dining Out",
223
- "amount": 400,
224
- "spent": 182.55,
225
- "remaining": 217.45,
226
- "percent_used": 45.64,
227
- "period": "monthly",
228
- "status": "on_track"
229
- }
230
- ],
231
- "goals": [
232
- {
233
- "name": "Emergency Fund",
234
- "target": 10000,
235
- "current": 4200,
236
- "progress_percent": 42,
237
- "target_date": null,
238
- "status": "active",
239
- "category": "safety",
240
- "monthly_contribution_needed": 400
241
- }
242
- ],
243
- "health": {
244
- "score": 78,
245
- "label": "Good",
246
- "data_completeness": 94,
247
- "pillars": {
248
- "spend": 20,
249
- "save": 23,
250
- "borrow": 15,
251
- "build": 20
252
- },
253
- "message": null
254
- },
255
- "liabilities": [
256
- {
257
- "account_id": "acc_loan_1",
258
- "type": "student",
259
- "balance": 12450.22,
260
- "apr_percentage": 5.2,
261
- "minimum_payment": 145,
262
- "next_payment_due_date": "2026-03-22",
263
- "is_overdue": false
264
- }
265
- ],
266
- "holdings": [
267
- {
268
- "account_id": "acc_inv_1",
269
- "security_name": "Vanguard Total Stock Market ETF",
270
- "symbol": "VTI",
271
- "type": "etf",
272
- "quantity": 12.5,
273
- "value": 3541.25,
274
- "cost_basis": 3010
275
- }
276
- ],
277
- "category_spending": [
278
- {
279
- "category": "FOOD_AND_DRINK",
280
- "amount": 182.55
281
- }
282
- ]
218
+ "since": "2026-06-26",
219
+ "window_days": 10,
220
+ "count": 1,
221
+ "items": []
283
222
  }
284
223
  ```
285
224
 
286
- If Warm does not have enough state data yet, nullable fields remain `null`.
287
-
288
225
  ### `verify_key`
289
226
 
290
227
  Input:
@@ -302,6 +239,17 @@ Returns:
302
239
  }
303
240
  ```
304
241
 
242
+ ## v6 Breaking Changes
243
+
244
+ v6 is a clean break with no backwards compatibility and no alias window.
245
+
246
+ - `get_accounts` is removed. Use `get_financial_context.status.accounts`.
247
+ - `get_financial_state` is removed. Use `get_financial_context`.
248
+ - `get_transactions` no longer supports `cursor`, `search`, or `last_knowledge`.
249
+ - The old `txns` transaction array is gone. Transaction responses use `items`.
250
+ - Renamed fields follow the FinancialContext contract, including `subcategory`, `used_percent`,
251
+ `status`, `due_date`, and `other_assets`.
252
+
305
253
  ## Security
306
254
 
307
255
  - Read-only: no write, delete, transfer, or mutation tools
@@ -313,6 +261,7 @@ Returns:
313
261
  ```bash
314
262
  npm install
315
263
  npm run build
264
+ npm test
316
265
  ```
317
266
 
318
267
  ## License
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ import { startHttpServer } from './http.js';
4
4
  import { loadDotEnv } from './load-env.js';
5
5
  import { createWarmServer } from './server.js';
6
6
  import { AdaptiveStdioTransport } from './stdio.js';
7
+ import { createWarmApiClient } from './warm-api-client.js';
7
8
  function isInteractiveSession() {
8
9
  return process.stdin.isTTY === true && process.stdout.isTTY === true;
9
10
  }
@@ -18,6 +19,13 @@ function printUsage() {
18
19
  console.log(' warmio install [--force] [--no-validate]');
19
20
  console.log(' warmio mcp');
20
21
  console.log(' warmio http [--host 127.0.0.1] [--port 3000] [--path /mcp]');
22
+ console.log(' warmio context get');
23
+ console.log(' warmio context meta');
24
+ console.log(' warmio transactions get --month YYYY-MM');
25
+ console.log(' warmio transactions get --latest');
26
+ console.log('');
27
+ console.log(' Transactions require exactly one selector: --month YYYY-MM or --latest.');
28
+ console.log(' Month and latest are mutually exclusive. Latest is a fixed 10-day window.');
21
29
  console.log('');
22
30
  }
23
31
  function parseCliArgs(args) {
@@ -44,6 +52,53 @@ function parseCliArgs(args) {
44
52
  options.command = 'http';
45
53
  continue;
46
54
  }
55
+ if (arg === 'context') {
56
+ const subcommand = args[index + 1];
57
+ if (subcommand === 'get') {
58
+ options.command = 'context-get';
59
+ index += 1;
60
+ continue;
61
+ }
62
+ if (subcommand === 'meta') {
63
+ options.command = 'context-meta';
64
+ index += 1;
65
+ continue;
66
+ }
67
+ throw new Error('Usage: warmio context get | warmio context meta');
68
+ }
69
+ if (arg === 'transactions') {
70
+ const subcommand = args[index + 1];
71
+ if (subcommand !== 'get') {
72
+ throw new Error('Usage: warmio transactions get --month YYYY-MM | --latest');
73
+ }
74
+ options.command = 'transactions-get';
75
+ index += 1;
76
+ continue;
77
+ }
78
+ if (arg === '--format') {
79
+ const format = args[index + 1];
80
+ if (format !== 'json') {
81
+ throw new Error('Only --format json is supported.');
82
+ }
83
+ index += 1;
84
+ continue;
85
+ }
86
+ if (arg === '--month') {
87
+ const month = args[index + 1];
88
+ if (!month) {
89
+ throw new Error('Missing value for --month');
90
+ }
91
+ if (!/^\d{4}-\d{2}$/.test(month)) {
92
+ throw new Error(`Invalid value for --month: ${month}. Expected YYYY-MM.`);
93
+ }
94
+ options.month = month;
95
+ index += 1;
96
+ continue;
97
+ }
98
+ if (arg === '--latest') {
99
+ options.latest = true;
100
+ continue;
101
+ }
47
102
  if (arg === '--host') {
48
103
  options.httpHost = args[index + 1];
49
104
  index += 1;
@@ -79,6 +134,23 @@ function parseCliArgs(args) {
79
134
  }
80
135
  return options;
81
136
  }
137
+ function validateCliOptions(options) {
138
+ if (options.command !== 'transactions-get') {
139
+ if (options.month || options.latest) {
140
+ throw new Error('--month and --latest are only valid with `warmio transactions get`.');
141
+ }
142
+ return;
143
+ }
144
+ if (options.month && options.latest) {
145
+ throw new Error('--month and --latest are mutually exclusive.');
146
+ }
147
+ if (!options.month && !options.latest) {
148
+ throw new Error('Usage: warmio transactions get --month YYYY-MM | --latest');
149
+ }
150
+ }
151
+ function printJson(value) {
152
+ console.log(JSON.stringify(value, null, 2));
153
+ }
82
154
  export async function startStdioServer() {
83
155
  const server = createWarmServer();
84
156
  const transport = new AdaptiveStdioTransport();
@@ -88,7 +160,17 @@ export async function startStdioServer() {
88
160
  async function main() {
89
161
  loadDotEnv();
90
162
  const options = parseCliArgs(process.argv.slice(2));
163
+ validateCliOptions(options);
91
164
  switch (options.command) {
165
+ case 'context-get':
166
+ printJson(await createWarmApiClient().getFinancialContext());
167
+ return;
168
+ case 'context-meta':
169
+ printJson(await createWarmApiClient().getFinancialContextMeta());
170
+ return;
171
+ case 'transactions-get':
172
+ printJson(await createWarmApiClient().getTransactions(options.month ? { month: options.month } : { latest: true }));
173
+ return;
92
174
  case 'help':
93
175
  printUsage();
94
176
  return;