@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.
@@ -1,184 +1,192 @@
1
- type NormalizedAccountType = 'depository' | 'credit' | 'loan' | 'investment' | 'other';
2
1
  export interface WarmApiClientOptions {
3
2
  apiKeyResolver?: () => string | null;
4
3
  apiUrl?: string;
5
4
  fetchImplementation?: typeof fetch;
6
5
  requestTimeoutMs?: number;
7
6
  }
8
- export interface WarmApiAccount {
9
- account_id?: string | null;
10
- available_balance?: number | null;
11
- current_balance?: number | null;
12
- institution_name?: string | null;
13
- mask?: string | null;
14
- name?: string | null;
15
- subtype?: string | null;
16
- type?: string | null;
17
- }
18
- export interface WarmApiTransaction {
19
- account_id?: string | null;
20
- amount?: number | null;
21
- date?: string | null;
22
- detailed_category?: string | null;
23
- id?: string | null;
24
- merchant_name?: string | null;
25
- name?: string | null;
26
- primary_category?: string | null;
27
- }
28
- export interface WarmApiBudget {
29
- amount?: number | null;
30
- name?: string | null;
31
- period?: string | null;
32
- percent_used?: number | null;
33
- remaining?: number | null;
34
- spent?: number | null;
35
- status?: string | null;
36
- }
37
- export interface WarmApiGoal {
38
- category?: string | null;
39
- current?: number | null;
40
- monthly_contribution_needed?: number | null;
41
- name?: string | null;
42
- progress_percent?: number | null;
43
- status?: string | null;
44
- target?: number | null;
45
- target_date?: string | null;
46
- }
47
- export interface WarmFinancialState extends Record<string, unknown> {
48
- budgets: Array<{
49
- amount: number;
50
- name: string;
51
- percent_used: number;
52
- period: string;
53
- remaining: number;
54
- spent: number;
55
- status: string | null;
56
- }>;
57
- category_spending: Array<{
58
- amount: number;
59
- category: string;
60
- }>;
61
- generated_at: string;
62
- goals: Array<{
63
- category: string | null;
64
- current: number;
65
- monthly_contribution_needed: number | null;
66
- name: string;
67
- progress_percent: number;
68
- status: string | null;
69
- target: number;
70
- target_date: string | null;
71
- }>;
72
- health: {
73
- data_completeness: number | null;
74
- label: string | null;
75
- message: string | null;
76
- pillars: {
77
- borrow: number | null;
78
- build: number | null;
79
- save: number | null;
80
- spend: number | null;
81
- } | null;
82
- score: number | null;
83
- };
84
- holdings: Array<{
85
- account_id: string;
86
- cost_basis: number | null;
87
- quantity: number;
88
- security_name: string | null;
89
- symbol: string | null;
90
- type: string | null;
91
- value: number | null;
92
- }>;
93
- liabilities: Array<{
94
- account_id: string;
95
- apr_percentage: number | null;
96
- balance: number | null;
97
- is_overdue: boolean | null;
98
- minimum_payment: number | null;
99
- next_payment_due_date: string | null;
100
- type: string;
101
- }>;
102
- recurring: Array<{
103
- active: boolean;
104
- amount: number;
105
- frequency: string;
106
- merchant: string;
107
- next_date: string | null;
108
- type: string | null;
109
- }>;
110
- snapshots: Array<{
111
- date: string;
112
- net_worth: number;
113
- total_assets: number;
114
- total_liabilities: number;
7
+ export interface TransactionIndex {
8
+ total: number;
9
+ months: Array<{
10
+ month: string;
11
+ count: number;
115
12
  }>;
116
13
  }
117
- export interface GetTransactionsInput {
118
- cursor?: string;
119
- last_knowledge?: string;
120
- limit: number;
121
- search?: string;
122
- }
123
- export interface GetTransactionsOutput extends Record<string, unknown> {
124
- generated_at: string | null;
125
- next_knowledge: string | null;
126
- pagination: {
127
- has_more: boolean;
128
- limit: number;
129
- next_cursor: string | null;
130
- };
131
- txns: Array<{
132
- amount: number;
133
- category: string | null;
134
- date: string | null;
135
- description: string | null;
136
- detailed_category: string | null;
137
- id: string | null;
138
- merchant: string | null;
139
- }>;
14
+ export interface Position {
15
+ date: string | null;
16
+ net_worth: number | null;
17
+ cash: number | null;
18
+ debt: number | null;
19
+ investments: number | null;
20
+ other_assets: number | null;
21
+ total_assets: number | null;
140
22
  }
141
- export interface GetAccountsOutput extends Record<string, unknown> {
142
- accounts: Array<{
143
- balance: number;
144
- institution: string | null;
145
- mask: string | null;
146
- name: string;
147
- subtype: string | null;
148
- type: NormalizedAccountType;
149
- }>;
23
+ export interface Account {
24
+ id: string;
25
+ name: string;
26
+ type: 'depository' | 'credit' | 'loan' | 'investment' | 'brokerage' | 'other';
27
+ subtype: string | null;
28
+ group: 'cash' | 'debt' | 'investments' | null;
29
+ institution: string | null;
30
+ mask: string | null;
31
+ balance: number | null;
32
+ available: number | null;
33
+ currency: string | null;
34
+ updated_at: string | null;
35
+ }
36
+ export interface Status {
37
+ position: Position | null;
38
+ accounts: Account[];
39
+ }
40
+ export interface Recurring {
41
+ id: string;
42
+ account_id: string | null;
43
+ direction: 'inflow' | 'outflow' | null;
44
+ frequency: 'WEEKLY' | 'BIWEEKLY' | 'MONTHLY' | 'QUARTERLY' | 'SEMI_ANNUALLY' | 'ANNUALLY' | null;
45
+ status: 'active' | 'inactive' | 'dismissed' | null;
46
+ merchant: string | null;
47
+ amount: number | null;
48
+ next_date: string | null;
49
+ }
50
+ export interface Budget {
51
+ id: string;
52
+ name: string;
53
+ type: 'category' | 'merchant';
54
+ period: 'weekly' | 'biweekly' | 'monthly';
55
+ status: 'under' | 'warning' | 'over';
56
+ amount: number;
57
+ spent: number;
58
+ remaining: number;
59
+ used_percent: number;
60
+ }
61
+ export interface Goal {
62
+ id: string;
63
+ name: string;
64
+ category: string;
65
+ status: 'not_started' | 'in_progress' | 'on_track' | 'behind' | 'completed';
66
+ target: number;
67
+ current: number;
68
+ progress_percent: number;
69
+ target_date: string | null;
70
+ }
71
+ export interface Snapshot {
72
+ date: string;
73
+ net_worth: number | null;
74
+ cash: number | null;
75
+ debt: number | null;
76
+ income: number | null;
77
+ expenses: number | null;
78
+ cash_flow: number | null;
79
+ savings_rate: number | null;
80
+ investments: number | null;
81
+ assets: number | null;
82
+ health_score: number | null;
83
+ }
84
+ export interface Liability {
85
+ account_id: string | null;
86
+ type: 'credit' | 'student' | 'mortgage' | 'other' | null;
87
+ balance: number | null;
88
+ minimum_payment: number | null;
89
+ due_date: string | null;
90
+ interest_rate: number | null;
91
+ rate_type: 'fixed' | 'variable' | null;
92
+ overdue: boolean | null;
93
+ }
94
+ export interface Holding {
95
+ account_id: string | null;
96
+ symbol: string | null;
97
+ name: string | null;
98
+ type: 'cash' | 'derivative' | 'equity' | 'etf' | 'fixed_income' | 'mutual_fund' | 'other' | null;
99
+ quantity: number | null;
100
+ value: number | null;
101
+ cost_basis: number | null;
102
+ }
103
+ export interface Health {
104
+ score: number;
105
+ label: 'Needs Attention' | 'Good' | 'Strong';
106
+ level: 'critical' | 'poor' | 'fair' | 'good' | 'strong';
107
+ summary: string;
108
+ data_completeness: number;
109
+ pillars: {
110
+ spend: number | null;
111
+ save: number | null;
112
+ borrow: number | null;
113
+ build: number | null;
114
+ } | null;
115
+ }
116
+ export interface FinancialContext extends Record<string, unknown> {
117
+ version: 'v1';
118
+ updated_at: string;
119
+ currency: 'USD';
120
+ status: Status;
121
+ transactions: TransactionIndex;
122
+ recurring: Recurring[];
123
+ budgets: Budget[];
124
+ goals: Goal[];
125
+ snapshots: Snapshot[];
126
+ liabilities: Liability[];
127
+ holdings: Holding[];
128
+ health: Health | null;
129
+ }
130
+ export interface Transaction {
131
+ id: string;
132
+ account_id: string | null;
133
+ date: string | null;
134
+ amount: number;
135
+ merchant: string | null;
136
+ name: string | null;
137
+ category: string | null;
138
+ subcategory: string | null;
139
+ pending: boolean | null;
140
+ currency: string | null;
141
+ }
142
+ export interface TransactionMonth extends Record<string, unknown> {
143
+ month: string;
144
+ start_date: string;
145
+ end_date: string;
146
+ count: number;
147
+ items: Transaction[];
148
+ }
149
+ export interface LatestTransactions extends Record<string, unknown> {
150
+ since: string;
151
+ window_days: 10;
152
+ count: number;
153
+ items: Transaction[];
154
+ }
155
+ export type GetTransactionsInput = {
156
+ month: string;
157
+ latest?: never;
158
+ } | {
159
+ latest: true;
160
+ month?: never;
161
+ };
162
+ export type GetTransactionsOutput = TransactionMonth | LatestTransactions;
163
+ export interface FinancialContextMeta extends Record<string, unknown> {
164
+ version: 'v1';
165
+ user_id: string;
166
+ context_id: string;
167
+ generated_at: string;
168
+ updated_at: string;
169
+ content_hash: string;
170
+ byte_length: number;
171
+ counts: {
172
+ accounts: number;
173
+ transaction_months: number;
174
+ transactions: number;
175
+ snapshots: number;
176
+ };
150
177
  }
151
178
  export interface VerifyKeyOutput extends Record<string, unknown> {
152
179
  status: string;
153
180
  valid: boolean;
154
181
  }
155
182
  export interface WarmApiClient {
156
- getAccounts(): Promise<GetAccountsOutput>;
157
- getFinancialState(): Promise<WarmFinancialState>;
183
+ getFinancialContext(): Promise<FinancialContext>;
184
+ getFinancialContextMeta(): Promise<FinancialContextMeta>;
158
185
  getTransactions(input: GetTransactionsInput): Promise<GetTransactionsOutput>;
159
186
  verifyKey(): Promise<VerifyKeyOutput>;
160
187
  }
161
- export interface WarmApiLiability {
162
- account_id?: string | null;
163
- apr_percentage?: number | null;
164
- balance?: number | null;
165
- is_overdue?: boolean | null;
166
- minimum_payment?: number | null;
167
- next_payment_due_date?: string | null;
168
- type?: string | null;
169
- }
170
- export interface WarmApiHolding {
171
- account_id?: string | null;
172
- cost_basis?: number | null;
173
- quantity?: number | null;
174
- security_name?: string | null;
175
- symbol?: string | null;
176
- type?: string | null;
177
- value?: number | null;
178
- }
179
188
  export declare function getConfiguredApiKey(): string | null;
180
189
  export declare function resetConfiguredApiKeyCache(): void;
181
190
  export declare function apiRequest<TResponse>(endpoint: string, params?: Record<string, string | undefined>, options?: WarmApiClientOptions): Promise<TResponse>;
182
191
  export declare function createWarmApiClient(options?: WarmApiClientOptions): WarmApiClient;
183
192
  export declare function verifyWarmApiKey(apiKey: string): Promise<VerifyKeyOutput>;
184
- export {};
@@ -5,9 +5,6 @@ const DEFAULT_REQUEST_TIMEOUT_MS = (() => {
5
5
  return Number.isFinite(raw) && raw > 0 ? raw : 10_000;
6
6
  })();
7
7
  let cachedApiKey;
8
- function roundMoney(value) {
9
- return Math.round(value * 100) / 100;
10
- }
11
8
  function getRequestSignal(timeoutMs) {
12
9
  if (typeof AbortSignal.timeout === 'function') {
13
10
  return AbortSignal.timeout(timeoutMs);
@@ -17,127 +14,6 @@ function getRequestSignal(timeoutMs) {
17
14
  timer.unref?.();
18
15
  return controller.signal;
19
16
  }
20
- function normalizeAccountType(rawType) {
21
- switch (rawType) {
22
- case 'depository':
23
- case 'credit':
24
- case 'loan':
25
- case 'investment':
26
- return rawType;
27
- default:
28
- return 'other';
29
- }
30
- }
31
- function normalizeStateAccount(account) {
32
- return {
33
- available_balance: account.available_balance == null ? null : roundMoney(account.available_balance),
34
- balance: roundMoney(account.current_balance ?? 0),
35
- institution: account.institution_name ?? null,
36
- mask: account.mask ?? null,
37
- name: account.name || 'Unknown Account',
38
- subtype: account.subtype ?? null,
39
- type: normalizeAccountType(account.type),
40
- };
41
- }
42
- function normalizeAccounts(accounts) {
43
- return {
44
- accounts: accounts.map((account) => {
45
- const normalized = normalizeStateAccount(account);
46
- return {
47
- balance: normalized.balance,
48
- institution: normalized.institution,
49
- mask: normalized.mask,
50
- name: normalized.name,
51
- subtype: normalized.subtype,
52
- type: normalized.type,
53
- };
54
- }),
55
- };
56
- }
57
- function normalizeFinancialState(input) {
58
- const latestSnapshot = input.snapshots?.[0];
59
- const categorySpending = latestSnapshot?.spending_by_category ?? [];
60
- return {
61
- budgets: (input.budgets || []).map((budget) => ({
62
- amount: budget.amount ?? 0,
63
- name: budget.name || '',
64
- percent_used: budget.percent_used ?? 0,
65
- period: budget.period || '',
66
- remaining: budget.remaining ?? 0,
67
- spent: budget.spent ?? 0,
68
- status: budget.status ?? null,
69
- })),
70
- category_spending: categorySpending.map((item) => ({
71
- amount: roundMoney(item?.amount ?? item?.total ?? 0),
72
- category: item?.category || 'Uncategorized',
73
- })),
74
- generated_at: input.generatedAt,
75
- goals: (input.goals || []).map((goal) => ({
76
- category: goal.category ?? null,
77
- current: goal.current ?? 0,
78
- monthly_contribution_needed: goal.monthly_contribution_needed ?? null,
79
- name: goal.name || '',
80
- progress_percent: goal.progress_percent ?? 0,
81
- status: goal.status ?? null,
82
- target: goal.target ?? 0,
83
- target_date: goal.target_date ?? null,
84
- })),
85
- health: {
86
- data_completeness: input.health?.data_completeness ?? null,
87
- label: input.health?.label ?? null,
88
- message: input.health?.message ?? null,
89
- pillars: input.health?.pillars
90
- ? {
91
- borrow: input.health.pillars.borrow ?? null,
92
- build: input.health.pillars.build ?? null,
93
- save: input.health.pillars.save ?? null,
94
- spend: input.health.pillars.spend ?? null,
95
- }
96
- : null,
97
- score: input.health?.score ?? null,
98
- },
99
- holdings: (input.holdings || []).map((holding) => ({
100
- account_id: holding.account_id || '',
101
- cost_basis: holding.cost_basis ?? null,
102
- quantity: roundMoney(holding.quantity ?? 0),
103
- security_name: holding.security_name ?? null,
104
- symbol: holding.symbol ?? null,
105
- type: holding.type ?? null,
106
- value: holding.value == null ? null : roundMoney(holding.value),
107
- })),
108
- liabilities: (input.liabilities || []).map((liability) => ({
109
- account_id: liability.account_id || '',
110
- apr_percentage: liability.apr_percentage ?? null,
111
- balance: liability.balance == null ? null : roundMoney(liability.balance),
112
- is_overdue: liability.is_overdue ?? null,
113
- minimum_payment: liability.minimum_payment == null ? null : roundMoney(liability.minimum_payment),
114
- next_payment_due_date: liability.next_payment_due_date ?? null,
115
- type: liability.type || 'unknown',
116
- })),
117
- recurring: (input.recurring || []).map((stream) => ({
118
- active: stream.active === true || stream.is_active === true,
119
- amount: roundMoney(stream.amount ?? stream.last_amount ?? stream.average_amount ?? 0),
120
- frequency: stream.frequency || '',
121
- merchant: stream.merchant || stream.merchant_name || stream.description || '',
122
- next_date: stream.next_date ?? null,
123
- type: stream.type ?? stream.stream_type ?? null,
124
- })),
125
- snapshots: (input.snapshots || [])
126
- .map((snapshot) => ({
127
- date: snapshot.date || snapshot.snapshot_date || snapshot.period_end || null,
128
- net_worth: snapshot.net_worth ?? 0,
129
- total_assets: snapshot.total_assets ?? 0,
130
- total_liabilities: snapshot.total_liabilities ?? 0,
131
- }))
132
- .filter((snapshot) => typeof snapshot.date === 'string')
133
- .map((snapshot) => ({
134
- date: snapshot.date,
135
- net_worth: roundMoney(snapshot.net_worth),
136
- total_assets: roundMoney(snapshot.total_assets),
137
- total_liabilities: roundMoney(snapshot.total_liabilities),
138
- })),
139
- };
140
- }
141
17
  function createWarmApiClientConfig(options) {
142
18
  return {
143
19
  apiKeyResolver: options.apiKeyResolver,
@@ -215,66 +91,20 @@ export async function apiRequest(endpoint, params = {}, options = {}) {
215
91
  return (await response.json());
216
92
  }
217
93
  export function createWarmApiClient(options = {}) {
218
- const getAccounts = async () => {
219
- const response = await apiRequest('/api/export', { dataset: 'accounts' }, options);
220
- return normalizeAccounts(response.accounts || []);
221
- };
222
- const getFinancialState = async () => {
223
- const [snapshotsResponse, recurringResponse, budgetsResponse, goalsResponse, healthResponse, liabilitiesResponse, holdingsResponse,] = await Promise.all([
224
- apiRequest('/api/export', { dataset: 'snapshots' }, options),
225
- apiRequest('/api/export', { dataset: 'recurring' }, options),
226
- apiRequest('/api/export', { dataset: 'budgets' }, options),
227
- apiRequest('/api/export', { dataset: 'goals' }, options),
228
- apiRequest('/api/export', { dataset: 'health' }, options),
229
- apiRequest('/api/export', { dataset: 'liabilities' }, options),
230
- apiRequest('/api/export', { dataset: 'holdings' }, options),
231
- ]);
232
- const generatedAt = holdingsResponse.generated_at ||
233
- liabilitiesResponse.generated_at ||
234
- healthResponse.generated_at ||
235
- goalsResponse.generated_at ||
236
- budgetsResponse.generated_at ||
237
- recurringResponse.generated_at ||
238
- snapshotsResponse.generated_at ||
239
- new Date().toISOString();
240
- return normalizeFinancialState({
241
- budgets: budgetsResponse.budgets,
242
- generatedAt,
243
- goals: goalsResponse.goals,
244
- health: healthResponse,
245
- holdings: holdingsResponse.holdings,
246
- liabilities: liabilitiesResponse.liabilities,
247
- recurring: recurringResponse.recurring || recurringResponse.recurring_transactions,
248
- snapshots: snapshotsResponse.snapshots,
249
- });
250
- };
94
+ const getFinancialContext = async () => await apiRequest('/api/financial-context', {}, options);
95
+ const getFinancialContextMeta = async () => await apiRequest('/api/financial-context/meta', {}, options);
251
96
  const getTransactions = async (input) => {
252
- const response = await apiRequest('/api/export', {
253
- cursor: input.cursor,
254
- dataset: 'transactions',
255
- last_knowledge: input.last_knowledge,
256
- limit: String(input.limit),
257
- search: input.search,
258
- }, options);
259
- const nextCursor = response.pagination?.next_cursor ?? null;
260
- return {
261
- generated_at: response.generated_at ?? null,
262
- next_knowledge: response.next_knowledge ?? null,
263
- pagination: {
264
- has_more: nextCursor !== null,
265
- limit: response.pagination?.limit ?? input.limit,
266
- next_cursor: nextCursor,
267
- },
268
- txns: (response.transactions || []).map((transaction) => ({
269
- amount: roundMoney(transaction.amount ?? 0),
270
- category: transaction.primary_category ?? null,
271
- date: transaction.date ?? null,
272
- description: transaction.name ?? null,
273
- detailed_category: transaction.detailed_category ?? null,
274
- id: transaction.id ?? null,
275
- merchant: transaction.merchant_name ?? null,
276
- })),
277
- };
97
+ const rawInput = input;
98
+ if (typeof rawInput.month === 'string' && rawInput.latest !== undefined) {
99
+ throw new Error('`month` and `latest` are mutually exclusive.');
100
+ }
101
+ if (typeof rawInput.month === 'string') {
102
+ return await apiRequest('/api/financial-context/transactions', { month: rawInput.month, latest: undefined }, options);
103
+ }
104
+ if (rawInput.latest === true) {
105
+ return await apiRequest('/api/financial-context/transactions', { month: undefined, latest: '1' }, options);
106
+ }
107
+ throw new Error('Call getTransactions with `month` in YYYY-MM format or `latest: true`.');
278
108
  };
279
109
  const verifyKey = async () => {
280
110
  const response = await apiRequest('/api/verify', {}, options);
@@ -284,8 +114,8 @@ export function createWarmApiClient(options = {}) {
284
114
  };
285
115
  };
286
116
  return {
287
- getAccounts,
288
- getFinancialState,
117
+ getFinancialContext,
118
+ getFinancialContextMeta,
289
119
  getTransactions,
290
120
  verifyKey,
291
121
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@warmio/mcp",
3
- "version": "5.0.1",
3
+ "version": "6.0.0",
4
4
  "description": "Warm CLI and MCP server for personal financial data",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",