@warmio/mcp 7.0.1 → 9.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 +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 +78 -74
- package/dist/schemas.d.ts +2 -1289
- package/dist/schemas.js +2 -248
- package/dist/server.d.ts +2 -1
- package/dist/server.js +43 -55
- package/dist/types.d.ts +1 -1
- package/dist/warm-api-client.d.ts +20 -183
- package/dist/warm-api-client.js +10 -5
- package/package.json +10 -2
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,6 +2,7 @@ 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
8
|
const HOME = homedir();
|
|
@@ -9,56 +10,50 @@ const CWD = process.cwd();
|
|
|
9
10
|
function isRecord(value) {
|
|
10
11
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
11
12
|
}
|
|
12
|
-
function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
{
|
|
36
|
-
name: 'OpenCode',
|
|
37
|
-
configPath: join(HOME, '.config', 'opencode', 'opencode.json'),
|
|
38
|
-
format: 'json',
|
|
39
|
-
},
|
|
40
|
-
{ name: 'Codex CLI', configPath: join(HOME, '.codex', 'config.toml'), format: 'toml' },
|
|
41
|
-
{
|
|
42
|
-
name: 'Antigravity',
|
|
43
|
-
configPath: join(HOME, '.gemini', 'antigravity', 'mcp_config.json'),
|
|
44
|
-
format: 'json',
|
|
45
|
-
},
|
|
46
|
-
{ name: 'Gemini CLI', configPath: join(HOME, '.gemini', 'settings.json'), format: 'json' },
|
|
47
|
-
];
|
|
48
|
-
const PROJECT_CONFIGS = ['.mcp.json', '.cursor/mcp.json', '.vscode/mcp.json'];
|
|
49
|
-
const MCP_PACKAGE_SPEC = '@warmio/mcp@latest';
|
|
13
|
+
function resolveClientConfigPath(configPath) {
|
|
14
|
+
const root = configPath.root === 'appData' ? process.env.APPDATA || join(HOME, 'AppData', 'Roaming') : HOME;
|
|
15
|
+
return join(root, ...configPath.segments);
|
|
16
|
+
}
|
|
17
|
+
function getClientConfigPath(clientId) {
|
|
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);
|
|
29
|
+
}
|
|
30
|
+
const GLOBAL_CLIENTS = WARM_MCP_CLIENTS.map((client) => ({
|
|
31
|
+
name: client.name,
|
|
32
|
+
format: client.format,
|
|
33
|
+
configPath: getClientConfigPath(client.id),
|
|
34
|
+
alwaysInclude: 'alwaysInclude' in client && client.alwaysInclude,
|
|
35
|
+
}));
|
|
50
36
|
function getServerName(mode) {
|
|
51
|
-
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'];
|
|
52
44
|
}
|
|
53
45
|
function getMcpConfig(mode) {
|
|
54
|
-
const
|
|
46
|
+
const serverConfig = WARM_MCP_SERVER_CONFIGS[mode];
|
|
55
47
|
return platform() === 'win32'
|
|
56
|
-
? { command: 'cmd', args: ['/c',
|
|
57
|
-
: { 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(', ')}]`;
|
|
58
53
|
}
|
|
59
54
|
function detectProjectClients() {
|
|
60
55
|
const found = [];
|
|
61
|
-
for (const name of
|
|
56
|
+
for (const name of WARM_MCP_PROJECT_CONFIGS) {
|
|
62
57
|
const configPath = resolve(CWD, name);
|
|
63
58
|
if (existsSync(configPath)) {
|
|
64
59
|
found.push({
|
|
@@ -97,18 +92,21 @@ function isSupportedWarmInvocation(command, args, mode) {
|
|
|
97
92
|
}
|
|
98
93
|
function getJsonEnvStatus(server, mode) {
|
|
99
94
|
const env = isRecord(server?.env) ? server.env : undefined;
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
: 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;
|
|
106
100
|
return {
|
|
107
101
|
apiKeyFileOverride: typeof env?.[apiKeyFileName] === 'string' && env[apiKeyFileName].trim().length > 0
|
|
108
102
|
? env[apiKeyFileName].trim()
|
|
109
103
|
: null,
|
|
110
|
-
hasIrrelevantCredential: [
|
|
111
|
-
|
|
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),
|
|
112
110
|
inlineApiKey,
|
|
113
111
|
};
|
|
114
112
|
}
|
|
@@ -196,19 +194,23 @@ function getTomlEnvStatus(content, mode) {
|
|
|
196
194
|
inlineApiKey: null,
|
|
197
195
|
};
|
|
198
196
|
}
|
|
199
|
-
const envLines = envBlock
|
|
200
|
-
|
|
201
|
-
|
|
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;
|
|
202
204
|
const inlineApiKey = getTomlStringValue(envBlock, apiKeyName)?.trim() || null;
|
|
203
|
-
const apiKeyFile = getTomlStringValue(envBlock,
|
|
205
|
+
const apiKeyFile = getTomlStringValue(envBlock, credential.apiKeyFileEnv);
|
|
204
206
|
return {
|
|
205
207
|
apiKeyFileOverride: typeof apiKeyFile === 'string' && apiKeyFile.trim() ? apiKeyFile.trim() : null,
|
|
206
208
|
envLines,
|
|
207
209
|
hasIrrelevantCredential: [
|
|
208
210
|
'WARM_API_KEY',
|
|
209
211
|
'WARM_API_KEY_FILE',
|
|
210
|
-
|
|
211
|
-
|
|
212
|
+
oppositeCredential.apiKeyEnv,
|
|
213
|
+
oppositeCredential.apiKeyFileEnv,
|
|
212
214
|
].some((name) => Boolean(getTomlStringValue(envBlock, name)?.trim())),
|
|
213
215
|
inlineApiKey,
|
|
214
216
|
};
|
|
@@ -268,14 +270,15 @@ function configureJson(client, mode, preserveSelectedKeyFile) {
|
|
|
268
270
|
const preserveProjectCommand = client.isProjectLevel &&
|
|
269
271
|
!!existingCommand &&
|
|
270
272
|
isSupportedWarmInvocation(existingCommand, existingArgs, mode);
|
|
271
|
-
const
|
|
272
|
-
const
|
|
273
|
+
const credential = getCredential(mode);
|
|
274
|
+
const oppositeCredential = getOppositeCredential(mode);
|
|
275
|
+
const selectedKeyFile = credential.apiKeyFileEnv;
|
|
273
276
|
for (const name of [
|
|
274
277
|
'WARM_API_KEY',
|
|
275
278
|
'WARM_API_KEY_FILE',
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
+
credential.apiKeyEnv,
|
|
280
|
+
oppositeCredential.apiKeyEnv,
|
|
281
|
+
oppositeCredential.apiKeyFileEnv,
|
|
279
282
|
]) {
|
|
280
283
|
delete nextEnv[name];
|
|
281
284
|
}
|
|
@@ -309,19 +312,21 @@ function configureToml(client, mode, preserveSelectedKeyFile) {
|
|
|
309
312
|
}
|
|
310
313
|
}
|
|
311
314
|
const serverName = getServerName(mode);
|
|
312
|
-
const
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
|
|
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;
|
|
316
320
|
const warmBlock = `[mcp_servers.${serverName}]${lineEnding}command = "${tomlCommand}"${lineEnding}args = ${tomlArgs}${lineEnding}`;
|
|
317
|
-
const
|
|
318
|
-
const
|
|
321
|
+
const credential = getCredential(mode);
|
|
322
|
+
const oppositeCredential = getOppositeCredential(mode);
|
|
323
|
+
const selectedKeyFile = credential.apiKeyFileEnv;
|
|
319
324
|
const removedEnvNames = new Set([
|
|
320
325
|
'WARM_API_KEY',
|
|
321
326
|
'WARM_API_KEY_FILE',
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
327
|
+
credential.apiKeyEnv,
|
|
328
|
+
oppositeCredential.apiKeyEnv,
|
|
329
|
+
oppositeCredential.apiKeyFileEnv,
|
|
325
330
|
...(!preserveSelectedKeyFile ? [selectedKeyFile] : []),
|
|
326
331
|
]);
|
|
327
332
|
const preservedEnvLines = getTomlEnvStatus(content, mode).envLines.filter((line) => {
|
|
@@ -452,8 +457,7 @@ export async function install(options = {}) {
|
|
|
452
457
|
}
|
|
453
458
|
const apiKeyPath = getWarmApiKeyPath(mode);
|
|
454
459
|
const storedApiKey = readConfigFile(apiKeyPath);
|
|
455
|
-
const migratedApiKey = needsSetup.find((status) => typeof status.inlineApiKey === 'string' && status.inlineApiKey.length > 0)
|
|
456
|
-
?.inlineApiKey ?? null;
|
|
460
|
+
const migratedApiKey = needsSetup.find((status) => typeof status.inlineApiKey === 'string' && status.inlineApiKey.length > 0)?.inlineApiKey ?? null;
|
|
457
461
|
const usableApiKeyFileOverride = needsSetup.find((status) => status.apiKeyFileOverride && !status.inlineApiKey && status.credentialApiKey);
|
|
458
462
|
const targetMissingCredential = needsSetup.some((status) => !status.credentialApiKey);
|
|
459
463
|
let apiKeyToStore = null;
|
|
@@ -472,7 +476,7 @@ export async function install(options = {}) {
|
|
|
472
476
|
shouldStoreApiKey = true;
|
|
473
477
|
}
|
|
474
478
|
else if (usableApiKeyFileOverride && !targetMissingCredential) {
|
|
475
|
-
console.log(` Reusing existing ${mode
|
|
479
|
+
console.log(` Reusing existing ${getCredential(mode).apiKeyFileEnv} override.`);
|
|
476
480
|
console.log('');
|
|
477
481
|
}
|
|
478
482
|
else {
|