@alfe.ai/integrations 0.0.32 → 0.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/dist/index.d.ts +76 -196
- package/dist/index.js +179 -139
- package/package.json +3 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
import { IntegrationManifest, IntegrationStateEntry, IntegrationStatus, IntegrationsStateFile, McpServerDeclaration, PluginInstall, SkillInstall } from "@alfe.ai/integration-manifest";
|
|
2
|
+
import { Manager } from "@alfe.ai/mcp-bundler";
|
|
3
|
+
|
|
1
4
|
//#region src/registry.d.ts
|
|
5
|
+
|
|
2
6
|
/**
|
|
3
7
|
* Registry — fetches the integration registry index from the integrations service.
|
|
4
8
|
*
|
|
@@ -216,187 +220,16 @@ declare class Installer {
|
|
|
216
220
|
isInstalled(name: string): boolean;
|
|
217
221
|
}
|
|
218
222
|
//#endregion
|
|
219
|
-
//#region ../integration-manifest/dist/index.d.ts
|
|
220
|
-
//#region src/types.d.ts
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* TypeScript types for the Alfe integration manifest (`alfe-integration.yaml`).
|
|
224
|
-
*
|
|
225
|
-
* These are the canonical types used by all packages that interact with
|
|
226
|
-
* integration manifests -- registry, lifecycle manager, CLI, etc.
|
|
227
|
-
*/
|
|
228
|
-
type ConfigFieldType = 'secret' | 'string' | 'number' | 'boolean' | 'enum' | 'select' | 'multi_select' | 'oauth_connect';
|
|
229
|
-
interface SelectOption {
|
|
230
|
-
value: string;
|
|
231
|
-
label: string;
|
|
232
|
-
}
|
|
233
|
-
interface ConfigSchemaField {
|
|
234
|
-
key: string;
|
|
235
|
-
type: ConfigFieldType;
|
|
236
|
-
label: string;
|
|
237
|
-
description?: string;
|
|
238
|
-
required: boolean;
|
|
239
|
-
default?: string | number | boolean;
|
|
240
|
-
/** Who can mutate this field at runtime. Default: 'admin' */
|
|
241
|
-
editable: 'admin' | 'agent';
|
|
242
|
-
/** Only used when type === 'enum' */
|
|
243
|
-
options?: string[];
|
|
244
|
-
/** Structured options for select/multi_select fields */
|
|
245
|
-
select_options?: SelectOption[];
|
|
246
|
-
/** OAuth provider identifier for oauth_connect fields */
|
|
247
|
-
oauth_provider?: string;
|
|
248
|
-
/** Force specific scope groups for OAuth (e.g. ["chat"]) — hides other scopes in the dashboard */
|
|
249
|
-
oauth_scopes?: string[];
|
|
250
|
-
/** Integration ID to patch on OAuth callback (when using a shared OAuth provider) */
|
|
251
|
-
oauth_integration_id?: string;
|
|
252
|
-
/** If true, this field is not shown in the dashboard UI (install wizard or configure modal) */
|
|
253
|
-
hidden?: boolean;
|
|
254
|
-
/** Only show this field if another field has a truthy value (string) or matches a specific value (object) */
|
|
255
|
-
depends_on_field?: string | {
|
|
256
|
-
key: string;
|
|
257
|
-
value: string | number | boolean;
|
|
258
|
-
};
|
|
259
|
-
/** Only show this field if a specific integration is installed */
|
|
260
|
-
depends_on_integration?: string;
|
|
261
|
-
}
|
|
262
|
-
interface McpServerDeclaration {
|
|
263
|
-
/** Unique identifier within this integration */
|
|
264
|
-
id: string;
|
|
265
|
-
/** Command to spawn (e.g., 'npx', 'node', 'xero-mcp-proxy') */
|
|
266
|
-
command: string;
|
|
267
|
-
/** Arguments for the command */
|
|
268
|
-
args?: string[];
|
|
269
|
-
/** Environment variables — supports {{config.KEY}} interpolation from config + secrets */
|
|
270
|
-
env?: Record<string, string>;
|
|
271
|
-
/** Working directory (optional) */
|
|
272
|
-
cwd?: string;
|
|
273
|
-
/** If true, applier skips auto-application — a lifecycle hook manages this MCP server instead */
|
|
274
|
-
hook_managed?: boolean;
|
|
275
|
-
}
|
|
276
|
-
interface CommandDeclaration {
|
|
277
|
-
/** Dot-namespaced command name (e.g. "support.diagnostic") */
|
|
278
|
-
name: string;
|
|
279
|
-
/** Relative path to handler file within integration directory */
|
|
280
|
-
handler: string;
|
|
281
|
-
/** Exported function name (default: "handle") */
|
|
282
|
-
method?: string;
|
|
283
|
-
/** Timeout in milliseconds (default: 30000) */
|
|
284
|
-
timeout_ms?: number;
|
|
285
|
-
/** Human-readable description */
|
|
286
|
-
description?: string;
|
|
287
|
-
}
|
|
288
|
-
interface SkillInstall {
|
|
289
|
-
/** Relative path within the integration repo to the skill directory */
|
|
290
|
-
path?: string;
|
|
291
|
-
/** ClawHub skill slug to install from the registry */
|
|
292
|
-
clawhub?: string;
|
|
293
|
-
}
|
|
294
|
-
interface PluginInstall {
|
|
295
|
-
/** npm package name to install */
|
|
296
|
-
package: string;
|
|
297
|
-
}
|
|
298
|
-
type AgentRuntime = 'openclaw' | 'nanoclaw' | (string & {});
|
|
299
|
-
interface RuntimeInstall {
|
|
300
|
-
plugins?: PluginInstall[];
|
|
301
|
-
skills?: SkillInstall[];
|
|
302
|
-
/** Deep-merged into the runtime's agent config on activation */
|
|
303
|
-
config?: Record<string, unknown>;
|
|
304
|
-
}
|
|
305
|
-
interface InstallTargets {
|
|
306
|
-
/** Universal skills — applied to all runtimes */
|
|
307
|
-
skills?: SkillInstall[];
|
|
308
|
-
/** Universal plugins — applied to all runtimes */
|
|
309
|
-
plugins?: PluginInstall[];
|
|
310
|
-
/** Per-runtime installs */
|
|
311
|
-
runtimes?: Record<AgentRuntime, RuntimeInstall>;
|
|
312
|
-
}
|
|
313
|
-
interface IntegrationHooks {
|
|
314
|
-
pre_install?: string;
|
|
315
|
-
post_install?: string;
|
|
316
|
-
post_activate?: string;
|
|
317
|
-
pre_uninstall?: string;
|
|
318
|
-
post_uninstall?: string;
|
|
319
|
-
health_check?: string;
|
|
320
|
-
}
|
|
321
|
-
interface IntegrationPricingPlan {
|
|
322
|
-
name: string;
|
|
323
|
-
price: number;
|
|
324
|
-
currency?: string;
|
|
325
|
-
interval?: 'month' | 'year';
|
|
326
|
-
}
|
|
327
|
-
interface IntegrationPricing {
|
|
328
|
-
type: 'free' | 'paid' | 'usage';
|
|
329
|
-
/** Single price (shorthand for integrations with one plan) */
|
|
330
|
-
price?: number;
|
|
331
|
-
currency?: string;
|
|
332
|
-
interval?: 'month' | 'year';
|
|
333
|
-
/** Description of usage-based pricing (for type: 'usage') */
|
|
334
|
-
description?: string;
|
|
335
|
-
/** Multiple plans/tiers (e.g., starter, growth, scale) */
|
|
336
|
-
plans?: Record<string, IntegrationPricingPlan>;
|
|
337
|
-
}
|
|
338
|
-
interface IntegrationAuthor {
|
|
339
|
-
name: string;
|
|
340
|
-
url?: string;
|
|
341
|
-
}
|
|
342
|
-
interface IntegrationManifest {
|
|
343
|
-
id: string;
|
|
344
|
-
name: string;
|
|
345
|
-
version: string;
|
|
346
|
-
description: string;
|
|
347
|
-
/** Simple author string (legacy) or structured author object */
|
|
348
|
-
author: string | IntegrationAuthor;
|
|
349
|
-
license: string;
|
|
350
|
-
depends_on: string[];
|
|
351
|
-
min_gateway_version: string;
|
|
352
|
-
installs: InstallTargets;
|
|
353
|
-
config_schema: ConfigSchemaField[];
|
|
354
|
-
capabilities: string[];
|
|
355
|
-
hooks: IntegrationHooks;
|
|
356
|
-
commands: CommandDeclaration[];
|
|
357
|
-
/** MCP servers to configure in the agent runtime */
|
|
358
|
-
mcp_servers: McpServerDeclaration[];
|
|
359
|
-
/** Git repository URL (HTTPS) — not present in YAML, injected by the publish API */
|
|
360
|
-
repository?: string;
|
|
361
|
-
/**
|
|
362
|
-
* Agent runtimes this integration supports.
|
|
363
|
-
* If omitted or empty, the integration is considered universal (all runtimes).
|
|
364
|
-
* Example: ['openclaw'] means this integration only works with OpenClaw.
|
|
365
|
-
*/
|
|
366
|
-
supported_agents?: AgentRuntime[];
|
|
367
|
-
/**
|
|
368
|
-
* Scopes where this integration can be installed.
|
|
369
|
-
* Default: ['agent'] (per-agent only).
|
|
370
|
-
* 'org' means it can be installed at the org level and cascades to all agents.
|
|
371
|
-
*/
|
|
372
|
-
supported_scopes?: ('agent' | 'org')[];
|
|
373
|
-
/** Marketplace metadata */
|
|
374
|
-
publisherId?: string;
|
|
375
|
-
icon?: string;
|
|
376
|
-
pricing?: IntegrationPricing;
|
|
377
|
-
preview_images?: string[];
|
|
378
|
-
/** Long-form features list for the detail view */
|
|
379
|
-
features?: string[];
|
|
380
|
-
}
|
|
381
|
-
type IntegrationStatus = 'installing' | 'installed' | 'configured' | 'active' | 'error';
|
|
382
|
-
interface IntegrationStateEntry {
|
|
383
|
-
status: IntegrationStatus;
|
|
384
|
-
version: string;
|
|
385
|
-
installedAt: string;
|
|
386
|
-
config: Record<string, unknown>;
|
|
387
|
-
error?: string;
|
|
388
|
-
/** Number of consecutive auto-reinstall attempts. Reset on success or explicit reinstall. */
|
|
389
|
-
reinstallAttempts?: number;
|
|
390
|
-
}
|
|
391
|
-
interface IntegrationsStateFile {
|
|
392
|
-
version: number;
|
|
393
|
-
integrations: Record<string, IntegrationStateEntry>;
|
|
394
|
-
}
|
|
395
|
-
//#endregion
|
|
396
223
|
//#region src/runtime-applier.d.ts
|
|
397
224
|
/**
|
|
398
225
|
* RuntimeApplier — interface for applying/removing plugins and skills
|
|
399
226
|
* to a specific agent runtime (OpenClaw, NanoClaw, etc.).
|
|
227
|
+
*
|
|
228
|
+
* Note: MCP server registration is NOT here — it moved to the
|
|
229
|
+
* runtime-agnostic `McpApplier` (`appliers/mcp-applier.ts`) which
|
|
230
|
+
* writes via the bundler manager + openclaw.json mirror. Removing
|
|
231
|
+
* the per-runtime `applyMcpServers` / `removeMcpServers` was part of
|
|
232
|
+
* the bundler-as-primitive cleanup (see `project_mcp_tool_lift_workstream`).
|
|
400
233
|
*/
|
|
401
234
|
interface RuntimeApplier {
|
|
402
235
|
/** The runtime identifier (e.g. 'openclaw', 'nanoclaw') */
|
|
@@ -419,14 +252,65 @@ interface RuntimeApplier {
|
|
|
419
252
|
applyConfig(integrationId: string, config: Record<string, unknown>): Promise<void>;
|
|
420
253
|
/** Remove config previously applied by an integration */
|
|
421
254
|
removeConfig(integrationId: string): Promise<void>;
|
|
422
|
-
/** Configure MCP servers in this runtime */
|
|
423
|
-
applyMcpServers(integrationId: string, servers: McpServerDeclaration[]): Promise<void>;
|
|
424
|
-
/** Remove MCP servers previously applied by an integration */
|
|
425
|
-
removeMcpServers(integrationId: string): Promise<void>;
|
|
426
255
|
/** Check if this runtime is available (e.g. workspace directory exists) */
|
|
427
256
|
isAvailable(): Promise<boolean>;
|
|
428
257
|
}
|
|
429
258
|
//#endregion
|
|
259
|
+
//#region src/appliers/mcp-applier.d.ts
|
|
260
|
+
/**
|
|
261
|
+
* Minimal contract the applier needs from the agent API client.
|
|
262
|
+
* Wider than a single typed method so callers don't have to depend on
|
|
263
|
+
* the exact `AgentApiClient` shape — particularly handy for tests and
|
|
264
|
+
* for the eventual `getCredentials(provider)` generalisation.
|
|
265
|
+
*/
|
|
266
|
+
interface CredentialsResolver {
|
|
267
|
+
getCredentials: (provider: string) => Promise<Record<string, unknown> | undefined>;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Daemon-level platform context exposed to `{{alfe.<key>}}` env
|
|
271
|
+
* interpolation. Reserved namespace so manifests can reference things
|
|
272
|
+
* like the agent's cloud apiUrl (for OAuth redirect URIs) without
|
|
273
|
+
* colliding with user-supplied `{{config.<key>}}`.
|
|
274
|
+
*/
|
|
275
|
+
interface PlatformContext {
|
|
276
|
+
/** The agent's cloud apiUrl (e.g. https://api.alfe.ai). */
|
|
277
|
+
apiUrl?: string;
|
|
278
|
+
}
|
|
279
|
+
interface McpApplierOptions {
|
|
280
|
+
manager: Manager;
|
|
281
|
+
credentials: CredentialsResolver;
|
|
282
|
+
/** Optional — when omitted, `{{alfe.X}}` references are left as-is. */
|
|
283
|
+
platform?: PlatformContext;
|
|
284
|
+
}
|
|
285
|
+
declare class McpApplier {
|
|
286
|
+
private readonly manager;
|
|
287
|
+
private readonly credentials;
|
|
288
|
+
private readonly platform;
|
|
289
|
+
constructor(opts: McpApplierOptions);
|
|
290
|
+
/**
|
|
291
|
+
* Register every server declared by an integration. Idempotent —
|
|
292
|
+
* re-running for the same integration overwrites entries in place
|
|
293
|
+
* (the manager preserves `addedAt`). Returns the list of ids that
|
|
294
|
+
* actually landed in the store.
|
|
295
|
+
*
|
|
296
|
+
* Awaits `manager.flush()` before returning so the openclaw.json
|
|
297
|
+
* mirror write has fired by the time the caller proceeds. Without
|
|
298
|
+
* this, an integration activation's success ack would race ahead of
|
|
299
|
+
* the runtime's openclaw.json update — the cloud would see
|
|
300
|
+
* `actual.status = active` while OpenClaw still has the stale MCP
|
|
301
|
+
* set in memory, violating the actual-status protocol contract.
|
|
302
|
+
*/
|
|
303
|
+
applyForIntegration(integrationId: string, servers: McpServerDeclaration[], mergedConfig: Record<string, unknown>): Promise<string[]>;
|
|
304
|
+
/**
|
|
305
|
+
* Drop every entry owned by this integration. Like `applyForIntegration`,
|
|
306
|
+
* awaits the mirror flush so the runtime is signalled before the call
|
|
307
|
+
* resolves — the uninstall ack shouldn't outrun openclaw.json being
|
|
308
|
+
* cleaned up.
|
|
309
|
+
*/
|
|
310
|
+
removeForIntegration(integrationId: string): Promise<string[]>;
|
|
311
|
+
private resolveEnv;
|
|
312
|
+
}
|
|
313
|
+
//#endregion
|
|
430
314
|
//#region src/types.d.ts
|
|
431
315
|
/**
|
|
432
316
|
* Integration command parameter types.
|
|
@@ -462,6 +346,15 @@ interface IntegrationManagerOptions {
|
|
|
462
346
|
lockPath?: string;
|
|
463
347
|
/** Fetcher for the integration registry — should be wired to api-client */
|
|
464
348
|
registryFetcher: RegistryFetcher;
|
|
349
|
+
/**
|
|
350
|
+
* Runtime-agnostic MCP server applier. The manager routes
|
|
351
|
+
* `mcp_servers` declarations through it once per integration (the
|
|
352
|
+
* applier writes to the bundler store + openclaw.json mirror).
|
|
353
|
+
* Required — there is no fallback path; integrations with
|
|
354
|
+
* `mcp_servers` will skip MCP registration silently if this is
|
|
355
|
+
* omitted (the warn log makes this visible).
|
|
356
|
+
*/
|
|
357
|
+
mcpApplier?: McpApplier;
|
|
465
358
|
}
|
|
466
359
|
interface ManagerResponse {
|
|
467
360
|
ok: boolean;
|
|
@@ -496,6 +389,7 @@ declare class IntegrationManager {
|
|
|
496
389
|
private installer;
|
|
497
390
|
private runtimeAppliers;
|
|
498
391
|
private lockManager;
|
|
392
|
+
private mcpApplier;
|
|
499
393
|
/** In-memory secret store — NEVER persisted to disk */
|
|
500
394
|
private secrets;
|
|
501
395
|
constructor(options: IntegrationManagerOptions);
|
|
@@ -825,20 +719,6 @@ declare class OpenClawApplier implements RuntimeApplier {
|
|
|
825
719
|
* then removes them via `openclaw config unset`.
|
|
826
720
|
*/
|
|
827
721
|
removeConfig(integrationId: string): Promise<void>;
|
|
828
|
-
/**
|
|
829
|
-
* Configure MCP servers in OpenClaw via `openclaw config set --batch-json`.
|
|
830
|
-
*
|
|
831
|
-
* Each server is written as `mcp.servers.{integrationId}-{serverId}.*`.
|
|
832
|
-
* Applied servers are tracked in the tracking file for clean removal.
|
|
833
|
-
*/
|
|
834
|
-
applyMcpServers(integrationId: string, servers: McpServerDeclaration[]): Promise<void>;
|
|
835
|
-
/**
|
|
836
|
-
* Remove MCP servers previously applied by an integration.
|
|
837
|
-
*
|
|
838
|
-
* Reads the tracking file to find which `mcp.servers.*` keys this integration set,
|
|
839
|
-
* then removes them via `openclaw config unset`.
|
|
840
|
-
*/
|
|
841
|
-
removeMcpServers(integrationId: string): Promise<void>;
|
|
842
722
|
isAvailable(): Promise<boolean>;
|
|
843
723
|
private readTracking;
|
|
844
724
|
private writeTracking;
|
|
@@ -954,4 +834,4 @@ declare class IntegrationManagerAdapter implements IIntegrationManager {
|
|
|
954
834
|
resetReinstallAttempts(integrationId: string): void;
|
|
955
835
|
}
|
|
956
836
|
//#endregion
|
|
957
|
-
export { type HookEnvOptions, type HookResult, type IIntegrationManager, type InstalledInfo, Installer, InstallerError, type IntegrationConfigureParams, type IntegrationHealthParams, type IntegrationInfo, type IntegrationInstallParams, IntegrationManager, IntegrationManagerAdapter, type IntegrationManagerOptions, type IntegrationRemoveParams, LockManager, OpenClawApplier, type OpenClawApplierOptions, Registry, type RegistryEntry, type RegistryFetcher, type RegistryIndex, RegistryResolveError, type ResolvedIntegration, Resolver, type RuntimeApplier, type RuntimeDesiredState, type RuntimeLockFile, type RuntimePluginEntry, type RuntimeSkillEntry, StateManager, buildHookEnv, resolveInstallsForRuntime, runHook, runHookWithContext };
|
|
837
|
+
export { type CredentialsResolver, type HookEnvOptions, type HookResult, type IIntegrationManager, type InstalledInfo, Installer, InstallerError, type IntegrationConfigureParams, type IntegrationHealthParams, type IntegrationInfo, type IntegrationInstallParams, IntegrationManager, IntegrationManagerAdapter, type IntegrationManagerOptions, type IntegrationRemoveParams, LockManager, McpApplier, type McpApplierOptions, OpenClawApplier, type OpenClawApplierOptions, type PlatformContext, Registry, type RegistryEntry, type RegistryFetcher, type RegistryIndex, RegistryResolveError, type ResolvedIntegration, Resolver, type RuntimeApplier, type RuntimeDesiredState, type RuntimeLockFile, type RuntimePluginEntry, type RuntimeSkillEntry, StateManager, buildHookEnv, resolveInstallsForRuntime, runHook, runHookWithContext };
|
package/dist/index.js
CHANGED
|
@@ -130,7 +130,7 @@ var Resolver = class {
|
|
|
130
130
|
* can resolve them via Node's upward module resolution.
|
|
131
131
|
*/
|
|
132
132
|
const execFileAsync$1 = promisify(execFile);
|
|
133
|
-
const log$
|
|
133
|
+
const log$2 = createLogger("Installer");
|
|
134
134
|
const INTEGRATIONS_DIR = join(homedir(), ".alfe", "integrations");
|
|
135
135
|
const GIT_TIMEOUT_MS = 6e4;
|
|
136
136
|
const NPM_TIMEOUT_MS = 6e4;
|
|
@@ -292,7 +292,7 @@ var Installer = class {
|
|
|
292
292
|
dependencies: { ...SHARED_PACKAGES }
|
|
293
293
|
};
|
|
294
294
|
writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + "\n", "utf-8");
|
|
295
|
-
log$
|
|
295
|
+
log$2.info("Installing shared @alfe.ai packages for integration hooks");
|
|
296
296
|
await this.runNpmInstall(this.basePath);
|
|
297
297
|
this.sharedPackagesReady = true;
|
|
298
298
|
}
|
|
@@ -302,7 +302,7 @@ var Installer = class {
|
|
|
302
302
|
*/
|
|
303
303
|
async installLocalDependencies(installPath) {
|
|
304
304
|
if (!existsSync(join(installPath, "package.json"))) return;
|
|
305
|
-
log$
|
|
305
|
+
log$2.info({ path: installPath }, "Installing integration-specific npm dependencies");
|
|
306
306
|
await this.runNpmInstall(installPath);
|
|
307
307
|
}
|
|
308
308
|
async runNpmInstall(cwd) {
|
|
@@ -811,35 +811,30 @@ async function runHookWithContext(integrationPath, hookScript, options) {
|
|
|
811
811
|
* Deep-walk an object and replace `{{config.KEY}}` patterns with values
|
|
812
812
|
* from the per-agent config. Used to compose manifest runtime config
|
|
813
813
|
* with per-agent secrets (e.g., gateway token).
|
|
814
|
+
*
|
|
815
|
+
* Whole-value substitution: when a string is exactly `{{config.KEY}}` with
|
|
816
|
+
* no surrounding text, the raw config value is substituted preserving its
|
|
817
|
+
* type (array, object, boolean, …). For interpolation inside a larger
|
|
818
|
+
* string, only string/number values are substituted; other types fall
|
|
819
|
+
* through to keep the placeholder.
|
|
814
820
|
*/
|
|
821
|
+
const FULL_PLACEHOLDER = /^\{\{config\.([a-zA-Z0-9_]+)\}\}$/;
|
|
815
822
|
function interpolateSelfConfig(obj, config) {
|
|
816
823
|
const result = {};
|
|
817
|
-
for (const [key, value] of Object.entries(obj)) if (typeof value === "string")
|
|
818
|
-
const
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
* Interpolate `{{config.KEY}}` patterns in MCP server env vars using
|
|
827
|
-
* the merged config + secrets dict. Returns new declarations with
|
|
828
|
-
* interpolated env values; skips hook_managed servers.
|
|
829
|
-
*/
|
|
830
|
-
function interpolateMcpServerEnvs(servers, mergedConfig) {
|
|
831
|
-
return servers.filter((s) => !s.hook_managed).map((server) => {
|
|
832
|
-
if (!server.env) return server;
|
|
833
|
-
const interpolatedEnv = {};
|
|
834
|
-
for (const [key, value] of Object.entries(server.env)) interpolatedEnv[key] = value.replace(/\{\{config\.([a-zA-Z0-9_]+)\}\}/g, (_match, configKey) => {
|
|
835
|
-
const val = mergedConfig[configKey];
|
|
824
|
+
for (const [key, value] of Object.entries(obj)) if (typeof value === "string") {
|
|
825
|
+
const wholeMatch = FULL_PLACEHOLDER.exec(value);
|
|
826
|
+
if (wholeMatch) {
|
|
827
|
+
const raw = config[wholeMatch[1]];
|
|
828
|
+
result[key] = raw !== void 0 ? raw : value;
|
|
829
|
+
continue;
|
|
830
|
+
}
|
|
831
|
+
result[key] = value.replace(/\{\{config\.([a-zA-Z0-9_]+)\}\}/g, (_match, configKey) => {
|
|
832
|
+
const val = config[configKey];
|
|
836
833
|
return typeof val === "string" || typeof val === "number" ? String(val) : _match;
|
|
837
834
|
});
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
};
|
|
842
|
-
});
|
|
835
|
+
} else if (value && typeof value === "object" && !Array.isArray(value)) result[key] = interpolateSelfConfig(value, config);
|
|
836
|
+
else result[key] = value;
|
|
837
|
+
return result;
|
|
843
838
|
}
|
|
844
839
|
/**
|
|
845
840
|
* Merge universal installs with runtime-specific installs from the manifest.
|
|
@@ -861,6 +856,7 @@ var IntegrationManager = class {
|
|
|
861
856
|
installer;
|
|
862
857
|
runtimeAppliers;
|
|
863
858
|
lockManager;
|
|
859
|
+
mcpApplier;
|
|
864
860
|
/** In-memory secret store — NEVER persisted to disk */
|
|
865
861
|
secrets = /* @__PURE__ */ new Map();
|
|
866
862
|
constructor(options) {
|
|
@@ -870,6 +866,7 @@ var IntegrationManager = class {
|
|
|
870
866
|
this.installer = new Installer(options.integrationsDir);
|
|
871
867
|
this.runtimeAppliers = options.runtimeAppliers ?? /* @__PURE__ */ new Map();
|
|
872
868
|
this.lockManager = new LockManager(options.lockPath);
|
|
869
|
+
this.mcpApplier = options.mcpApplier;
|
|
873
870
|
}
|
|
874
871
|
/**
|
|
875
872
|
* Install an integration from the registry.
|
|
@@ -1088,22 +1085,20 @@ var IntegrationManager = class {
|
|
|
1088
1085
|
await applier.applyConfig(integrationId, interpolatedConfig);
|
|
1089
1086
|
configApplied = true;
|
|
1090
1087
|
}
|
|
1091
|
-
const mcpServers = manifest.mcp_servers ?? [];
|
|
1092
|
-
if (mcpServers.length > 0) {
|
|
1093
|
-
const secretEntries = this.secrets.get(integrationId);
|
|
1094
|
-
const interpolatedServers = interpolateMcpServerEnvs(mcpServers, {
|
|
1095
|
-
...entry.config,
|
|
1096
|
-
...secretEntries ? Object.fromEntries(secretEntries) : {}
|
|
1097
|
-
});
|
|
1098
|
-
if (interpolatedServers.length > 0) {
|
|
1099
|
-
this.log.info(`Applying ${String(interpolatedServers.length)} MCP server(s) for ${integrationId} to ${runtimeName}`);
|
|
1100
|
-
await applier.applyMcpServers(integrationId, interpolatedServers);
|
|
1101
|
-
configApplied = true;
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
1088
|
const appliedPlugins = plugins.filter((p) => !pluginFailures.includes(p.package));
|
|
1105
1089
|
if (appliedPlugins.length > 0 || skills.length > 0) this.lockManager.addEntries(runtimeName, integrationId, manifest.version, appliedPlugins, skills, installPath);
|
|
1106
1090
|
}
|
|
1091
|
+
const mcpServers = manifest.mcp_servers ?? [];
|
|
1092
|
+
if (mcpServers.length > 0) if (!this.mcpApplier) this.log.warn(`Integration "${integrationId}" declares ${String(mcpServers.length)} mcp_server(s) but no mcpApplier is wired — skipping MCP registration`);
|
|
1093
|
+
else {
|
|
1094
|
+
const secretEntries = this.secrets.get(integrationId);
|
|
1095
|
+
const mergedConfig = {
|
|
1096
|
+
...entry.config,
|
|
1097
|
+
...secretEntries ? Object.fromEntries(secretEntries) : {}
|
|
1098
|
+
};
|
|
1099
|
+
this.log.info(`Applying ${String(mcpServers.length)} MCP server(s) for ${integrationId} via bundler manager`);
|
|
1100
|
+
await this.mcpApplier.applyForIntegration(integrationId, mcpServers, mergedConfig);
|
|
1101
|
+
}
|
|
1107
1102
|
this.state.setStatus(integrationId, "active");
|
|
1108
1103
|
if (manifest.hooks.post_activate) {
|
|
1109
1104
|
this.log.info(`Running post_activate hook: ${manifest.hooks.post_activate}`);
|
|
@@ -1201,13 +1196,12 @@ var IntegrationManager = class {
|
|
|
1201
1196
|
this.log.warn(`Failed to remove config for ${integrationId} from ${runtimeName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1202
1197
|
}
|
|
1203
1198
|
}
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
this.log.info(`Removing MCP servers for ${integrationId} from ${runtimeName}`);
|
|
1199
|
+
if (this.mcpApplier) {
|
|
1200
|
+
this.log.info(`Removing MCP servers for ${integrationId} via bundler manager`);
|
|
1207
1201
|
try {
|
|
1208
|
-
await
|
|
1202
|
+
await this.mcpApplier.removeForIntegration(integrationId);
|
|
1209
1203
|
} catch (err) {
|
|
1210
|
-
this.log.warn(`Failed to remove MCP servers for ${integrationId}
|
|
1204
|
+
this.log.warn(`Failed to remove MCP servers for ${integrationId} via bundler manager: ${err instanceof Error ? err.message : String(err)}`);
|
|
1211
1205
|
}
|
|
1212
1206
|
}
|
|
1213
1207
|
this.state.setStatus(integrationId, "configured");
|
|
@@ -1507,7 +1501,7 @@ var IntegrationManager = class {
|
|
|
1507
1501
|
* is stored in a separate tracking file (config.json) for clean removal.
|
|
1508
1502
|
*/
|
|
1509
1503
|
const execFileAsync = promisify(execFile);
|
|
1510
|
-
const log = createLogger("OpenClawApplier");
|
|
1504
|
+
const log$1 = createLogger("OpenClawApplier");
|
|
1511
1505
|
const DEFAULT_SKILLS_DIR = join(homedir(), ".alfe", "skills");
|
|
1512
1506
|
function flattenConfig(obj, prefix = "") {
|
|
1513
1507
|
const entries = [];
|
|
@@ -1585,11 +1579,11 @@ var OpenClawApplier = class {
|
|
|
1585
1579
|
await this.ensurePluginsAllow(pkg);
|
|
1586
1580
|
this.cleanupUntrackedExtensionInstall(pkg);
|
|
1587
1581
|
if (opts?.force && this.isPluginInstalled(pkg)) {
|
|
1588
|
-
log.info({ pkg }, "Force mode — uninstalling plugin before reinstall");
|
|
1582
|
+
log$1.info({ pkg }, "Force mode — uninstalling plugin before reinstall");
|
|
1589
1583
|
try {
|
|
1590
1584
|
await this.removePlugin(pkg);
|
|
1591
1585
|
} catch (err) {
|
|
1592
|
-
log.warn({
|
|
1586
|
+
log$1.warn({
|
|
1593
1587
|
pkg,
|
|
1594
1588
|
err: err instanceof Error ? err.message : String(err)
|
|
1595
1589
|
}, "Failed to uninstall plugin during force reinstall — proceeding");
|
|
@@ -1610,7 +1604,7 @@ var OpenClawApplier = class {
|
|
|
1610
1604
|
} catch (err) {
|
|
1611
1605
|
const errText = err instanceof Error ? `${err.message}\n${err.stderr ?? ""}` : String(err);
|
|
1612
1606
|
if (useUnsafeFlag && errText.includes("unknown option")) {
|
|
1613
|
-
log.info({ pkg }, "OpenClaw does not support --dangerously-force-unsafe-install, retrying without");
|
|
1607
|
+
log$1.info({ pkg }, "OpenClaw does not support --dangerously-force-unsafe-install, retrying without");
|
|
1614
1608
|
await execFileAsync("openclaw", baseArgs, { timeout: 6e4 });
|
|
1615
1609
|
} else throw err;
|
|
1616
1610
|
}
|
|
@@ -1619,7 +1613,7 @@ var OpenClawApplier = class {
|
|
|
1619
1613
|
setTimeout(r, 500);
|
|
1620
1614
|
});
|
|
1621
1615
|
if (!this.isPluginInstalled(pkg)) throw err;
|
|
1622
|
-
log.warn({
|
|
1616
|
+
log$1.warn({
|
|
1623
1617
|
pkg,
|
|
1624
1618
|
err: err instanceof Error ? err.message : String(err)
|
|
1625
1619
|
}, "openclaw plugins install exited with warnings but plugin is installed");
|
|
@@ -1654,7 +1648,7 @@ var OpenClawApplier = class {
|
|
|
1654
1648
|
JSON.stringify(updated)
|
|
1655
1649
|
], { timeout: 1e4 });
|
|
1656
1650
|
} catch (err) {
|
|
1657
|
-
log.warn({
|
|
1651
|
+
log$1.warn({
|
|
1658
1652
|
err: err instanceof Error ? err.message : String(err),
|
|
1659
1653
|
pkg
|
|
1660
1654
|
}, "Failed to set plugins.allow via openclaw config set");
|
|
@@ -1707,12 +1701,12 @@ var OpenClawApplier = class {
|
|
|
1707
1701
|
recursive: true,
|
|
1708
1702
|
force: true
|
|
1709
1703
|
});
|
|
1710
|
-
log.info({
|
|
1704
|
+
log$1.info({
|
|
1711
1705
|
pkg,
|
|
1712
1706
|
removed: fullPath
|
|
1713
1707
|
}, "Removed untracked extensions/ install — will reinstall via npm path");
|
|
1714
1708
|
} catch (err) {
|
|
1715
|
-
log.warn({
|
|
1709
|
+
log$1.warn({
|
|
1716
1710
|
pkg,
|
|
1717
1711
|
removed: fullPath,
|
|
1718
1712
|
err: err instanceof Error ? err.message : String(err)
|
|
@@ -1735,21 +1729,21 @@ var OpenClawApplier = class {
|
|
|
1735
1729
|
return Promise.resolve();
|
|
1736
1730
|
}
|
|
1737
1731
|
async applyClawHubSkill(slug) {
|
|
1738
|
-
log.info({ slug }, "Installing skill from ClawHub");
|
|
1732
|
+
log$1.info({ slug }, "Installing skill from ClawHub");
|
|
1739
1733
|
try {
|
|
1740
1734
|
await execFileAsync("openclaw", [
|
|
1741
1735
|
"skills",
|
|
1742
1736
|
"install",
|
|
1743
1737
|
slug
|
|
1744
1738
|
], { timeout: 6e4 });
|
|
1745
|
-
log.info({ slug }, "ClawHub skill installed");
|
|
1739
|
+
log$1.info({ slug }, "ClawHub skill installed");
|
|
1746
1740
|
} catch (err) {
|
|
1747
1741
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1748
1742
|
if (msg.includes("already exists") || msg.includes("Skill already exists")) {
|
|
1749
|
-
log.info({ slug }, "ClawHub skill already installed (skipped)");
|
|
1743
|
+
log$1.info({ slug }, "ClawHub skill already installed (skipped)");
|
|
1750
1744
|
return;
|
|
1751
1745
|
}
|
|
1752
|
-
log.error({
|
|
1746
|
+
log$1.error({
|
|
1753
1747
|
slug,
|
|
1754
1748
|
err: msg
|
|
1755
1749
|
}, "ClawHub skill install failed");
|
|
@@ -1762,7 +1756,7 @@ var OpenClawApplier = class {
|
|
|
1762
1756
|
recursive: true,
|
|
1763
1757
|
force: true
|
|
1764
1758
|
});
|
|
1765
|
-
log.info({ slug }, "ClawHub skill removed");
|
|
1759
|
+
log$1.info({ slug }, "ClawHub skill removed");
|
|
1766
1760
|
}
|
|
1767
1761
|
return Promise.resolve();
|
|
1768
1762
|
}
|
|
@@ -1798,7 +1792,7 @@ var OpenClawApplier = class {
|
|
|
1798
1792
|
JSON.stringify(merged)
|
|
1799
1793
|
], { timeout: 1e4 });
|
|
1800
1794
|
} catch (err) {
|
|
1801
|
-
log.error({
|
|
1795
|
+
log$1.error({
|
|
1802
1796
|
err: err instanceof Error ? err.message : String(err),
|
|
1803
1797
|
parentPath
|
|
1804
1798
|
}, "Failed to set config subtree via openclaw config set");
|
|
@@ -1813,7 +1807,7 @@ var OpenClawApplier = class {
|
|
|
1813
1807
|
JSON.stringify(leaves)
|
|
1814
1808
|
], { timeout: 1e4 });
|
|
1815
1809
|
} catch (err) {
|
|
1816
|
-
log.error({
|
|
1810
|
+
log$1.error({
|
|
1817
1811
|
err: err instanceof Error ? err.message : String(err),
|
|
1818
1812
|
batch: leaves
|
|
1819
1813
|
}, "Failed to set config via openclaw config set --batch-json");
|
|
@@ -1839,7 +1833,7 @@ var OpenClawApplier = class {
|
|
|
1839
1833
|
path
|
|
1840
1834
|
], { timeout: 1e4 });
|
|
1841
1835
|
} catch (err) {
|
|
1842
|
-
log.warn({
|
|
1836
|
+
log$1.warn({
|
|
1843
1837
|
err: err instanceof Error ? err.message : String(err),
|
|
1844
1838
|
path
|
|
1845
1839
|
}, "Failed to unset config via openclaw config unset");
|
|
@@ -1861,7 +1855,7 @@ var OpenClawApplier = class {
|
|
|
1861
1855
|
JSON.stringify(remaining)
|
|
1862
1856
|
], { timeout: 1e4 });
|
|
1863
1857
|
} catch (err) {
|
|
1864
|
-
log.warn({
|
|
1858
|
+
log$1.warn({
|
|
1865
1859
|
err: err instanceof Error ? err.message : String(err),
|
|
1866
1860
|
parentPath
|
|
1867
1861
|
}, "Failed to update parent config during remove");
|
|
@@ -1870,82 +1864,6 @@ var OpenClawApplier = class {
|
|
|
1870
1864
|
tracking._integrations = Object.fromEntries(Object.entries(integrations).filter(([key]) => key !== integrationId));
|
|
1871
1865
|
this.writeTracking(tracking);
|
|
1872
1866
|
}
|
|
1873
|
-
/**
|
|
1874
|
-
* Configure MCP servers in OpenClaw via `openclaw config set --batch-json`.
|
|
1875
|
-
*
|
|
1876
|
-
* Each server is written as `mcp.servers.{integrationId}-{serverId}.*`.
|
|
1877
|
-
* Applied servers are tracked in the tracking file for clean removal.
|
|
1878
|
-
*/
|
|
1879
|
-
async applyMcpServers(integrationId, servers) {
|
|
1880
|
-
if (servers.length === 0) return;
|
|
1881
|
-
const batch = [];
|
|
1882
|
-
const trackedKeys = [];
|
|
1883
|
-
for (const server of servers) {
|
|
1884
|
-
const prefix = `mcp.servers.${`${integrationId}-${server.id}`}`;
|
|
1885
|
-
batch.push({
|
|
1886
|
-
path: `${prefix}.command`,
|
|
1887
|
-
value: server.command
|
|
1888
|
-
});
|
|
1889
|
-
if (server.args && server.args.length > 0) batch.push({
|
|
1890
|
-
path: `${prefix}.args`,
|
|
1891
|
-
value: server.args
|
|
1892
|
-
});
|
|
1893
|
-
if (server.env) for (const [envKey, envVal] of Object.entries(server.env)) batch.push({
|
|
1894
|
-
path: `${prefix}.env.${envKey}`,
|
|
1895
|
-
value: envVal
|
|
1896
|
-
});
|
|
1897
|
-
if (server.cwd) batch.push({
|
|
1898
|
-
path: `${prefix}.cwd`,
|
|
1899
|
-
value: server.cwd
|
|
1900
|
-
});
|
|
1901
|
-
trackedKeys.push(prefix);
|
|
1902
|
-
}
|
|
1903
|
-
try {
|
|
1904
|
-
await execFileAsync("openclaw", [
|
|
1905
|
-
"config",
|
|
1906
|
-
"set",
|
|
1907
|
-
"--batch-json",
|
|
1908
|
-
JSON.stringify(batch)
|
|
1909
|
-
], { timeout: 1e4 });
|
|
1910
|
-
} catch (err) {
|
|
1911
|
-
log.error({
|
|
1912
|
-
err: err instanceof Error ? err.message : String(err),
|
|
1913
|
-
batch
|
|
1914
|
-
}, "Failed to set MCP server config via openclaw config set --batch-json");
|
|
1915
|
-
throw err;
|
|
1916
|
-
}
|
|
1917
|
-
const tracking = this.readTracking();
|
|
1918
|
-
const mcpTracking = tracking._mcpServers ?? {};
|
|
1919
|
-
mcpTracking[integrationId] = trackedKeys;
|
|
1920
|
-
tracking._mcpServers = mcpTracking;
|
|
1921
|
-
this.writeTracking(tracking);
|
|
1922
|
-
}
|
|
1923
|
-
/**
|
|
1924
|
-
* Remove MCP servers previously applied by an integration.
|
|
1925
|
-
*
|
|
1926
|
-
* Reads the tracking file to find which `mcp.servers.*` keys this integration set,
|
|
1927
|
-
* then removes them via `openclaw config unset`.
|
|
1928
|
-
*/
|
|
1929
|
-
async removeMcpServers(integrationId) {
|
|
1930
|
-
const tracking = this.readTracking();
|
|
1931
|
-
const mcpTracking = tracking._mcpServers ?? {};
|
|
1932
|
-
if (!(integrationId in mcpTracking)) return;
|
|
1933
|
-
const prefixes = mcpTracking[integrationId];
|
|
1934
|
-
for (const prefix of prefixes) try {
|
|
1935
|
-
await execFileAsync("openclaw", [
|
|
1936
|
-
"config",
|
|
1937
|
-
"unset",
|
|
1938
|
-
prefix
|
|
1939
|
-
], { timeout: 1e4 });
|
|
1940
|
-
} catch (err) {
|
|
1941
|
-
log.warn({
|
|
1942
|
-
err: err instanceof Error ? err.message : String(err),
|
|
1943
|
-
prefix
|
|
1944
|
-
}, "Failed to unset MCP server config via openclaw config unset");
|
|
1945
|
-
}
|
|
1946
|
-
tracking._mcpServers = Object.fromEntries(Object.entries(mcpTracking).filter(([key]) => key !== integrationId));
|
|
1947
|
-
this.writeTracking(tracking);
|
|
1948
|
-
}
|
|
1949
1867
|
isAvailable() {
|
|
1950
1868
|
return Promise.resolve(existsSync(this.home));
|
|
1951
1869
|
}
|
|
@@ -1963,6 +1881,128 @@ var OpenClawApplier = class {
|
|
|
1963
1881
|
}
|
|
1964
1882
|
};
|
|
1965
1883
|
//#endregion
|
|
1884
|
+
//#region src/appliers/mcp-applier.ts
|
|
1885
|
+
const log = createLogger("McpApplier");
|
|
1886
|
+
const CONFIG_TEMPLATE_RE = /\{\{config\.([a-zA-Z0-9_]+)\}\}/g;
|
|
1887
|
+
const CREDENTIALS_TEMPLATE_RE = /\{\{credentials\.([a-z0-9-]+)\.([a-zA-Z0-9_]+)\}\}/g;
|
|
1888
|
+
const ALFE_TEMPLATE_RE = /\{\{alfe\.([a-zA-Z0-9_]+)\}\}/g;
|
|
1889
|
+
var McpApplier = class {
|
|
1890
|
+
manager;
|
|
1891
|
+
credentials;
|
|
1892
|
+
platform;
|
|
1893
|
+
constructor(opts) {
|
|
1894
|
+
this.manager = opts.manager;
|
|
1895
|
+
this.credentials = opts.credentials;
|
|
1896
|
+
this.platform = opts.platform ?? {};
|
|
1897
|
+
}
|
|
1898
|
+
/**
|
|
1899
|
+
* Register every server declared by an integration. Idempotent —
|
|
1900
|
+
* re-running for the same integration overwrites entries in place
|
|
1901
|
+
* (the manager preserves `addedAt`). Returns the list of ids that
|
|
1902
|
+
* actually landed in the store.
|
|
1903
|
+
*
|
|
1904
|
+
* Awaits `manager.flush()` before returning so the openclaw.json
|
|
1905
|
+
* mirror write has fired by the time the caller proceeds. Without
|
|
1906
|
+
* this, an integration activation's success ack would race ahead of
|
|
1907
|
+
* the runtime's openclaw.json update — the cloud would see
|
|
1908
|
+
* `actual.status = active` while OpenClaw still has the stale MCP
|
|
1909
|
+
* set in memory, violating the actual-status protocol contract.
|
|
1910
|
+
*/
|
|
1911
|
+
async applyForIntegration(integrationId, servers, mergedConfig) {
|
|
1912
|
+
const owner = `integration:${integrationId}`;
|
|
1913
|
+
const applied = [];
|
|
1914
|
+
for (const server of servers) {
|
|
1915
|
+
const id = `${integrationId}-${server.id}`;
|
|
1916
|
+
const envResolved = await this.resolveEnv(server, mergedConfig);
|
|
1917
|
+
if (envResolved == null) {
|
|
1918
|
+
log.info({
|
|
1919
|
+
integrationId,
|
|
1920
|
+
server: server.id,
|
|
1921
|
+
provider: server.requires_credentials
|
|
1922
|
+
}, "Skipping MCP server registration — required credentials missing or fetch failed");
|
|
1923
|
+
continue;
|
|
1924
|
+
}
|
|
1925
|
+
await this.manager.addServer(toBundlerConfig(server, envResolved), {
|
|
1926
|
+
id,
|
|
1927
|
+
owner
|
|
1928
|
+
});
|
|
1929
|
+
applied.push(id);
|
|
1930
|
+
}
|
|
1931
|
+
await this.manager.flush();
|
|
1932
|
+
return applied;
|
|
1933
|
+
}
|
|
1934
|
+
/**
|
|
1935
|
+
* Drop every entry owned by this integration. Like `applyForIntegration`,
|
|
1936
|
+
* awaits the mirror flush so the runtime is signalled before the call
|
|
1937
|
+
* resolves — the uninstall ack shouldn't outrun openclaw.json being
|
|
1938
|
+
* cleaned up.
|
|
1939
|
+
*/
|
|
1940
|
+
async removeForIntegration(integrationId) {
|
|
1941
|
+
const owner = `integration:${integrationId}`;
|
|
1942
|
+
const removed = await this.manager.removeServersByOwner(owner);
|
|
1943
|
+
await this.manager.flush();
|
|
1944
|
+
return removed;
|
|
1945
|
+
}
|
|
1946
|
+
async resolveEnv(server, mergedConfig) {
|
|
1947
|
+
if (!server.env || Object.keys(server.env).length === 0) return {};
|
|
1948
|
+
const needsCredentials = server.requires_credentials;
|
|
1949
|
+
let credentialsCache;
|
|
1950
|
+
if (needsCredentials) {
|
|
1951
|
+
try {
|
|
1952
|
+
credentialsCache = await this.credentials.getCredentials(needsCredentials);
|
|
1953
|
+
} catch (err) {
|
|
1954
|
+
log.warn({
|
|
1955
|
+
err: errMsg(err),
|
|
1956
|
+
provider: needsCredentials
|
|
1957
|
+
}, "Credentials fetch threw — skipping MCP registration");
|
|
1958
|
+
return null;
|
|
1959
|
+
}
|
|
1960
|
+
if (!credentialsCache || Object.keys(credentialsCache).length === 0) return null;
|
|
1961
|
+
}
|
|
1962
|
+
const resolved = {};
|
|
1963
|
+
for (const [key, value] of Object.entries(server.env)) {
|
|
1964
|
+
const interpolated = interpolateString(value, mergedConfig, credentialsCache, this.platform);
|
|
1965
|
+
if (needsCredentials && hasCredentialsPlaceholder(interpolated)) {
|
|
1966
|
+
log.warn({
|
|
1967
|
+
provider: needsCredentials,
|
|
1968
|
+
key
|
|
1969
|
+
}, "Credentials interpolation left a placeholder — skipping MCP registration");
|
|
1970
|
+
return null;
|
|
1971
|
+
}
|
|
1972
|
+
resolved[key] = interpolated;
|
|
1973
|
+
}
|
|
1974
|
+
return resolved;
|
|
1975
|
+
}
|
|
1976
|
+
};
|
|
1977
|
+
function hasCredentialsPlaceholder(value) {
|
|
1978
|
+
return /\{\{credentials\.[a-z0-9-]+\.[a-zA-Z0-9_]+\}\}/.test(value);
|
|
1979
|
+
}
|
|
1980
|
+
function interpolateString(value, mergedConfig, credentials, platform) {
|
|
1981
|
+
let next = value.replace(CONFIG_TEMPLATE_RE, (match, configKey) => {
|
|
1982
|
+
const v = mergedConfig[configKey];
|
|
1983
|
+
return typeof v === "string" || typeof v === "number" ? String(v) : match;
|
|
1984
|
+
});
|
|
1985
|
+
if (credentials) next = next.replace(CREDENTIALS_TEMPLATE_RE, (match, _provider, field) => {
|
|
1986
|
+
const v = credentials[field];
|
|
1987
|
+
return typeof v === "string" || typeof v === "number" ? String(v) : match;
|
|
1988
|
+
});
|
|
1989
|
+
next = next.replace(ALFE_TEMPLATE_RE, (match, platformKey) => {
|
|
1990
|
+
const v = platform[platformKey];
|
|
1991
|
+
return typeof v === "string" || typeof v === "number" ? String(v) : match;
|
|
1992
|
+
});
|
|
1993
|
+
return next;
|
|
1994
|
+
}
|
|
1995
|
+
function toBundlerConfig(server, resolvedEnv) {
|
|
1996
|
+
const cfg = { command: server.command };
|
|
1997
|
+
if (server.args && server.args.length > 0) cfg.args = server.args;
|
|
1998
|
+
if (Object.keys(resolvedEnv).length > 0) cfg.env = resolvedEnv;
|
|
1999
|
+
if (server.cwd) cfg.cwd = server.cwd;
|
|
2000
|
+
return cfg;
|
|
2001
|
+
}
|
|
2002
|
+
function errMsg(err) {
|
|
2003
|
+
return err instanceof Error ? err.message : String(err);
|
|
2004
|
+
}
|
|
2005
|
+
//#endregion
|
|
1966
2006
|
//#region src/adapter.ts
|
|
1967
2007
|
var IntegrationManagerAdapter = class {
|
|
1968
2008
|
constructor(manager) {
|
|
@@ -2021,4 +2061,4 @@ var IntegrationManagerAdapter = class {
|
|
|
2021
2061
|
}
|
|
2022
2062
|
};
|
|
2023
2063
|
//#endregion
|
|
2024
|
-
export { Installer, InstallerError, IntegrationManager, IntegrationManagerAdapter, LockManager, OpenClawApplier, Registry, RegistryResolveError, Resolver, StateManager, buildHookEnv, resolveInstallsForRuntime, runHook, runHookWithContext };
|
|
2064
|
+
export { Installer, InstallerError, IntegrationManager, IntegrationManagerAdapter, LockManager, McpApplier, OpenClawApplier, Registry, RegistryResolveError, Resolver, StateManager, buildHookEnv, resolveInstallsForRuntime, runHook, runHookWithContext };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alfe.ai/integrations",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Integration lifecycle management for Alfe — registry, resolution, installation, and state",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@auriclabs/logger": "^0.1.1",
|
|
16
|
-
"@alfe.ai/integration-manifest": "^0.0
|
|
16
|
+
"@alfe.ai/integration-manifest": "^0.1.0",
|
|
17
|
+
"@alfe.ai/mcp-bundler": "^0.1.0"
|
|
17
18
|
},
|
|
18
19
|
"files": [
|
|
19
20
|
"dist"
|