@warmio/mcp 5.0.0 → 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 +107 -155
- package/dist/index.js +82 -0
- package/dist/install.js +2 -2
- 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 +16 -188
- package/package.json +2 -1
|
@@ -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
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
157
|
-
|
|
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 {};
|
package/dist/warm-api-client.js
CHANGED
|
@@ -4,11 +4,7 @@ const DEFAULT_REQUEST_TIMEOUT_MS = (() => {
|
|
|
4
4
|
const raw = Number(process.env.WARM_API_TIMEOUT_MS || 10_000);
|
|
5
5
|
return Number.isFinite(raw) && raw > 0 ? raw : 10_000;
|
|
6
6
|
})();
|
|
7
|
-
const DEFAULT_SPENDING_MONTHS = 6;
|
|
8
7
|
let cachedApiKey;
|
|
9
|
-
function roundMoney(value) {
|
|
10
|
-
return Math.round(value * 100) / 100;
|
|
11
|
-
}
|
|
12
8
|
function getRequestSignal(timeoutMs) {
|
|
13
9
|
if (typeof AbortSignal.timeout === 'function') {
|
|
14
10
|
return AbortSignal.timeout(timeoutMs);
|
|
@@ -18,125 +14,6 @@ function getRequestSignal(timeoutMs) {
|
|
|
18
14
|
timer.unref?.();
|
|
19
15
|
return controller.signal;
|
|
20
16
|
}
|
|
21
|
-
function normalizeAccountType(rawType) {
|
|
22
|
-
switch (rawType) {
|
|
23
|
-
case 'depository':
|
|
24
|
-
case 'credit':
|
|
25
|
-
case 'loan':
|
|
26
|
-
case 'investment':
|
|
27
|
-
return rawType;
|
|
28
|
-
default:
|
|
29
|
-
return 'other';
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
function normalizeStateAccount(account) {
|
|
33
|
-
return {
|
|
34
|
-
available_balance: account.available_balance == null ? null : roundMoney(account.available_balance),
|
|
35
|
-
balance: roundMoney(account.current_balance ?? 0),
|
|
36
|
-
institution: account.institution_name ?? null,
|
|
37
|
-
mask: account.mask ?? null,
|
|
38
|
-
name: account.name || 'Unknown Account',
|
|
39
|
-
subtype: account.subtype ?? null,
|
|
40
|
-
type: normalizeAccountType(account.type),
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
function normalizeAccounts(accounts) {
|
|
44
|
-
return {
|
|
45
|
-
accounts: accounts.map((account) => {
|
|
46
|
-
const normalized = normalizeStateAccount(account);
|
|
47
|
-
return {
|
|
48
|
-
balance: normalized.balance,
|
|
49
|
-
institution: normalized.institution,
|
|
50
|
-
mask: normalized.mask,
|
|
51
|
-
name: normalized.name,
|
|
52
|
-
subtype: normalized.subtype,
|
|
53
|
-
type: normalized.type,
|
|
54
|
-
};
|
|
55
|
-
}),
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
function normalizeFinancialState(input) {
|
|
59
|
-
return {
|
|
60
|
-
budgets: (input.budgets || []).map((budget) => ({
|
|
61
|
-
amount: budget.amount ?? 0,
|
|
62
|
-
name: budget.name || '',
|
|
63
|
-
percent_used: budget.percent_used ?? 0,
|
|
64
|
-
period: budget.period || '',
|
|
65
|
-
remaining: budget.remaining ?? 0,
|
|
66
|
-
spent: budget.spent ?? 0,
|
|
67
|
-
status: budget.status ?? null,
|
|
68
|
-
})),
|
|
69
|
-
category_spending: (input.categorySpending || []).map((item) => ({
|
|
70
|
-
amount: roundMoney(item?.total ?? 0),
|
|
71
|
-
category: item?.category || 'Uncategorized',
|
|
72
|
-
})),
|
|
73
|
-
generated_at: input.generatedAt,
|
|
74
|
-
goals: (input.goals || []).map((goal) => ({
|
|
75
|
-
category: goal.category ?? null,
|
|
76
|
-
current: goal.current ?? 0,
|
|
77
|
-
monthly_contribution_needed: goal.monthly_contribution_needed ?? null,
|
|
78
|
-
name: goal.name || '',
|
|
79
|
-
progress_percent: goal.progress_percent ?? 0,
|
|
80
|
-
status: goal.status ?? null,
|
|
81
|
-
target: goal.target ?? 0,
|
|
82
|
-
target_date: goal.target_date ?? null,
|
|
83
|
-
})),
|
|
84
|
-
health: {
|
|
85
|
-
data_completeness: input.health?.data_completeness ?? null,
|
|
86
|
-
label: input.health?.label ?? null,
|
|
87
|
-
message: input.health?.message ?? null,
|
|
88
|
-
pillars: input.health?.pillars
|
|
89
|
-
? {
|
|
90
|
-
borrow: input.health.pillars.borrow ?? null,
|
|
91
|
-
build: input.health.pillars.build ?? null,
|
|
92
|
-
save: input.health.pillars.save ?? null,
|
|
93
|
-
spend: input.health.pillars.spend ?? null,
|
|
94
|
-
}
|
|
95
|
-
: null,
|
|
96
|
-
score: input.health?.score ?? null,
|
|
97
|
-
},
|
|
98
|
-
holdings: (input.holdings || []).map((holding) => ({
|
|
99
|
-
account_id: holding.account_id || '',
|
|
100
|
-
cost_basis: holding.cost_basis ?? null,
|
|
101
|
-
quantity: roundMoney(holding.quantity ?? 0),
|
|
102
|
-
security_name: holding.security_name ?? null,
|
|
103
|
-
symbol: holding.symbol ?? null,
|
|
104
|
-
type: holding.type ?? null,
|
|
105
|
-
value: holding.value == null ? null : roundMoney(holding.value),
|
|
106
|
-
})),
|
|
107
|
-
liabilities: (input.liabilities || []).map((liability) => ({
|
|
108
|
-
account_id: liability.account_id || '',
|
|
109
|
-
apr_percentage: liability.apr_percentage ?? null,
|
|
110
|
-
balance: liability.balance == null ? null : roundMoney(liability.balance),
|
|
111
|
-
is_overdue: liability.is_overdue ?? null,
|
|
112
|
-
minimum_payment: liability.minimum_payment == null ? null : roundMoney(liability.minimum_payment),
|
|
113
|
-
next_payment_due_date: liability.next_payment_due_date ?? null,
|
|
114
|
-
type: liability.type || 'unknown',
|
|
115
|
-
})),
|
|
116
|
-
recurring: (input.recurring || []).map((stream) => ({
|
|
117
|
-
active: stream.active === true || stream.is_active === true,
|
|
118
|
-
amount: roundMoney(stream.amount ?? stream.last_amount ?? stream.average_amount ?? 0),
|
|
119
|
-
frequency: stream.frequency || '',
|
|
120
|
-
merchant: stream.merchant || stream.merchant_name || stream.description || '',
|
|
121
|
-
next_date: stream.next_date ?? null,
|
|
122
|
-
type: stream.type ?? stream.stream_type ?? null,
|
|
123
|
-
})),
|
|
124
|
-
snapshots: (input.snapshots || [])
|
|
125
|
-
.map((snapshot) => ({
|
|
126
|
-
date: snapshot.date || snapshot.snapshot_date || snapshot.period_end || null,
|
|
127
|
-
net_worth: snapshot.net_worth ?? 0,
|
|
128
|
-
total_assets: snapshot.total_assets ?? 0,
|
|
129
|
-
total_liabilities: snapshot.total_liabilities ?? 0,
|
|
130
|
-
}))
|
|
131
|
-
.filter((snapshot) => typeof snapshot.date === 'string')
|
|
132
|
-
.map((snapshot) => ({
|
|
133
|
-
date: snapshot.date,
|
|
134
|
-
net_worth: roundMoney(snapshot.net_worth),
|
|
135
|
-
total_assets: roundMoney(snapshot.total_assets),
|
|
136
|
-
total_liabilities: roundMoney(snapshot.total_liabilities),
|
|
137
|
-
})),
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
17
|
function createWarmApiClientConfig(options) {
|
|
141
18
|
return {
|
|
142
19
|
apiKeyResolver: options.apiKeyResolver,
|
|
@@ -193,7 +70,7 @@ export async function apiRequest(endpoint, params = {}, options = {}) {
|
|
|
193
70
|
if (!response.ok) {
|
|
194
71
|
const errorMessages = {
|
|
195
72
|
401: 'Invalid or expired API key. Regenerate at https://warm.io/settings',
|
|
196
|
-
403: '
|
|
73
|
+
403: 'API access is available on paid plans only. Upgrade at https://warm.io/settings',
|
|
197
74
|
429: 'Rate limit exceeded. Try again in a few minutes.',
|
|
198
75
|
};
|
|
199
76
|
if (errorMessages[response.status]) {
|
|
@@ -214,69 +91,20 @@ export async function apiRequest(endpoint, params = {}, options = {}) {
|
|
|
214
91
|
return (await response.json());
|
|
215
92
|
}
|
|
216
93
|
export function createWarmApiClient(options = {}) {
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
return normalizeAccounts(response.accounts || []);
|
|
220
|
-
};
|
|
221
|
-
const getFinancialState = async () => {
|
|
222
|
-
const [snapshotsResponse, recurringResponse, budgetsResponse, goalsResponse, healthResponse, liabilitiesResponse, holdingsResponse, spendingResponse,] = await Promise.all([
|
|
223
|
-
apiRequest('/api/export', { dataset: 'snapshots' }, options),
|
|
224
|
-
apiRequest('/api/export', { dataset: 'recurring' }, options),
|
|
225
|
-
apiRequest('/api/export', { dataset: 'budgets' }, options),
|
|
226
|
-
apiRequest('/api/export', { dataset: 'goals' }, options),
|
|
227
|
-
apiRequest('/api/export', { dataset: 'health' }, options),
|
|
228
|
-
apiRequest('/api/export', { dataset: 'liabilities' }, options),
|
|
229
|
-
apiRequest('/api/export', { dataset: 'holdings' }, options),
|
|
230
|
-
apiRequest('/api/spending', { months: String(DEFAULT_SPENDING_MONTHS) }, options),
|
|
231
|
-
]);
|
|
232
|
-
const generatedAt = spendingResponse.generated_at ||
|
|
233
|
-
holdingsResponse.generated_at ||
|
|
234
|
-
liabilitiesResponse.generated_at ||
|
|
235
|
-
healthResponse.generated_at ||
|
|
236
|
-
goalsResponse.generated_at ||
|
|
237
|
-
budgetsResponse.generated_at ||
|
|
238
|
-
recurringResponse.generated_at ||
|
|
239
|
-
snapshotsResponse.generated_at ||
|
|
240
|
-
new Date().toISOString();
|
|
241
|
-
return normalizeFinancialState({
|
|
242
|
-
budgets: budgetsResponse.budgets,
|
|
243
|
-
categorySpending: spendingResponse.spending,
|
|
244
|
-
generatedAt,
|
|
245
|
-
goals: goalsResponse.goals,
|
|
246
|
-
health: healthResponse,
|
|
247
|
-
holdings: holdingsResponse.holdings,
|
|
248
|
-
liabilities: liabilitiesResponse.liabilities,
|
|
249
|
-
recurring: recurringResponse.recurring || recurringResponse.recurring_transactions,
|
|
250
|
-
snapshots: snapshotsResponse.snapshots,
|
|
251
|
-
});
|
|
252
|
-
};
|
|
94
|
+
const getFinancialContext = async () => await apiRequest('/api/financial-context', {}, options);
|
|
95
|
+
const getFinancialContextMeta = async () => await apiRequest('/api/financial-context/meta', {}, options);
|
|
253
96
|
const getTransactions = async (input) => {
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
pagination: {
|
|
266
|
-
has_more: nextCursor !== null,
|
|
267
|
-
limit: response.pagination?.limit ?? input.limit,
|
|
268
|
-
next_cursor: nextCursor,
|
|
269
|
-
},
|
|
270
|
-
txns: (response.transactions || []).map((transaction) => ({
|
|
271
|
-
amount: roundMoney(transaction.amount ?? 0),
|
|
272
|
-
category: transaction.primary_category ?? null,
|
|
273
|
-
date: transaction.date ?? null,
|
|
274
|
-
description: transaction.name ?? null,
|
|
275
|
-
detailed_category: transaction.detailed_category ?? null,
|
|
276
|
-
id: transaction.id ?? null,
|
|
277
|
-
merchant: transaction.merchant_name ?? null,
|
|
278
|
-
})),
|
|
279
|
-
};
|
|
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`.');
|
|
280
108
|
};
|
|
281
109
|
const verifyKey = async () => {
|
|
282
110
|
const response = await apiRequest('/api/verify', {}, options);
|
|
@@ -286,8 +114,8 @@ export function createWarmApiClient(options = {}) {
|
|
|
286
114
|
};
|
|
287
115
|
};
|
|
288
116
|
return {
|
|
289
|
-
|
|
290
|
-
|
|
117
|
+
getFinancialContext,
|
|
118
|
+
getFinancialContextMeta,
|
|
291
119
|
getTransactions,
|
|
292
120
|
verifyKey,
|
|
293
121
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@warmio/mcp",
|
|
3
|
-
"version": "
|
|
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",
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
],
|
|
35
35
|
"scripts": {
|
|
36
36
|
"build": "tsc",
|
|
37
|
+
"test": "npm run build && node --test test/*.mjs",
|
|
37
38
|
"start": "node dist/index.js mcp",
|
|
38
39
|
"start:http": "node dist/index.js http",
|
|
39
40
|
"start:install": "node dist/index.js install",
|