@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 +97 -148
- package/dist/index.js +82 -0
- package/dist/schemas.d.ts +1158 -186
- package/dist/schemas.js +205 -142
- package/dist/server.js +20 -20
- package/dist/types.d.ts +2 -2
- package/dist/types.js +1 -2
- package/dist/warm-api-client.d.ts +169 -161
- package/dist/warm-api-client.js +15 -185
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
110
|
+
Warm's v6 MCP surface is exactly three tools:
|
|
100
111
|
|
|
101
|
-
| Tool
|
|
102
|
-
|
|
|
103
|
-
| `
|
|
104
|
-
| `get_transactions`
|
|
105
|
-
| `
|
|
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
|
-
##
|
|
118
|
+
## Parameter Contract
|
|
109
119
|
|
|
110
120
|
- Every tool takes a JSON object input and returns a JSON object output.
|
|
111
|
-
-
|
|
112
|
-
-
|
|
113
|
-
-
|
|
114
|
-
-
|
|
115
|
-
|
|
116
|
-
-
|
|
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
|
-
### `
|
|
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
|
-
"
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
"
|
|
136
|
-
"
|
|
137
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
183
|
+
Month response:
|
|
157
184
|
|
|
158
185
|
```json
|
|
159
186
|
{
|
|
160
|
-
"
|
|
161
|
-
"
|
|
162
|
-
"
|
|
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": "
|
|
165
|
-
"
|
|
166
|
-
"
|
|
167
|
-
"
|
|
168
|
-
"
|
|
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
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
214
|
+
Latest response:
|
|
198
215
|
|
|
199
216
|
```json
|
|
200
217
|
{
|
|
201
|
-
"
|
|
202
|
-
"
|
|
203
|
-
|
|
204
|
-
|
|
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;
|