@alfe.ai/integrations 0.0.24 → 0.0.25
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 +34 -0
- package/dist/index.js +133 -2
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -248,6 +248,20 @@ interface ConfigSchemaField {
|
|
|
248
248
|
/** Only show this field if a specific integration is installed */
|
|
249
249
|
depends_on_integration?: string;
|
|
250
250
|
}
|
|
251
|
+
interface McpServerDeclaration {
|
|
252
|
+
/** Unique identifier within this integration */
|
|
253
|
+
id: string;
|
|
254
|
+
/** Command to spawn (e.g., 'npx', 'node', 'xero-mcp-proxy') */
|
|
255
|
+
command: string;
|
|
256
|
+
/** Arguments for the command */
|
|
257
|
+
args?: string[];
|
|
258
|
+
/** Environment variables — supports {{config.KEY}} interpolation from config + secrets */
|
|
259
|
+
env?: Record<string, string>;
|
|
260
|
+
/** Working directory (optional) */
|
|
261
|
+
cwd?: string;
|
|
262
|
+
/** If true, applier skips auto-application — a lifecycle hook manages this MCP server instead */
|
|
263
|
+
hook_managed?: boolean;
|
|
264
|
+
}
|
|
251
265
|
interface CommandDeclaration {
|
|
252
266
|
/** Dot-namespaced command name (e.g. "support.diagnostic") */
|
|
253
267
|
name: string;
|
|
@@ -329,6 +343,8 @@ interface IntegrationManifest {
|
|
|
329
343
|
capabilities: string[];
|
|
330
344
|
hooks: IntegrationHooks;
|
|
331
345
|
commands: CommandDeclaration[];
|
|
346
|
+
/** MCP servers to configure in the agent runtime */
|
|
347
|
+
mcp_servers: McpServerDeclaration[];
|
|
332
348
|
/** Git repository URL (HTTPS) — not present in YAML, injected by the publish API */
|
|
333
349
|
repository?: string;
|
|
334
350
|
/**
|
|
@@ -388,6 +404,10 @@ interface RuntimeApplier {
|
|
|
388
404
|
applyConfig(integrationId: string, config: Record<string, unknown>): Promise<void>;
|
|
389
405
|
/** Remove config previously applied by an integration */
|
|
390
406
|
removeConfig(integrationId: string): Promise<void>;
|
|
407
|
+
/** Configure MCP servers in this runtime */
|
|
408
|
+
applyMcpServers(integrationId: string, servers: McpServerDeclaration[]): Promise<void>;
|
|
409
|
+
/** Remove MCP servers previously applied by an integration */
|
|
410
|
+
removeMcpServers(integrationId: string): Promise<void>;
|
|
391
411
|
/** Check if this runtime is available (e.g. workspace directory exists) */
|
|
392
412
|
isAvailable(): Promise<boolean>;
|
|
393
413
|
}
|
|
@@ -738,6 +758,20 @@ declare class OpenClawApplier implements RuntimeApplier {
|
|
|
738
758
|
* then removes them via `openclaw config unset`.
|
|
739
759
|
*/
|
|
740
760
|
removeConfig(integrationId: string): Promise<void>;
|
|
761
|
+
/**
|
|
762
|
+
* Configure MCP servers in OpenClaw via `openclaw config set --batch-json`.
|
|
763
|
+
*
|
|
764
|
+
* Each server is written as `mcp.servers.{integrationId}-{serverId}.*`.
|
|
765
|
+
* Applied servers are tracked in the tracking file for clean removal.
|
|
766
|
+
*/
|
|
767
|
+
applyMcpServers(integrationId: string, servers: McpServerDeclaration[]): Promise<void>;
|
|
768
|
+
/**
|
|
769
|
+
* Remove MCP servers previously applied by an integration.
|
|
770
|
+
*
|
|
771
|
+
* Reads the tracking file to find which `mcp.servers.*` keys this integration set,
|
|
772
|
+
* then removes them via `openclaw config unset`.
|
|
773
|
+
*/
|
|
774
|
+
removeMcpServers(integrationId: string): Promise<void>;
|
|
741
775
|
isAvailable(): Promise<boolean>;
|
|
742
776
|
private readTracking;
|
|
743
777
|
private writeTracking;
|
package/dist/index.js
CHANGED
|
@@ -808,6 +808,25 @@ function interpolateSelfConfig(obj, config) {
|
|
|
808
808
|
return result;
|
|
809
809
|
}
|
|
810
810
|
/**
|
|
811
|
+
* Interpolate `{{config.KEY}}` patterns in MCP server env vars using
|
|
812
|
+
* the merged config + secrets dict. Returns new declarations with
|
|
813
|
+
* interpolated env values; skips hook_managed servers.
|
|
814
|
+
*/
|
|
815
|
+
function interpolateMcpServerEnvs(servers, mergedConfig) {
|
|
816
|
+
return servers.filter((s) => !s.hook_managed).map((server) => {
|
|
817
|
+
if (!server.env) return server;
|
|
818
|
+
const interpolatedEnv = {};
|
|
819
|
+
for (const [key, value] of Object.entries(server.env)) interpolatedEnv[key] = value.replace(/\{\{config\.([a-zA-Z0-9_]+)\}\}/g, (_match, configKey) => {
|
|
820
|
+
const val = mergedConfig[configKey];
|
|
821
|
+
return typeof val === "string" || typeof val === "number" ? String(val) : _match;
|
|
822
|
+
});
|
|
823
|
+
return {
|
|
824
|
+
...server,
|
|
825
|
+
env: interpolatedEnv
|
|
826
|
+
};
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
811
830
|
* Merge universal installs with runtime-specific installs from the manifest.
|
|
812
831
|
*/
|
|
813
832
|
function resolveInstallsForRuntime(manifest, runtime) {
|
|
@@ -1045,6 +1064,19 @@ var IntegrationManager = class {
|
|
|
1045
1064
|
await applier.applyConfig(integrationId, interpolatedConfig);
|
|
1046
1065
|
configApplied = true;
|
|
1047
1066
|
}
|
|
1067
|
+
const mcpServers = manifest.mcp_servers;
|
|
1068
|
+
if (mcpServers.length > 0) {
|
|
1069
|
+
const secretEntries = this.secrets.get(integrationId);
|
|
1070
|
+
const interpolatedServers = interpolateMcpServerEnvs(mcpServers, {
|
|
1071
|
+
...entry.config,
|
|
1072
|
+
...secretEntries ? Object.fromEntries(secretEntries) : {}
|
|
1073
|
+
});
|
|
1074
|
+
if (interpolatedServers.length > 0) {
|
|
1075
|
+
this.log.info(`Applying ${String(interpolatedServers.length)} MCP server(s) for ${integrationId} to ${runtimeName}`);
|
|
1076
|
+
await applier.applyMcpServers(integrationId, interpolatedServers);
|
|
1077
|
+
configApplied = true;
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1048
1080
|
if (plugins.length > 0 || skills.length > 0) this.lockManager.addEntries(runtimeName, integrationId, manifest.version, plugins, skills, installPath);
|
|
1049
1081
|
}
|
|
1050
1082
|
this.state.setStatus(integrationId, "active");
|
|
@@ -1143,6 +1175,15 @@ var IntegrationManager = class {
|
|
|
1143
1175
|
this.log.warn(`Failed to remove config for ${integrationId} from ${runtimeName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1144
1176
|
}
|
|
1145
1177
|
}
|
|
1178
|
+
for (const [runtimeName, applier] of this.runtimeAppliers) {
|
|
1179
|
+
if (!await applier.isAvailable()) continue;
|
|
1180
|
+
this.log.info(`Removing MCP servers for ${integrationId} from ${runtimeName}`);
|
|
1181
|
+
try {
|
|
1182
|
+
await applier.removeMcpServers(integrationId);
|
|
1183
|
+
} catch (err) {
|
|
1184
|
+
this.log.warn(`Failed to remove MCP servers for ${integrationId} from ${runtimeName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1146
1187
|
this.state.setStatus(integrationId, "configured");
|
|
1147
1188
|
return {
|
|
1148
1189
|
ok: true,
|
|
@@ -1456,8 +1497,21 @@ var OpenClawApplier = class {
|
|
|
1456
1497
|
"install",
|
|
1457
1498
|
pkg
|
|
1458
1499
|
];
|
|
1459
|
-
|
|
1460
|
-
|
|
1500
|
+
const useUnsafeFlag = pkg.startsWith("@alfe.ai/");
|
|
1501
|
+
if (useUnsafeFlag) args.push("--dangerously-force-unsafe-install");
|
|
1502
|
+
try {
|
|
1503
|
+
await execFileAsync("openclaw", args, { timeout: 6e4 });
|
|
1504
|
+
} catch (err) {
|
|
1505
|
+
const errText = err instanceof Error ? `${err.message}\n${err.stderr ?? ""}` : String(err);
|
|
1506
|
+
if (useUnsafeFlag && errText.includes("unknown option")) {
|
|
1507
|
+
log.info({ pkg }, "OpenClaw does not support --dangerously-force-unsafe-install, retrying without");
|
|
1508
|
+
await execFileAsync("openclaw", [
|
|
1509
|
+
"plugins",
|
|
1510
|
+
"install",
|
|
1511
|
+
pkg
|
|
1512
|
+
], { timeout: 6e4 });
|
|
1513
|
+
} else throw err;
|
|
1514
|
+
}
|
|
1461
1515
|
} catch (err) {
|
|
1462
1516
|
await new Promise((r) => {
|
|
1463
1517
|
setTimeout(r, 500);
|
|
@@ -1624,6 +1678,83 @@ var OpenClawApplier = class {
|
|
|
1624
1678
|
tracking._integrations = Object.fromEntries(Object.entries(integrations).filter(([key]) => key !== integrationId));
|
|
1625
1679
|
this.writeTracking(tracking);
|
|
1626
1680
|
}
|
|
1681
|
+
/**
|
|
1682
|
+
* Configure MCP servers in OpenClaw via `openclaw config set --batch-json`.
|
|
1683
|
+
*
|
|
1684
|
+
* Each server is written as `mcp.servers.{integrationId}-{serverId}.*`.
|
|
1685
|
+
* Applied servers are tracked in the tracking file for clean removal.
|
|
1686
|
+
*/
|
|
1687
|
+
async applyMcpServers(integrationId, servers) {
|
|
1688
|
+
if (servers.length === 0) return;
|
|
1689
|
+
const batch = [];
|
|
1690
|
+
const trackedKeys = [];
|
|
1691
|
+
for (const server of servers) {
|
|
1692
|
+
const prefix = `mcp.servers.${`${integrationId}-${server.id}`}`;
|
|
1693
|
+
batch.push({
|
|
1694
|
+
path: `${prefix}.command`,
|
|
1695
|
+
value: server.command
|
|
1696
|
+
});
|
|
1697
|
+
if (server.args && server.args.length > 0) batch.push({
|
|
1698
|
+
path: `${prefix}.args`,
|
|
1699
|
+
value: server.args
|
|
1700
|
+
});
|
|
1701
|
+
if (server.env) for (const [envKey, envVal] of Object.entries(server.env)) batch.push({
|
|
1702
|
+
path: `${prefix}.env.${envKey}`,
|
|
1703
|
+
value: envVal
|
|
1704
|
+
});
|
|
1705
|
+
if (server.cwd) batch.push({
|
|
1706
|
+
path: `${prefix}.cwd`,
|
|
1707
|
+
value: server.cwd
|
|
1708
|
+
});
|
|
1709
|
+
trackedKeys.push(prefix);
|
|
1710
|
+
}
|
|
1711
|
+
try {
|
|
1712
|
+
await execFileAsync("openclaw", [
|
|
1713
|
+
"config",
|
|
1714
|
+
"set",
|
|
1715
|
+
"--batch-json",
|
|
1716
|
+
JSON.stringify(batch),
|
|
1717
|
+
"--strict-json"
|
|
1718
|
+
], { timeout: 1e4 });
|
|
1719
|
+
} catch (err) {
|
|
1720
|
+
log.error({
|
|
1721
|
+
err: err instanceof Error ? err.message : String(err),
|
|
1722
|
+
batch
|
|
1723
|
+
}, "Failed to set MCP server config via openclaw config set --batch-json");
|
|
1724
|
+
throw err;
|
|
1725
|
+
}
|
|
1726
|
+
const tracking = this.readTracking();
|
|
1727
|
+
const mcpTracking = tracking._mcpServers ?? {};
|
|
1728
|
+
mcpTracking[integrationId] = trackedKeys;
|
|
1729
|
+
tracking._mcpServers = mcpTracking;
|
|
1730
|
+
this.writeTracking(tracking);
|
|
1731
|
+
}
|
|
1732
|
+
/**
|
|
1733
|
+
* Remove MCP servers previously applied by an integration.
|
|
1734
|
+
*
|
|
1735
|
+
* Reads the tracking file to find which `mcp.servers.*` keys this integration set,
|
|
1736
|
+
* then removes them via `openclaw config unset`.
|
|
1737
|
+
*/
|
|
1738
|
+
async removeMcpServers(integrationId) {
|
|
1739
|
+
const tracking = this.readTracking();
|
|
1740
|
+
const mcpTracking = tracking._mcpServers ?? {};
|
|
1741
|
+
if (!(integrationId in mcpTracking)) return;
|
|
1742
|
+
const prefixes = mcpTracking[integrationId];
|
|
1743
|
+
for (const prefix of prefixes) try {
|
|
1744
|
+
await execFileAsync("openclaw", [
|
|
1745
|
+
"config",
|
|
1746
|
+
"unset",
|
|
1747
|
+
prefix
|
|
1748
|
+
], { timeout: 1e4 });
|
|
1749
|
+
} catch (err) {
|
|
1750
|
+
log.warn({
|
|
1751
|
+
err: err instanceof Error ? err.message : String(err),
|
|
1752
|
+
prefix
|
|
1753
|
+
}, "Failed to unset MCP server config via openclaw config unset");
|
|
1754
|
+
}
|
|
1755
|
+
tracking._mcpServers = Object.fromEntries(Object.entries(mcpTracking).filter(([key]) => key !== integrationId));
|
|
1756
|
+
this.writeTracking(tracking);
|
|
1757
|
+
}
|
|
1627
1758
|
isAvailable() {
|
|
1628
1759
|
return Promise.resolve(existsSync(this.workspace));
|
|
1629
1760
|
}
|
package/package.json
CHANGED