@warmio/mcp 2.1.0 → 2.2.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/dist/api-types.js +2 -34
- package/dist/sandbox.js +0 -5
- package/dist/server.js +26 -174
- package/package.json +1 -1
package/dist/api-types.js
CHANGED
|
@@ -12,54 +12,22 @@ export function generateApiTypeString() {
|
|
|
12
12
|
accounts: Array<{ name: string; type: string; balance: number; institution: string }>;
|
|
13
13
|
}>;
|
|
14
14
|
|
|
15
|
-
/** Get transactions. Amounts: positive = expense, negative = income. Category c: "INCOME"/"TRANSFER_IN" = income, others = expenses. */
|
|
15
|
+
/** Get transactions (up to 1000). Amounts: positive = expense, negative = income. Category c: "INCOME"/"TRANSFER_IN" = income, others = expenses. */
|
|
16
16
|
getTransactions(params?: {
|
|
17
17
|
since?: string; // YYYY-MM-DD inclusive
|
|
18
18
|
until?: string; // YYYY-MM-DD inclusive
|
|
19
|
-
limit?: number; // default: 200, max: 1000
|
|
20
19
|
}): Promise<{
|
|
21
20
|
summary: { total: number; count: number; avg: number };
|
|
22
21
|
txns: Array<{ d: string; a: number; m: string; c: string | null }>;
|
|
23
22
|
more?: number;
|
|
24
23
|
}>;
|
|
25
24
|
|
|
26
|
-
/** Get
|
|
27
|
-
getRecurring(): Promise<{
|
|
28
|
-
recurring: Array<{ merchant: string; amount: number; frequency: string; next_date: string | null }>;
|
|
29
|
-
}>;
|
|
30
|
-
|
|
31
|
-
/** Get net worth history snapshots */
|
|
25
|
+
/** Get daily net worth history */
|
|
32
26
|
getSnapshots(params?: {
|
|
33
|
-
granularity?: "daily" | "monthly";
|
|
34
27
|
limit?: number;
|
|
35
28
|
since?: string;
|
|
36
29
|
}): Promise<{
|
|
37
|
-
granularity: string;
|
|
38
30
|
snapshots: Array<{ d: string; nw: number; a: number; l: number }>;
|
|
39
31
|
}>;
|
|
40
|
-
|
|
41
|
-
/** Get budgets with spending progress */
|
|
42
|
-
getBudgets(): Promise<{
|
|
43
|
-
budgets: Array<{ name: string; amount: number; spent: number; remaining: number; percent_used: number; period: string; status: string }>;
|
|
44
|
-
}>;
|
|
45
|
-
|
|
46
|
-
/** Get savings goals with progress */
|
|
47
|
-
getGoals(): Promise<{
|
|
48
|
-
goals: Array<{ name: string; target: number; current: number; progress_percent: number; target_date: string | null; status: string }>;
|
|
49
|
-
}>;
|
|
50
|
-
|
|
51
|
-
/** Get financial health score (0-100) with pillar breakdown */
|
|
52
|
-
getHealth(): Promise<{
|
|
53
|
-
score: number | null;
|
|
54
|
-
label: string | null;
|
|
55
|
-
pillars: { spend: number; save: number; borrow: number; build: number } | null;
|
|
56
|
-
data_completeness: number | null;
|
|
57
|
-
}>;
|
|
58
|
-
|
|
59
|
-
/** Get spending breakdown by category. Amounts are positive. */
|
|
60
|
-
getSpending(params?: { months?: number }): Promise<{
|
|
61
|
-
spending: Array<{ category: string; total: number; count: number }>;
|
|
62
|
-
period: { start: string; end: string };
|
|
63
|
-
}>;
|
|
64
32
|
};`;
|
|
65
33
|
}
|
package/dist/sandbox.js
CHANGED
|
@@ -53,12 +53,7 @@ async function __run() {
|
|
|
53
53
|
const warm = {
|
|
54
54
|
getAccounts: () => __callApi('get_accounts', '{}').then(JSON.parse),
|
|
55
55
|
getTransactions: (p) => __callApi('get_transactions', JSON.stringify(p || {})).then(JSON.parse),
|
|
56
|
-
getRecurring: () => __callApi('get_recurring', '{}').then(JSON.parse),
|
|
57
56
|
getSnapshots: (p) => __callApi('get_snapshots', JSON.stringify(p || {})).then(JSON.parse),
|
|
58
|
-
getBudgets: () => __callApi('get_budgets', '{}').then(JSON.parse),
|
|
59
|
-
getGoals: () => __callApi('get_goals', '{}').then(JSON.parse),
|
|
60
|
-
getHealth: () => __callApi('get_health', '{}').then(JSON.parse),
|
|
61
|
-
getSpending: (p) => __callApi('get_spending', JSON.stringify(p || {})).then(JSON.parse),
|
|
62
57
|
};
|
|
63
58
|
${code}
|
|
64
59
|
}
|
package/dist/server.js
CHANGED
|
@@ -113,20 +113,23 @@ async function apiRequest(endpoint, params = {}) {
|
|
|
113
113
|
403: 'Pro subscription required. Upgrade at https://warm.io/settings',
|
|
114
114
|
429: 'Rate limit exceeded. Try again in a few minutes.',
|
|
115
115
|
};
|
|
116
|
-
|
|
116
|
+
if (errorMessages[response.status]) {
|
|
117
|
+
throw new Error(errorMessages[response.status]);
|
|
118
|
+
}
|
|
119
|
+
// Read the actual error message from the API response body
|
|
120
|
+
let detail = `HTTP ${response.status}`;
|
|
121
|
+
try {
|
|
122
|
+
const body = (await response.json());
|
|
123
|
+
if (body?.error)
|
|
124
|
+
detail = body.error;
|
|
125
|
+
}
|
|
126
|
+
catch { /* ignore parse failures */ }
|
|
127
|
+
throw new Error(detail);
|
|
117
128
|
}
|
|
118
129
|
return response.json();
|
|
119
130
|
}
|
|
120
|
-
function sizeCheck(data, maxSize) {
|
|
121
|
-
let output = JSON.stringify(data);
|
|
122
|
-
if (output.length > maxSize) {
|
|
123
|
-
const reducedCount = Math.floor(data.length * (maxSize / output.length) * 0.8);
|
|
124
|
-
return data.slice(0, reducedCount);
|
|
125
|
-
}
|
|
126
|
-
return data;
|
|
127
|
-
}
|
|
128
131
|
// ============================================
|
|
129
|
-
//
|
|
132
|
+
// TOOL HANDLERS
|
|
130
133
|
// ============================================
|
|
131
134
|
async function handleGetAccounts() {
|
|
132
135
|
const response = (await apiRequest('/api/accounts'));
|
|
@@ -137,10 +140,6 @@ async function handleGetAccounts() {
|
|
|
137
140
|
async function handleGetTransactions(args) {
|
|
138
141
|
const since = args?.since ? String(args.since) : undefined;
|
|
139
142
|
const until = args?.until ? String(args.until) : undefined;
|
|
140
|
-
const parsedLimit = args?.limit ? Number(args.limit) : 200;
|
|
141
|
-
const requestedLimit = Number.isFinite(parsedLimit)
|
|
142
|
-
? Math.max(1, Math.min(Math.floor(parsedLimit), 1000))
|
|
143
|
-
: 200;
|
|
144
143
|
let transactions = [];
|
|
145
144
|
let cursor;
|
|
146
145
|
let pagesFetched = 0;
|
|
@@ -149,7 +148,8 @@ async function handleGetTransactions(args) {
|
|
|
149
148
|
const params = {
|
|
150
149
|
limit: String(TRANSACTION_PAGE_SIZE),
|
|
151
150
|
};
|
|
152
|
-
|
|
151
|
+
// API rejects last_knowledge + cursor together; only use last_knowledge on first page
|
|
152
|
+
if (since && !cursor)
|
|
153
153
|
params.last_knowledge = since;
|
|
154
154
|
if (cursor)
|
|
155
155
|
params.cursor = cursor;
|
|
@@ -165,11 +165,11 @@ async function handleGetTransactions(args) {
|
|
|
165
165
|
}
|
|
166
166
|
const compactTxns = transactions.map(compactTransaction);
|
|
167
167
|
const summary = calculateSummary(compactTxns);
|
|
168
|
-
const limited = compactTxns.slice(0,
|
|
169
|
-
const truncated = compactTxns.length >
|
|
168
|
+
const limited = compactTxns.slice(0, 1000);
|
|
169
|
+
const truncated = compactTxns.length > 1000;
|
|
170
170
|
const result = { summary, txns: limited };
|
|
171
171
|
if (truncated) {
|
|
172
|
-
result.more = compactTxns.length -
|
|
172
|
+
result.more = compactTxns.length - 1000;
|
|
173
173
|
}
|
|
174
174
|
let output = JSON.stringify(result);
|
|
175
175
|
if (output.length > MAX_RESPONSE_SIZE) {
|
|
@@ -179,29 +179,10 @@ async function handleGetTransactions(args) {
|
|
|
179
179
|
}
|
|
180
180
|
return result;
|
|
181
181
|
}
|
|
182
|
-
async function handleGetRecurring() {
|
|
183
|
-
const response = (await apiRequest('/api/subscriptions'));
|
|
184
|
-
const raw = response.recurring_transactions || [];
|
|
185
|
-
const recurring = raw.map((r) => ({
|
|
186
|
-
merchant: String(r.merchant_name || r.merchant || r.name || 'Unknown'),
|
|
187
|
-
// Normalize to positive amounts
|
|
188
|
-
amount: Math.round(Math.abs(Number(r.amount) || 0) * 100) / 100,
|
|
189
|
-
frequency: String(r.frequency || ''),
|
|
190
|
-
next_date: r.next_date ?? null,
|
|
191
|
-
}));
|
|
192
|
-
const checked = sizeCheck(recurring, MAX_RESPONSE_SIZE);
|
|
193
|
-
const result = { recurring: checked };
|
|
194
|
-
if (checked.length < recurring.length) {
|
|
195
|
-
result.more = recurring.length - checked.length;
|
|
196
|
-
}
|
|
197
|
-
return result;
|
|
198
|
-
}
|
|
199
182
|
async function handleGetSnapshots(args) {
|
|
200
183
|
const response = (await apiRequest('/api/snapshots'));
|
|
201
184
|
const snapshots = response.snapshots || [];
|
|
202
|
-
const
|
|
203
|
-
const defaultLimit = granularity === 'daily' ? 30 : 0;
|
|
204
|
-
const limit = args?.limit ? Number(args.limit) : defaultLimit;
|
|
185
|
+
const limit = args?.limit ? Number(args.limit) : 30;
|
|
205
186
|
const since = args?.since;
|
|
206
187
|
// Normalize snapshot dates (support both snapshot_date and d)
|
|
207
188
|
const normalized = snapshots.map((s) => ({
|
|
@@ -214,16 +195,6 @@ async function handleGetSnapshots(args) {
|
|
|
214
195
|
if (since) {
|
|
215
196
|
filtered = filtered.filter((s) => s.date >= since);
|
|
216
197
|
}
|
|
217
|
-
if (granularity === 'monthly') {
|
|
218
|
-
const byMonth = new Map();
|
|
219
|
-
filtered.forEach((s) => {
|
|
220
|
-
const month = s.date.substring(0, 7);
|
|
221
|
-
if (!byMonth.has(month) || s.date > (byMonth.get(month)?.date || '')) {
|
|
222
|
-
byMonth.set(month, s);
|
|
223
|
-
}
|
|
224
|
-
});
|
|
225
|
-
filtered = Array.from(byMonth.values());
|
|
226
|
-
}
|
|
227
198
|
filtered.sort((a, b) => b.date.localeCompare(a.date));
|
|
228
199
|
if (limit > 0) {
|
|
229
200
|
filtered = filtered.slice(0, limit);
|
|
@@ -234,7 +205,7 @@ async function handleGetSnapshots(args) {
|
|
|
234
205
|
a: Math.round(s.total_assets * 100) / 100,
|
|
235
206
|
l: Math.round(s.total_liabilities * 100) / 100,
|
|
236
207
|
}));
|
|
237
|
-
return {
|
|
208
|
+
return { snapshots: result };
|
|
238
209
|
}
|
|
239
210
|
async function handleVerifyKey() {
|
|
240
211
|
const response = (await apiRequest('/api/verify'));
|
|
@@ -243,72 +214,12 @@ async function handleVerifyKey() {
|
|
|
243
214
|
status: response.status || (response.valid ? 'ok' : 'invalid'),
|
|
244
215
|
};
|
|
245
216
|
}
|
|
246
|
-
async function handleGetBudgets() {
|
|
247
|
-
const response = (await apiRequest('/api/budgets'));
|
|
248
|
-
// Filter to only include spec-defined fields
|
|
249
|
-
const budgets = (response.budgets || []).map((b) => ({
|
|
250
|
-
name: String(b.name || ''),
|
|
251
|
-
amount: Number(b.amount || 0),
|
|
252
|
-
spent: Number(b.spent || 0),
|
|
253
|
-
remaining: Number(b.remaining || 0),
|
|
254
|
-
percent_used: Number(b.percent_used || 0),
|
|
255
|
-
period: String(b.period || ''),
|
|
256
|
-
status: String(b.status || ''),
|
|
257
|
-
}));
|
|
258
|
-
return { budgets };
|
|
259
|
-
}
|
|
260
|
-
async function handleGetGoals() {
|
|
261
|
-
const response = (await apiRequest('/api/goals'));
|
|
262
|
-
// Filter to only include spec-defined fields
|
|
263
|
-
const goals = (response.goals || []).map((g) => ({
|
|
264
|
-
name: String(g.name || ''),
|
|
265
|
-
target: Number(g.target || 0),
|
|
266
|
-
current: Number(g.current || 0),
|
|
267
|
-
progress_percent: Number(g.progress_percent || 0),
|
|
268
|
-
target_date: g.target_date ?? null,
|
|
269
|
-
status: String(g.status || ''),
|
|
270
|
-
}));
|
|
271
|
-
return { goals };
|
|
272
|
-
}
|
|
273
|
-
async function handleGetHealth() {
|
|
274
|
-
const response = (await apiRequest('/api/health'));
|
|
275
|
-
return {
|
|
276
|
-
score: response.score ?? null,
|
|
277
|
-
label: response.label ?? null,
|
|
278
|
-
pillars: response.pillars
|
|
279
|
-
? {
|
|
280
|
-
spend: Number(response.pillars.spend || 0),
|
|
281
|
-
save: Number(response.pillars.save || 0),
|
|
282
|
-
borrow: Number(response.pillars.borrow || 0),
|
|
283
|
-
build: Number(response.pillars.build || 0),
|
|
284
|
-
}
|
|
285
|
-
: null,
|
|
286
|
-
data_completeness: response.data_completeness ?? null,
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
async function handleGetSpending(args) {
|
|
290
|
-
const months = args?.months ? String(args.months) : '6';
|
|
291
|
-
const response = (await apiRequest('/api/spending', { months }));
|
|
292
|
-
return {
|
|
293
|
-
spending: (response.spending || []).map((s) => ({
|
|
294
|
-
category: String(s.category || ''),
|
|
295
|
-
total: Math.round(Number(s.total || 0) * 100) / 100,
|
|
296
|
-
count: Number(s.count || 0),
|
|
297
|
-
})),
|
|
298
|
-
period: response.period || { start: '', end: '' },
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
217
|
// Tool name → handler mapping for sandbox dispatch
|
|
302
218
|
const toolHandlers = {
|
|
303
219
|
get_accounts: handleGetAccounts,
|
|
304
220
|
get_transactions: handleGetTransactions,
|
|
305
|
-
get_recurring: handleGetRecurring,
|
|
306
221
|
get_snapshots: handleGetSnapshots,
|
|
307
222
|
verify_key: handleVerifyKey,
|
|
308
|
-
get_budgets: handleGetBudgets,
|
|
309
|
-
get_goals: handleGetGoals,
|
|
310
|
-
get_health: handleGetHealth,
|
|
311
|
-
get_spending: handleGetSpending,
|
|
312
223
|
};
|
|
313
224
|
// ============================================
|
|
314
225
|
// SERVER SETUP
|
|
@@ -327,49 +238,31 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
327
238
|
},
|
|
328
239
|
{
|
|
329
240
|
name: 'get_transactions',
|
|
330
|
-
description: 'Get transactions
|
|
241
|
+
description: 'Get transactions (up to 1000). This is the PRIMARY tool for all spending, income, and merchant questions. Filter results by merchant name `m` or category `c` to answer specific questions. Categories in `c`: INCOME and TRANSFER_IN = income, all others = expenses. Amounts: positive = expense, negative = income/deposit. Call with NO parameters to get all recent transactions.\nReturns: { summary: { total: number; count: number; avg: number }; txns: Array<{ d: string; a: number; m: string; c: string | null }>; more?: number }',
|
|
331
242
|
inputSchema: {
|
|
332
243
|
type: 'object',
|
|
333
244
|
properties: {
|
|
334
245
|
since: {
|
|
335
246
|
type: 'string',
|
|
336
|
-
description: 'Start date inclusive (YYYY-MM-DD)',
|
|
247
|
+
description: 'Start date inclusive (YYYY-MM-DD). Omit to get all available transactions.',
|
|
337
248
|
},
|
|
338
249
|
until: {
|
|
339
250
|
type: 'string',
|
|
340
|
-
description: 'End date inclusive (YYYY-MM-DD)',
|
|
341
|
-
},
|
|
342
|
-
limit: {
|
|
343
|
-
type: 'number',
|
|
344
|
-
description: 'Max transactions to return (default: 200, max: 1000)',
|
|
251
|
+
description: 'End date inclusive (YYYY-MM-DD). Omit for no end date filter.',
|
|
345
252
|
},
|
|
346
253
|
},
|
|
347
254
|
},
|
|
348
255
|
annotations: { readOnlyHint: true },
|
|
349
256
|
},
|
|
350
|
-
{
|
|
351
|
-
name: 'get_recurring',
|
|
352
|
-
description: 'Get detected subscriptions and recurring payments. Use for: "What subscriptions do I have?", "Show my monthly bills", "What are my recurring charges?".\nReturns: { recurring: Array<{ merchant: string; amount: number; frequency: string; next_date: string | null }> }',
|
|
353
|
-
inputSchema: {
|
|
354
|
-
type: 'object',
|
|
355
|
-
properties: {},
|
|
356
|
-
},
|
|
357
|
-
annotations: { readOnlyHint: true },
|
|
358
|
-
},
|
|
359
257
|
{
|
|
360
258
|
name: 'get_snapshots',
|
|
361
|
-
description: 'Get net worth history
|
|
259
|
+
description: 'Get daily net worth history. Use for: "How has my net worth changed?", "Show my financial progress", "What was my net worth last month?".\nReturns: { snapshots: Array<{ d: string; nw: number; a: number; l: number }> }',
|
|
362
260
|
inputSchema: {
|
|
363
261
|
type: 'object',
|
|
364
262
|
properties: {
|
|
365
|
-
granularity: {
|
|
366
|
-
type: 'string',
|
|
367
|
-
enum: ['daily', 'monthly'],
|
|
368
|
-
description: 'daily or monthly (default: daily)',
|
|
369
|
-
},
|
|
370
263
|
limit: {
|
|
371
264
|
type: 'number',
|
|
372
|
-
description: 'Number of snapshots (default: 30)',
|
|
265
|
+
description: 'Number of daily snapshots to return (default: 30)',
|
|
373
266
|
},
|
|
374
267
|
since: {
|
|
375
268
|
type: 'string',
|
|
@@ -379,47 +272,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
379
272
|
},
|
|
380
273
|
annotations: { readOnlyHint: true },
|
|
381
274
|
},
|
|
382
|
-
{
|
|
383
|
-
name: 'get_budgets',
|
|
384
|
-
description: 'Get all budgets with current spending progress. Use for: "How are my budgets?", "Am I over budget?", "Show my budget status".\nReturns: { budgets: Array<{ name: string; amount: number; spent: number; remaining: number; percent_used: number; period: string; status: string }> }',
|
|
385
|
-
inputSchema: {
|
|
386
|
-
type: 'object',
|
|
387
|
-
properties: {},
|
|
388
|
-
},
|
|
389
|
-
annotations: { readOnlyHint: true },
|
|
390
|
-
},
|
|
391
|
-
{
|
|
392
|
-
name: 'get_goals',
|
|
393
|
-
description: 'Get savings goals with progress. Use for: "How are my goals?", "Savings progress", "Am I on track for my goals?".\nReturns: { goals: Array<{ name: string; target: number; current: number; progress_percent: number; target_date: string | null; status: string }> }',
|
|
394
|
-
inputSchema: {
|
|
395
|
-
type: 'object',
|
|
396
|
-
properties: {},
|
|
397
|
-
},
|
|
398
|
-
annotations: { readOnlyHint: true },
|
|
399
|
-
},
|
|
400
|
-
{
|
|
401
|
-
name: 'get_health',
|
|
402
|
-
description: 'Get financial health score and pillar breakdown. Use for: "What\'s my financial health?", "How am I doing financially?", "Health score".\nReturns: { score: number | null; label: string | null; pillars: { spend: number; save: number; borrow: number; build: number } | null; data_completeness: number | null }',
|
|
403
|
-
inputSchema: {
|
|
404
|
-
type: 'object',
|
|
405
|
-
properties: {},
|
|
406
|
-
},
|
|
407
|
-
annotations: { readOnlyHint: true },
|
|
408
|
-
},
|
|
409
|
-
{
|
|
410
|
-
name: 'get_spending',
|
|
411
|
-
description: 'Get spending breakdown by category over a period. Use for: "Where does my money go?", "Spending by category", "Top spending categories".\nReturns: { spending: Array<{ category: string; total: number; count: number }>; period: { start: string; end: string } }',
|
|
412
|
-
inputSchema: {
|
|
413
|
-
type: 'object',
|
|
414
|
-
properties: {
|
|
415
|
-
months: {
|
|
416
|
-
type: 'number',
|
|
417
|
-
description: 'Number of months to analyze (default: 6, max: 24)',
|
|
418
|
-
},
|
|
419
|
-
},
|
|
420
|
-
},
|
|
421
|
-
annotations: { readOnlyHint: true },
|
|
422
|
-
},
|
|
423
275
|
{
|
|
424
276
|
name: 'run_analysis',
|
|
425
277
|
description: `Run JavaScript code that calls warm.* functions for complex multi-step analysis. Use when a query requires combining data from multiple tools, custom calculations, or comparisons that would take 3+ tool calls.\n\nAvailable API:\n${generateApiTypeString()}\n\nUse console.log() to output results. Example:\nconst [accounts, txns] = await Promise.all([warm.getAccounts(), warm.getTransactions({ since: "2024-01-01" })]);\nconst total = txns.txns.reduce((s, t) => s + t.a, 0);\nconsole.log(JSON.stringify({ accounts: accounts.accounts.length, totalSpent: total }));`,
|
|
@@ -437,7 +289,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
437
289
|
},
|
|
438
290
|
{
|
|
439
291
|
name: 'verify_key',
|
|
440
|
-
description: 'Check if API key is valid and working.\nReturns: { valid: boolean;
|
|
292
|
+
description: 'Check if API key is valid and working.\nReturns: { valid: boolean; status: string }',
|
|
441
293
|
inputSchema: {
|
|
442
294
|
type: 'object',
|
|
443
295
|
properties: {},
|