@warmio/mcp 4.3.3 → 5.0.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 CHANGED
@@ -1,8 +1,6 @@
1
- # Warm MCP
1
+ # Warmio
2
2
 
3
- Read-only MCP server for Warm financial data.
4
-
5
- This package starts in local `stdio` mode by default and also supports optional Streamable HTTP.
3
+ Warmio is the one-command CLI and read-only MCP server for Warm financial data.
6
4
 
7
5
  ## Install
8
6
 
@@ -10,17 +8,9 @@ This package starts in local `stdio` mode by default and also supports optional
10
8
  npx -y @warmio/mcp@latest
11
9
  ```
12
10
 
13
- The installer detects supported MCP clients, prompts for your Warm API key, and writes the local
14
- `stdio` server config automatically. The key is stored once in your local Warm profile instead of
15
- being duplicated into every MCP client config.
16
-
17
- Use `@latest` when you want the newest published package. Bare `npx @warmio/mcp` can reuse a
18
- cached or local copy and leave you on an older version. If you want a deterministic install,
19
- replace `@latest` with an exact version such as `@warmio/mcp@4.3.3`.
20
-
21
- When launched without arguments in a non-interactive context, the binary automatically starts the
22
- `stdio` server instead of the installer. This keeps MCP clients that invoke the package directly
23
- from falling into the interactive setup flow.
11
+ The installer detects supported MCP clients, prompts for your Warm API key, validates it, and
12
+ writes the local `stdio` MCP config automatically. The key is stored once in your local Warm
13
+ profile instead of being duplicated into every MCP client config.
24
14
 
25
15
  ## Requirements
26
16
 
@@ -28,41 +18,65 @@ from falling into the interactive setup flow.
28
18
  - A Warm API key from [Settings -> API Keys](https://warm.io/settings)
29
19
  - Node.js 18+
30
20
 
31
- ## Manual `stdio` Config
21
+ ## Manual MCP Config
32
22
 
33
- The installer stores your API key in the Warm config directory and generated MCP client configs can
34
- stay secret-free:
23
+ Use this when a client asks you to paste an MCP JSON config:
35
24
 
36
25
  ```json
37
26
  {
38
27
  "mcpServers": {
39
28
  "warm": {
40
29
  "command": "npx",
41
- "args": ["-y", "@warmio/mcp@latest", "--server"]
30
+ "args": ["-y", "@warmio/mcp@latest", "mcp"]
42
31
  }
43
32
  }
44
33
  }
45
34
  ```
46
35
 
47
- If you already have a client config that uses bare `@warmio/mcp`, update it to `@warmio/mcp@latest`
48
- or an exact version and restart the client.
36
+ On Windows, prefer:
37
+
38
+ ```json
39
+ {
40
+ "mcpServers": {
41
+ "warm": {
42
+ "command": "cmd",
43
+ "args": ["/c", "npx", "-y", "@warmio/mcp@latest", "mcp"]
44
+ }
45
+ }
46
+ }
47
+ ```
49
48
 
50
49
  Optional auth overrides:
51
50
 
52
51
  - `WARM_API_KEY`
53
52
  - `WARM_API_KEY_FILE`
54
53
 
55
- On Windows, prefer:
54
+ ## Universal copy/paste setup prompt
55
+
56
+ ```text
57
+ I want to connect Warm to this AI app using MCP.
58
+
59
+ Use this MCP server config:
56
60
 
57
- ```json
58
61
  {
59
62
  "mcpServers": {
60
63
  "warm": {
61
- "command": "cmd",
62
- "args": ["/c", "npx", "-y", "@warmio/mcp@latest", "--server"]
64
+ "command": "npx",
65
+ "args": ["-y", "@warmio/mcp@latest", "mcp"]
63
66
  }
64
67
  }
65
68
  }
69
+
70
+ If this app supports MCP configuration, add a server named "warm" with that command.
71
+
72
+ Do not ask me to paste my Warm API key into this chat. Help me store it locally using Warm's setup
73
+ flow, then test the connection by calling Warm's verify_key tool.
74
+
75
+ After setup, I should be able to ask:
76
+ - What’s my net worth?
77
+ - Summarize my recent transactions.
78
+ - What changed recently?
79
+ - What recurring charges do I have?
66
80
  ```
67
81
 
68
82
  ## Optional Streamable HTTP
package/dist/index.js CHANGED
@@ -9,24 +9,20 @@ function isInteractiveSession() {
9
9
  }
10
10
  function printUsage() {
11
11
  console.log('');
12
- console.log(' Warm MCP');
13
- console.log(' --------');
12
+ console.log(' Warmio');
13
+ console.log(' ------');
14
14
  console.log('');
15
- console.log(' warm-mcp');
16
- console.log(' Interactive terminal: runs installer');
17
- console.log(' Non-interactive launch: starts stdio server');
15
+ console.log(' warmio');
16
+ console.log(' Run the interactive installer');
18
17
  console.log('');
19
- console.log(' warm-mcp install [--force] [--no-validate]');
20
- console.log(' warm-mcp stdio');
21
- console.log(' warm-mcp http [--host 127.0.0.1] [--port 3000] [--path /mcp]');
22
- console.log('');
23
- console.log(' Aliases:');
24
- console.log(' --server, --stdio Start stdio mode');
18
+ console.log(' warmio install [--force] [--no-validate]');
19
+ console.log(' warmio mcp');
20
+ console.log(' warmio http [--host 127.0.0.1] [--port 3000] [--path /mcp]');
25
21
  console.log('');
26
22
  }
27
23
  function parseCliArgs(args) {
28
24
  const options = {
29
- command: isInteractiveSession() ? 'install' : 'stdio',
25
+ command: isInteractiveSession() ? 'install' : 'mcp',
30
26
  force: false,
31
27
  validateApiKey: true,
32
28
  };
@@ -40,8 +36,8 @@ function parseCliArgs(args) {
40
36
  options.command = 'install';
41
37
  continue;
42
38
  }
43
- if (arg === 'stdio' || arg === 'server' || arg === '--stdio' || arg === '--server') {
44
- options.command = 'stdio';
39
+ if (arg === 'mcp') {
40
+ options.command = 'mcp';
45
41
  continue;
46
42
  }
47
43
  if (arg === 'http') {
@@ -96,7 +92,7 @@ async function main() {
96
92
  case 'help':
97
93
  printUsage();
98
94
  return;
99
- case 'stdio':
95
+ case 'mcp':
100
96
  await startStdioServer();
101
97
  return;
102
98
  case 'http':
package/dist/install.js CHANGED
@@ -48,8 +48,8 @@ const GLOBAL_CLIENTS = [
48
48
  const PROJECT_CONFIGS = ['.mcp.json', '.cursor/mcp.json', '.vscode/mcp.json'];
49
49
  const MCP_PACKAGE_SPEC = '@warmio/mcp@latest';
50
50
  const MCP_CONFIG = platform() === 'win32'
51
- ? { command: 'cmd', args: ['/c', 'npx', '-y', MCP_PACKAGE_SPEC, '--server'] }
52
- : { command: 'npx', args: ['-y', MCP_PACKAGE_SPEC, '--server'] };
51
+ ? { command: 'cmd', args: ['/c', 'npx', '-y', MCP_PACKAGE_SPEC, 'mcp'] }
52
+ : { command: 'npx', args: ['-y', MCP_PACKAGE_SPEC, 'mcp'] };
53
53
  const WARM_API_KEY_PATH = getWarmApiKeyPath();
54
54
  const WARM_TOML_BLOCK_PATTERN = /\n?\[mcp_servers\.warm\][\s\S]*?(?=\n\[[^\n]+\]|\s*$)/g;
55
55
  const WARM_TOML_ENV_BLOCK_PATTERN = /\n?\[mcp_servers\.warm\.env\][\s\S]*?(?=\n\[[^\n]+\]|\s*$)/g;
@@ -83,22 +83,11 @@ function getStringArray(value) {
83
83
  function isWarmPackageSpecifier(value) {
84
84
  return /^@warmio\/mcp(?:@.+)?$/.test(value);
85
85
  }
86
- function isLegacyWarmPackageSpecifier(value) {
87
- return /^@anthropic\/warm-mcp-server(?:@.+)?$/.test(value);
88
- }
89
- function normalizePathLikeValue(value) {
90
- return value.replace(/\\/g, '/').toLowerCase();
91
- }
92
- function isLocalWarmMcpPath(value) {
93
- const normalized = normalizePathLikeValue(value);
94
- return normalized.includes('/warm-mcp/dist/index.js') || normalized.endsWith('/warm-mcp');
95
- }
96
86
  function isSupportedWarmInvocation(command, args) {
97
- return [command, ...args].some((value) => typeof value === 'string' &&
98
- (isWarmPackageSpecifier(value) || isLocalWarmMcpPath(value)));
99
- }
100
- function isLegacyWarmInvocation(command, args) {
101
- return [command, ...args].some((value) => typeof value === 'string' && isLegacyWarmPackageSpecifier(value));
87
+ if (command !== 'npx' && command !== 'cmd') {
88
+ return false;
89
+ }
90
+ return args.some((value) => isWarmPackageSpecifier(value)) && args.includes('mcp');
102
91
  }
103
92
  function getJsonEnvStatus(server) {
104
93
  const env = isRecord(server?.env) ? server.env : undefined;
@@ -121,13 +110,10 @@ function getJsonStatus(client, content) {
121
110
  }
122
111
  const command = typeof server.command === 'string' ? server.command : undefined;
123
112
  const args = getStringArray(server.args);
124
- if (client.isProjectLevel && command && !isLegacyWarmInvocation(command, args)) {
125
- return { client, state: 'configured', ...envStatus };
126
- }
127
113
  if (isSupportedWarmInvocation(command, args)) {
128
114
  return { client, state: 'configured', ...envStatus };
129
115
  }
130
- return { client, state: 'needs_migration', ...envStatus };
116
+ return { client, state: 'needs_setup', ...envStatus };
131
117
  }
132
118
  catch {
133
119
  return { client, state: 'missing', hasApiKeyFileOverride: false, inlineApiKey: null };
@@ -190,7 +176,7 @@ function getTomlStatus(client, content) {
190
176
  if (isSupportedWarmInvocation(command, args)) {
191
177
  return { client, state: 'configured', ...envStatus };
192
178
  }
193
- return { client, state: 'needs_migration', ...envStatus };
179
+ return { client, state: 'needs_setup', ...envStatus };
194
180
  }
195
181
  function getClientStatus(client) {
196
182
  if (!existsSync(client.configPath)) {
@@ -223,8 +209,7 @@ function configureJson(client) {
223
209
  const existingArgs = getStringArray(existing?.args);
224
210
  const preserveProjectCommand = client.isProjectLevel &&
225
211
  !!existingCommand &&
226
- !isLegacyWarmInvocation(existingCommand, existingArgs) &&
227
- !isWarmPackageSpecifier(existingCommand);
212
+ isSupportedWarmInvocation(existingCommand, existingArgs);
228
213
  delete nextEnv.WARM_API_KEY;
229
214
  if (preserveProjectCommand) {
230
215
  servers.warm = {
@@ -252,8 +237,8 @@ function configureToml(client) {
252
237
  }
253
238
  const tomlCommand = platform() === 'win32' ? 'cmd' : 'npx';
254
239
  const tomlArgs = platform() === 'win32'
255
- ? `["/c", "npx", "-y", "${MCP_PACKAGE_SPEC}", "--server"]`
256
- : `["-y", "${MCP_PACKAGE_SPEC}", "--server"]`;
240
+ ? `["/c", "npx", "-y", "${MCP_PACKAGE_SPEC}", "mcp"]`
241
+ : `["-y", "${MCP_PACKAGE_SPEC}", "mcp"]`;
257
242
  const warmBlock = `[mcp_servers.warm]\ncommand = "${tomlCommand}"\nargs = ${tomlArgs}\n`;
258
243
  const preservedEnvLines = getTomlEnvStatus(content).envLines.filter((line) => !/^\s*WARM_API_KEY\s*=/.test(line));
259
244
  let nextContent = content
@@ -332,8 +317,8 @@ function getStatusLabel(status, force) {
332
317
  if (status === 'configured') {
333
318
  return 'configured';
334
319
  }
335
- if (status === 'needs_migration') {
336
- return 'needs migration';
320
+ if (status === 'needs_setup') {
321
+ return 'needs setup';
337
322
  }
338
323
  return 'not configured';
339
324
  }
@@ -341,8 +326,8 @@ export async function install(options = {}) {
341
326
  const force = options.force ?? false;
342
327
  const shouldValidateApiKey = options.validateApiKey ?? true;
343
328
  console.log('');
344
- console.log(' Warm MCP Server Installer');
345
- console.log(' -------------------------');
329
+ console.log(' Warmio Installer');
330
+ console.log(' ----------------');
346
331
  console.log('');
347
332
  const globalClients = GLOBAL_CLIENTS.filter(isDetected);
348
333
  const projectClients = detectProjectClients();
@@ -4,7 +4,6 @@ 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
8
  function roundMoney(value) {
10
9
  return Math.round(value * 100) / 100;
@@ -56,6 +55,8 @@ function normalizeAccounts(accounts) {
56
55
  };
57
56
  }
58
57
  function normalizeFinancialState(input) {
58
+ const latestSnapshot = input.snapshots?.[0];
59
+ const categorySpending = latestSnapshot?.spending_by_category ?? [];
59
60
  return {
60
61
  budgets: (input.budgets || []).map((budget) => ({
61
62
  amount: budget.amount ?? 0,
@@ -66,8 +67,8 @@ function normalizeFinancialState(input) {
66
67
  spent: budget.spent ?? 0,
67
68
  status: budget.status ?? null,
68
69
  })),
69
- category_spending: (input.categorySpending || []).map((item) => ({
70
- amount: roundMoney(item?.total ?? 0),
70
+ category_spending: categorySpending.map((item) => ({
71
+ amount: roundMoney(item?.amount ?? item?.total ?? 0),
71
72
  category: item?.category || 'Uncategorized',
72
73
  })),
73
74
  generated_at: input.generatedAt,
@@ -163,7 +164,7 @@ export async function apiRequest(endpoint, params = {}, options = {}) {
163
164
  const requestOptions = createWarmApiClientConfig(options);
164
165
  const apiKey = requestOptions.apiKeyResolver?.() ?? getConfiguredApiKey();
165
166
  if (!apiKey) {
166
- throw new Error('WARM_API_KEY not set. Run "npx -y @warmio/mcp@latest" to configure or set WARM_API_KEY.');
167
+ throw new Error('WARM_API_KEY not set. Run "npx @warmio/mcp" to configure or set WARM_API_KEY.');
167
168
  }
168
169
  const url = new URL(endpoint, requestOptions.apiUrl);
169
170
  Object.entries(params).forEach(([key, value]) => {
@@ -193,7 +194,7 @@ export async function apiRequest(endpoint, params = {}, options = {}) {
193
194
  if (!response.ok) {
194
195
  const errorMessages = {
195
196
  401: 'Invalid or expired API key. Regenerate at https://warm.io/settings',
196
- 403: 'Pro subscription required. Upgrade at https://warm.io/settings',
197
+ 403: 'API access is available on paid plans only. Upgrade at https://warm.io/settings',
197
198
  429: 'Rate limit exceeded. Try again in a few minutes.',
198
199
  };
199
200
  if (errorMessages[response.status]) {
@@ -219,7 +220,7 @@ export function createWarmApiClient(options = {}) {
219
220
  return normalizeAccounts(response.accounts || []);
220
221
  };
221
222
  const getFinancialState = async () => {
222
- const [snapshotsResponse, recurringResponse, budgetsResponse, goalsResponse, healthResponse, liabilitiesResponse, holdingsResponse, spendingResponse,] = await Promise.all([
223
+ const [snapshotsResponse, recurringResponse, budgetsResponse, goalsResponse, healthResponse, liabilitiesResponse, holdingsResponse,] = await Promise.all([
223
224
  apiRequest('/api/export', { dataset: 'snapshots' }, options),
224
225
  apiRequest('/api/export', { dataset: 'recurring' }, options),
225
226
  apiRequest('/api/export', { dataset: 'budgets' }, options),
@@ -227,10 +228,8 @@ export function createWarmApiClient(options = {}) {
227
228
  apiRequest('/api/export', { dataset: 'health' }, options),
228
229
  apiRequest('/api/export', { dataset: 'liabilities' }, options),
229
230
  apiRequest('/api/export', { dataset: 'holdings' }, options),
230
- apiRequest('/api/spending', { months: String(DEFAULT_SPENDING_MONTHS) }, options),
231
231
  ]);
232
- const generatedAt = spendingResponse.generated_at ||
233
- holdingsResponse.generated_at ||
232
+ const generatedAt = holdingsResponse.generated_at ||
234
233
  liabilitiesResponse.generated_at ||
235
234
  healthResponse.generated_at ||
236
235
  goalsResponse.generated_at ||
@@ -240,7 +239,6 @@ export function createWarmApiClient(options = {}) {
240
239
  new Date().toISOString();
241
240
  return normalizeFinancialState({
242
241
  budgets: budgetsResponse.budgets,
243
- categorySpending: spendingResponse.spending,
244
242
  generatedAt,
245
243
  goals: goalsResponse.goals,
246
244
  health: healthResponse,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@warmio/mcp",
3
- "version": "4.3.3",
4
- "description": "Warm MCP server for local stdio and Streamable HTTP access",
3
+ "version": "5.0.1",
4
+ "description": "Warm CLI and MCP server for personal financial data",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -27,24 +27,28 @@
27
27
  }
28
28
  },
29
29
  "bin": {
30
- "warm-mcp": "dist/index.js"
30
+ "warmio": "dist/index.js"
31
31
  },
32
32
  "files": [
33
33
  "dist"
34
34
  ],
35
35
  "scripts": {
36
36
  "build": "tsc",
37
- "start": "node dist/index.js --stdio",
37
+ "test": "npm run build && node --test test/*.mjs",
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",
40
- "prepublishOnly": "npm run build"
41
+ "prepublishOnly": "npm run build",
42
+ "start:mcp": "node dist/index.js mcp"
41
43
  },
42
44
  "keywords": [
43
45
  "mcp",
44
46
  "warm",
47
+ "warmio",
45
48
  "financial",
46
49
  "api",
47
- "claude"
50
+ "claude",
51
+ "chatgpt"
48
52
  ],
49
53
  "license": "MIT",
50
54
  "dependencies": {