@alfe.ai/integrations 0.5.3 → 0.6.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/dist/index.d.ts +15 -0
- package/dist/index.js +90 -0
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -290,6 +290,21 @@ declare class Installer {
|
|
|
290
290
|
*/
|
|
291
291
|
private installLocalDependencies;
|
|
292
292
|
private runNpmInstall;
|
|
293
|
+
/**
|
|
294
|
+
* Pre-fetch every npx-executed MCP server package the manifest declares
|
|
295
|
+
* (e.g. `command: npx, args: ["-y", "@alfe.ai/xero-mcp@0.3.8"]`) into the
|
|
296
|
+
* npx cache, WITHOUT executing the server. The first bundler connect then
|
|
297
|
+
* spawns from a warm cache instead of paying a 30-90s npm download during
|
|
298
|
+
* the MCP handshake — where it used to blow the connect budget and strand
|
|
299
|
+
* the server's tools until a daemon restart.
|
|
300
|
+
*
|
|
301
|
+
* Strictly best-effort: a missing/unparseable manifest, a non-npx command,
|
|
302
|
+
* or a failed download logs a warning and moves on — an install must never
|
|
303
|
+
* fail because a pre-warm did.
|
|
304
|
+
*/
|
|
305
|
+
prewarmMcpNpxPackages(installPath: string): Promise<void>;
|
|
306
|
+
/** Populate the npx cache for a package spec via a no-op command. */
|
|
307
|
+
private runNpx;
|
|
293
308
|
/**
|
|
294
309
|
* List all locally installed integrations.
|
|
295
310
|
*/
|
package/dist/index.js
CHANGED
|
@@ -157,6 +157,7 @@ const log$6 = createLogger("Installer");
|
|
|
157
157
|
const INTEGRATIONS_DIR = join(homedir(), ".alfe", "integrations");
|
|
158
158
|
const GIT_TIMEOUT_MS = 6e4;
|
|
159
159
|
const NPM_TIMEOUT_MS = 6e4;
|
|
160
|
+
const NPX_PREWARM_TIMEOUT_MS = 12e4;
|
|
160
161
|
/**
|
|
161
162
|
* Dot-prefix for the transient staging directories used by the diff-based
|
|
162
163
|
* in-place upgrade (`stage()` → `commitStaged()`). Dot-prefixed so `list()`
|
|
@@ -214,6 +215,7 @@ var Installer = class {
|
|
|
214
215
|
else await this.cloneDirect(resolved, installPath);
|
|
215
216
|
await this.ensureSharedPackages();
|
|
216
217
|
await this.installLocalDependencies(installPath);
|
|
218
|
+
await this.prewarmMcpNpxPackages(installPath);
|
|
217
219
|
return installPath;
|
|
218
220
|
}
|
|
219
221
|
/**
|
|
@@ -304,6 +306,7 @@ var Installer = class {
|
|
|
304
306
|
else await this.cloneDirect(resolved, stagingPath);
|
|
305
307
|
await this.ensureSharedPackages();
|
|
306
308
|
await this.installLocalDependencies(stagingPath);
|
|
309
|
+
await this.prewarmMcpNpxPackages(stagingPath);
|
|
307
310
|
return stagingPath;
|
|
308
311
|
} catch (err) {
|
|
309
312
|
if (existsSync(stagingPath)) rmSync(stagingPath, {
|
|
@@ -426,6 +429,63 @@ var Installer = class {
|
|
|
426
429
|
}
|
|
427
430
|
}
|
|
428
431
|
/**
|
|
432
|
+
* Pre-fetch every npx-executed MCP server package the manifest declares
|
|
433
|
+
* (e.g. `command: npx, args: ["-y", "@alfe.ai/xero-mcp@0.3.8"]`) into the
|
|
434
|
+
* npx cache, WITHOUT executing the server. The first bundler connect then
|
|
435
|
+
* spawns from a warm cache instead of paying a 30-90s npm download during
|
|
436
|
+
* the MCP handshake — where it used to blow the connect budget and strand
|
|
437
|
+
* the server's tools until a daemon restart.
|
|
438
|
+
*
|
|
439
|
+
* Strictly best-effort: a missing/unparseable manifest, a non-npx command,
|
|
440
|
+
* or a failed download logs a warning and moves on — an install must never
|
|
441
|
+
* fail because a pre-warm did.
|
|
442
|
+
*/
|
|
443
|
+
async prewarmMcpNpxPackages(installPath) {
|
|
444
|
+
let servers;
|
|
445
|
+
try {
|
|
446
|
+
const manifestPath = join(installPath, "alfe-integration.yaml");
|
|
447
|
+
if (!existsSync(manifestPath)) return;
|
|
448
|
+
servers = parseManifestFile(manifestPath).mcp_servers;
|
|
449
|
+
} catch (err) {
|
|
450
|
+
log$6.warn({
|
|
451
|
+
path: installPath,
|
|
452
|
+
err: err instanceof Error ? err.message : String(err)
|
|
453
|
+
}, "Skipping MCP npx pre-warm — manifest unreadable");
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
for (const server of servers) {
|
|
457
|
+
if (server.command !== "npx") continue;
|
|
458
|
+
const spec = (server.args ?? []).find((a) => !a.startsWith("-"));
|
|
459
|
+
if (!spec) continue;
|
|
460
|
+
try {
|
|
461
|
+
log$6.info({
|
|
462
|
+
server: server.id,
|
|
463
|
+
spec
|
|
464
|
+
}, "Pre-warming npx cache for MCP server package");
|
|
465
|
+
await this.runNpx(spec);
|
|
466
|
+
} catch (err) {
|
|
467
|
+
log$6.warn({
|
|
468
|
+
server: server.id,
|
|
469
|
+
spec,
|
|
470
|
+
err: err instanceof Error ? err.message : String(err)
|
|
471
|
+
}, "MCP npx pre-warm failed — first connect will pay the cold download");
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
/** Populate the npx cache for a package spec via a no-op command. */
|
|
476
|
+
async runNpx(spec) {
|
|
477
|
+
await execFileAsync$2("npx", [
|
|
478
|
+
"-y",
|
|
479
|
+
"-p",
|
|
480
|
+
spec,
|
|
481
|
+
"-c",
|
|
482
|
+
"exit 0"
|
|
483
|
+
], {
|
|
484
|
+
timeout: NPX_PREWARM_TIMEOUT_MS,
|
|
485
|
+
maxBuffer: 16 * 1024 * 1024
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
429
489
|
* List all locally installed integrations.
|
|
430
490
|
*/
|
|
431
491
|
list() {
|
|
@@ -4235,6 +4295,17 @@ const log = createLogger("McpApplier");
|
|
|
4235
4295
|
const CONFIG_TEMPLATE_RE = /\{\{config\.([a-zA-Z0-9_]+)\}\}/g;
|
|
4236
4296
|
const CREDENTIALS_TEMPLATE_RE = /\{\{credentials\.([a-z0-9-]+)\.([a-zA-Z0-9_]+)\}\}/g;
|
|
4237
4297
|
const ALFE_TEMPLATE_RE = /\{\{alfe\.([a-zA-Z0-9_]+)\}\}/g;
|
|
4298
|
+
/**
|
|
4299
|
+
* Budget for the activation-time warm of each applied server. Matches the
|
|
4300
|
+
* bundler's default `connectTimeoutMs` so the warm never reports "failed"
|
|
4301
|
+
* for a connect that would still succeed within its own bound. Generous
|
|
4302
|
+
* enough to cover a cold `npx` npm download when the installer's pre-warm
|
|
4303
|
+
* failed; typically the pre-warm makes this a <5s cache hit. The warm is
|
|
4304
|
+
* fire-and-forget (see applyForIntegration), so this budget never extends
|
|
4305
|
+
* the reconcile pass — a warm that fails anyway is picked up by the
|
|
4306
|
+
* bundler's `retryNeverConnected` sweep within a minute.
|
|
4307
|
+
*/
|
|
4308
|
+
const MCP_ACTIVATION_WARM_TIMEOUT_MS = 12e4;
|
|
4238
4309
|
var McpApplier = class {
|
|
4239
4310
|
manager;
|
|
4240
4311
|
credentials;
|
|
@@ -4275,6 +4346,25 @@ var McpApplier = class {
|
|
|
4275
4346
|
});
|
|
4276
4347
|
applied.push(id);
|
|
4277
4348
|
}
|
|
4349
|
+
for (const id of applied) this.manager.warmServer(id, MCP_ACTIVATION_WARM_TIMEOUT_MS).then((status) => {
|
|
4350
|
+
if (!status) return;
|
|
4351
|
+
if (status.connected) log.info({
|
|
4352
|
+
integrationId,
|
|
4353
|
+
server: id,
|
|
4354
|
+
toolCount: status.toolCount
|
|
4355
|
+
}, "MCP server warmed on activation");
|
|
4356
|
+
else log.warn({
|
|
4357
|
+
integrationId,
|
|
4358
|
+
server: id,
|
|
4359
|
+
lastError: status.lastError
|
|
4360
|
+
}, "MCP server failed activation warm — retry sweep will re-attempt");
|
|
4361
|
+
}).catch((err) => {
|
|
4362
|
+
log.warn({
|
|
4363
|
+
integrationId,
|
|
4364
|
+
server: id,
|
|
4365
|
+
err: errMsg(err)
|
|
4366
|
+
}, "MCP activation warm threw — retry sweep will re-attempt");
|
|
4367
|
+
});
|
|
4278
4368
|
return applied;
|
|
4279
4369
|
}
|
|
4280
4370
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alfe.ai/integrations",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Integration lifecycle management for Alfe — registry, resolution, installation, and state",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@auriclabs/logger": "^0.1.1",
|
|
16
16
|
"yaml": ">=2.8.3",
|
|
17
|
-
"@alfe.ai/integration-manifest": "^0.3.
|
|
18
|
-
"@alfe.ai/mcp-bundler": "^0.
|
|
17
|
+
"@alfe.ai/integration-manifest": "^0.3.4",
|
|
18
|
+
"@alfe.ai/mcp-bundler": "^0.4.0"
|
|
19
19
|
},
|
|
20
20
|
"files": [
|
|
21
21
|
"dist"
|