@warmio/mcp 4.2.2 → 4.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/schemas.js CHANGED
@@ -1,184 +1,161 @@
1
- import * as z from 'zod/v4';
2
- const dateSchema = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Expected date in YYYY-MM-DD format.');
1
+ import { z } from 'zod';
2
+ const dateSchema = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Expected YYYY-MM-DD date.');
3
3
  const dateTimeSchema = z.string().datetime({ offset: true });
4
- export const emptyInputSchema = z.object({}).strict().default({});
5
- export const accountTypeSchema = z.enum(['depository', 'credit', 'loan', 'investment', 'other']);
6
- export const accountSchema = z
7
- .object({
8
- name: z.string(),
9
- type: accountTypeSchema,
10
- subtype: z.string().nullable(),
11
- balance: z.number().finite(),
12
- institution: z.string().nullable(),
13
- mask: z.string().nullable(),
14
- })
15
- .strict();
16
- export const getAccountsOutputSchema = z
17
- .object({
18
- accounts: z.array(accountSchema),
19
- })
20
- .strict();
21
- const getTransactionsInputObjectSchema = z
22
- .object({
23
- limit: z
24
- .int()
25
- .min(1)
26
- .max(1000)
27
- .default(500)
28
- .describe('Maximum transactions to return in this page (default 500, max 1000).'),
4
+ export const emptyInputSchema = {};
5
+ export const accountTypeSchema = z.enum([
6
+ 'depository',
7
+ 'credit',
8
+ 'loan',
9
+ 'investment',
10
+ 'other',
11
+ ]);
12
+ export const getAccountsOutputSchema = {
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()),
23
+ };
24
+ export const getTransactionsInputSchema = {
29
25
  cursor: z
30
26
  .string()
31
27
  .min(1)
32
28
  .optional()
33
- .describe('Opaque pagination cursor from a previous get_transactions response.'),
29
+ .describe('Opaque pagination cursor from a prior get_transactions response.'),
34
30
  last_knowledge: dateTimeSchema
35
31
  .optional()
36
- .describe('Incremental sync checkpoint from a prior get_transactions response. Cannot be combined with cursor.'),
32
+ .describe('Incremental sync checkpoint. Cannot be combined with cursor.'),
33
+ limit: z
34
+ .number()
35
+ .int()
36
+ .min(1)
37
+ .max(1000)
38
+ .default(500)
39
+ .describe('Maximum transactions to return (default 500, max 1000).'),
37
40
  search: z
38
41
  .string()
39
42
  .min(1)
40
43
  .max(120)
41
44
  .optional()
42
- .describe('Optional merchant or description search string. Keep it unchanged while following a cursor chain.'),
43
- })
44
- .strict()
45
- .superRefine((value, ctx) => {
46
- if (value.cursor && value.last_knowledge) {
47
- ctx.addIssue({
48
- code: z.ZodIssueCode.custom,
49
- path: ['cursor'],
50
- message: '`cursor` cannot be combined with `last_knowledge`.',
51
- });
52
- }
53
- });
54
- export const getTransactionsInputSchema = getTransactionsInputObjectSchema;
55
- export const transactionSchema = z
56
- .object({
57
- id: z.string().nullable(),
58
- date: dateSchema.nullable(),
59
- amount: z.number().finite(),
60
- merchant: z.string().nullable(),
61
- description: z.string().nullable(),
62
- category: z.string().nullable(),
63
- detailed_category: z.string().nullable(),
64
- })
65
- .strict();
66
- export const getTransactionsOutputSchema = z
67
- .object({
45
+ .describe('Optional merchant or description search query.'),
46
+ };
47
+ export const getTransactionsOutputSchema = {
68
48
  generated_at: dateTimeSchema.nullable(),
69
49
  next_knowledge: dateTimeSchema.nullable(),
70
- txns: z.array(transactionSchema),
71
50
  pagination: z
72
51
  .object({
73
- limit: z.int().min(1).max(1000),
74
- next_cursor: z.string().nullable(),
75
52
  has_more: z.boolean(),
53
+ limit: z.number().int().min(1).max(1000),
54
+ next_cursor: z.string().nullable(),
76
55
  })
77
56
  .strict(),
78
- })
79
- .strict();
80
- export const snapshotSchema = z
81
- .object({
82
- date: dateSchema,
83
- net_worth: z.number().finite(),
84
- total_assets: z.number().finite(),
85
- total_liabilities: z.number().finite(),
86
- })
87
- .strict();
88
- export const recurringSchema = z
89
- .object({
90
- merchant: z.string(),
91
- amount: z.number().finite(),
92
- frequency: z.string(),
93
- next_date: dateSchema.nullable(),
94
- type: z.string().nullable(),
95
- active: z.boolean(),
96
- })
97
- .strict();
98
- export const budgetSchema = z
99
- .object({
100
- name: z.string(),
101
- amount: z.number().finite(),
102
- spent: z.number().finite(),
103
- remaining: z.number().finite(),
104
- percent_used: z.number().finite(),
105
- period: z.string(),
106
- status: z.string().nullable(),
107
- })
108
- .strict();
109
- export const goalSchema = z
110
- .object({
111
- name: z.string(),
112
- target: z.number().finite(),
113
- current: z.number().finite(),
114
- progress_percent: z.number().finite(),
115
- target_date: dateSchema.nullable(),
116
- status: z.string().nullable(),
117
- category: z.string().nullable(),
118
- monthly_contribution_needed: z.number().finite().nullable(),
119
- })
120
- .strict();
121
- export const healthPillarsSchema = z
122
- .object({
123
- spend: z.number().finite().nullable(),
124
- save: z.number().finite().nullable(),
125
- borrow: z.number().finite().nullable(),
126
- build: z.number().finite().nullable(),
127
- })
128
- .strict();
129
- export const liabilitySchema = z
130
- .object({
131
- account_id: z.string(),
132
- type: z.string(),
133
- balance: z.number().finite().nullable(),
134
- apr_percentage: z.number().finite().nullable(),
135
- minimum_payment: z.number().finite().nullable(),
136
- next_payment_due_date: dateSchema.nullable(),
137
- is_overdue: z.boolean().nullable(),
138
- })
139
- .strict();
140
- export const holdingSchema = z
141
- .object({
142
- account_id: z.string(),
143
- security_name: z.string().nullable(),
144
- symbol: z.string().nullable(),
145
- type: z.string().nullable(),
146
- quantity: z.number().finite(),
147
- value: z.number().finite().nullable(),
148
- cost_basis: z.number().finite().nullable(),
149
- })
150
- .strict();
151
- export const categorySpendingSchema = z
152
- .object({
153
- category: z.string(),
154
- amount: z.number().finite(),
155
- })
156
- .strict();
157
- export const financialHealthSchema = z
158
- .object({
159
- score: z.number().finite().nullable(),
160
- label: z.string().nullable(),
161
- data_completeness: z.number().finite().nullable(),
162
- pillars: healthPillarsSchema.nullable(),
163
- message: z.string().nullable(),
164
- })
165
- .strict();
166
- export const getFinancialStateOutputSchema = z
167
- .object({
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()),
68
+ };
69
+ export const getFinancialStateOutputSchema = {
70
+ budgets: z.array(z
71
+ .object({
72
+ amount: z.number().finite(),
73
+ name: z.string(),
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(),
79
+ })
80
+ .strict()),
81
+ category_spending: z.array(z
82
+ .object({
83
+ amount: z.number().finite(),
84
+ category: z.string(),
85
+ })
86
+ .strict()),
168
87
  generated_at: dateTimeSchema,
169
- snapshots: z.array(snapshotSchema),
170
- recurring: z.array(recurringSchema),
171
- budgets: z.array(budgetSchema),
172
- goals: z.array(goalSchema),
173
- health: financialHealthSchema,
174
- liabilities: z.array(liabilitySchema),
175
- holdings: z.array(holdingSchema),
176
- category_spending: z.array(categorySpendingSchema),
177
- })
178
- .strict();
179
- export const verifyKeyOutputSchema = z
180
- .object({
181
- valid: z.boolean(),
88
+ goals: z.array(z
89
+ .object({
90
+ category: z.string().nullable(),
91
+ current: z.number().finite(),
92
+ monthly_contribution_needed: z.number().finite().nullable(),
93
+ name: z.string(),
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(),
115
+ })
116
+ .strict(),
117
+ holdings: z.array(z
118
+ .object({
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
+ };
158
+ export const verifyKeyOutputSchema = {
182
159
  status: z.string(),
183
- })
184
- .strict();
160
+ valid: z.boolean(),
161
+ };
package/dist/server.d.ts CHANGED
@@ -1,8 +1,15 @@
1
- /**
2
- * Warm MCP stdio entrypoint and shared exports.
3
- *
4
- * The typed MCP contract and Warm API helpers live in `warm-server.ts`.
5
- */
6
- import { API_URL, WARM_SERVER_INFO, apiRequest, createWarmApiClient, createWarmServer, getConfiguredApiKey, verifyWarmApiKey } from './warm-server.js';
7
- export { API_URL, WARM_SERVER_INFO, apiRequest, createWarmApiClient, createWarmServer, getConfiguredApiKey, verifyWarmApiKey, };
8
- export declare function startStdioServer(): Promise<import("@modelcontextprotocol/sdk/server/mcp.js").McpServer>;
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { type Implementation } from '@modelcontextprotocol/sdk/types.js';
3
+ import { createWarmApiClient, type WarmApiClientOptions, getConfiguredApiKey, verifyWarmApiKey, apiRequest } from './warm-api-client.js';
4
+ export { apiRequest, createWarmApiClient, getConfiguredApiKey, verifyWarmApiKey };
5
+ export type { WarmApiClientOptions } from './warm-api-client.js';
6
+ export interface WarmServerOptions extends WarmApiClientOptions {
7
+ serverInfo?: Implementation;
8
+ }
9
+ export declare const API_URL: string;
10
+ export declare const WARM_SERVER_INFO: {
11
+ name: string;
12
+ version: string;
13
+ };
14
+ export declare function registerWarmTools(server: McpServer, options?: WarmApiClientOptions): void;
15
+ export declare function createWarmServer(options?: WarmServerOptions): McpServer;
package/dist/server.js CHANGED
@@ -1,14 +1,107 @@
1
- /**
2
- * Warm MCP stdio entrypoint and shared exports.
3
- *
4
- * The typed MCP contract and Warm API helpers live in `warm-server.ts`.
5
- */
6
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
7
- import { API_URL, WARM_SERVER_INFO, apiRequest, createWarmApiClient, createWarmServer, getConfiguredApiKey, verifyWarmApiKey, } from './warm-server.js';
8
- export { API_URL, WARM_SERVER_INFO, apiRequest, createWarmApiClient, createWarmServer, getConfiguredApiKey, verifyWarmApiKey, };
9
- export async function startStdioServer() {
10
- const server = createWarmServer();
11
- const transport = new StdioServerTransport();
12
- await server.connect(transport);
1
+ import * as fs from 'node:fs';
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { toJsonSchemaCompat } from '@modelcontextprotocol/sdk/server/zod-json-schema-compat.js';
4
+ import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js';
5
+ import { z } from 'zod';
6
+ import { createWarmApiClient, getConfiguredApiKey, verifyWarmApiKey, apiRequest, } from './warm-api-client.js';
7
+ import { emptyInputSchema, getTransactionsInputSchema, } from './schemas.js';
8
+ export { apiRequest, createWarmApiClient, getConfiguredApiKey, verifyWarmApiKey };
9
+ export const API_URL = process.env.WARM_API_URL || 'https://warm.io';
10
+ function getPackageVersion() {
11
+ try {
12
+ const packageJson = fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8');
13
+ const parsed = JSON.parse(packageJson);
14
+ return parsed.version || '0.0.0';
15
+ }
16
+ catch {
17
+ return '0.0.0';
18
+ }
19
+ }
20
+ export const WARM_SERVER_INFO = {
21
+ name: 'warm',
22
+ version: getPackageVersion(),
23
+ };
24
+ function createStructuredToolResult(structuredContent) {
25
+ return {
26
+ content: [
27
+ {
28
+ type: 'text',
29
+ text: JSON.stringify(structuredContent, null, 2),
30
+ },
31
+ ],
32
+ structuredContent,
33
+ };
34
+ }
35
+ export function registerWarmTools(server, options = {}) {
36
+ const client = createWarmApiClient(options);
37
+ const tools = [
38
+ {
39
+ description: 'Read-only list of connected financial accounts with balances, subtypes, institutions, and masks when available.',
40
+ handler: async () => await client.getAccounts(),
41
+ inputShape: emptyInputSchema,
42
+ name: 'get_accounts',
43
+ },
44
+ {
45
+ description: 'Read-only transaction export with strict opaque cursor pagination and optional last_knowledge incremental sync.',
46
+ handler: async (args) => {
47
+ const transactionArgs = {
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);
57
+ },
58
+ inputShape: getTransactionsInputSchema,
59
+ name: 'get_transactions',
60
+ },
61
+ {
62
+ description: 'Read-only broad financial state bundle with snapshots, recurring payments, budgets, goals, financial health, liabilities, holdings, and category spending.',
63
+ handler: async () => await client.getFinancialState(),
64
+ inputShape: emptyInputSchema,
65
+ name: 'get_financial_state',
66
+ },
67
+ {
68
+ description: 'Read-only API key validation for the configured Warm account.',
69
+ handler: async () => await client.verifyKey(),
70
+ inputShape: emptyInputSchema,
71
+ name: 'verify_key',
72
+ },
73
+ ];
74
+ server.server.registerCapabilities({
75
+ tools: {
76
+ listChanged: true,
77
+ },
78
+ });
79
+ server.server.setRequestHandler(ListToolsRequestSchema, () => ({
80
+ tools: tools.map((tool) => ({
81
+ description: tool.description,
82
+ inputSchema: toJsonSchemaCompat(z.object(tool.inputShape).strict(), {
83
+ pipeStrategy: 'input',
84
+ strictUnions: true,
85
+ }),
86
+ name: tool.name,
87
+ })),
88
+ }));
89
+ server.server.setRequestHandler(CallToolRequestSchema, async (request) => {
90
+ const tool = tools.find((candidate) => candidate.name === request.params.name);
91
+ if (!tool) {
92
+ throw new McpError(ErrorCode.InvalidParams, `Tool ${request.params.name} not found`);
93
+ }
94
+ const argsSchema = z.object(tool.inputShape).strict();
95
+ const parseResult = await argsSchema.safeParseAsync(request.params.arguments ?? {});
96
+ if (!parseResult.success) {
97
+ throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for tool ${request.params.name}: ${parseResult.error.message}`);
98
+ }
99
+ return createStructuredToolResult(await tool.handler(parseResult.data));
100
+ });
101
+ }
102
+ export function createWarmServer(options = {}) {
103
+ const { serverInfo, ...clientOptions } = options;
104
+ const server = new McpServer(serverInfo || WARM_SERVER_INFO);
105
+ registerWarmTools(server, clientOptions);
13
106
  return server;
14
107
  }
@@ -0,0 +1,29 @@
1
+ import type { Readable, Writable } from 'node:stream';
2
+ import type { Transport, TransportSendOptions } from '@modelcontextprotocol/sdk/shared/transport.js';
3
+ import type { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
4
+ export declare class AdaptiveStdioTransport implements Transport {
5
+ private readonly stdin;
6
+ private readonly stdout;
7
+ onclose?: () => void;
8
+ onerror?: (error: Error) => void;
9
+ onmessage?: <T extends JSONRPCMessage>(message: T) => void;
10
+ private buffer;
11
+ private clientFraming;
12
+ private closed;
13
+ private started;
14
+ constructor(stdin?: Readable, stdout?: Writable);
15
+ start(): Promise<void>;
16
+ close(): Promise<void>;
17
+ send(message: JSONRPCMessage, _options?: TransportSendOptions): Promise<void>;
18
+ private readonly handleData;
19
+ private readonly handleError;
20
+ private readonly handleEnd;
21
+ private processBuffer;
22
+ private trimLeadingNewlines;
23
+ private readMessage;
24
+ private detectFraming;
25
+ private readJsonLineMessage;
26
+ private readContentLengthMessage;
27
+ private findHeaderBoundary;
28
+ private parseContentLength;
29
+ }