@warmio/mcp 4.2.2 → 4.3.1
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 +16 -1
- package/dist/config-paths.d.ts +1 -0
- package/dist/config-paths.js +9 -0
- package/dist/http.d.ts +5 -6
- package/dist/http.js +12 -10
- package/dist/index.d.ts +1 -1
- package/dist/index.js +47 -3
- package/dist/load-env.d.ts +1 -0
- package/dist/load-env.js +36 -0
- package/dist/schemas.d.ts +242 -191
- package/dist/schemas.js +140 -163
- package/dist/server.d.ts +15 -8
- package/dist/server.js +73 -12
- package/dist/stdio.d.ts +29 -0
- package/dist/stdio.js +200 -0
- package/dist/types.d.ts +2 -158
- package/dist/warm-api-client.d.ts +184 -0
- package/dist/warm-api-client.js +299 -0
- package/dist/warm-server.d.ts +2 -14
- package/dist/warm-server.js +2 -245
- package/package.json +15 -6
package/dist/types.d.ts
CHANGED
|
@@ -1,160 +1,4 @@
|
|
|
1
|
-
import type { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import type { Implementation } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
-
import type { GetAccountsOutput, GetFinancialStateOutput, GetTransactionsInput, GetTransactionsOutput, VerifyKeyOutput } from './schemas.js';
|
|
4
1
|
export declare const WARM_TOOL_NAMES: readonly ["get_accounts", "get_transactions", "get_financial_state", "verify_key"];
|
|
5
2
|
export type WarmToolName = (typeof WARM_TOOL_NAMES)[number];
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
requestTimeoutMs?: number;
|
|
9
|
-
fetchImplementation?: typeof fetch;
|
|
10
|
-
apiKeyResolver?: () => string | null;
|
|
11
|
-
}
|
|
12
|
-
export interface WarmServerOptions extends WarmApiClientOptions {
|
|
13
|
-
serverInfo?: Implementation;
|
|
14
|
-
}
|
|
15
|
-
export type WarmToolRegistrationTarget = McpServer;
|
|
16
|
-
export type RegisteredWarmTools = Record<WarmToolName, RegisteredTool>;
|
|
17
|
-
export interface WarmApiClient {
|
|
18
|
-
getAccounts(): Promise<GetAccountsOutput>;
|
|
19
|
-
getFinancialState(): Promise<GetFinancialStateOutput>;
|
|
20
|
-
getTransactions(input: GetTransactionsInput): Promise<GetTransactionsOutput>;
|
|
21
|
-
verifyKey(): Promise<VerifyKeyOutput>;
|
|
22
|
-
}
|
|
23
|
-
export interface WarmApiAccount {
|
|
24
|
-
account_id?: string | null;
|
|
25
|
-
name?: string | null;
|
|
26
|
-
type?: string | null;
|
|
27
|
-
subtype?: string | null;
|
|
28
|
-
current_balance?: number | null;
|
|
29
|
-
available_balance?: number | null;
|
|
30
|
-
institution_name?: string | null;
|
|
31
|
-
mask?: string | null;
|
|
32
|
-
}
|
|
33
|
-
export interface WarmApiAccountsResponse {
|
|
34
|
-
accounts?: WarmApiAccount[];
|
|
35
|
-
generated_at?: string;
|
|
36
|
-
}
|
|
37
|
-
export interface WarmApiTransaction {
|
|
38
|
-
id?: string | null;
|
|
39
|
-
account_id?: string | null;
|
|
40
|
-
date?: string | null;
|
|
41
|
-
amount?: number | null;
|
|
42
|
-
merchant_name?: string | null;
|
|
43
|
-
name?: string | null;
|
|
44
|
-
primary_category?: string | null;
|
|
45
|
-
detailed_category?: string | null;
|
|
46
|
-
}
|
|
47
|
-
export interface WarmApiTransactionsResponse {
|
|
48
|
-
generated_at?: string;
|
|
49
|
-
next_knowledge?: string;
|
|
50
|
-
transactions?: WarmApiTransaction[];
|
|
51
|
-
pagination?: {
|
|
52
|
-
limit?: number | null;
|
|
53
|
-
next_cursor?: string | null;
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
export interface WarmApiSnapshot {
|
|
57
|
-
snapshot_date?: string | null;
|
|
58
|
-
period_end?: string | null;
|
|
59
|
-
net_worth?: number | null;
|
|
60
|
-
total_assets?: number | null;
|
|
61
|
-
total_liabilities?: number | null;
|
|
62
|
-
total_cash?: number | null;
|
|
63
|
-
investment_value?: number | null;
|
|
64
|
-
asset_value?: number | null;
|
|
65
|
-
total_debt?: number | null;
|
|
66
|
-
liability_value?: number | null;
|
|
67
|
-
}
|
|
68
|
-
export interface WarmApiSnapshotsResponse {
|
|
69
|
-
snapshots?: WarmApiSnapshot[];
|
|
70
|
-
generated_at?: string;
|
|
71
|
-
}
|
|
72
|
-
export interface WarmApiRecurring {
|
|
73
|
-
average_amount?: number | null;
|
|
74
|
-
description?: string | null;
|
|
75
|
-
frequency?: string | null;
|
|
76
|
-
is_active?: boolean | null;
|
|
77
|
-
last_amount?: number | null;
|
|
78
|
-
merchant_name?: string | null;
|
|
79
|
-
next_date?: string | null;
|
|
80
|
-
stream_type?: string | null;
|
|
81
|
-
}
|
|
82
|
-
export interface WarmApiRecurringResponse {
|
|
83
|
-
recurring_transactions?: WarmApiRecurring[];
|
|
84
|
-
generated_at?: string;
|
|
85
|
-
}
|
|
86
|
-
export interface WarmApiBudget {
|
|
87
|
-
amount?: number | null;
|
|
88
|
-
name?: string | null;
|
|
89
|
-
period?: string | null;
|
|
90
|
-
percent_used?: number | null;
|
|
91
|
-
remaining?: number | null;
|
|
92
|
-
spent?: number | null;
|
|
93
|
-
status?: string | null;
|
|
94
|
-
}
|
|
95
|
-
export interface WarmApiBudgetsResponse {
|
|
96
|
-
budgets?: WarmApiBudget[];
|
|
97
|
-
generated_at?: string;
|
|
98
|
-
}
|
|
99
|
-
export interface WarmApiGoal {
|
|
100
|
-
category?: string | null;
|
|
101
|
-
current?: number | null;
|
|
102
|
-
monthly_contribution_needed?: number | null;
|
|
103
|
-
name?: string | null;
|
|
104
|
-
progress_percent?: number | null;
|
|
105
|
-
status?: string | null;
|
|
106
|
-
target?: number | null;
|
|
107
|
-
target_date?: string | null;
|
|
108
|
-
}
|
|
109
|
-
export interface WarmApiGoalsResponse {
|
|
110
|
-
goals?: WarmApiGoal[];
|
|
111
|
-
generated_at?: string;
|
|
112
|
-
}
|
|
113
|
-
export interface WarmApiHealthResponse {
|
|
114
|
-
score?: number | null;
|
|
115
|
-
label?: string | null;
|
|
116
|
-
pillars?: {
|
|
117
|
-
spend?: number | null;
|
|
118
|
-
save?: number | null;
|
|
119
|
-
borrow?: number | null;
|
|
120
|
-
build?: number | null;
|
|
121
|
-
} | null;
|
|
122
|
-
data_completeness?: number | null;
|
|
123
|
-
message?: string | null;
|
|
124
|
-
generated_at?: string;
|
|
125
|
-
}
|
|
126
|
-
export interface WarmApiLiability {
|
|
127
|
-
account_id?: string | null;
|
|
128
|
-
type?: string | null;
|
|
129
|
-
balance?: number | null;
|
|
130
|
-
apr_percentage?: number | null;
|
|
131
|
-
interest_rate_percentage?: number | null;
|
|
132
|
-
interest_rate_type?: string | null;
|
|
133
|
-
minimum_payment?: number | null;
|
|
134
|
-
next_payment_due_date?: string | null;
|
|
135
|
-
expected_payoff_date?: string | null;
|
|
136
|
-
origination_principal_amount?: number | null;
|
|
137
|
-
is_overdue?: boolean | null;
|
|
138
|
-
}
|
|
139
|
-
export interface WarmApiLiabilitiesResponse {
|
|
140
|
-
liabilities?: WarmApiLiability[];
|
|
141
|
-
generated_at?: string;
|
|
142
|
-
}
|
|
143
|
-
export interface WarmApiHolding {
|
|
144
|
-
account_id?: string | null;
|
|
145
|
-
security_name?: string | null;
|
|
146
|
-
symbol?: string | null;
|
|
147
|
-
type?: string | null;
|
|
148
|
-
quantity?: number | null;
|
|
149
|
-
value?: number | null;
|
|
150
|
-
cost_basis?: number | null;
|
|
151
|
-
}
|
|
152
|
-
export interface WarmApiHoldingsResponse {
|
|
153
|
-
holdings?: WarmApiHolding[];
|
|
154
|
-
generated_at?: string;
|
|
155
|
-
}
|
|
156
|
-
export interface WarmApiVerifyResponse {
|
|
157
|
-
valid?: boolean;
|
|
158
|
-
status?: string;
|
|
159
|
-
error?: string;
|
|
160
|
-
}
|
|
3
|
+
export type { GetAccountsOutput, GetTransactionsInput, GetTransactionsOutput, VerifyKeyOutput, WarmApiAccount, WarmApiBudget, WarmApiClient, WarmApiClientOptions, WarmApiGoal, WarmApiHolding, WarmApiLiability, WarmApiTransaction, WarmFinancialState, } from './warm-api-client.js';
|
|
4
|
+
export type { WarmServerOptions } from './server.js';
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
type NormalizedAccountType = 'depository' | 'credit' | 'loan' | 'investment' | 'other';
|
|
2
|
+
export interface WarmApiClientOptions {
|
|
3
|
+
apiKeyResolver?: () => string | null;
|
|
4
|
+
apiUrl?: string;
|
|
5
|
+
fetchImplementation?: typeof fetch;
|
|
6
|
+
requestTimeoutMs?: number;
|
|
7
|
+
}
|
|
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;
|
|
115
|
+
}>;
|
|
116
|
+
}
|
|
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
|
+
}>;
|
|
140
|
+
}
|
|
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
|
+
}>;
|
|
150
|
+
}
|
|
151
|
+
export interface VerifyKeyOutput extends Record<string, unknown> {
|
|
152
|
+
status: string;
|
|
153
|
+
valid: boolean;
|
|
154
|
+
}
|
|
155
|
+
export interface WarmApiClient {
|
|
156
|
+
getAccounts(): Promise<GetAccountsOutput>;
|
|
157
|
+
getFinancialState(): Promise<WarmFinancialState>;
|
|
158
|
+
getTransactions(input: GetTransactionsInput): Promise<GetTransactionsOutput>;
|
|
159
|
+
verifyKey(): Promise<VerifyKeyOutput>;
|
|
160
|
+
}
|
|
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
|
+
export declare function getConfiguredApiKey(): string | null;
|
|
180
|
+
export declare function resetConfiguredApiKeyCache(): void;
|
|
181
|
+
export declare function apiRequest<TResponse>(endpoint: string, params?: Record<string, string | undefined>, options?: WarmApiClientOptions): Promise<TResponse>;
|
|
182
|
+
export declare function createWarmApiClient(options?: WarmApiClientOptions): WarmApiClient;
|
|
183
|
+
export declare function verifyWarmApiKey(apiKey: string): Promise<VerifyKeyOutput>;
|
|
184
|
+
export {};
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { getWarmApiKeyPath, readConfigFile } from './config-paths.js';
|
|
2
|
+
const DEFAULT_API_URL = process.env.WARM_API_URL || 'https://warm.io';
|
|
3
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = (() => {
|
|
4
|
+
const raw = Number(process.env.WARM_API_TIMEOUT_MS || 10_000);
|
|
5
|
+
return Number.isFinite(raw) && raw > 0 ? raw : 10_000;
|
|
6
|
+
})();
|
|
7
|
+
const DEFAULT_SPENDING_MONTHS = 6;
|
|
8
|
+
let cachedApiKey;
|
|
9
|
+
function roundMoney(value) {
|
|
10
|
+
return Math.round(value * 100) / 100;
|
|
11
|
+
}
|
|
12
|
+
function getRequestSignal(timeoutMs) {
|
|
13
|
+
if (typeof AbortSignal.timeout === 'function') {
|
|
14
|
+
return AbortSignal.timeout(timeoutMs);
|
|
15
|
+
}
|
|
16
|
+
const controller = new AbortController();
|
|
17
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
18
|
+
timer.unref?.();
|
|
19
|
+
return controller.signal;
|
|
20
|
+
}
|
|
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
|
+
function createWarmApiClientConfig(options) {
|
|
141
|
+
return {
|
|
142
|
+
apiKeyResolver: options.apiKeyResolver,
|
|
143
|
+
apiUrl: options.apiUrl || DEFAULT_API_URL,
|
|
144
|
+
fetchImplementation: options.fetchImplementation || fetch,
|
|
145
|
+
requestTimeoutMs: options.requestTimeoutMs || DEFAULT_REQUEST_TIMEOUT_MS,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
export function getConfiguredApiKey() {
|
|
149
|
+
if (cachedApiKey !== undefined) {
|
|
150
|
+
return cachedApiKey;
|
|
151
|
+
}
|
|
152
|
+
if (process.env.WARM_API_KEY?.trim()) {
|
|
153
|
+
cachedApiKey = process.env.WARM_API_KEY.trim();
|
|
154
|
+
return cachedApiKey;
|
|
155
|
+
}
|
|
156
|
+
cachedApiKey = readConfigFile(getWarmApiKeyPath());
|
|
157
|
+
return cachedApiKey;
|
|
158
|
+
}
|
|
159
|
+
export function resetConfiguredApiKeyCache() {
|
|
160
|
+
cachedApiKey = undefined;
|
|
161
|
+
}
|
|
162
|
+
export async function apiRequest(endpoint, params = {}, options = {}) {
|
|
163
|
+
const requestOptions = createWarmApiClientConfig(options);
|
|
164
|
+
const apiKey = requestOptions.apiKeyResolver?.() ?? getConfiguredApiKey();
|
|
165
|
+
if (!apiKey) {
|
|
166
|
+
throw new Error('WARM_API_KEY not set. Run "npx @warmio/mcp" to configure or set WARM_API_KEY.');
|
|
167
|
+
}
|
|
168
|
+
const url = new URL(endpoint, requestOptions.apiUrl);
|
|
169
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
170
|
+
if (value) {
|
|
171
|
+
url.searchParams.append(key, value);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
let response;
|
|
175
|
+
try {
|
|
176
|
+
response = await requestOptions.fetchImplementation(url.toString(), {
|
|
177
|
+
headers: {
|
|
178
|
+
Accept: 'application/json',
|
|
179
|
+
Authorization: `Bearer ${apiKey}`,
|
|
180
|
+
},
|
|
181
|
+
signal: getRequestSignal(requestOptions.requestTimeoutMs),
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
if (error instanceof Error && error.name === 'TimeoutError') {
|
|
186
|
+
throw new Error(`Warm API timed out after ${requestOptions.requestTimeoutMs}ms`);
|
|
187
|
+
}
|
|
188
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
189
|
+
throw new Error(`Warm API request aborted after ${requestOptions.requestTimeoutMs}ms`);
|
|
190
|
+
}
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
if (!response.ok) {
|
|
194
|
+
const errorMessages = {
|
|
195
|
+
401: 'Invalid or expired API key. Regenerate at https://warm.io/settings',
|
|
196
|
+
403: 'Pro subscription required. Upgrade at https://warm.io/settings',
|
|
197
|
+
429: 'Rate limit exceeded. Try again in a few minutes.',
|
|
198
|
+
};
|
|
199
|
+
if (errorMessages[response.status]) {
|
|
200
|
+
throw new Error(errorMessages[response.status]);
|
|
201
|
+
}
|
|
202
|
+
let detail = `HTTP ${response.status}`;
|
|
203
|
+
try {
|
|
204
|
+
const body = (await response.json());
|
|
205
|
+
if (body?.error) {
|
|
206
|
+
detail = body.error;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
// Fall back to the HTTP status when the error body is not JSON.
|
|
211
|
+
}
|
|
212
|
+
throw new Error(detail);
|
|
213
|
+
}
|
|
214
|
+
return (await response.json());
|
|
215
|
+
}
|
|
216
|
+
export function createWarmApiClient(options = {}) {
|
|
217
|
+
const getAccounts = async () => {
|
|
218
|
+
const response = await apiRequest('/api/export', { dataset: 'accounts' }, options);
|
|
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
|
+
};
|
|
253
|
+
const getTransactions = async (input) => {
|
|
254
|
+
const response = await apiRequest('/api/export', {
|
|
255
|
+
cursor: input.cursor,
|
|
256
|
+
dataset: 'transactions',
|
|
257
|
+
last_knowledge: input.last_knowledge,
|
|
258
|
+
limit: String(input.limit),
|
|
259
|
+
search: input.search,
|
|
260
|
+
}, options);
|
|
261
|
+
const nextCursor = response.pagination?.next_cursor ?? null;
|
|
262
|
+
return {
|
|
263
|
+
generated_at: response.generated_at ?? null,
|
|
264
|
+
next_knowledge: response.next_knowledge ?? null,
|
|
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
|
+
};
|
|
280
|
+
};
|
|
281
|
+
const verifyKey = async () => {
|
|
282
|
+
const response = await apiRequest('/api/verify', {}, options);
|
|
283
|
+
return {
|
|
284
|
+
status: response.status || (response.valid ? 'ok' : 'invalid'),
|
|
285
|
+
valid: response.valid === true,
|
|
286
|
+
};
|
|
287
|
+
};
|
|
288
|
+
return {
|
|
289
|
+
getAccounts,
|
|
290
|
+
getFinancialState,
|
|
291
|
+
getTransactions,
|
|
292
|
+
verifyKey,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
export async function verifyWarmApiKey(apiKey) {
|
|
296
|
+
return createWarmApiClient({
|
|
297
|
+
apiKeyResolver: () => apiKey,
|
|
298
|
+
}).verifyKey();
|
|
299
|
+
}
|
package/dist/warm-server.d.ts
CHANGED
|
@@ -1,14 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import type { RegisteredWarmTools, WarmApiClient, WarmApiClientOptions, WarmServerOptions, WarmToolRegistrationTarget } from './types.js';
|
|
4
|
-
export declare const WARM_SERVER_INFO: {
|
|
5
|
-
readonly name: "warm";
|
|
6
|
-
readonly version: string;
|
|
7
|
-
};
|
|
8
|
-
export declare const API_URL: string;
|
|
9
|
-
export declare function getConfiguredApiKey(): string | null;
|
|
10
|
-
export declare function apiRequest<TResponse>(endpoint: string, params?: Record<string, string | undefined>, options?: WarmApiClientOptions): Promise<TResponse>;
|
|
11
|
-
export declare function createWarmApiClient(options?: WarmApiClientOptions): WarmApiClient;
|
|
12
|
-
export declare function registerWarmTools(server: WarmToolRegistrationTarget, options?: WarmApiClientOptions): RegisteredWarmTools;
|
|
13
|
-
export declare function createWarmServer(options?: WarmServerOptions): McpServer;
|
|
14
|
-
export declare function verifyWarmApiKey(apiKey: string): Promise<VerifyKeyOutput>;
|
|
1
|
+
export * from './server.js';
|
|
2
|
+
export * from './warm-api-client.js';
|