@warmio/mcp 5.0.1 → 7.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 +167 -165
- package/dist/config-paths.d.ts +2 -1
- package/dist/config-paths.js +6 -4
- package/dist/http.d.ts +6 -0
- package/dist/http.js +36 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +100 -6
- package/dist/install.d.ts +2 -0
- package/dist/install.js +209 -94
- package/dist/schemas.d.ts +1198 -186
- package/dist/schemas.js +226 -139
- package/dist/server.d.ts +5 -3
- package/dist/server.js +96 -32
- package/dist/types.d.ts +5 -2
- package/dist/types.js +8 -3
- package/dist/warm-api-client.d.ts +222 -163
- package/dist/warm-api-client.js +110 -222
- package/package.json +2 -2
package/dist/schemas.js
CHANGED
|
@@ -1,160 +1,247 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
const monthSchema = z.string().regex(/^\d{4}-\d{2}$/, 'Expected YYYY-MM month.');
|
|
2
3
|
const dateSchema = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Expected YYYY-MM-DD date.');
|
|
3
4
|
const dateTimeSchema = z.string().datetime({ offset: true });
|
|
4
5
|
export const emptyInputSchema = {};
|
|
5
|
-
export const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
accounts: z.array(z
|
|
14
|
-
.object({
|
|
15
|
-
balance: z.number().finite(),
|
|
16
|
-
institution: z.string().nullable(),
|
|
17
|
-
mask: z.string().nullable(),
|
|
18
|
-
name: z.string(),
|
|
19
|
-
subtype: z.string().nullable(),
|
|
20
|
-
type: accountTypeSchema,
|
|
21
|
-
})
|
|
22
|
-
.strict()),
|
|
6
|
+
export const automationInputSchema = z
|
|
7
|
+
.object({
|
|
8
|
+
body: z.record(z.unknown()).optional(),
|
|
9
|
+
params: z.record(z.string()).optional(),
|
|
10
|
+
})
|
|
11
|
+
.strict();
|
|
12
|
+
export const searchOperationsInputSchema = {
|
|
13
|
+
query: z.string().max(200).optional().describe('Words describing the action or data you need.'),
|
|
23
14
|
};
|
|
24
|
-
export const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
.min(1)
|
|
15
|
+
export const describeOperationInputSchema = {
|
|
16
|
+
operation_id: z.string().min(1),
|
|
17
|
+
input: automationInputSchema
|
|
28
18
|
.optional()
|
|
29
|
-
.describe('
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
.int()
|
|
36
|
-
.min(1)
|
|
37
|
-
.max(1000)
|
|
38
|
-
.default(500)
|
|
39
|
-
.describe('Maximum transactions to return (default 500, max 1000).'),
|
|
40
|
-
search: z
|
|
19
|
+
.describe('Exact intended input. Required to obtain approval for a write operation.'),
|
|
20
|
+
};
|
|
21
|
+
export const invokeOperationInputSchema = {
|
|
22
|
+
operation_id: z.string().min(1),
|
|
23
|
+
input: automationInputSchema.optional(),
|
|
24
|
+
approval_id: z
|
|
41
25
|
.string()
|
|
42
|
-
.
|
|
43
|
-
.max(120)
|
|
26
|
+
.uuid()
|
|
44
27
|
.optional()
|
|
45
|
-
.describe('
|
|
46
|
-
};
|
|
47
|
-
export const getTransactionsOutputSchema = {
|
|
48
|
-
generated_at: dateTimeSchema.nullable(),
|
|
49
|
-
next_knowledge: dateTimeSchema.nullable(),
|
|
50
|
-
pagination: z
|
|
51
|
-
.object({
|
|
52
|
-
has_more: z.boolean(),
|
|
53
|
-
limit: z.number().int().min(1).max(1000),
|
|
54
|
-
next_cursor: z.string().nullable(),
|
|
55
|
-
})
|
|
56
|
-
.strict(),
|
|
57
|
-
txns: z.array(z
|
|
58
|
-
.object({
|
|
59
|
-
amount: z.number().finite(),
|
|
60
|
-
category: z.string().nullable(),
|
|
61
|
-
date: dateSchema.nullable(),
|
|
62
|
-
description: z.string().nullable(),
|
|
63
|
-
detailed_category: z.string().nullable(),
|
|
64
|
-
id: z.string().nullable(),
|
|
65
|
-
merchant: z.string().nullable(),
|
|
66
|
-
})
|
|
67
|
-
.strict()),
|
|
28
|
+
.describe('Approval ID returned by describe_operation for the exact write input.'),
|
|
68
29
|
};
|
|
69
|
-
export const
|
|
70
|
-
|
|
30
|
+
export const transactionIndexSchema = z
|
|
31
|
+
.object({
|
|
32
|
+
total: z.number().int().nonnegative(),
|
|
33
|
+
months: z.array(z
|
|
71
34
|
.object({
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
percent_used: z.number().finite(),
|
|
75
|
-
period: z.string(),
|
|
76
|
-
remaining: z.number().finite(),
|
|
77
|
-
spent: z.number().finite(),
|
|
78
|
-
status: z.string().nullable(),
|
|
35
|
+
month: monthSchema,
|
|
36
|
+
count: z.number().int().nonnegative(),
|
|
79
37
|
})
|
|
80
38
|
.strict()),
|
|
81
|
-
|
|
39
|
+
})
|
|
40
|
+
.strict();
|
|
41
|
+
export const positionSchema = z
|
|
42
|
+
.object({
|
|
43
|
+
date: dateSchema.nullable(),
|
|
44
|
+
net_worth: z.number().finite().nullable(),
|
|
45
|
+
cash: z.number().finite().nullable(),
|
|
46
|
+
debt: z.number().finite().nullable(),
|
|
47
|
+
investments: z.number().finite().nullable(),
|
|
48
|
+
other_assets: z.number().finite().nullable(),
|
|
49
|
+
total_assets: z.number().finite().nullable(),
|
|
50
|
+
})
|
|
51
|
+
.strict();
|
|
52
|
+
export const accountSchema = z
|
|
53
|
+
.object({
|
|
54
|
+
id: z.string(),
|
|
55
|
+
name: z.string(),
|
|
56
|
+
type: z.enum(['depository', 'credit', 'loan', 'investment', 'brokerage', 'other']),
|
|
57
|
+
subtype: z.string().nullable(),
|
|
58
|
+
group: z.enum(['cash', 'debt', 'investments']).nullable(),
|
|
59
|
+
institution: z.string().nullable(),
|
|
60
|
+
mask: z.string().nullable(),
|
|
61
|
+
balance: z.number().finite().nullable(),
|
|
62
|
+
available: z.number().finite().nullable(),
|
|
63
|
+
currency: z.string().nullable(),
|
|
64
|
+
updated_at: dateTimeSchema.nullable(),
|
|
65
|
+
})
|
|
66
|
+
.strict();
|
|
67
|
+
export const statusSchema = z
|
|
68
|
+
.object({
|
|
69
|
+
position: positionSchema.nullable(),
|
|
70
|
+
accounts: z.array(accountSchema),
|
|
71
|
+
})
|
|
72
|
+
.strict();
|
|
73
|
+
export const recurringSchema = z
|
|
74
|
+
.object({
|
|
75
|
+
id: z.string(),
|
|
76
|
+
account_id: z.string().nullable(),
|
|
77
|
+
direction: z.enum(['inflow', 'outflow']).nullable(),
|
|
78
|
+
frequency: z
|
|
79
|
+
.enum(['WEEKLY', 'BIWEEKLY', 'MONTHLY', 'QUARTERLY', 'SEMI_ANNUALLY', 'ANNUALLY'])
|
|
80
|
+
.nullable(),
|
|
81
|
+
status: z.enum(['active', 'inactive', 'dismissed']).nullable(),
|
|
82
|
+
merchant: z.string().nullable(),
|
|
83
|
+
amount: z.number().finite().nullable(),
|
|
84
|
+
next_date: dateSchema.nullable(),
|
|
85
|
+
})
|
|
86
|
+
.strict();
|
|
87
|
+
export const budgetSchema = z
|
|
88
|
+
.object({
|
|
89
|
+
id: z.string(),
|
|
90
|
+
name: z.string(),
|
|
91
|
+
type: z.enum(['category', 'merchant']),
|
|
92
|
+
period: z.enum(['weekly', 'biweekly', 'monthly']),
|
|
93
|
+
status: z.enum(['under', 'warning', 'over']),
|
|
94
|
+
amount: z.number().finite(),
|
|
95
|
+
spent: z.number().finite(),
|
|
96
|
+
remaining: z.number().finite(),
|
|
97
|
+
used_percent: z.number().finite(),
|
|
98
|
+
})
|
|
99
|
+
.strict();
|
|
100
|
+
export const goalSchema = z
|
|
101
|
+
.object({
|
|
102
|
+
id: z.string(),
|
|
103
|
+
name: z.string(),
|
|
104
|
+
category: z.string(),
|
|
105
|
+
status: z.enum(['not_started', 'in_progress', 'on_track', 'behind', 'completed']),
|
|
106
|
+
target: z.number().finite(),
|
|
107
|
+
current: z.number().finite(),
|
|
108
|
+
progress_percent: z.number().finite(),
|
|
109
|
+
target_date: dateSchema.nullable(),
|
|
110
|
+
})
|
|
111
|
+
.strict();
|
|
112
|
+
export const snapshotSchema = z
|
|
113
|
+
.object({
|
|
114
|
+
date: dateSchema,
|
|
115
|
+
net_worth: z.number().finite().nullable(),
|
|
116
|
+
cash: z.number().finite().nullable(),
|
|
117
|
+
debt: z.number().finite().nullable(),
|
|
118
|
+
income: z.number().finite().nullable(),
|
|
119
|
+
expenses: z.number().finite().nullable(),
|
|
120
|
+
cash_flow: z.number().finite().nullable(),
|
|
121
|
+
savings_rate: z.number().finite().nullable(),
|
|
122
|
+
investments: z.number().finite().nullable(),
|
|
123
|
+
assets: z.number().finite().nullable(),
|
|
124
|
+
health_score: z.number().finite().nullable(),
|
|
125
|
+
})
|
|
126
|
+
.strict();
|
|
127
|
+
export const liabilitySchema = z
|
|
128
|
+
.object({
|
|
129
|
+
account_id: z.string().nullable(),
|
|
130
|
+
type: z.enum(['credit', 'student', 'mortgage', 'other']).nullable(),
|
|
131
|
+
balance: z.number().finite().nullable(),
|
|
132
|
+
minimum_payment: z.number().finite().nullable(),
|
|
133
|
+
due_date: dateSchema.nullable(),
|
|
134
|
+
interest_rate: z.number().finite().nullable(),
|
|
135
|
+
rate_type: z.enum(['fixed', 'variable']).nullable(),
|
|
136
|
+
overdue: z.boolean().nullable(),
|
|
137
|
+
})
|
|
138
|
+
.strict();
|
|
139
|
+
export const holdingSchema = z
|
|
140
|
+
.object({
|
|
141
|
+
account_id: z.string().nullable(),
|
|
142
|
+
symbol: z.string().nullable(),
|
|
143
|
+
name: z.string().nullable(),
|
|
144
|
+
type: z
|
|
145
|
+
.enum(['cash', 'derivative', 'equity', 'etf', 'fixed_income', 'mutual_fund', 'other'])
|
|
146
|
+
.nullable(),
|
|
147
|
+
quantity: z.number().finite().nullable(),
|
|
148
|
+
value: z.number().finite().nullable(),
|
|
149
|
+
cost_basis: z.number().finite().nullable(),
|
|
150
|
+
})
|
|
151
|
+
.strict();
|
|
152
|
+
export const healthSchema = z
|
|
153
|
+
.object({
|
|
154
|
+
score: z.number().finite(),
|
|
155
|
+
label: z.enum(['Needs Attention', 'Good', 'Strong']),
|
|
156
|
+
level: z.enum(['critical', 'poor', 'fair', 'good', 'strong']),
|
|
157
|
+
summary: z.string(),
|
|
158
|
+
data_completeness: z.number().finite(),
|
|
159
|
+
pillars: z
|
|
82
160
|
.object({
|
|
83
|
-
|
|
84
|
-
|
|
161
|
+
spend: z.number().finite().nullable(),
|
|
162
|
+
save: z.number().finite().nullable(),
|
|
163
|
+
borrow: z.number().finite().nullable(),
|
|
164
|
+
build: z.number().finite().nullable(),
|
|
85
165
|
})
|
|
86
|
-
.strict()
|
|
166
|
+
.strict()
|
|
167
|
+
.nullable(),
|
|
168
|
+
})
|
|
169
|
+
.strict();
|
|
170
|
+
export const financialContextSchema = z
|
|
171
|
+
.object({
|
|
172
|
+
version: z.literal('v1'),
|
|
173
|
+
updated_at: dateTimeSchema,
|
|
174
|
+
currency: z.literal('USD'),
|
|
175
|
+
status: statusSchema,
|
|
176
|
+
transactions: transactionIndexSchema,
|
|
177
|
+
recurring: z.array(recurringSchema),
|
|
178
|
+
budgets: z.array(budgetSchema),
|
|
179
|
+
goals: z.array(goalSchema),
|
|
180
|
+
snapshots: z.array(snapshotSchema),
|
|
181
|
+
liabilities: z.array(liabilitySchema),
|
|
182
|
+
holdings: z.array(holdingSchema),
|
|
183
|
+
health: healthSchema.nullable(),
|
|
184
|
+
})
|
|
185
|
+
.strict();
|
|
186
|
+
export const transactionSchema = z
|
|
187
|
+
.object({
|
|
188
|
+
id: z.string(),
|
|
189
|
+
account_id: z.string().nullable(),
|
|
190
|
+
date: dateSchema.nullable(),
|
|
191
|
+
amount: z.number().finite(),
|
|
192
|
+
merchant: z.string().nullable(),
|
|
193
|
+
name: z.string().nullable(),
|
|
194
|
+
category: z.string().nullable(),
|
|
195
|
+
subcategory: z.string().nullable(),
|
|
196
|
+
pending: z.boolean().nullable(),
|
|
197
|
+
currency: z.string().nullable(),
|
|
198
|
+
})
|
|
199
|
+
.strict();
|
|
200
|
+
export const transactionMonthSchema = z
|
|
201
|
+
.object({
|
|
202
|
+
month: monthSchema,
|
|
203
|
+
start_date: dateSchema,
|
|
204
|
+
end_date: dateSchema,
|
|
205
|
+
count: z.number().int().nonnegative(),
|
|
206
|
+
items: z.array(transactionSchema),
|
|
207
|
+
})
|
|
208
|
+
.strict();
|
|
209
|
+
export const latestTransactionsSchema = z
|
|
210
|
+
.object({
|
|
211
|
+
since: dateSchema,
|
|
212
|
+
window_days: z.literal(10),
|
|
213
|
+
count: z.number().int().nonnegative(),
|
|
214
|
+
items: z.array(transactionSchema),
|
|
215
|
+
})
|
|
216
|
+
.strict();
|
|
217
|
+
export const getTransactionsInputSchema = {
|
|
218
|
+
month: monthSchema
|
|
219
|
+
.optional()
|
|
220
|
+
.describe('Transaction month in YYYY-MM format. Mutually exclusive with latest. Months outside the covered range return an error.'),
|
|
221
|
+
latest: z
|
|
222
|
+
.boolean()
|
|
223
|
+
.optional()
|
|
224
|
+
.describe('When true, return the fixed 10-day latest window. Mutually exclusive with month. A bare call with no arguments defaults to latest: true.'),
|
|
225
|
+
};
|
|
226
|
+
export const financialContextMetaSchema = z
|
|
227
|
+
.object({
|
|
228
|
+
version: z.literal('v1'),
|
|
229
|
+
user_id: z.string(),
|
|
230
|
+
context_id: z.string(),
|
|
87
231
|
generated_at: dateTimeSchema,
|
|
88
|
-
|
|
232
|
+
updated_at: dateTimeSchema,
|
|
233
|
+
content_hash: z.string(),
|
|
234
|
+
byte_length: z.number().int().nonnegative(),
|
|
235
|
+
counts: z
|
|
89
236
|
.object({
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
progress_percent: z.number().finite(),
|
|
95
|
-
status: z.string().nullable(),
|
|
96
|
-
target: z.number().finite(),
|
|
97
|
-
target_date: dateSchema.nullable(),
|
|
98
|
-
})
|
|
99
|
-
.strict()),
|
|
100
|
-
health: z
|
|
101
|
-
.object({
|
|
102
|
-
data_completeness: z.number().finite().nullable(),
|
|
103
|
-
label: z.string().nullable(),
|
|
104
|
-
message: z.string().nullable(),
|
|
105
|
-
pillars: z
|
|
106
|
-
.object({
|
|
107
|
-
borrow: z.number().finite().nullable(),
|
|
108
|
-
build: z.number().finite().nullable(),
|
|
109
|
-
save: z.number().finite().nullable(),
|
|
110
|
-
spend: z.number().finite().nullable(),
|
|
111
|
-
})
|
|
112
|
-
.strict()
|
|
113
|
-
.nullable(),
|
|
114
|
-
score: z.number().finite().nullable(),
|
|
237
|
+
accounts: z.number().int().nonnegative(),
|
|
238
|
+
transaction_months: z.number().int().nonnegative(),
|
|
239
|
+
transactions: z.number().int().nonnegative(),
|
|
240
|
+
snapshots: z.number().int().nonnegative(),
|
|
115
241
|
})
|
|
116
242
|
.strict(),
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
account_id: z.string(),
|
|
120
|
-
cost_basis: z.number().finite().nullable(),
|
|
121
|
-
quantity: z.number().finite(),
|
|
122
|
-
security_name: z.string().nullable(),
|
|
123
|
-
symbol: z.string().nullable(),
|
|
124
|
-
type: z.string().nullable(),
|
|
125
|
-
value: z.number().finite().nullable(),
|
|
126
|
-
})
|
|
127
|
-
.strict()),
|
|
128
|
-
liabilities: z.array(z
|
|
129
|
-
.object({
|
|
130
|
-
account_id: z.string(),
|
|
131
|
-
apr_percentage: z.number().finite().nullable(),
|
|
132
|
-
balance: z.number().finite().nullable(),
|
|
133
|
-
is_overdue: z.boolean().nullable(),
|
|
134
|
-
minimum_payment: z.number().finite().nullable(),
|
|
135
|
-
next_payment_due_date: dateSchema.nullable(),
|
|
136
|
-
type: z.string(),
|
|
137
|
-
})
|
|
138
|
-
.strict()),
|
|
139
|
-
recurring: z.array(z
|
|
140
|
-
.object({
|
|
141
|
-
active: z.boolean(),
|
|
142
|
-
amount: z.number().finite(),
|
|
143
|
-
frequency: z.string(),
|
|
144
|
-
merchant: z.string(),
|
|
145
|
-
next_date: dateSchema.nullable(),
|
|
146
|
-
type: z.string().nullable(),
|
|
147
|
-
})
|
|
148
|
-
.strict()),
|
|
149
|
-
snapshots: z.array(z
|
|
150
|
-
.object({
|
|
151
|
-
date: dateSchema,
|
|
152
|
-
net_worth: z.number().finite(),
|
|
153
|
-
total_assets: z.number().finite(),
|
|
154
|
-
total_liabilities: z.number().finite(),
|
|
155
|
-
})
|
|
156
|
-
.strict()),
|
|
157
|
-
};
|
|
243
|
+
})
|
|
244
|
+
.strict();
|
|
158
245
|
export const verifyKeyOutputSchema = {
|
|
159
246
|
status: z.string(),
|
|
160
247
|
valid: z.boolean(),
|
package/dist/server.d.ts
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { type Implementation } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
-
import { createWarmApiClient, type WarmApiClientOptions, getConfiguredApiKey, verifyWarmApiKey,
|
|
4
|
-
export {
|
|
3
|
+
import { createWarmApiClient, type WarmApiClientOptions, getConfiguredApiKey, verifyWarmApiKey, WarmApiError } from './warm-api-client.js';
|
|
4
|
+
export { createWarmApiClient, getConfiguredApiKey, verifyWarmApiKey, WarmApiError };
|
|
5
5
|
export type { WarmApiClientOptions } from './warm-api-client.js';
|
|
6
6
|
export interface WarmServerOptions extends WarmApiClientOptions {
|
|
7
|
+
mode?: WarmServerMode;
|
|
7
8
|
serverInfo?: Implementation;
|
|
8
9
|
}
|
|
10
|
+
export type WarmServerMode = 'automation' | 'context';
|
|
9
11
|
export declare const API_URL: string;
|
|
10
12
|
export declare const WARM_SERVER_INFO: {
|
|
11
13
|
name: string;
|
|
12
14
|
version: string;
|
|
13
15
|
};
|
|
14
|
-
export declare function registerWarmTools(server: McpServer, options?: WarmApiClientOptions): void;
|
|
16
|
+
export declare function registerWarmTools(server: McpServer, options?: WarmApiClientOptions, mode?: WarmServerMode): void;
|
|
15
17
|
export declare function createWarmServer(options?: WarmServerOptions): McpServer;
|
package/dist/server.js
CHANGED
|
@@ -3,10 +3,10 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
3
3
|
import { toJsonSchemaCompat } from '@modelcontextprotocol/sdk/server/zod-json-schema-compat.js';
|
|
4
4
|
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js';
|
|
5
5
|
import { z } from 'zod';
|
|
6
|
-
import { createWarmApiClient, getConfiguredApiKey, verifyWarmApiKey,
|
|
7
|
-
import { emptyInputSchema, getTransactionsInputSchema, } from './schemas.js';
|
|
8
|
-
export {
|
|
9
|
-
export const API_URL = process.env.WARM_API_URL || 'https://warm.io';
|
|
6
|
+
import { createWarmApiClient, getConfiguredApiKey, verifyWarmApiKey, WarmApiError, } from './warm-api-client.js';
|
|
7
|
+
import { describeOperationInputSchema, emptyInputSchema, getTransactionsInputSchema, invokeOperationInputSchema, searchOperationsInputSchema, } from './schemas.js';
|
|
8
|
+
export { createWarmApiClient, getConfiguredApiKey, verifyWarmApiKey, WarmApiError };
|
|
9
|
+
export const API_URL = process.env.WARM_API_URL || 'https://app.warm.io';
|
|
10
10
|
function getPackageVersion() {
|
|
11
11
|
try {
|
|
12
12
|
const packageJson = fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8');
|
|
@@ -21,7 +21,7 @@ export const WARM_SERVER_INFO = {
|
|
|
21
21
|
name: 'warm',
|
|
22
22
|
version: getPackageVersion(),
|
|
23
23
|
};
|
|
24
|
-
function createStructuredToolResult(structuredContent) {
|
|
24
|
+
function createStructuredToolResult(structuredContent, isError = false) {
|
|
25
25
|
return {
|
|
26
26
|
content: [
|
|
27
27
|
{
|
|
@@ -29,48 +29,91 @@ function createStructuredToolResult(structuredContent) {
|
|
|
29
29
|
text: JSON.stringify(structuredContent, null, 2),
|
|
30
30
|
},
|
|
31
31
|
],
|
|
32
|
+
...(isError ? { isError: true } : {}),
|
|
32
33
|
structuredContent,
|
|
33
34
|
};
|
|
34
35
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
function createToolFailure(error) {
|
|
37
|
+
if (error instanceof WarmApiError) {
|
|
38
|
+
return createStructuredToolResult({
|
|
39
|
+
body: error.body,
|
|
40
|
+
headers: error.headers,
|
|
41
|
+
message: error.message,
|
|
42
|
+
status: error.status,
|
|
43
|
+
}, true);
|
|
44
|
+
}
|
|
45
|
+
return createStructuredToolResult({
|
|
46
|
+
error: {
|
|
47
|
+
message: error instanceof Error ? error.message : String(error),
|
|
48
|
+
},
|
|
49
|
+
}, true);
|
|
50
|
+
}
|
|
51
|
+
function parseGetTransactionsArgs(args) {
|
|
52
|
+
const hasMonth = typeof args.month === 'string';
|
|
53
|
+
const hasLatestKey = Object.prototype.hasOwnProperty.call(args, 'latest');
|
|
54
|
+
const hasLatest = args.latest === true;
|
|
55
|
+
if (hasMonth && hasLatestKey) {
|
|
56
|
+
throw new Error('`month` and `latest` are mutually exclusive.');
|
|
57
|
+
}
|
|
58
|
+
if (hasMonth) {
|
|
59
|
+
return { month: args.month };
|
|
60
|
+
}
|
|
61
|
+
if (hasLatest || Object.keys(args).length === 0) {
|
|
62
|
+
return { latest: true };
|
|
63
|
+
}
|
|
64
|
+
throw new Error('Call get_transactions with `month` in YYYY-MM format, `latest: true`, or no arguments.');
|
|
65
|
+
}
|
|
66
|
+
export function registerWarmTools(server, options = {}, mode = 'context') {
|
|
67
|
+
const client = createWarmApiClient({ ...options, audience: mode });
|
|
68
|
+
const contextTools = [
|
|
38
69
|
{
|
|
39
|
-
|
|
40
|
-
|
|
70
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
71
|
+
description: 'Read-only compact FinancialContext JSON. Includes status.position, status.accounts, transaction index total/months, recurring, budgets, goals, snapshots, liabilities, holdings, and health. Transaction items are not inline; use get_transactions for items.',
|
|
72
|
+
handler: async () => await client.getFinancialContext(),
|
|
41
73
|
inputShape: emptyInputSchema,
|
|
42
|
-
name: '
|
|
74
|
+
name: 'get_financial_context',
|
|
43
75
|
},
|
|
44
76
|
{
|
|
45
|
-
|
|
77
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
78
|
+
description: 'Read-only transactions from the FinancialContext artifact. Pass exactly one selector: `month` in YYYY-MM format for a month page, or `latest: true` for the fixed latest window. A bare call with no arguments defaults to `latest: true`. The latest window is fixed at 10 days and is not caller-configurable. `month` and `latest` are mutually exclusive. Months outside the covered range return an error.',
|
|
46
79
|
handler: async (args) => {
|
|
47
|
-
|
|
48
|
-
cursor: typeof args.cursor === 'string' ? args.cursor : undefined,
|
|
49
|
-
last_knowledge: typeof args.last_knowledge === 'string' ? args.last_knowledge : undefined,
|
|
50
|
-
limit: typeof args.limit === 'number' ? args.limit : 500,
|
|
51
|
-
search: typeof args.search === 'string' ? args.search : undefined,
|
|
52
|
-
};
|
|
53
|
-
if (transactionArgs.cursor && transactionArgs.last_knowledge) {
|
|
54
|
-
throw new Error('`cursor` cannot be combined with `last_knowledge`.');
|
|
55
|
-
}
|
|
56
|
-
return await client.getTransactions(transactionArgs);
|
|
80
|
+
return await client.getTransactions(parseGetTransactionsArgs(args));
|
|
57
81
|
},
|
|
58
82
|
inputShape: getTransactionsInputSchema,
|
|
59
83
|
name: 'get_transactions',
|
|
60
84
|
},
|
|
61
85
|
{
|
|
62
|
-
|
|
63
|
-
handler: async () => await client.getFinancialState(),
|
|
64
|
-
inputShape: emptyInputSchema,
|
|
65
|
-
name: 'get_financial_state',
|
|
66
|
-
},
|
|
67
|
-
{
|
|
86
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
68
87
|
description: 'Read-only API key validation for the configured Warm account.',
|
|
69
|
-
handler: async () => await client.
|
|
88
|
+
handler: async () => await client.validateAudience(),
|
|
70
89
|
inputShape: emptyInputSchema,
|
|
71
90
|
name: 'verify_key',
|
|
72
91
|
},
|
|
73
92
|
];
|
|
93
|
+
const automationTools = [
|
|
94
|
+
{
|
|
95
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
96
|
+
description: 'Search the supported Warm operation catalog. Use this first to find the smallest matching operation.',
|
|
97
|
+
handler: async (args) => await client.searchOperations(typeof args.query === 'string' ? args.query : undefined),
|
|
98
|
+
inputShape: searchOperationsInputSchema,
|
|
99
|
+
name: 'search_operations',
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
|
|
103
|
+
description: 'Describe one operation, including its input schema and risk. For write operations, pass the exact intended input to receive an approval with its ID, status, URL, and expiry.',
|
|
104
|
+
handler: async (args) => await client.describeOperation(args.operation_id, args.input),
|
|
105
|
+
inputShape: describeOperationInputSchema,
|
|
106
|
+
name: 'describe_operation',
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
110
|
+
description: 'Invoke one previously discovered Warm operation. Every write operation requires the approval_id returned for the exact same input by describe_operation.',
|
|
111
|
+
handler: async (args) => await client.invokeOperation(args.operation_id, args.input, typeof args.approval_id === 'string' ? args.approval_id : undefined),
|
|
112
|
+
inputShape: invokeOperationInputSchema,
|
|
113
|
+
name: 'invoke_operation',
|
|
114
|
+
},
|
|
115
|
+
];
|
|
116
|
+
const tools = mode === 'automation' ? automationTools : contextTools;
|
|
74
117
|
server.server.registerCapabilities({
|
|
75
118
|
tools: {
|
|
76
119
|
listChanged: true,
|
|
@@ -79,6 +122,7 @@ export function registerWarmTools(server, options = {}) {
|
|
|
79
122
|
server.server.setRequestHandler(ListToolsRequestSchema, () => ({
|
|
80
123
|
tools: tools.map((tool) => ({
|
|
81
124
|
description: tool.description,
|
|
125
|
+
annotations: tool.annotations,
|
|
82
126
|
inputSchema: toJsonSchemaCompat(z.object(tool.inputShape).strict(), {
|
|
83
127
|
pipeStrategy: 'input',
|
|
84
128
|
strictUnions: true,
|
|
@@ -96,12 +140,32 @@ export function registerWarmTools(server, options = {}) {
|
|
|
96
140
|
if (!parseResult.success) {
|
|
97
141
|
throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for tool ${request.params.name}: ${parseResult.error.message}`);
|
|
98
142
|
}
|
|
99
|
-
|
|
143
|
+
if (tool.name === 'get_transactions') {
|
|
144
|
+
try {
|
|
145
|
+
parseGetTransactionsArgs(parseResult.data);
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
throw new McpError(ErrorCode.InvalidParams, error instanceof Error ? error.message : String(error));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
if (tool.name !== 'verify_key') {
|
|
153
|
+
await client.validateAudience();
|
|
154
|
+
}
|
|
155
|
+
const result = await tool.handler(parseResult.data);
|
|
156
|
+
const isFailedAutomationOperation = tool.name === 'invoke_operation' &&
|
|
157
|
+
typeof result.status === 'number' &&
|
|
158
|
+
result.status >= 400;
|
|
159
|
+
return createStructuredToolResult(result, isFailedAutomationOperation);
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
return createToolFailure(error);
|
|
163
|
+
}
|
|
100
164
|
});
|
|
101
165
|
}
|
|
102
166
|
export function createWarmServer(options = {}) {
|
|
103
|
-
const { serverInfo, ...clientOptions } = options;
|
|
167
|
+
const { mode = 'context', serverInfo, ...clientOptions } = options;
|
|
104
168
|
const server = new McpServer(serverInfo || WARM_SERVER_INFO);
|
|
105
|
-
registerWarmTools(server, clientOptions);
|
|
169
|
+
registerWarmTools(server, clientOptions, mode);
|
|
106
170
|
return server;
|
|
107
171
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
export declare const
|
|
1
|
+
export declare const WARM_CONTEXT_TOOL_NAMES: readonly ["get_financial_context", "get_transactions", "verify_key"];
|
|
2
|
+
export declare const WARM_AUTOMATION_TOOL_NAMES: readonly ["search_operations", "describe_operation", "invoke_operation"];
|
|
3
|
+
export declare const WARM_TOOL_NAMES: readonly ["get_financial_context", "get_transactions", "verify_key", "search_operations", "describe_operation", "invoke_operation"];
|
|
2
4
|
export type WarmToolName = (typeof WARM_TOOL_NAMES)[number];
|
|
3
|
-
export type {
|
|
5
|
+
export type { Account, AutomationInput, AutomationOperation, AutomationOperationDescription, Budget, DescribeOperationOutput, FinancialContext, FinancialContextMeta, GetTransactionsInput, GetTransactionsOutput, Goal, Health, Holding, InvokeOperationOutput, LatestTransactions, Liability, Position, Recurring, Snapshot, Status, Transaction, TransactionIndex, TransactionMonth, VerifyKeyOutput, WarmApiClient, WarmApiClientOptions, } from './warm-api-client.js';
|
|
6
|
+
export type { WarmApiAudience } from './config-paths.js';
|
|
4
7
|
export type { WarmServerOptions } from './server.js';
|
package/dist/types.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
export const
|
|
2
|
-
'
|
|
1
|
+
export const WARM_CONTEXT_TOOL_NAMES = [
|
|
2
|
+
'get_financial_context',
|
|
3
3
|
'get_transactions',
|
|
4
|
-
'get_financial_state',
|
|
5
4
|
'verify_key',
|
|
6
5
|
];
|
|
6
|
+
export const WARM_AUTOMATION_TOOL_NAMES = [
|
|
7
|
+
'search_operations',
|
|
8
|
+
'describe_operation',
|
|
9
|
+
'invoke_operation',
|
|
10
|
+
];
|
|
11
|
+
export const WARM_TOOL_NAMES = [...WARM_CONTEXT_TOOL_NAMES, ...WARM_AUTOMATION_TOOL_NAMES];
|