@warmio/mcp 8.0.0 → 9.1.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 +15 -10
- package/dist/config-paths.d.ts +2 -2
- package/dist/config-paths.js +4 -3
- package/dist/install.d.ts +2 -2
- package/dist/install.js +71 -64
- package/dist/server.d.ts +2 -1
- package/dist/types.d.ts +6 -5
- package/dist/types.js +8 -11
- package/dist/warm-api-client.d.ts +4 -4
- package/dist/warm-api-client.js +33 -13
- package/package.json +6 -7
- package/dist/manifest.d.ts +0 -85
- package/dist/manifest.js +0 -30
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ are stored separately and are never shared between modes.
|
|
|
14
14
|
|
|
15
15
|
## Requirements
|
|
16
16
|
|
|
17
|
-
- Warm Pro
|
|
17
|
+
- Any paid Warm plan (Plus or Pro)
|
|
18
18
|
- A context key or an automation key from [Settings -> API Keys](https://warm.io/settings)
|
|
19
19
|
- Node.js 18+
|
|
20
20
|
|
|
@@ -272,7 +272,20 @@ Latest response:
|
|
|
272
272
|
"since": "2026-06-26",
|
|
273
273
|
"window_days": 10,
|
|
274
274
|
"count": 1,
|
|
275
|
-
"items": [
|
|
275
|
+
"items": [
|
|
276
|
+
{
|
|
277
|
+
"id": "txn_1",
|
|
278
|
+
"account_id": "acct_1",
|
|
279
|
+
"date": "2026-07-05",
|
|
280
|
+
"amount": 42.18,
|
|
281
|
+
"merchant": "Whole Foods",
|
|
282
|
+
"name": "WHOLE FOODS",
|
|
283
|
+
"category": "FOOD_AND_DRINK",
|
|
284
|
+
"subcategory": "FOOD_AND_DRINK_GROCERIES",
|
|
285
|
+
"pending": false,
|
|
286
|
+
"currency": "USD"
|
|
287
|
+
}
|
|
288
|
+
]
|
|
276
289
|
}
|
|
277
290
|
```
|
|
278
291
|
|
|
@@ -293,14 +306,6 @@ Returns:
|
|
|
293
306
|
}
|
|
294
307
|
```
|
|
295
308
|
|
|
296
|
-
## v7 Breaking Changes
|
|
297
|
-
|
|
298
|
-
v7 is a clean pre-launch cutover with no backwards compatibility or alias window.
|
|
299
|
-
|
|
300
|
-
- API keys are audience-bound: context keys cannot invoke actions and automation keys cannot run context mode.
|
|
301
|
-
- `X-API-Key` is removed. Use `Authorization: Bearer`.
|
|
302
|
-
- Automation is a separate opt-in MCP server mode, not extra tools on the context server.
|
|
303
|
-
|
|
304
309
|
## Security
|
|
305
310
|
|
|
306
311
|
- Context mode is read-only and uses a context-only key.
|
package/dist/config-paths.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import { type PrivateMcpMode } from '@warmio/contracts/mcp';
|
|
2
2
|
export declare function getWarmConfigDir(): string;
|
|
3
|
-
export declare function getWarmApiKeyPath(audience?:
|
|
3
|
+
export declare function getWarmApiKeyPath(audience?: PrivateMcpMode): string;
|
|
4
4
|
export declare function readConfigFile(configPath: string): string | null;
|
package/dist/config-paths.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
2
|
import * as os from 'node:os';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
|
+
import { WARM_MCP_CREDENTIALS } from '@warmio/contracts/mcp';
|
|
4
5
|
export function getWarmConfigDir() {
|
|
5
6
|
if (process.env.WARM_CONFIG_DIR?.trim()) {
|
|
6
7
|
return process.env.WARM_CONFIG_DIR.trim();
|
|
@@ -17,12 +18,12 @@ export function getWarmConfigDir() {
|
|
|
17
18
|
return path.join(os.homedir(), '.config', 'warm');
|
|
18
19
|
}
|
|
19
20
|
export function getWarmApiKeyPath(audience = 'context') {
|
|
20
|
-
const
|
|
21
|
-
const configuredPath = process.env[
|
|
21
|
+
const credential = WARM_MCP_CREDENTIALS[audience];
|
|
22
|
+
const configuredPath = process.env[credential.apiKeyFileEnv]?.trim();
|
|
22
23
|
if (configuredPath) {
|
|
23
24
|
return configuredPath;
|
|
24
25
|
}
|
|
25
|
-
return path.join(getWarmConfigDir(),
|
|
26
|
+
return path.join(getWarmConfigDir(), credential.defaultFileName);
|
|
26
27
|
}
|
|
27
28
|
export function readConfigFile(configPath) {
|
|
28
29
|
try {
|
package/dist/install.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type PrivateMcpMode } from '@warmio/contracts/mcp';
|
|
2
2
|
export interface InstallOptions {
|
|
3
3
|
force?: boolean;
|
|
4
|
-
mode?:
|
|
4
|
+
mode?: PrivateMcpMode;
|
|
5
5
|
validateApiKey?: boolean;
|
|
6
6
|
}
|
|
7
7
|
export declare function install(options?: InstallOptions): Promise<void>;
|
package/dist/install.js
CHANGED
|
@@ -2,56 +2,54 @@ import { createInterface } from 'readline';
|
|
|
2
2
|
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
3
3
|
import { dirname, join, resolve } from 'path';
|
|
4
4
|
import { homedir, platform } from 'os';
|
|
5
|
+
import { WARM_MCP_CLIENTS, WARM_MCP_CREDENTIALS, WARM_MCP_PROJECT_CONFIGS, WARM_MCP_SERVER_CONFIGS, } from '@warmio/contracts/mcp';
|
|
5
6
|
import { verifyWarmApiKey, WarmApiError } from './server.js';
|
|
6
7
|
import { getWarmApiKeyPath, readConfigFile } from './config-paths.js';
|
|
7
|
-
import { WARM_MCP_CLIENTS, WARM_MCP_PACKAGE_SPEC, WARM_MCP_PROJECT_CONFIGS, } from './manifest.js';
|
|
8
8
|
const HOME = homedir();
|
|
9
9
|
const CWD = process.cwd();
|
|
10
10
|
function isRecord(value) {
|
|
11
11
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
12
12
|
}
|
|
13
|
-
function
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
if (platform() === 'darwin') {
|
|
18
|
-
return join(HOME, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
19
|
-
}
|
|
20
|
-
return join(HOME, '.config', 'claude', 'claude_desktop_config.json');
|
|
13
|
+
function resolveClientConfigPath(configPath) {
|
|
14
|
+
const root = configPath.root === 'appData' ? process.env.APPDATA || join(HOME, 'AppData', 'Roaming') : HOME;
|
|
15
|
+
return join(root, ...configPath.segments);
|
|
21
16
|
}
|
|
22
17
|
function getClientConfigPath(clientId) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
case 'codex-cli':
|
|
35
|
-
return join(HOME, '.codex', 'config.toml');
|
|
36
|
-
case 'antigravity':
|
|
37
|
-
return join(HOME, '.gemini', 'antigravity', 'mcp_config.json');
|
|
38
|
-
case 'gemini-cli':
|
|
39
|
-
return join(HOME, '.gemini', 'settings.json');
|
|
40
|
-
}
|
|
18
|
+
const client = WARM_MCP_CLIENTS.find((candidate) => candidate.id === clientId);
|
|
19
|
+
if (!client) {
|
|
20
|
+
throw new Error(`Unsupported MCP client: ${clientId}`);
|
|
21
|
+
}
|
|
22
|
+
const currentPlatform = platform();
|
|
23
|
+
const configPath = currentPlatform === 'darwin'
|
|
24
|
+
? client.configPaths.darwin
|
|
25
|
+
: currentPlatform === 'win32'
|
|
26
|
+
? client.configPaths.win32
|
|
27
|
+
: client.configPaths.linux;
|
|
28
|
+
return resolveClientConfigPath(configPath);
|
|
41
29
|
}
|
|
42
30
|
const GLOBAL_CLIENTS = WARM_MCP_CLIENTS.map((client) => ({
|
|
43
|
-
|
|
31
|
+
name: client.name,
|
|
32
|
+
format: client.format,
|
|
44
33
|
configPath: getClientConfigPath(client.id),
|
|
45
|
-
alwaysInclude: client
|
|
34
|
+
alwaysInclude: 'alwaysInclude' in client && client.alwaysInclude,
|
|
46
35
|
}));
|
|
47
36
|
function getServerName(mode) {
|
|
48
|
-
return mode
|
|
37
|
+
return WARM_MCP_SERVER_CONFIGS[mode].serverName;
|
|
38
|
+
}
|
|
39
|
+
function getCredential(mode) {
|
|
40
|
+
return WARM_MCP_CREDENTIALS[mode];
|
|
41
|
+
}
|
|
42
|
+
function getOppositeCredential(mode) {
|
|
43
|
+
return WARM_MCP_CREDENTIALS[mode === 'automation' ? 'context' : 'automation'];
|
|
49
44
|
}
|
|
50
45
|
function getMcpConfig(mode) {
|
|
51
|
-
const
|
|
46
|
+
const serverConfig = WARM_MCP_SERVER_CONFIGS[mode];
|
|
52
47
|
return platform() === 'win32'
|
|
53
|
-
? { command: 'cmd', args: ['/c',
|
|
54
|
-
: { command:
|
|
48
|
+
? { command: 'cmd', args: ['/c', serverConfig.command, ...serverConfig.args] }
|
|
49
|
+
: { command: serverConfig.command, args: [...serverConfig.args] };
|
|
50
|
+
}
|
|
51
|
+
function formatTomlStringArray(values) {
|
|
52
|
+
return `[${values.map((value) => JSON.stringify(value)).join(', ')}]`;
|
|
55
53
|
}
|
|
56
54
|
function detectProjectClients() {
|
|
57
55
|
const found = [];
|
|
@@ -94,18 +92,21 @@ function isSupportedWarmInvocation(command, args, mode) {
|
|
|
94
92
|
}
|
|
95
93
|
function getJsonEnvStatus(server, mode) {
|
|
96
94
|
const env = isRecord(server?.env) ? server.env : undefined;
|
|
97
|
-
const
|
|
98
|
-
const
|
|
99
|
-
const
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
: null;
|
|
95
|
+
const credential = getCredential(mode);
|
|
96
|
+
const oppositeCredential = getOppositeCredential(mode);
|
|
97
|
+
const apiKeyName = credential.apiKeyEnv;
|
|
98
|
+
const apiKeyFileName = credential.apiKeyFileEnv;
|
|
99
|
+
const inlineApiKey = typeof env?.[apiKeyName] === 'string' && env[apiKeyName].trim() ? env[apiKeyName].trim() : null;
|
|
103
100
|
return {
|
|
104
101
|
apiKeyFileOverride: typeof env?.[apiKeyFileName] === 'string' && env[apiKeyFileName].trim().length > 0
|
|
105
102
|
? env[apiKeyFileName].trim()
|
|
106
103
|
: null,
|
|
107
|
-
hasIrrelevantCredential: [
|
|
108
|
-
|
|
104
|
+
hasIrrelevantCredential: [
|
|
105
|
+
'WARM_API_KEY',
|
|
106
|
+
'WARM_API_KEY_FILE',
|
|
107
|
+
oppositeCredential.apiKeyEnv,
|
|
108
|
+
oppositeCredential.apiKeyFileEnv,
|
|
109
|
+
].some((name) => typeof env?.[name] === 'string' && env[name].trim().length > 0),
|
|
109
110
|
inlineApiKey,
|
|
110
111
|
};
|
|
111
112
|
}
|
|
@@ -193,19 +194,23 @@ function getTomlEnvStatus(content, mode) {
|
|
|
193
194
|
inlineApiKey: null,
|
|
194
195
|
};
|
|
195
196
|
}
|
|
196
|
-
const envLines = envBlock
|
|
197
|
-
|
|
198
|
-
|
|
197
|
+
const envLines = envBlock
|
|
198
|
+
.split(/\r?\n/)
|
|
199
|
+
.slice(1)
|
|
200
|
+
.filter((line) => line.trim().length > 0);
|
|
201
|
+
const credential = getCredential(mode);
|
|
202
|
+
const oppositeCredential = getOppositeCredential(mode);
|
|
203
|
+
const apiKeyName = credential.apiKeyEnv;
|
|
199
204
|
const inlineApiKey = getTomlStringValue(envBlock, apiKeyName)?.trim() || null;
|
|
200
|
-
const apiKeyFile = getTomlStringValue(envBlock,
|
|
205
|
+
const apiKeyFile = getTomlStringValue(envBlock, credential.apiKeyFileEnv);
|
|
201
206
|
return {
|
|
202
207
|
apiKeyFileOverride: typeof apiKeyFile === 'string' && apiKeyFile.trim() ? apiKeyFile.trim() : null,
|
|
203
208
|
envLines,
|
|
204
209
|
hasIrrelevantCredential: [
|
|
205
210
|
'WARM_API_KEY',
|
|
206
211
|
'WARM_API_KEY_FILE',
|
|
207
|
-
|
|
208
|
-
|
|
212
|
+
oppositeCredential.apiKeyEnv,
|
|
213
|
+
oppositeCredential.apiKeyFileEnv,
|
|
209
214
|
].some((name) => Boolean(getTomlStringValue(envBlock, name)?.trim())),
|
|
210
215
|
inlineApiKey,
|
|
211
216
|
};
|
|
@@ -265,14 +270,15 @@ function configureJson(client, mode, preserveSelectedKeyFile) {
|
|
|
265
270
|
const preserveProjectCommand = client.isProjectLevel &&
|
|
266
271
|
!!existingCommand &&
|
|
267
272
|
isSupportedWarmInvocation(existingCommand, existingArgs, mode);
|
|
268
|
-
const
|
|
269
|
-
const
|
|
273
|
+
const credential = getCredential(mode);
|
|
274
|
+
const oppositeCredential = getOppositeCredential(mode);
|
|
275
|
+
const selectedKeyFile = credential.apiKeyFileEnv;
|
|
270
276
|
for (const name of [
|
|
271
277
|
'WARM_API_KEY',
|
|
272
278
|
'WARM_API_KEY_FILE',
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
279
|
+
credential.apiKeyEnv,
|
|
280
|
+
oppositeCredential.apiKeyEnv,
|
|
281
|
+
oppositeCredential.apiKeyFileEnv,
|
|
276
282
|
]) {
|
|
277
283
|
delete nextEnv[name];
|
|
278
284
|
}
|
|
@@ -306,19 +312,21 @@ function configureToml(client, mode, preserveSelectedKeyFile) {
|
|
|
306
312
|
}
|
|
307
313
|
}
|
|
308
314
|
const serverName = getServerName(mode);
|
|
309
|
-
const
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
315
|
+
const serverConfig = WARM_MCP_SERVER_CONFIGS[mode];
|
|
316
|
+
const tomlCommand = platform() === 'win32' ? 'cmd' : serverConfig.command;
|
|
317
|
+
const posixArgs = formatTomlStringArray(serverConfig.args);
|
|
318
|
+
const windowsArgs = formatTomlStringArray(['/c', serverConfig.command, ...serverConfig.args]);
|
|
319
|
+
const tomlArgs = platform() === 'win32' ? windowsArgs : posixArgs;
|
|
313
320
|
const warmBlock = `[mcp_servers.${serverName}]${lineEnding}command = "${tomlCommand}"${lineEnding}args = ${tomlArgs}${lineEnding}`;
|
|
314
|
-
const
|
|
315
|
-
const
|
|
321
|
+
const credential = getCredential(mode);
|
|
322
|
+
const oppositeCredential = getOppositeCredential(mode);
|
|
323
|
+
const selectedKeyFile = credential.apiKeyFileEnv;
|
|
316
324
|
const removedEnvNames = new Set([
|
|
317
325
|
'WARM_API_KEY',
|
|
318
326
|
'WARM_API_KEY_FILE',
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
327
|
+
credential.apiKeyEnv,
|
|
328
|
+
oppositeCredential.apiKeyEnv,
|
|
329
|
+
oppositeCredential.apiKeyFileEnv,
|
|
322
330
|
...(!preserveSelectedKeyFile ? [selectedKeyFile] : []),
|
|
323
331
|
]);
|
|
324
332
|
const preservedEnvLines = getTomlEnvStatus(content, mode).envLines.filter((line) => {
|
|
@@ -449,8 +457,7 @@ export async function install(options = {}) {
|
|
|
449
457
|
}
|
|
450
458
|
const apiKeyPath = getWarmApiKeyPath(mode);
|
|
451
459
|
const storedApiKey = readConfigFile(apiKeyPath);
|
|
452
|
-
const migratedApiKey = needsSetup.find((status) => typeof status.inlineApiKey === 'string' && status.inlineApiKey.length > 0)
|
|
453
|
-
?.inlineApiKey ?? null;
|
|
460
|
+
const migratedApiKey = needsSetup.find((status) => typeof status.inlineApiKey === 'string' && status.inlineApiKey.length > 0)?.inlineApiKey ?? null;
|
|
454
461
|
const usableApiKeyFileOverride = needsSetup.find((status) => status.apiKeyFileOverride && !status.inlineApiKey && status.credentialApiKey);
|
|
455
462
|
const targetMissingCredential = needsSetup.some((status) => !status.credentialApiKey);
|
|
456
463
|
let apiKeyToStore = null;
|
|
@@ -469,7 +476,7 @@ export async function install(options = {}) {
|
|
|
469
476
|
shouldStoreApiKey = true;
|
|
470
477
|
}
|
|
471
478
|
else if (usableApiKeyFileOverride && !targetMissingCredential) {
|
|
472
|
-
console.log(` Reusing existing ${mode
|
|
479
|
+
console.log(` Reusing existing ${getCredential(mode).apiKeyFileEnv} override.`);
|
|
473
480
|
console.log('');
|
|
474
481
|
}
|
|
475
482
|
else {
|
package/dist/server.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { type Implementation } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
+
import { type PrivateMcpMode } from '@warmio/contracts/mcp';
|
|
3
4
|
import { createWarmApiClient, type WarmApiClientOptions, getConfiguredApiKey, verifyWarmApiKey, WarmApiError } from './warm-api-client.js';
|
|
4
5
|
export { createWarmApiClient, getConfiguredApiKey, verifyWarmApiKey, WarmApiError };
|
|
5
6
|
export type { WarmApiClientOptions } from './warm-api-client.js';
|
|
@@ -7,7 +8,7 @@ export interface WarmServerOptions extends WarmApiClientOptions {
|
|
|
7
8
|
mode?: WarmServerMode;
|
|
8
9
|
serverInfo?: Implementation;
|
|
9
10
|
}
|
|
10
|
-
export type WarmServerMode =
|
|
11
|
+
export type WarmServerMode = PrivateMcpMode;
|
|
11
12
|
export declare const API_URL: string;
|
|
12
13
|
export declare const WARM_SERVER_INFO: {
|
|
13
14
|
name: string;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
export declare const
|
|
3
|
-
export declare const
|
|
4
|
-
export
|
|
1
|
+
import { privateMcpToolDefinitions } from '@warmio/contracts/mcp';
|
|
2
|
+
export declare const WARM_CONTEXT_TOOL_NAMES: string[];
|
|
3
|
+
export declare const WARM_AUTOMATION_TOOL_NAMES: string[];
|
|
4
|
+
export declare const WARM_TOOL_NAMES: string[];
|
|
5
|
+
export type WarmToolName = (typeof privateMcpToolDefinitions)[number]['name'];
|
|
5
6
|
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 {
|
|
7
|
+
export type { PrivateMcpMode } from '@warmio/contracts/mcp';
|
|
7
8
|
export type { WarmServerOptions } from './server.js';
|
package/dist/types.js
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
'
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
'invoke_operation',
|
|
10
|
-
];
|
|
11
|
-
export const WARM_TOOL_NAMES = [...WARM_CONTEXT_TOOL_NAMES, ...WARM_AUTOMATION_TOOL_NAMES];
|
|
1
|
+
import { privateMcpToolDefinitions } from '@warmio/contracts/mcp';
|
|
2
|
+
export const WARM_CONTEXT_TOOL_NAMES = privateMcpToolDefinitions
|
|
3
|
+
.filter((tool) => tool.mode === 'context')
|
|
4
|
+
.map((tool) => tool.name);
|
|
5
|
+
export const WARM_AUTOMATION_TOOL_NAMES = privateMcpToolDefinitions
|
|
6
|
+
.filter((tool) => tool.mode === 'automation')
|
|
7
|
+
.map((tool) => tool.name);
|
|
8
|
+
export const WARM_TOOL_NAMES = privateMcpToolDefinitions.map((tool) => tool.name);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type PrivateMcpMode } from '@warmio/contracts/mcp';
|
|
2
2
|
import type { AutomationInput, AutomationOperation, FinancialContext, FinancialContextAccount, FinancialContextBudget, FinancialContextGoal, FinancialContextHealth, FinancialContextHolding, FinancialContextLiability, FinancialContextMeta, FinancialContextPosition, FinancialContextRecurring, FinancialContextSnapshot, FinancialContextStatus, FinancialContextTransaction, FinancialContextTransactionIndex, LatestTransactions, TransactionMonth } from '@warmio/contracts/types';
|
|
3
3
|
export type { AutomationInput, AutomationOperation, FinancialContext, FinancialContextMeta, LatestTransactions, TransactionMonth, } from '@warmio/contracts/types';
|
|
4
4
|
export interface WarmApiClientOptions {
|
|
5
|
-
audience?:
|
|
5
|
+
audience?: PrivateMcpMode;
|
|
6
6
|
apiKeyResolver?: () => string | null;
|
|
7
7
|
apiUrl?: string;
|
|
8
8
|
fetchImplementation?: typeof fetch;
|
|
@@ -63,7 +63,7 @@ export interface WarmApiClient {
|
|
|
63
63
|
validateAudience(): Promise<VerifyKeyOutput>;
|
|
64
64
|
verifyKey(): Promise<VerifyKeyOutput>;
|
|
65
65
|
}
|
|
66
|
-
export declare function getConfiguredApiKey(audience?:
|
|
66
|
+
export declare function getConfiguredApiKey(audience?: PrivateMcpMode): string | null;
|
|
67
67
|
export declare function resetConfiguredApiKeyCache(): void;
|
|
68
68
|
interface WarmApiResponse<TBody> {
|
|
69
69
|
body: TBody;
|
|
@@ -77,4 +77,4 @@ export declare class WarmApiError extends Error {
|
|
|
77
77
|
constructor(message: string, response: WarmApiResponse<unknown>);
|
|
78
78
|
}
|
|
79
79
|
export declare function createWarmApiClient(options?: WarmApiClientOptions): WarmApiClient;
|
|
80
|
-
export declare function verifyWarmApiKey(apiKey: string, audience?:
|
|
80
|
+
export declare function verifyWarmApiKey(apiKey: string, audience?: PrivateMcpMode): Promise<VerifyKeyOutput>;
|
package/dist/warm-api-client.js
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
|
-
import { getWarmApiKeyPath, readConfigFile
|
|
2
|
-
|
|
1
|
+
import { getWarmApiKeyPath, readConfigFile } from './config-paths.js';
|
|
2
|
+
import { API_ORIGIN, apiEndpointManifest, } from '@warmio/contracts/api';
|
|
3
|
+
import { WARM_MCP_CREDENTIALS, WARM_MCP_INSTALLER_COMMANDS, } from '@warmio/contracts/mcp';
|
|
4
|
+
const DEFAULT_API_URL = process.env.WARM_API_URL || API_ORIGIN;
|
|
3
5
|
const DEFAULT_REQUEST_TIMEOUT_MS = (() => {
|
|
4
6
|
const raw = Number(process.env.WARM_API_TIMEOUT_MS || 10_000);
|
|
5
7
|
return Number.isFinite(raw) && raw > 0 ? raw : 10_000;
|
|
6
8
|
})();
|
|
7
9
|
const cachedApiKeys = new Map();
|
|
10
|
+
const endpointByOperationId = Object.fromEntries(apiEndpointManifest.map((endpoint) => [endpoint.operation_id, endpoint]));
|
|
11
|
+
function getEndpointPath(operationId, audience) {
|
|
12
|
+
const endpoint = endpointByOperationId[operationId];
|
|
13
|
+
if (endpoint.audience !== audience) {
|
|
14
|
+
throw new Error(`API operation ${operationId} is not available to ${audience}.`);
|
|
15
|
+
}
|
|
16
|
+
return endpoint.path;
|
|
17
|
+
}
|
|
8
18
|
function getRequestSignal(timeoutMs) {
|
|
9
19
|
if (typeof AbortSignal.timeout === 'function') {
|
|
10
20
|
return AbortSignal.timeout(timeoutMs);
|
|
@@ -27,7 +37,7 @@ export function getConfiguredApiKey(audience = 'context') {
|
|
|
27
37
|
if (cachedApiKeys.has(audience)) {
|
|
28
38
|
return cachedApiKeys.get(audience) ?? null;
|
|
29
39
|
}
|
|
30
|
-
const environmentKey = audience
|
|
40
|
+
const environmentKey = WARM_MCP_CREDENTIALS[audience].apiKeyEnv;
|
|
31
41
|
const apiKey = process.env[environmentKey]?.trim() || readConfigFile(getWarmApiKeyPath(audience));
|
|
32
42
|
cachedApiKeys.set(audience, apiKey);
|
|
33
43
|
return apiKey;
|
|
@@ -65,7 +75,7 @@ async function requestWarmApi(request, options) {
|
|
|
65
75
|
const requestOptions = createWarmApiClientConfig(options);
|
|
66
76
|
const apiKey = requestOptions.apiKeyResolver?.() ?? getConfiguredApiKey(requestOptions.audience);
|
|
67
77
|
if (!apiKey) {
|
|
68
|
-
throw new Error(`No ${requestOptions.audience} API key configured. Run "
|
|
78
|
+
throw new Error(`No ${requestOptions.audience} API key configured. Run "${WARM_MCP_INSTALLER_COMMANDS[requestOptions.audience]}" or set ${WARM_MCP_CREDENTIALS[requestOptions.audience].apiKeyEnv}.`);
|
|
69
79
|
}
|
|
70
80
|
const url = new URL(request.endpoint, requestOptions.apiUrl);
|
|
71
81
|
Object.entries(request.query || {}).forEach(([key, value]) => {
|
|
@@ -119,23 +129,29 @@ async function requestSuccessfulJson(request, options) {
|
|
|
119
129
|
return response.body;
|
|
120
130
|
}
|
|
121
131
|
export function createWarmApiClient(options = {}) {
|
|
122
|
-
const getFinancialContext = async () => await requestSuccessfulJson({ endpoint: '
|
|
123
|
-
const getFinancialContextMeta = async () => await requestSuccessfulJson({ endpoint: '
|
|
132
|
+
const getFinancialContext = async () => await requestSuccessfulJson({ endpoint: getEndpointPath('getFinancialContext', 'context') }, options);
|
|
133
|
+
const getFinancialContextMeta = async () => await requestSuccessfulJson({ endpoint: getEndpointPath('getFinancialContextMeta', 'context') }, options);
|
|
124
134
|
const getTransactions = async (input) => {
|
|
125
135
|
const rawInput = input;
|
|
126
136
|
if (typeof rawInput.month === 'string' && rawInput.latest !== undefined) {
|
|
127
137
|
throw new Error('`month` and `latest` are mutually exclusive.');
|
|
128
138
|
}
|
|
129
139
|
if (typeof rawInput.month === 'string') {
|
|
130
|
-
return await requestSuccessfulJson({
|
|
140
|
+
return await requestSuccessfulJson({
|
|
141
|
+
endpoint: getEndpointPath('getFinancialContextTransactions', 'context'),
|
|
142
|
+
query: { month: rawInput.month },
|
|
143
|
+
}, options);
|
|
131
144
|
}
|
|
132
145
|
if (rawInput.latest === true) {
|
|
133
|
-
return await requestSuccessfulJson({
|
|
146
|
+
return await requestSuccessfulJson({
|
|
147
|
+
endpoint: getEndpointPath('getFinancialContextTransactions', 'context'),
|
|
148
|
+
query: { latest: '1' },
|
|
149
|
+
}, options);
|
|
134
150
|
}
|
|
135
151
|
throw new Error('Call getTransactions with `month` in YYYY-MM format or `latest: true`.');
|
|
136
152
|
};
|
|
137
153
|
const verifyKey = async () => {
|
|
138
|
-
const response = await requestSuccessfulJson({ endpoint: '
|
|
154
|
+
const response = await requestSuccessfulJson({ endpoint: getEndpointPath('verifyApiAccess', 'context') }, options);
|
|
139
155
|
return {
|
|
140
156
|
audience: response.audience,
|
|
141
157
|
status: response.status || (response.valid ? 'ok' : 'invalid'),
|
|
@@ -147,14 +163,18 @@ export function createWarmApiClient(options = {}) {
|
|
|
147
163
|
const expectedAudience = createWarmApiClientConfig(options).audience;
|
|
148
164
|
if (!result.valid || result.audience !== expectedAudience) {
|
|
149
165
|
const article = expectedAudience === 'automation' ? 'an' : 'a';
|
|
150
|
-
throw new Error(`This MCP mode requires ${article} ${expectedAudience} API key. Run "
|
|
166
|
+
throw new Error(`This MCP mode requires ${article} ${expectedAudience} API key. Run "${WARM_MCP_INSTALLER_COMMANDS[expectedAudience]}" to configure a separate key.`);
|
|
151
167
|
}
|
|
152
168
|
return result;
|
|
153
169
|
};
|
|
154
|
-
const searchOperations = async (query) => await requestSuccessfulJson({
|
|
170
|
+
const searchOperations = async (query) => await requestSuccessfulJson({
|
|
171
|
+
body: query ? { query } : {},
|
|
172
|
+
endpoint: getEndpointPath('searchOperations', 'automation'),
|
|
173
|
+
method: 'POST',
|
|
174
|
+
}, options);
|
|
155
175
|
const describeOperation = async (operationId, input) => await requestSuccessfulJson({
|
|
156
176
|
body: { operation_id: operationId, ...(input ? { input } : {}) },
|
|
157
|
-
endpoint: '
|
|
177
|
+
endpoint: getEndpointPath('describeOperation', 'automation'),
|
|
158
178
|
method: 'POST',
|
|
159
179
|
}, options);
|
|
160
180
|
const invokeOperation = async (operationId, input, approvalId) => await requestSuccessfulJson({
|
|
@@ -163,7 +183,7 @@ export function createWarmApiClient(options = {}) {
|
|
|
163
183
|
input: input ?? {},
|
|
164
184
|
...(approvalId ? { approval_id: approvalId } : {}),
|
|
165
185
|
},
|
|
166
|
-
endpoint: '
|
|
186
|
+
endpoint: getEndpointPath('invokeOperation', 'automation'),
|
|
167
187
|
method: 'POST',
|
|
168
188
|
}, options);
|
|
169
189
|
return {
|
package/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@warmio/mcp",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.1.0",
|
|
4
4
|
"mcpName": "io.github.jasonstockman/warmio",
|
|
5
5
|
"description": "Warm CLI and compact context or automation MCP server",
|
|
6
6
|
"type": "module",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=18"
|
|
9
|
+
},
|
|
7
10
|
"main": "dist/index.js",
|
|
8
11
|
"types": "dist/index.d.ts",
|
|
9
12
|
"publishConfig": {
|
|
@@ -25,10 +28,6 @@
|
|
|
25
28
|
"./install": {
|
|
26
29
|
"types": "./dist/install.d.ts",
|
|
27
30
|
"default": "./dist/install.js"
|
|
28
|
-
},
|
|
29
|
-
"./manifest": {
|
|
30
|
-
"types": "./dist/manifest.d.ts",
|
|
31
|
-
"default": "./dist/manifest.js"
|
|
32
31
|
}
|
|
33
32
|
},
|
|
34
33
|
"bin": {
|
|
@@ -38,7 +37,7 @@
|
|
|
38
37
|
"dist"
|
|
39
38
|
],
|
|
40
39
|
"scripts": {
|
|
41
|
-
"build": "tsc",
|
|
40
|
+
"build": "node scripts/clean.mjs && tsc",
|
|
42
41
|
"test": "npm run build && node --test test/*.mjs",
|
|
43
42
|
"start": "node dist/index.js mcp",
|
|
44
43
|
"start:http": "node dist/index.js http",
|
|
@@ -61,7 +60,7 @@
|
|
|
61
60
|
"url": "git+https://github.com/jasonstockman/warm-mcp.git"
|
|
62
61
|
},
|
|
63
62
|
"dependencies": {
|
|
64
|
-
"@warmio/contracts": "1.
|
|
63
|
+
"@warmio/contracts": "1.2.0",
|
|
65
64
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
66
65
|
"dotenv": "^17.2.3",
|
|
67
66
|
"zod": "^3.25.76"
|
package/dist/manifest.d.ts
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
export declare const WARM_MCP_PACKAGE_NAME: "@warmio/mcp";
|
|
2
|
-
export declare const WARM_MCP_PACKAGE_SPEC: "@warmio/mcp@latest";
|
|
3
|
-
export declare const WARM_MCP_INSTALL_COMMAND: "npx -y @warmio/mcp@latest";
|
|
4
|
-
export declare const WARM_MCP_CLIENTS: readonly [{
|
|
5
|
-
readonly id: "claude-code";
|
|
6
|
-
readonly name: "Claude Code";
|
|
7
|
-
readonly format: "json";
|
|
8
|
-
}, {
|
|
9
|
-
readonly id: "claude-desktop";
|
|
10
|
-
readonly name: "Claude Desktop";
|
|
11
|
-
readonly format: "json";
|
|
12
|
-
}, {
|
|
13
|
-
readonly id: "cursor";
|
|
14
|
-
readonly name: "Cursor";
|
|
15
|
-
readonly format: "json";
|
|
16
|
-
}, {
|
|
17
|
-
readonly id: "windsurf";
|
|
18
|
-
readonly name: "Windsurf";
|
|
19
|
-
readonly format: "json";
|
|
20
|
-
}, {
|
|
21
|
-
readonly id: "opencode";
|
|
22
|
-
readonly name: "OpenCode";
|
|
23
|
-
readonly format: "json";
|
|
24
|
-
}, {
|
|
25
|
-
readonly id: "codex-cli";
|
|
26
|
-
readonly name: "Codex CLI";
|
|
27
|
-
readonly format: "toml";
|
|
28
|
-
}, {
|
|
29
|
-
readonly id: "antigravity";
|
|
30
|
-
readonly name: "Antigravity";
|
|
31
|
-
readonly format: "json";
|
|
32
|
-
}, {
|
|
33
|
-
readonly id: "gemini-cli";
|
|
34
|
-
readonly name: "Gemini CLI";
|
|
35
|
-
readonly format: "json";
|
|
36
|
-
}];
|
|
37
|
-
export type WarmMcpClientId = (typeof WARM_MCP_CLIENTS)[number]['id'];
|
|
38
|
-
export declare const WARM_MCP_PROJECT_CONFIGS: readonly [".mcp.json", ".cursor/mcp.json", ".vscode/mcp.json"];
|
|
39
|
-
export declare const WARM_MCP_SERVER_COMMANDS: {
|
|
40
|
-
readonly automation: "npx -y @warmio/mcp@latest mcp --mode automation";
|
|
41
|
-
readonly context: "npx -y @warmio/mcp@latest mcp --mode context";
|
|
42
|
-
};
|
|
43
|
-
export declare const WARM_MCP_MANIFEST: {
|
|
44
|
-
readonly clients: readonly [{
|
|
45
|
-
readonly id: "claude-code";
|
|
46
|
-
readonly name: "Claude Code";
|
|
47
|
-
readonly format: "json";
|
|
48
|
-
}, {
|
|
49
|
-
readonly id: "claude-desktop";
|
|
50
|
-
readonly name: "Claude Desktop";
|
|
51
|
-
readonly format: "json";
|
|
52
|
-
}, {
|
|
53
|
-
readonly id: "cursor";
|
|
54
|
-
readonly name: "Cursor";
|
|
55
|
-
readonly format: "json";
|
|
56
|
-
}, {
|
|
57
|
-
readonly id: "windsurf";
|
|
58
|
-
readonly name: "Windsurf";
|
|
59
|
-
readonly format: "json";
|
|
60
|
-
}, {
|
|
61
|
-
readonly id: "opencode";
|
|
62
|
-
readonly name: "OpenCode";
|
|
63
|
-
readonly format: "json";
|
|
64
|
-
}, {
|
|
65
|
-
readonly id: "codex-cli";
|
|
66
|
-
readonly name: "Codex CLI";
|
|
67
|
-
readonly format: "toml";
|
|
68
|
-
}, {
|
|
69
|
-
readonly id: "antigravity";
|
|
70
|
-
readonly name: "Antigravity";
|
|
71
|
-
readonly format: "json";
|
|
72
|
-
}, {
|
|
73
|
-
readonly id: "gemini-cli";
|
|
74
|
-
readonly name: "Gemini CLI";
|
|
75
|
-
readonly format: "json";
|
|
76
|
-
}];
|
|
77
|
-
readonly installCommand: "npx -y @warmio/mcp@latest";
|
|
78
|
-
readonly packageName: "@warmio/mcp";
|
|
79
|
-
readonly packageSpec: "@warmio/mcp@latest";
|
|
80
|
-
readonly projectConfigs: readonly [".mcp.json", ".cursor/mcp.json", ".vscode/mcp.json"];
|
|
81
|
-
readonly serverCommands: {
|
|
82
|
-
readonly automation: "npx -y @warmio/mcp@latest mcp --mode automation";
|
|
83
|
-
readonly context: "npx -y @warmio/mcp@latest mcp --mode context";
|
|
84
|
-
};
|
|
85
|
-
};
|
package/dist/manifest.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
export const WARM_MCP_PACKAGE_NAME = '@warmio/mcp';
|
|
2
|
-
export const WARM_MCP_PACKAGE_SPEC = `${WARM_MCP_PACKAGE_NAME}@latest`;
|
|
3
|
-
export const WARM_MCP_INSTALL_COMMAND = `npx -y ${WARM_MCP_PACKAGE_SPEC}`;
|
|
4
|
-
export const WARM_MCP_CLIENTS = [
|
|
5
|
-
{ id: 'claude-code', name: 'Claude Code', format: 'json' },
|
|
6
|
-
{ id: 'claude-desktop', name: 'Claude Desktop', format: 'json' },
|
|
7
|
-
{ id: 'cursor', name: 'Cursor', format: 'json' },
|
|
8
|
-
{ id: 'windsurf', name: 'Windsurf', format: 'json' },
|
|
9
|
-
{ id: 'opencode', name: 'OpenCode', format: 'json' },
|
|
10
|
-
{ id: 'codex-cli', name: 'Codex CLI', format: 'toml' },
|
|
11
|
-
{ id: 'antigravity', name: 'Antigravity', format: 'json' },
|
|
12
|
-
{ id: 'gemini-cli', name: 'Gemini CLI', format: 'json' },
|
|
13
|
-
];
|
|
14
|
-
export const WARM_MCP_PROJECT_CONFIGS = [
|
|
15
|
-
'.mcp.json',
|
|
16
|
-
'.cursor/mcp.json',
|
|
17
|
-
'.vscode/mcp.json',
|
|
18
|
-
];
|
|
19
|
-
export const WARM_MCP_SERVER_COMMANDS = {
|
|
20
|
-
automation: `${WARM_MCP_INSTALL_COMMAND} mcp --mode automation`,
|
|
21
|
-
context: `${WARM_MCP_INSTALL_COMMAND} mcp --mode context`,
|
|
22
|
-
};
|
|
23
|
-
export const WARM_MCP_MANIFEST = {
|
|
24
|
-
clients: WARM_MCP_CLIENTS,
|
|
25
|
-
installCommand: WARM_MCP_INSTALL_COMMAND,
|
|
26
|
-
packageName: WARM_MCP_PACKAGE_NAME,
|
|
27
|
-
packageSpec: WARM_MCP_PACKAGE_SPEC,
|
|
28
|
-
projectConfigs: WARM_MCP_PROJECT_CONFIGS,
|
|
29
|
-
serverCommands: WARM_MCP_SERVER_COMMANDS,
|
|
30
|
-
};
|