@alfe.ai/openclaw-mcp-bundler 0.0.12 → 0.0.13
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/plugin.d.cts.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin2.cjs +38 -14
- package/dist/plugin2.js +38 -14
- package/dist/plugin2.js.map +1 -1
- package/package.json +1 -1
package/dist/plugin.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.cts","names":[],"sources":["../src/ipc-client.ts","../src/plugin.ts"],"mappings":";UA0BiB;EAAA,EAAA,EAAA,MAAA;;YAGL;;ICqBF,IAAA,EAAM,MAAA;IAON,OAAA,EAAA,MAAc;EAAA,CAAA;;;;;UAPd,MAAA,CA2BI;EAGJ,KAAA,CAAA,GAAA,EAAA,MAAA,EAAA,GAAkB,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;EAAA,IAAA,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;MACjB,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;OACO,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;;UAzBR,cAAA,CA0B+B;EAG/B,MAAA,CAAA,EAAA;IAAqB,QAAA,CAAA,EAAA;MAEI,KAAA,CAAA,EAAA,MAAA;MACA,WAAA,CAAA,EA/BqB,MA+BrB,CAAA,MAAA,EAAA,OAAA,CAAA;IAAO,CAAA;EAGhC,CAAA;EAAiB,GAAA,CAAA,EAAA;IACjB,OAAA,CAAA,EAlCU,MAkCV,CAAA,MAAA,EAAA,OAAA,CAAA;;;UA/BA,eAAA,CAqCA;SAAmB,EApClB,MAoCkB,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA;SAAuB,CAAA,EAnCxC,MAmCwC,CAAA,MAAA,EAAA,OAAA,CAAA;;UAhC1C,SAAA,CAmCoB;EAAqB,IAAA,EAAA,MAAA;
|
|
1
|
+
{"version":3,"file":"plugin.d.cts","names":[],"sources":["../src/ipc-client.ts","../src/plugin.ts"],"mappings":";UA0BiB;EAAA,EAAA,EAAA,MAAA;;YAGL;;ICqBF,IAAA,EAAM,MAAA;IAON,OAAA,EAAA,MAAc;EAAA,CAAA;;;;;UAPd,MAAA,CA2BI;EAGJ,KAAA,CAAA,GAAA,EAAA,MAAA,EAAA,GAAkB,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;EAAA,IAAA,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;MACjB,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;OACO,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;;UAzBR,cAAA,CA0B+B;EAG/B,MAAA,CAAA,EAAA;IAAqB,QAAA,CAAA,EAAA;MAEI,KAAA,CAAA,EAAA,MAAA;MACA,WAAA,CAAA,EA/BqB,MA+BrB,CAAA,MAAA,EAAA,OAAA,CAAA;IAAO,CAAA;EAGhC,CAAA;EAAiB,GAAA,CAAA,EAAA;IACjB,OAAA,CAAA,EAlCU,MAkCV,CAAA,MAAA,EAAA,OAAA,CAAA;;;UA/BA,eAAA,CAqCA;SAAmB,EApClB,MAoCkB,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA;SAAuB,CAAA,EAnCxC,MAmCwC,CAAA,MAAA,EAAA,OAAA,CAAA;;UAhC1C,SAAA,CAmCoB;EAAqB,IAAA,EAAA,MAAA;EA6V7C,KAAA,CAAA,EAAA,MAsQL;EAAA,WAAA,EAAA,MAAA;YAjMe,EAjcF,MAicE,CAAA,MAAA,EAAA,OAAA,CAAA;SA6LE,EAAA,CAAA,UAAA,EAAA,MAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,CAAA,EA1nBL,WA0nBK,EAAA,QAAA,CAAA,EAAA,CAAA,MAAA,EAAA,OAAA,EAAA,GAAA,IAAA,EAAA,GAxnBX,OAwnBW,CAxnBH,eAwnBG,CAAA;;UArnBR,kBAAA;WACC;kBACO;2BACS;;UAGjB,qBAAA;;mCAEyB;mCACA;;UAGzB,iBAAA;UACA;WACC;;;;;;;sBAG8B;;;uBAE/B,mBAAmB,uBAAuB,YAAY;;;;;8BAGlC;;cA6VxB;;;;;;;;gBAqEU;kBA6LE"}
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","names":[],"sources":["../src/ipc-client.ts","../src/plugin.ts"],"mappings":";UA0BiB;EAAA,EAAA,EAAA,MAAA;;YAGL;;ICqBF,IAAA,EAAM,MAAA;IAON,OAAA,EAAA,MAAc;EAAA,CAAA;;;;;UAPd,MAAA,CA2BI;EAGJ,KAAA,CAAA,GAAA,EAAA,MAAA,EAAA,GAAkB,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;EAAA,IAAA,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;MACjB,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;OACO,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;;UAzBR,cAAA,CA0B+B;EAG/B,MAAA,CAAA,EAAA;IAAqB,QAAA,CAAA,EAAA;MAEI,KAAA,CAAA,EAAA,MAAA;MACA,WAAA,CAAA,EA/BqB,MA+BrB,CAAA,MAAA,EAAA,OAAA,CAAA;IAAO,CAAA;EAGhC,CAAA;EAAiB,GAAA,CAAA,EAAA;IACjB,OAAA,CAAA,EAlCU,MAkCV,CAAA,MAAA,EAAA,OAAA,CAAA;;;UA/BA,eAAA,CAqCA;SAAmB,EApClB,MAoCkB,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA;SAAuB,CAAA,EAnCxC,MAmCwC,CAAA,MAAA,EAAA,OAAA,CAAA;;UAhC1C,SAAA,CAmCoB;EAAqB,IAAA,EAAA,MAAA;
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","names":[],"sources":["../src/ipc-client.ts","../src/plugin.ts"],"mappings":";UA0BiB;EAAA,EAAA,EAAA,MAAA;;YAGL;;ICqBF,IAAA,EAAM,MAAA;IAON,OAAA,EAAA,MAAc;EAAA,CAAA;;;;;UAPd,MAAA,CA2BI;EAGJ,KAAA,CAAA,GAAA,EAAA,MAAA,EAAA,GAAkB,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;EAAA,IAAA,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;MACjB,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;OACO,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;;UAzBR,cAAA,CA0B+B;EAG/B,MAAA,CAAA,EAAA;IAAqB,QAAA,CAAA,EAAA;MAEI,KAAA,CAAA,EAAA,MAAA;MACA,WAAA,CAAA,EA/BqB,MA+BrB,CAAA,MAAA,EAAA,OAAA,CAAA;IAAO,CAAA;EAGhC,CAAA;EAAiB,GAAA,CAAA,EAAA;IACjB,OAAA,CAAA,EAlCU,MAkCV,CAAA,MAAA,EAAA,OAAA,CAAA;;;UA/BA,eAAA,CAqCA;SAAmB,EApClB,MAoCkB,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA;SAAuB,CAAA,EAnCxC,MAmCwC,CAAA,MAAA,EAAA,OAAA,CAAA;;UAhC1C,SAAA,CAmCoB;EAAqB,IAAA,EAAA,MAAA;EA6V7C,KAAA,CAAA,EAAA,MAsQL;EAAA,WAAA,EAAA,MAAA;YAjMe,EAjcF,MAicE,CAAA,MAAA,EAAA,OAAA,CAAA;SA6LE,EAAA,CAAA,UAAA,EAAA,MAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,CAAA,EA1nBL,WA0nBK,EAAA,QAAA,CAAA,EAAA,CAAA,MAAA,EAAA,OAAA,EAAA,GAAA,IAAA,EAAA,GAxnBX,OAwnBW,CAxnBH,eAwnBG,CAAA;;UArnBR,kBAAA;WACC;kBACO;2BACS;;UAGjB,qBAAA;;mCAEyB;mCACA;;UAGzB,iBAAA;UACA;WACC;;;;;;;sBAG8B;;;uBAE/B,mBAAmB,uBAAuB,YAAY;;;;;8BAGlC;;cA6VxB;;;;;;;;gBAqEU;kBA6LE"}
|
package/dist/plugin2.cjs
CHANGED
|
@@ -239,8 +239,11 @@ const CACHE_FILE = (0, node_path.join)((0, node_os.homedir)(), ".alfe", "mcp", "
|
|
|
239
239
|
let ipcClient;
|
|
240
240
|
let cachedDescriptors = [];
|
|
241
241
|
let refreshInFlight = null;
|
|
242
|
+
let periodicRefreshTimer = null;
|
|
242
243
|
const ACTIVATED_KEY = "__alfeMcpBundlerPluginActivated";
|
|
243
244
|
const g = globalThis;
|
|
245
|
+
const PREWARM_TIMEOUT_MS = 3e3;
|
|
246
|
+
const PERIODIC_REFRESH_INTERVAL_MS = 1e4;
|
|
244
247
|
function resolveLiveConfig(api, ctx) {
|
|
245
248
|
return ctx?.runtimeConfig ?? ctx?.getRuntimeConfig?.() ?? api.runtime?.config?.current?.() ?? ctx?.config ?? api.config;
|
|
246
249
|
}
|
|
@@ -542,18 +545,35 @@ const plugin = {
|
|
|
542
545
|
refreshCachedTools(api);
|
|
543
546
|
return cachedDescriptors.map((d) => buildAgentTool(api, d));
|
|
544
547
|
}, { optional: true });
|
|
545
|
-
const startService = () => {
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
},
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
548
|
+
const startService = async () => {
|
|
549
|
+
const prewarmTimer = { handle: null };
|
|
550
|
+
if (await new Promise((resolve) => {
|
|
551
|
+
prewarmTimer.handle = setTimeout(() => {
|
|
552
|
+
prewarmTimer.handle = null;
|
|
553
|
+
resolve("timeout");
|
|
554
|
+
}, PREWARM_TIMEOUT_MS);
|
|
555
|
+
ipcClient = new McpIpcClient({
|
|
556
|
+
logger: log,
|
|
557
|
+
plugin: {
|
|
558
|
+
name: plugin.id,
|
|
559
|
+
version: plugin.version
|
|
560
|
+
},
|
|
561
|
+
onConnect: () => {
|
|
562
|
+
refreshCachedTools(api).finally(() => {
|
|
563
|
+
if (prewarmTimer.handle !== null) {
|
|
564
|
+
clearTimeout(prewarmTimer.handle);
|
|
565
|
+
prewarmTimer.handle = null;
|
|
566
|
+
}
|
|
567
|
+
resolve("completed");
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
ipcClient.start();
|
|
572
|
+
}) === "timeout") log.warn(`[mcp-bundler] pre-warm timed out after ${String(PREWARM_TIMEOUT_MS)}ms — first chat turn may see a stale tool list (likely a slow or unreachable daemon at ~/.alfe/gateway.sock)`);
|
|
573
|
+
periodicRefreshTimer = setInterval(() => {
|
|
574
|
+
if (ipcClient?.isConnected()) refreshCachedTools(api);
|
|
575
|
+
}, PERIODIC_REFRESH_INTERVAL_MS);
|
|
576
|
+
periodicRefreshTimer.unref?.();
|
|
557
577
|
if (deferToNative) {
|
|
558
578
|
log.info(`[mcp-bundler] activated v${plugin.version} (management tools only — native bundleMcp owns runtime)`);
|
|
559
579
|
return;
|
|
@@ -562,6 +582,10 @@ const plugin = {
|
|
|
562
582
|
};
|
|
563
583
|
const stopService = () => {
|
|
564
584
|
log.info("[mcp-bundler] stopping");
|
|
585
|
+
if (periodicRefreshTimer !== null) {
|
|
586
|
+
clearInterval(periodicRefreshTimer);
|
|
587
|
+
periodicRefreshTimer = null;
|
|
588
|
+
}
|
|
565
589
|
if (ipcClient) {
|
|
566
590
|
ipcClient.stop();
|
|
567
591
|
ipcClient = void 0;
|
|
@@ -572,8 +596,8 @@ const plugin = {
|
|
|
572
596
|
};
|
|
573
597
|
api.registerService({
|
|
574
598
|
id: "alfe-mcp-bundler",
|
|
575
|
-
start: () => {
|
|
576
|
-
startService();
|
|
599
|
+
start: async () => {
|
|
600
|
+
await startService();
|
|
577
601
|
},
|
|
578
602
|
stop: async () => {
|
|
579
603
|
await stopService();
|
package/dist/plugin2.js
CHANGED
|
@@ -239,8 +239,11 @@ const CACHE_FILE = join(homedir(), ".alfe", "mcp", "openclaw-bundler-cache.json"
|
|
|
239
239
|
let ipcClient;
|
|
240
240
|
let cachedDescriptors = [];
|
|
241
241
|
let refreshInFlight = null;
|
|
242
|
+
let periodicRefreshTimer = null;
|
|
242
243
|
const ACTIVATED_KEY = "__alfeMcpBundlerPluginActivated";
|
|
243
244
|
const g = globalThis;
|
|
245
|
+
const PREWARM_TIMEOUT_MS = 3e3;
|
|
246
|
+
const PERIODIC_REFRESH_INTERVAL_MS = 1e4;
|
|
244
247
|
function resolveLiveConfig(api, ctx) {
|
|
245
248
|
return ctx?.runtimeConfig ?? ctx?.getRuntimeConfig?.() ?? api.runtime?.config?.current?.() ?? ctx?.config ?? api.config;
|
|
246
249
|
}
|
|
@@ -542,18 +545,35 @@ const plugin = {
|
|
|
542
545
|
refreshCachedTools(api);
|
|
543
546
|
return cachedDescriptors.map((d) => buildAgentTool(api, d));
|
|
544
547
|
}, { optional: true });
|
|
545
|
-
const startService = () => {
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
},
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
548
|
+
const startService = async () => {
|
|
549
|
+
const prewarmTimer = { handle: null };
|
|
550
|
+
if (await new Promise((resolve) => {
|
|
551
|
+
prewarmTimer.handle = setTimeout(() => {
|
|
552
|
+
prewarmTimer.handle = null;
|
|
553
|
+
resolve("timeout");
|
|
554
|
+
}, PREWARM_TIMEOUT_MS);
|
|
555
|
+
ipcClient = new McpIpcClient({
|
|
556
|
+
logger: log,
|
|
557
|
+
plugin: {
|
|
558
|
+
name: plugin.id,
|
|
559
|
+
version: plugin.version
|
|
560
|
+
},
|
|
561
|
+
onConnect: () => {
|
|
562
|
+
refreshCachedTools(api).finally(() => {
|
|
563
|
+
if (prewarmTimer.handle !== null) {
|
|
564
|
+
clearTimeout(prewarmTimer.handle);
|
|
565
|
+
prewarmTimer.handle = null;
|
|
566
|
+
}
|
|
567
|
+
resolve("completed");
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
ipcClient.start();
|
|
572
|
+
}) === "timeout") log.warn(`[mcp-bundler] pre-warm timed out after ${String(PREWARM_TIMEOUT_MS)}ms — first chat turn may see a stale tool list (likely a slow or unreachable daemon at ~/.alfe/gateway.sock)`);
|
|
573
|
+
periodicRefreshTimer = setInterval(() => {
|
|
574
|
+
if (ipcClient?.isConnected()) refreshCachedTools(api);
|
|
575
|
+
}, PERIODIC_REFRESH_INTERVAL_MS);
|
|
576
|
+
periodicRefreshTimer.unref?.();
|
|
557
577
|
if (deferToNative) {
|
|
558
578
|
log.info(`[mcp-bundler] activated v${plugin.version} (management tools only — native bundleMcp owns runtime)`);
|
|
559
579
|
return;
|
|
@@ -562,6 +582,10 @@ const plugin = {
|
|
|
562
582
|
};
|
|
563
583
|
const stopService = () => {
|
|
564
584
|
log.info("[mcp-bundler] stopping");
|
|
585
|
+
if (periodicRefreshTimer !== null) {
|
|
586
|
+
clearInterval(periodicRefreshTimer);
|
|
587
|
+
periodicRefreshTimer = null;
|
|
588
|
+
}
|
|
565
589
|
if (ipcClient) {
|
|
566
590
|
ipcClient.stop();
|
|
567
591
|
ipcClient = void 0;
|
|
@@ -572,8 +596,8 @@ const plugin = {
|
|
|
572
596
|
};
|
|
573
597
|
api.registerService({
|
|
574
598
|
id: "alfe-mcp-bundler",
|
|
575
|
-
start: () => {
|
|
576
|
-
startService();
|
|
599
|
+
start: async () => {
|
|
600
|
+
await startService();
|
|
577
601
|
},
|
|
578
602
|
stop: async () => {
|
|
579
603
|
await stopService();
|
package/dist/plugin2.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin2.js","names":[],"sources":["../src/cli-backend-detect.ts","../src/ipc-client.ts","../src/plugin.ts"],"sourcesContent":["/**\n * Decide whether to defer to OpenClaw's native `bundleMcp:true` path for\n * this run. Pre-May-2026 this gated on backend type (claude-cli /\n * codex-cli), but the actual hazard was double-registration: when\n * `openclaw.json#mcp.servers` had entries AND the plugin registered the\n * same tools via `api.registerTool`, claude-cli's bundleMcp would spawn\n * its own copy and OpenClaw would suffix-disambiguate the duplicates.\n *\n * The single-source-of-truth refactor stops mirror-writing the alfe\n * store into openclaw.json, so `mcp.servers` is empty in practice. The\n * native path on claude-cli has nothing to spawn, our plugin's IPC path\n * is the only one in play, and we register on every backend.\n *\n * The escape hatch survives: a user who hand-edits `mcp.servers` in\n * openclaw.json (private dev MCP, etc.) gets the native path back —\n * this function returns true, the plugin no-ops, and claude-cli/codex-cli\n * spawn that entry themselves.\n */\n\ninterface ConfigLike {\n mcp?: {\n servers?: Record<string, unknown>;\n };\n}\n\n/**\n * Returns true when openclaw.json#mcp.servers has at least one entry —\n * signalling that the native bundleMcp path is in play for THIS run\n * (regardless of backend) and the plugin should defer to it.\n *\n * Empty object / missing key → false → plugin registers IPC-proxied tools.\n */\nexport function hasNativeOpenclawMcpServers(cfg: ConfigLike | undefined): boolean {\n if (!cfg) return false;\n const servers = cfg.mcp?.servers;\n if (!servers || typeof servers !== 'object') return false;\n return Object.keys(servers).length > 0;\n}\n\n/**\n * Back-compat alias for callers still on the old name. Internal — drop\n * once we cut the next major.\n */\nexport const detectNativeBundleMcp = hasNativeOpenclawMcpServers;\n","/**\n * Minimal newline-delimited-JSON IPC client over Unix socket. Talks to\n * the alfe-gateway daemon at `~/.alfe/gateway.sock` so this plugin can\n * proxy `mcp.list_tools` / `mcp.call_tool` requests to the daemon-hosted\n * `McpBundler` rather than spawning its own children.\n *\n * Why minimal: the @alfe.ai/openclaw package already ships a full IPC\n * client with reconnect + event handling, but pulling it in would create\n * a (semantically) circular dep — plugin importing from its host. This\n * file is ~120 lines and only does what the bundler plugin needs.\n *\n * The daemon's IPC server (`packages/gateway/src/ipc-server.ts`) speaks:\n * Request: { type: 'req', id, method, params }\n * Response: { id, ok, payload?, error? }\n * Event: { type: 'event', event, payload } ← ignored here, we never subscribe.\n */\nimport { createConnection, type Socket } from 'node:net';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { randomUUID } from 'node:crypto';\n\nconst DEFAULT_SOCKET_PATH = join(homedir(), '.alfe', 'gateway.sock');\nconst RECONNECT_MIN_MS = 500;\nconst RECONNECT_MAX_MS = 10_000;\nconst REQUEST_TIMEOUT_MS = 30_000;\n\nexport interface IpcResponse<T = unknown> {\n id: string;\n ok: boolean;\n payload?: T;\n error?: { code: string; message: string };\n}\n\ninterface Logger {\n debug(msg: string, ctx?: Record<string, unknown>): void;\n info(msg: string, ctx?: Record<string, unknown>): void;\n warn(msg: string, ctx?: Record<string, unknown>): void;\n error(msg: string, ctx?: Record<string, unknown>): void;\n}\n\nexport interface McpIpcClientOptions {\n socketPath?: string;\n logger?: Logger;\n /** Plugin identity broadcast to the daemon on connect. */\n plugin: { name: string; version: string };\n /**\n * Fires every time the socket transitions from disconnected to\n * connected — i.e. on the initial connect AND on every reconnect after\n * the daemon restarts. Invoked AFTER `connected = true` is set so\n * `call(...)` from inside the handler will actually round-trip the\n * daemon (the previous shape called `refreshCachedTools` synchronously\n * inside the plugin's `startService` while `connected` was still false,\n * which silently returned `NOT_CONNECTED` and left the tool cache\n * permanently empty on the first session — see plugin.ts comment for\n * the QA Tester repro).\n *\n * Contract:\n * - Treated as fire-and-forget. The IPC client does NOT await the\n * handler's return value.\n * - Synchronous throws are caught + logged at warn — they will not\n * tear down the IPC client.\n * - If a handler returns a Promise that rejects later, the rejection\n * surfaces as an unhandled promise rejection. Wrap with `void` or\n * handle internally if you don't want that behaviour. The wired\n * handler in `plugin.ts` does exactly that:\n * `() => { void refreshCachedTools(api); }`.\n */\n onConnect?: () => void;\n}\n\ninterface PendingRequest {\n resolve: (response: IpcResponse) => void;\n timer: ReturnType<typeof setTimeout>;\n}\n\n/**\n * Plugin-side IPC client. Single instance per plugin lifecycle: start on\n * `registerService.start`, stop on `registerService.stop`. Calls\n * before the socket is connected return a typed `NOT_CONNECTED` response\n * so the plugin can fall back to \"no tools yet\" rather than throwing.\n */\nexport class McpIpcClient {\n private socket: Socket | null = null;\n private buffer = '';\n private backoffMs = RECONNECT_MIN_MS;\n private closed = false;\n private connected = false;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private pending = new Map<string, PendingRequest>();\n private readonly socketPath: string;\n private readonly logger?: Logger;\n private readonly plugin: { name: string; version: string };\n private readonly onConnect?: () => void;\n\n constructor(opts: McpIpcClientOptions) {\n this.socketPath = opts.socketPath ?? DEFAULT_SOCKET_PATH;\n this.logger = opts.logger;\n this.plugin = opts.plugin;\n this.onConnect = opts.onConnect;\n }\n\n isConnected(): boolean {\n return this.connected;\n }\n\n start(): void {\n this.closed = false;\n this.openSocket();\n }\n\n stop(): void {\n this.closed = true;\n this.clearReconnectTimer();\n for (const [id, pending] of this.pending) {\n clearTimeout(pending.timer);\n pending.resolve({\n id,\n ok: false,\n error: { code: 'CLIENT_STOPPED', message: 'IPC client stopped' },\n });\n }\n this.pending.clear();\n if (this.socket) {\n try {\n this.socket.end();\n } catch {\n // ignore — socket may already be in a half-closed state\n }\n this.socket = null;\n }\n this.connected = false;\n }\n\n async call<T = unknown>(\n method: string,\n params: Record<string, unknown> = {},\n ): Promise<IpcResponse<T>> {\n if (!this.connected || !this.socket) {\n return {\n id: '',\n ok: false,\n error: { code: 'NOT_CONNECTED', message: 'Not connected to alfe-gateway' },\n };\n }\n const id = randomUUID();\n return new Promise<IpcResponse<T>>((resolve) => {\n const timer = setTimeout(() => {\n this.pending.delete(id);\n resolve({\n id,\n ok: false,\n error: { code: 'TIMEOUT', message: `${method} timed out after ${String(REQUEST_TIMEOUT_MS)}ms` },\n });\n }, REQUEST_TIMEOUT_MS);\n this.pending.set(id, { resolve: resolve as (r: IpcResponse) => void, timer });\n try {\n this.socket?.write(`${JSON.stringify({ type: 'req', id, method, params })}\\n`);\n } catch (err) {\n clearTimeout(timer);\n this.pending.delete(id);\n resolve({\n id,\n ok: false,\n error: {\n code: 'SEND_FAILED',\n message: err instanceof Error ? err.message : String(err),\n },\n });\n }\n });\n }\n\n private openSocket(): void {\n if (this.closed) return;\n this.buffer = '';\n const socket = createConnection(this.socketPath, () => {\n this.logger?.info('[mcp-bundler/ipc] connected', { socket: this.socketPath });\n this.connected = true;\n this.backoffMs = RECONNECT_MIN_MS;\n // Best-effort register. We don't await the response — the daemon\n // routes mcp.* methods even on unregistered connections, so the\n // tool-list / tool-call path doesn't need register to complete.\n void this.call('register', {\n name: this.plugin.name,\n version: this.plugin.version,\n protocolVersion: 1,\n capabilities: ['mcp.list_tools', 'mcp.call_tool'],\n pid: process.pid,\n });\n // Notify caller AFTER `connected = true` so any `.call(...)` issued\n // from the handler (e.g. plugin.ts's tool-cache refresh) actually\n // makes it onto the wire instead of getting the NOT_CONNECTED\n // short-circuit at the top of `call()`.\n if (this.onConnect) {\n try {\n this.onConnect();\n } catch (err) {\n this.logger?.warn('[mcp-bundler/ipc] onConnect handler threw', {\n err: err instanceof Error ? err.message : String(err),\n });\n }\n }\n });\n this.socket = socket;\n\n socket.on('data', (data: Buffer) => {\n this.buffer += data.toString();\n this.processBuffer();\n });\n\n socket.on('close', () => {\n this.connected = false;\n for (const [id, pending] of this.pending) {\n clearTimeout(pending.timer);\n pending.resolve({\n id,\n ok: false,\n error: { code: 'DISCONNECTED', message: 'Connection closed' },\n });\n }\n this.pending.clear();\n this.socket = null;\n if (!this.closed) this.scheduleReconnect();\n });\n\n socket.on('error', (err: Error) => {\n this.logger?.debug('[mcp-bundler/ipc] socket error', { err: err.message });\n // 'close' fires after 'error'; reconnect handled there.\n });\n }\n\n private processBuffer(): void {\n let idx: number;\n while ((idx = this.buffer.indexOf('\\n')) !== -1) {\n const line = this.buffer.slice(0, idx).trim();\n this.buffer = this.buffer.slice(idx + 1);\n if (!line) continue;\n let parsed: unknown;\n try {\n parsed = JSON.parse(line);\n } catch {\n continue;\n }\n if (!isIpcResponse(parsed)) continue;\n const pending = this.pending.get(parsed.id);\n if (!pending) continue;\n clearTimeout(pending.timer);\n this.pending.delete(parsed.id);\n pending.resolve(parsed);\n }\n }\n\n private scheduleReconnect(): void {\n if (this.reconnectTimer) return;\n const delay = this.backoffMs;\n this.backoffMs = Math.min(this.backoffMs * 2, RECONNECT_MAX_MS);\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n this.openSocket();\n }, delay);\n if (typeof this.reconnectTimer === 'object' && 'unref' in this.reconnectTimer) {\n (this.reconnectTimer as { unref: () => void }).unref();\n }\n }\n\n private clearReconnectTimer(): void {\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n }\n}\n\nfunction isIpcResponse(value: unknown): value is IpcResponse {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'id' in value &&\n 'ok' in value &&\n typeof (value as { id: unknown }).id === 'string'\n );\n}\n","/**\n * @alfe.ai/openclaw-mcp-bundler — OpenClaw plugin entry\n *\n * Thin IPC client over the alfe-gateway daemon. The daemon hosts the\n * actual `McpBundler` (children + tool catalog), this plugin forwards\n * the OpenClaw tool-surface to that bundler via `mcp.list_tools` and\n * `mcp.call_tool` over `~/.alfe/gateway.sock`.\n *\n * Works on every LLM backend — `api.registerTool` registrations reach\n * MiniMax/Mistral/generic AND claude-cli/codex-cli. The previous\n * blanket no-op on claude-cli was load-bearing only when the same MCP\n * was ALSO in `openclaw.json#mcp.servers` (double registration);\n * since the alfe store no longer mirror-writes there, the native\n * bundleMcp path has nothing to spawn and we can register on every\n * backend uniformly.\n *\n * Escape hatch: a user who hand-adds an entry to\n * `openclaw.json#mcp.servers` for a private dev MCP gets the native\n * path back for that run — `hasNativeOpenclawMcpServers` returns true,\n * we no-op, claude-cli/codex-cli spawn that entry themselves.\n */\n\nimport type { McpToolDescriptor, McpToolCallResult } from '@alfe.ai/mcp-bundler';\nimport { hasNativeOpenclawMcpServers } from './cli-backend-detect.js';\nimport { McpIpcClient, type IpcResponse } from './ipc-client.js';\nimport { createRequire } from 'node:module';\nimport { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\nimport { randomUUID } from 'node:crypto';\nconst require = createRequire(import.meta.url);\nconst pkg = require('../package.json') as { version: string };\n\n// Disk-backed snapshot of the daemon's last-known tool catalogue. Tool-\n// discovery mode (used by OpenClaw whenever it builds the agent's tool\n// list — see `resolvePluginTools` in openclaw's loader) sets\n// `runtimeChannel: false`, so `api.registerService(...)` callbacks\n// (where the IPC client lives) NEVER run in that mode. Without a disk\n// snapshot the plugin contributes zero tools to the agent's list every\n// time it's loaded for tool-discovery — exactly the QA Tester symptom\n// traced 2026-06-09 on bundler v0.0.10. Cache file is written by\n// `refreshCachedTools` on every successful `mcp.list_tools` and read at\n// `activate` time. Lives under the alfe daemon's state dir so the same\n// box's full-mode and tool-discovery-mode loads share it.\nconst CACHE_FILE = join(homedir(), '.alfe', 'mcp', 'openclaw-bundler-cache.json');\n\n// ── Type shims for the OpenClaw plugin SDK ──────────────────────\n// We avoid a hard dep on the SDK to keep this plugin loadable across\n// OpenClaw versions; the surface used here is stable.\n\ninterface Logger {\n debug(msg: string, ...args: unknown[]): void;\n info(msg: string, ...args: unknown[]): void;\n warn(msg: string, ...args: unknown[]): void;\n error(msg: string, ...args: unknown[]): void;\n}\n\ninterface ConfigSnapshot {\n agents?: { defaults?: { model?: string; cliBackends?: Record<string, unknown> } };\n mcp?: { servers?: Record<string, unknown> };\n}\n\ninterface AgentToolResult {\n content: Record<string, unknown>[];\n details?: Record<string, unknown>;\n}\n\ninterface AgentTool {\n name: string;\n label?: string;\n description: string;\n parameters: Record<string, unknown>;\n execute: (\n toolCallId: string,\n params: unknown,\n signal?: AbortSignal,\n onUpdate?: (update: unknown) => void,\n ) => Promise<AgentToolResult>;\n}\n\ninterface ToolFactoryContext {\n config?: ConfigSnapshot;\n runtimeConfig?: ConfigSnapshot;\n getRuntimeConfig?: () => ConfigSnapshot | undefined;\n}\n\ninterface OpenClawPluginService {\n id: string;\n start: (ctx?: unknown) => void | Promise<void>;\n stop?: (ctx?: unknown) => void | Promise<void>;\n}\n\ninterface OpenClawPluginApi {\n logger: Logger;\n config?: ConfigSnapshot;\n pluginConfig?: { disabled?: boolean };\n registrationMode?: string;\n runtime?: { config?: { current?: () => ConfigSnapshot | undefined } };\n registerTool: (\n tool: AgentTool | ((ctx: ToolFactoryContext) => AgentTool | AgentTool[] | null | undefined),\n opts?: { name?: string; names?: string[]; optional?: boolean },\n ) => void;\n registerService?: (service: OpenClawPluginService) => void;\n}\n\n// ── Plugin state ───────────────────────────────────────────────\nlet ipcClient: McpIpcClient | undefined;\n// Module-scoped catalogue. We keep `McpToolDescriptor` here (rather\n// than pre-baked `AgentTool` objects) so the catalogue is JSON-\n// serialisable for the disk cache. `buildAgentTool` constructs an\n// `AgentTool` at factory-invocation time and closes over the live\n// `ipcClient` via the module-scoped reference, so the\n// tools work as soon as IPC is ready — without serialising the\n// IPC client into the cache.\nlet cachedDescriptors: McpToolDescriptor[] = [];\nlet refreshInFlight: Promise<void> | null = null;\nconst ACTIVATED_KEY = '__alfeMcpBundlerPluginActivated';\nconst g = globalThis as Record<string, unknown>;\n\nfunction resolveLiveConfig(api: OpenClawPluginApi, ctx?: ToolFactoryContext): ConfigSnapshot | undefined {\n // Prefer most-recent → ctx live → ctx snapshot → api.runtime live → api.config snapshot\n return (\n ctx?.runtimeConfig ??\n ctx?.getRuntimeConfig?.() ??\n api.runtime?.config?.current?.() ??\n ctx?.config ??\n api.config\n );\n}\n\nfunction buildAgentTool(api: OpenClawPluginApi, tool: McpToolDescriptor): AgentTool {\n return {\n name: tool.prefixed,\n label: tool.label,\n description: tool.description || `MCP tool ${tool.original} from server ${tool.server}`,\n // OpenClaw expects a JSON-Schema-shaped object. The MCP server already\n // gives us one in `tool.parameters`. Pass through unchanged.\n parameters: tool.parameters,\n async execute(_toolCallId, params) {\n // `ipcClient` is module-scoped and set by `startService` at runtime.\n // In tool-discovery mode (no service.start) `ipcClient` is\n // undefined here — return a graceful error so the LLM gets a\n // useful message rather than a hard crash. In full mode this\n // closure resolves at execute() time, after `service.start` has\n // wired the IPC client.\n if (!ipcClient?.isConnected()) {\n return errorResult(\n `mcp ${tool.prefixed}: bundler IPC not connected yet. Try again in a moment.`,\n );\n }\n const response = await ipcClient.call<McpToolCallResult>('mcp.call_tool', {\n name: tool.prefixed,\n args: params,\n });\n if (!response.ok || !response.payload) {\n api.logger.warn('[mcp-bundler] tool call failed', {\n tool: tool.prefixed,\n err: response.error?.message ?? 'unknown error',\n });\n return errorResult(\n response.error?.message\n ? `mcp ${tool.prefixed}: ${response.error.message}`\n : `mcp ${tool.prefixed}: call failed`,\n );\n }\n const result = response.payload;\n return {\n content: result.content,\n details: { isError: result.isError ?? false },\n };\n },\n };\n}\n\nfunction errorResult(message: string): AgentToolResult {\n return {\n content: [{ type: 'text', text: message }],\n details: { isError: true },\n };\n}\n\n/**\n * Async-aware shim around the module-scoped `ipcClient`. The management\n * tools and dynamic MCP tools registered in `activate()` execute LATER\n * (when the agent invokes them), but `ipcClient` is only set inside\n * `startService`. The `?.` chains return useful errors when the agent\n * tries to call a tool before the IPC is ready, instead of throwing.\n */\nasync function callIpc<T = unknown>(\n method: string,\n params: Record<string, unknown> = {},\n): Promise<IpcResponse<T>> {\n if (!ipcClient) {\n return {\n id: '',\n ok: false,\n error: { code: 'NOT_CONNECTED', message: 'Bundler IPC client not initialized yet' },\n };\n }\n return ipcClient.call<T>(method, params);\n}\n\n/**\n * Load the last-known tool catalogue from disk. Used at `activate()`\n * time so the agent's tool list contains real tools even in tool-\n * discovery mode (where `service.start` — and therefore the IPC\n * connection — never runs). Silently falls back to an empty list on\n * any read/parse failure so a first-run agent (no cache file yet)\n * still loads cleanly.\n */\nfunction isValidDescriptor(value: unknown): value is McpToolDescriptor {\n // Cheap shape check guarding against a corrupt or torn-write cache —\n // a half-written JSON array that parses but holds garbage entries\n // would otherwise reach `buildAgentTool` where `tool.prefixed` ends up\n // undefined and registers a tool with `name: undefined`. Catching the\n // shape here protects the agent's tool list from disk-level corruption.\n if (typeof value !== 'object' || value === null) return false;\n const v = value as Record<string, unknown>;\n return (\n typeof v.prefixed === 'string' &&\n typeof v.original === 'string' &&\n typeof v.server === 'string' &&\n typeof v.parameters === 'object' &&\n v.parameters !== null\n );\n}\n\nfunction loadCachedDescriptorsFromDisk(logger: Logger): McpToolDescriptor[] {\n try {\n const raw = readFileSync(CACHE_FILE, 'utf-8');\n const parsed = JSON.parse(raw) as unknown;\n if (!Array.isArray(parsed)) return [];\n return parsed.filter(isValidDescriptor);\n } catch (err) {\n if (err instanceof Error && (err as NodeJS.ErrnoException).code !== 'ENOENT') {\n logger.debug('[mcp-bundler] cache read failed', { err: err.message });\n }\n return [];\n }\n}\n\n/**\n * Persist the current catalogue to disk so the next tool-discovery\n * load sees the tools. Best-effort — disk failures are logged at\n * `warn` and otherwise swallowed.\n */\nfunction persistCachedDescriptorsToDisk(\n descriptors: McpToolDescriptor[],\n logger: Logger,\n): void {\n // Atomic write: write to a temp sibling then rename. `writeFileSync` is\n // NOT atomic on its own — two processes racing on the same path (a\n // daemon restart concurrent with a tool-discovery load's inline\n // refresh, say) can interleave bytes and corrupt the JSON. The\n // tool-discovery load then silently drops the catalogue until the\n // next successful refresh. `renameSync` is atomic on the same\n // filesystem on every platform we ship to.\n const tmp = `${CACHE_FILE}.${randomUUID()}.tmp`;\n try {\n mkdirSync(dirname(CACHE_FILE), { recursive: true });\n writeFileSync(tmp, JSON.stringify(descriptors));\n renameSync(tmp, CACHE_FILE);\n } catch (err) {\n logger.warn('[mcp-bundler] cache write failed', {\n err: err instanceof Error ? err.message : String(err),\n });\n }\n}\n\n/**\n * Self-service tools the agent can call to add/remove MCP servers in\n * the Alfe bundler store. The agent gets the same surface the CLI's\n * `alfe mcp add/remove/list` commands expose — same IPC methods, same\n * `'manual'` ownership semantics. Daemon-owned (`'cli'` /\n * `'integration:*'`) entries can't be clobbered.\n */\nfunction registerAgentMcpManagementTools(api: OpenClawPluginApi): void {\n // Tools are registered during `activate()` (whether or not the\n // service has started yet), so we can't capture a live `ipcClient`\n // here. Each tool's `execute()` reaches the module-scoped\n // `ipcClient` via `callIpc()` at invocation time — by then,\n // `service.start` has wired the IPC client in full mode. In tool-\n // discovery mode (where service.start never runs) these tools still\n // appear in the agent's tool list, but executing them returns a\n // `NOT_CONNECTED` error from `callIpc` rather than crashing.\n\n api.registerTool({\n name: 'alfe_mcp_list',\n label: 'List Alfe MCP servers',\n description:\n 'List every MCP server currently registered in the Alfe bundler store, with id, owner, and transport. Use this before adding a new server to avoid name collisions or to confirm what is currently available to call.',\n parameters: { type: 'object', properties: {}, additionalProperties: false },\n async execute() {\n const response = await callIpc<{ servers: unknown[] }>('mcp.list_servers', {});\n if (!response.ok) {\n return errorResult(response.error?.message ?? 'mcp_list failed');\n }\n return {\n content: [{ type: 'text', text: JSON.stringify(response.payload?.servers ?? [], null, 2) }],\n details: { isError: false },\n };\n },\n });\n\n api.registerTool({\n name: 'alfe_mcp_add',\n label: 'Register an MCP server with Alfe',\n description:\n 'Register a new MCP server in the Alfe bundler store. The Alfe daemon will spawn the child and its tools become available to the agent on the next turn (no runtime restart). Use a stdio command for local-process MCPs (most common) or url+transport for remote SSE / streamable-http MCPs.',\n parameters: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'Unique id under the bundler store. Letters, digits, hyphens. Becomes the `mcp__<id>__` prefix on every tool name from this server.',\n },\n command: {\n type: 'string',\n description: 'Executable to spawn for stdio transport (e.g. \"npx\", \"uvx\", \"/path/to/bin\"). Required for stdio mode.',\n },\n args: {\n type: 'array',\n items: { type: 'string' },\n description: 'Arguments to pass to the command. Optional.',\n },\n env: {\n type: 'object',\n additionalProperties: { type: 'string' },\n description: 'Environment variables for the child process. Optional.',\n },\n cwd: {\n type: 'string',\n description: 'Working directory for the child process. Optional.',\n },\n url: {\n type: 'string',\n description: 'Remote MCP endpoint URL. Required for sse / streamable-http transport.',\n },\n transport: {\n type: 'string',\n enum: ['sse', 'streamable-http'],\n description: 'Remote MCP transport. Defaults to \"sse\" when url is set.',\n },\n headers: {\n type: 'object',\n additionalProperties: { type: 'string' },\n description: 'HTTP headers for remote MCP transports. Optional.',\n },\n },\n required: ['id'],\n additionalProperties: false,\n },\n async execute(_toolCallId, params) {\n const response = await callIpc<{ id: string }>('mcp.add_server', (params ?? {}) as Record<string, unknown>);\n if (!response.ok) {\n return errorResult(response.error?.message ?? 'mcp_add failed');\n }\n return {\n content: [{ type: 'text', text: `Registered MCP server \"${response.payload?.id ?? '?'}\". Tools will appear on the next turn.` }],\n details: { isError: false },\n };\n },\n });\n\n api.registerTool({\n name: 'alfe_mcp_remove',\n label: 'Unregister an MCP server from Alfe',\n description:\n 'Remove an MCP server you previously registered with alfe_mcp_add. Only entries you registered (owner=manual) can be removed this way — integration-installed servers (atlassian, github, etc.) and the built-in alfe-platform server are owner-protected and must be removed via the dashboard or CLI.',\n parameters: {\n type: 'object',\n properties: {\n id: { type: 'string', description: 'Bundler store id of the entry to remove.' },\n },\n required: ['id'],\n additionalProperties: false,\n },\n async execute(_toolCallId, params) {\n const response = await callIpc<{ removed: boolean }>(\n 'mcp.remove_server',\n (params ?? {}) as Record<string, unknown>,\n );\n if (!response.ok) {\n return errorResult(response.error?.message ?? 'mcp_remove failed');\n }\n const removed = response.payload?.removed ?? false;\n return {\n content: [\n {\n type: 'text',\n text: removed\n ? `Removed MCP server. Its tools will disappear on the next turn.`\n : `No MCP server matched — nothing to remove.`,\n },\n ],\n details: { isError: false },\n };\n },\n });\n}\n\n/**\n * Pull the latest tool catalog from the daemon-hosted bundler. Coalesces\n * concurrent calls so the LLM hitting the factory many times in a tight\n * loop only triggers one in-flight IPC request.\n */\nfunction refreshCachedTools(api: OpenClawPluginApi): Promise<void> {\n if (!ipcClient) return Promise.resolve();\n if (refreshInFlight) return refreshInFlight;\n refreshInFlight = (async () => {\n try {\n const response = await callIpc<{ tools: McpToolDescriptor[] }>('mcp.list_tools', {});\n if (!response.ok || !response.payload) {\n // NOT_CONNECTED / TIMEOUT / etc — keep the prior cache so a\n // transient daemon hiccup doesn't drop tools mid-session.\n api.logger.debug('[mcp-bundler] list_tools failed', {\n err: response.error?.message ?? 'unknown',\n });\n return;\n }\n cachedDescriptors = response.payload.tools;\n // Persist for tool-discovery-mode loads — that's how this fix\n // lands tools on the agent's tool list. See CACHE_FILE comment.\n persistCachedDescriptorsToDisk(cachedDescriptors, api.logger);\n } finally {\n refreshInFlight = null;\n }\n })();\n return refreshInFlight;\n}\n\nconst plugin = {\n id: '@alfe.ai/openclaw-mcp-bundler',\n name: 'Alfe MCP Bundler',\n description: 'Proxies the daemon-hosted MCP bundler tools to the LLM',\n version: pkg.version,\n contracts: {\n // OpenClaw's `contracts.tools` matcher (in\n // `loader.js#registerTool` and `tools.js#resolvePluginTools`) uses\n // strict literal `Set.has()` — it does NOT honour glob patterns. So\n // the previous `'mcp__*'` entry was non-functional: every tool the\n // factory returned whose name started with anything else (e.g.\n // `atlassian-atlassian__atlassian_list_accounts`) was silently\n // rejected as \"undeclared\" at runtime, and never surfaced to the\n // agent. Diagnosed 2026-06-09 on QA Tester after the v0.0.9 IPC\n // fix proved correct but tools still didn't appear in chat\n // sessions. See CHANGELOG for the trace.\n //\n // This list enumerates every tool name the proxy can return today.\n // The three `alfe_mcp_*` entries are the agent self-service\n // surface. The `<provider>-<provider>__<provider>_*` entries are\n // the Pattern-A proxy meta-tools (list_accounts /\n // check_connection / refresh_token) each provider's MCP server\n // surfaces before a cloudId is selected. The `alfe-platform__*`\n // entries cover the CLI-managed alfe-platform MCP server.\n //\n // ADDING A NEW INTEGRATION: append its 3 Pattern-A meta-tools\n // here AND any post-cloudId tools the proxy exposes that you want\n // the agent to be able to invoke. There is no shortcut — until\n // OpenClaw learns glob patterns in `contracts.tools` (tracked\n // separately) every tool name must be listed literally.\n tools: [\n 'alfe_mcp_list',\n 'alfe_mcp_add',\n 'alfe_mcp_remove',\n 'atlassian-atlassian__atlassian_list_accounts',\n 'atlassian-atlassian__atlassian_check_connection',\n 'atlassian-atlassian__atlassian_refresh_token',\n 'notion-notion__notion_list_accounts',\n 'notion-notion__notion_check_connection',\n 'notion-notion__notion_refresh_token',\n 'github-github__github_list_accounts',\n 'github-github__github_check_connection',\n 'github-github__github_refresh_token',\n 'microsoft-microsoft__microsoft_list_accounts',\n 'microsoft-microsoft__microsoft_check_connection',\n 'microsoft-microsoft__microsoft_refresh_token',\n 'google-google__google_list_accounts',\n 'google-google__google_check_connection',\n 'google-google__google_refresh_token',\n 'slack-slack__slack_list_accounts',\n 'slack-slack__slack_check_connection',\n 'slack-slack__slack_refresh_token',\n 'discord-discord__discord_list_accounts',\n 'discord-discord__discord_check_connection',\n 'discord-discord__discord_refresh_token',\n 'mobile-mobile__mobile_list_accounts',\n 'mobile-mobile__mobile_check_connection',\n 'mobile-mobile__mobile_refresh_token',\n 'myob-myob__myob_list_accounts',\n 'myob-myob__myob_check_connection',\n 'myob-myob__myob_refresh_token',\n 'xero-xero__xero_list_accounts',\n 'xero-xero__xero_check_connection',\n 'xero-xero__xero_refresh_token',\n 'alfe-platform__session_status',\n 'alfe-platform__gateway_status',\n ],\n },\n\n activate(api: OpenClawPluginApi) {\n const log = api.logger;\n\n if (g[ACTIVATED_KEY] === true) {\n log.debug('[mcp-bundler] already activated — skipping duplicate');\n return;\n }\n g[ACTIVATED_KEY] = true;\n\n if (api.pluginConfig?.disabled) {\n g[ACTIVATED_KEY] = false;\n log.info('[mcp-bundler] disabled via plugin config — no-op');\n return;\n }\n\n if (!api.registerService) {\n g[ACTIVATED_KEY] = false;\n log.warn('[mcp-bundler] api.registerService unavailable — plugin is a no-op on this runtime');\n return;\n }\n\n const deferToNative = hasNativeOpenclawMcpServers(resolveLiveConfig(api));\n if (deferToNative) {\n log.info(\n '[mcp-bundler] openclaw.json#mcp.servers has entries — MCP-proxy factory will defer to native bundleMcp, but agent self-service tools (alfe_mcp_*) stay available',\n );\n }\n\n // ── Register tools synchronously in activate() ──────────────\n //\n // OpenClaw loads plugins in \"tool-discovery\" mode whenever it\n // builds the agent's tool list (see `resolvePluginTools` in\n // `loader.js`). The registration plan for that mode sets\n // `runtimeChannel: false`, which means `api.registerService(...)`\n // start/stop callbacks are NEVER invoked there — only `activate`\n // runs. v0.0.8 → v0.0.10 put tool registration inside\n // `service.start`, so tool-discovery loads contributed ZERO tools\n // to the agent's list (QA Tester repro traced 2026-06-09). We now\n // register every tool here in `activate` so they show up in both\n // modes. The IPC client + lifecycle stays in `registerService` —\n // each tool's `execute()` reaches the module-scoped `ipcClient`\n // lazily via `callIpc()`, so tools registered without a live IPC\n // still show up in the catalogue and return a graceful\n // NOT_CONNECTED error if invoked before `service.start` lands.\n\n // Seed the descriptor cache from disk so tool-discovery mode\n // (which has no IPC) can still return real MCP tools to the\n // agent. Refreshed and re-persisted by `refreshCachedTools` on\n // every IPC connect.\n cachedDescriptors = loadCachedDescriptorsFromDisk(log);\n\n // Agent self-service tools — always available, regardless of the\n // native-bundleMcp escape hatch. The alfe store is independent of\n // openclaw.json so agents can manage their MCP roster in either\n // mode.\n registerAgentMcpManagementTools(api);\n\n if (!deferToNative) {\n api.registerTool((ctx: ToolFactoryContext) => {\n const live = resolveLiveConfig(api, ctx);\n if (hasNativeOpenclawMcpServers(live)) return null;\n // Best-effort background refresh so each turn picks up any new\n // entries the daemon's bundler has discovered (only effective\n // when the IPC client is up — i.e. in full mode).\n void refreshCachedTools(api);\n return cachedDescriptors.map((d) => buildAgentTool(api, d));\n }, { optional: true });\n }\n\n // ── Service lifecycle: IPC client only ─────────────────────\n const startService = (): void => {\n ipcClient = new McpIpcClient({\n logger: log,\n plugin: { name: plugin.id, version: plugin.version },\n // Refresh the tool cache the moment the IPC connection lands.\n // `ipcClient.start()` only initiates the socket connect —\n // `connected` flips true ~50-300ms later inside the\n // createConnection callback. Hooking onConnect makes the first\n // refresh fire AFTER the socket lands, and re-fires on every\n // reconnect after a daemon restart. The refresh result is\n // persisted to disk so the NEXT tool-discovery load sees it.\n onConnect: () => {\n void refreshCachedTools(api);\n },\n });\n ipcClient.start();\n\n if (deferToNative) {\n log.info(`[mcp-bundler] activated v${plugin.version} (management tools only — native bundleMcp owns runtime)`);\n return;\n }\n\n log.info(`[mcp-bundler] activated v${plugin.version} (thin IPC client, tools registered in activate)`);\n };\n\n const stopService = (): Promise<void> => {\n log.info('[mcp-bundler] stopping');\n if (ipcClient) {\n ipcClient.stop();\n ipcClient = undefined;\n }\n // Keep `cachedDescriptors` and the on-disk cache intact across\n // restarts — that's the WHOLE POINT of the disk cache. We only\n // tear down the live IPC reference here.\n refreshInFlight = null;\n // Reset the duplicate-activate guard so OpenClaw can re-run\n // `activate(api)` after a `service.stop` without going through a\n // full `deactivate`. Without this, a stop-then-reactivate path\n // (e.g. an OpenClaw reload that doesn't tear the plugin module\n // down) would silently skip ALL tool registration on the second\n // activate and the agent ends up with no bundler tools. The\n // guard's only job is preventing the same module from running\n // activate() twice concurrently — once the service is stopped,\n // that risk is gone.\n g[ACTIVATED_KEY] = false;\n return Promise.resolve();\n };\n\n api.registerService({\n id: 'alfe-mcp-bundler',\n start: () => { startService(); },\n stop: async () => { await stopService(); },\n });\n },\n\n deactivate(api: OpenClawPluginApi) {\n g[ACTIVATED_KEY] = false;\n api.logger.debug('[mcp-bundler] deactivating');\n },\n};\n\nexport { type IpcResponse };\nexport default plugin;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"plugin2.js","names":[],"sources":["../src/cli-backend-detect.ts","../src/ipc-client.ts","../src/plugin.ts"],"sourcesContent":["/**\n * Decide whether to defer to OpenClaw's native `bundleMcp:true` path for\n * this run. Pre-May-2026 this gated on backend type (claude-cli /\n * codex-cli), but the actual hazard was double-registration: when\n * `openclaw.json#mcp.servers` had entries AND the plugin registered the\n * same tools via `api.registerTool`, claude-cli's bundleMcp would spawn\n * its own copy and OpenClaw would suffix-disambiguate the duplicates.\n *\n * The single-source-of-truth refactor stops mirror-writing the alfe\n * store into openclaw.json, so `mcp.servers` is empty in practice. The\n * native path on claude-cli has nothing to spawn, our plugin's IPC path\n * is the only one in play, and we register on every backend.\n *\n * The escape hatch survives: a user who hand-edits `mcp.servers` in\n * openclaw.json (private dev MCP, etc.) gets the native path back —\n * this function returns true, the plugin no-ops, and claude-cli/codex-cli\n * spawn that entry themselves.\n */\n\ninterface ConfigLike {\n mcp?: {\n servers?: Record<string, unknown>;\n };\n}\n\n/**\n * Returns true when openclaw.json#mcp.servers has at least one entry —\n * signalling that the native bundleMcp path is in play for THIS run\n * (regardless of backend) and the plugin should defer to it.\n *\n * Empty object / missing key → false → plugin registers IPC-proxied tools.\n */\nexport function hasNativeOpenclawMcpServers(cfg: ConfigLike | undefined): boolean {\n if (!cfg) return false;\n const servers = cfg.mcp?.servers;\n if (!servers || typeof servers !== 'object') return false;\n return Object.keys(servers).length > 0;\n}\n\n/**\n * Back-compat alias for callers still on the old name. Internal — drop\n * once we cut the next major.\n */\nexport const detectNativeBundleMcp = hasNativeOpenclawMcpServers;\n","/**\n * Minimal newline-delimited-JSON IPC client over Unix socket. Talks to\n * the alfe-gateway daemon at `~/.alfe/gateway.sock` so this plugin can\n * proxy `mcp.list_tools` / `mcp.call_tool` requests to the daemon-hosted\n * `McpBundler` rather than spawning its own children.\n *\n * Why minimal: the @alfe.ai/openclaw package already ships a full IPC\n * client with reconnect + event handling, but pulling it in would create\n * a (semantically) circular dep — plugin importing from its host. This\n * file is ~120 lines and only does what the bundler plugin needs.\n *\n * The daemon's IPC server (`packages/gateway/src/ipc-server.ts`) speaks:\n * Request: { type: 'req', id, method, params }\n * Response: { id, ok, payload?, error? }\n * Event: { type: 'event', event, payload } ← ignored here, we never subscribe.\n */\nimport { createConnection, type Socket } from 'node:net';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { randomUUID } from 'node:crypto';\n\nconst DEFAULT_SOCKET_PATH = join(homedir(), '.alfe', 'gateway.sock');\nconst RECONNECT_MIN_MS = 500;\nconst RECONNECT_MAX_MS = 10_000;\nconst REQUEST_TIMEOUT_MS = 30_000;\n\nexport interface IpcResponse<T = unknown> {\n id: string;\n ok: boolean;\n payload?: T;\n error?: { code: string; message: string };\n}\n\ninterface Logger {\n debug(msg: string, ctx?: Record<string, unknown>): void;\n info(msg: string, ctx?: Record<string, unknown>): void;\n warn(msg: string, ctx?: Record<string, unknown>): void;\n error(msg: string, ctx?: Record<string, unknown>): void;\n}\n\nexport interface McpIpcClientOptions {\n socketPath?: string;\n logger?: Logger;\n /** Plugin identity broadcast to the daemon on connect. */\n plugin: { name: string; version: string };\n /**\n * Fires every time the socket transitions from disconnected to\n * connected — i.e. on the initial connect AND on every reconnect after\n * the daemon restarts. Invoked AFTER `connected = true` is set so\n * `call(...)` from inside the handler will actually round-trip the\n * daemon (the previous shape called `refreshCachedTools` synchronously\n * inside the plugin's `startService` while `connected` was still false,\n * which silently returned `NOT_CONNECTED` and left the tool cache\n * permanently empty on the first session — see plugin.ts comment for\n * the QA Tester repro).\n *\n * Contract:\n * - Treated as fire-and-forget. The IPC client does NOT await the\n * handler's return value.\n * - Synchronous throws are caught + logged at warn — they will not\n * tear down the IPC client.\n * - If a handler returns a Promise that rejects later, the rejection\n * surfaces as an unhandled promise rejection. Wrap with `void` or\n * handle internally if you don't want that behaviour. The wired\n * handler in `plugin.ts` does exactly that:\n * `() => { void refreshCachedTools(api); }`.\n */\n onConnect?: () => void;\n}\n\ninterface PendingRequest {\n resolve: (response: IpcResponse) => void;\n timer: ReturnType<typeof setTimeout>;\n}\n\n/**\n * Plugin-side IPC client. Single instance per plugin lifecycle: start on\n * `registerService.start`, stop on `registerService.stop`. Calls\n * before the socket is connected return a typed `NOT_CONNECTED` response\n * so the plugin can fall back to \"no tools yet\" rather than throwing.\n */\nexport class McpIpcClient {\n private socket: Socket | null = null;\n private buffer = '';\n private backoffMs = RECONNECT_MIN_MS;\n private closed = false;\n private connected = false;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private pending = new Map<string, PendingRequest>();\n private readonly socketPath: string;\n private readonly logger?: Logger;\n private readonly plugin: { name: string; version: string };\n private readonly onConnect?: () => void;\n\n constructor(opts: McpIpcClientOptions) {\n this.socketPath = opts.socketPath ?? DEFAULT_SOCKET_PATH;\n this.logger = opts.logger;\n this.plugin = opts.plugin;\n this.onConnect = opts.onConnect;\n }\n\n isConnected(): boolean {\n return this.connected;\n }\n\n start(): void {\n this.closed = false;\n this.openSocket();\n }\n\n stop(): void {\n this.closed = true;\n this.clearReconnectTimer();\n for (const [id, pending] of this.pending) {\n clearTimeout(pending.timer);\n pending.resolve({\n id,\n ok: false,\n error: { code: 'CLIENT_STOPPED', message: 'IPC client stopped' },\n });\n }\n this.pending.clear();\n if (this.socket) {\n try {\n this.socket.end();\n } catch {\n // ignore — socket may already be in a half-closed state\n }\n this.socket = null;\n }\n this.connected = false;\n }\n\n async call<T = unknown>(\n method: string,\n params: Record<string, unknown> = {},\n ): Promise<IpcResponse<T>> {\n if (!this.connected || !this.socket) {\n return {\n id: '',\n ok: false,\n error: { code: 'NOT_CONNECTED', message: 'Not connected to alfe-gateway' },\n };\n }\n const id = randomUUID();\n return new Promise<IpcResponse<T>>((resolve) => {\n const timer = setTimeout(() => {\n this.pending.delete(id);\n resolve({\n id,\n ok: false,\n error: { code: 'TIMEOUT', message: `${method} timed out after ${String(REQUEST_TIMEOUT_MS)}ms` },\n });\n }, REQUEST_TIMEOUT_MS);\n this.pending.set(id, { resolve: resolve as (r: IpcResponse) => void, timer });\n try {\n this.socket?.write(`${JSON.stringify({ type: 'req', id, method, params })}\\n`);\n } catch (err) {\n clearTimeout(timer);\n this.pending.delete(id);\n resolve({\n id,\n ok: false,\n error: {\n code: 'SEND_FAILED',\n message: err instanceof Error ? err.message : String(err),\n },\n });\n }\n });\n }\n\n private openSocket(): void {\n if (this.closed) return;\n this.buffer = '';\n const socket = createConnection(this.socketPath, () => {\n this.logger?.info('[mcp-bundler/ipc] connected', { socket: this.socketPath });\n this.connected = true;\n this.backoffMs = RECONNECT_MIN_MS;\n // Best-effort register. We don't await the response — the daemon\n // routes mcp.* methods even on unregistered connections, so the\n // tool-list / tool-call path doesn't need register to complete.\n void this.call('register', {\n name: this.plugin.name,\n version: this.plugin.version,\n protocolVersion: 1,\n capabilities: ['mcp.list_tools', 'mcp.call_tool'],\n pid: process.pid,\n });\n // Notify caller AFTER `connected = true` so any `.call(...)` issued\n // from the handler (e.g. plugin.ts's tool-cache refresh) actually\n // makes it onto the wire instead of getting the NOT_CONNECTED\n // short-circuit at the top of `call()`.\n if (this.onConnect) {\n try {\n this.onConnect();\n } catch (err) {\n this.logger?.warn('[mcp-bundler/ipc] onConnect handler threw', {\n err: err instanceof Error ? err.message : String(err),\n });\n }\n }\n });\n this.socket = socket;\n\n socket.on('data', (data: Buffer) => {\n this.buffer += data.toString();\n this.processBuffer();\n });\n\n socket.on('close', () => {\n this.connected = false;\n for (const [id, pending] of this.pending) {\n clearTimeout(pending.timer);\n pending.resolve({\n id,\n ok: false,\n error: { code: 'DISCONNECTED', message: 'Connection closed' },\n });\n }\n this.pending.clear();\n this.socket = null;\n if (!this.closed) this.scheduleReconnect();\n });\n\n socket.on('error', (err: Error) => {\n this.logger?.debug('[mcp-bundler/ipc] socket error', { err: err.message });\n // 'close' fires after 'error'; reconnect handled there.\n });\n }\n\n private processBuffer(): void {\n let idx: number;\n while ((idx = this.buffer.indexOf('\\n')) !== -1) {\n const line = this.buffer.slice(0, idx).trim();\n this.buffer = this.buffer.slice(idx + 1);\n if (!line) continue;\n let parsed: unknown;\n try {\n parsed = JSON.parse(line);\n } catch {\n continue;\n }\n if (!isIpcResponse(parsed)) continue;\n const pending = this.pending.get(parsed.id);\n if (!pending) continue;\n clearTimeout(pending.timer);\n this.pending.delete(parsed.id);\n pending.resolve(parsed);\n }\n }\n\n private scheduleReconnect(): void {\n if (this.reconnectTimer) return;\n const delay = this.backoffMs;\n this.backoffMs = Math.min(this.backoffMs * 2, RECONNECT_MAX_MS);\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n this.openSocket();\n }, delay);\n if (typeof this.reconnectTimer === 'object' && 'unref' in this.reconnectTimer) {\n (this.reconnectTimer as { unref: () => void }).unref();\n }\n }\n\n private clearReconnectTimer(): void {\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n }\n}\n\nfunction isIpcResponse(value: unknown): value is IpcResponse {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'id' in value &&\n 'ok' in value &&\n typeof (value as { id: unknown }).id === 'string'\n );\n}\n","/**\n * @alfe.ai/openclaw-mcp-bundler — OpenClaw plugin entry\n *\n * Thin IPC client over the alfe-gateway daemon. The daemon hosts the\n * actual `McpBundler` (children + tool catalog), this plugin forwards\n * the OpenClaw tool-surface to that bundler via `mcp.list_tools` and\n * `mcp.call_tool` over `~/.alfe/gateway.sock`.\n *\n * Works on every LLM backend — `api.registerTool` registrations reach\n * MiniMax/Mistral/generic AND claude-cli/codex-cli. The previous\n * blanket no-op on claude-cli was load-bearing only when the same MCP\n * was ALSO in `openclaw.json#mcp.servers` (double registration);\n * since the alfe store no longer mirror-writes there, the native\n * bundleMcp path has nothing to spawn and we can register on every\n * backend uniformly.\n *\n * Escape hatch: a user who hand-adds an entry to\n * `openclaw.json#mcp.servers` for a private dev MCP gets the native\n * path back for that run — `hasNativeOpenclawMcpServers` returns true,\n * we no-op, claude-cli/codex-cli spawn that entry themselves.\n */\n\nimport type { McpToolDescriptor, McpToolCallResult } from '@alfe.ai/mcp-bundler';\nimport { hasNativeOpenclawMcpServers } from './cli-backend-detect.js';\nimport { McpIpcClient, type IpcResponse } from './ipc-client.js';\nimport { createRequire } from 'node:module';\nimport { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\nimport { randomUUID } from 'node:crypto';\nconst require = createRequire(import.meta.url);\nconst pkg = require('../package.json') as { version: string };\n\n// Disk-backed snapshot of the daemon's last-known tool catalogue. Tool-\n// discovery mode (used by OpenClaw whenever it builds the agent's tool\n// list — see `resolvePluginTools` in openclaw's loader) sets\n// `runtimeChannel: false`, so `api.registerService(...)` callbacks\n// (where the IPC client lives) NEVER run in that mode. Without a disk\n// snapshot the plugin contributes zero tools to the agent's list every\n// time it's loaded for tool-discovery — exactly the QA Tester symptom\n// traced 2026-06-09 on bundler v0.0.10. Cache file is written by\n// `refreshCachedTools` on every successful `mcp.list_tools` and read at\n// `activate` time. Lives under the alfe daemon's state dir so the same\n// box's full-mode and tool-discovery-mode loads share it.\nconst CACHE_FILE = join(homedir(), '.alfe', 'mcp', 'openclaw-bundler-cache.json');\n\n// ── Type shims for the OpenClaw plugin SDK ──────────────────────\n// We avoid a hard dep on the SDK to keep this plugin loadable across\n// OpenClaw versions; the surface used here is stable.\n\ninterface Logger {\n debug(msg: string, ...args: unknown[]): void;\n info(msg: string, ...args: unknown[]): void;\n warn(msg: string, ...args: unknown[]): void;\n error(msg: string, ...args: unknown[]): void;\n}\n\ninterface ConfigSnapshot {\n agents?: { defaults?: { model?: string; cliBackends?: Record<string, unknown> } };\n mcp?: { servers?: Record<string, unknown> };\n}\n\ninterface AgentToolResult {\n content: Record<string, unknown>[];\n details?: Record<string, unknown>;\n}\n\ninterface AgentTool {\n name: string;\n label?: string;\n description: string;\n parameters: Record<string, unknown>;\n execute: (\n toolCallId: string,\n params: unknown,\n signal?: AbortSignal,\n onUpdate?: (update: unknown) => void,\n ) => Promise<AgentToolResult>;\n}\n\ninterface ToolFactoryContext {\n config?: ConfigSnapshot;\n runtimeConfig?: ConfigSnapshot;\n getRuntimeConfig?: () => ConfigSnapshot | undefined;\n}\n\ninterface OpenClawPluginService {\n id: string;\n start: (ctx?: unknown) => void | Promise<void>;\n stop?: (ctx?: unknown) => void | Promise<void>;\n}\n\ninterface OpenClawPluginApi {\n logger: Logger;\n config?: ConfigSnapshot;\n pluginConfig?: { disabled?: boolean };\n registrationMode?: string;\n runtime?: { config?: { current?: () => ConfigSnapshot | undefined } };\n registerTool: (\n tool: AgentTool | ((ctx: ToolFactoryContext) => AgentTool | AgentTool[] | null | undefined),\n opts?: { name?: string; names?: string[]; optional?: boolean },\n ) => void;\n registerService?: (service: OpenClawPluginService) => void;\n}\n\n// ── Plugin state ───────────────────────────────────────────────\nlet ipcClient: McpIpcClient | undefined;\n// Module-scoped catalogue. We keep `McpToolDescriptor` here (rather\n// than pre-baked `AgentTool` objects) so the catalogue is JSON-\n// serialisable for the disk cache. `buildAgentTool` constructs an\n// `AgentTool` at factory-invocation time and closes over the live\n// `ipcClient` via the module-scoped reference, so the\n// tools work as soon as IPC is ready — without serialising the\n// IPC client into the cache.\nlet cachedDescriptors: McpToolDescriptor[] = [];\nlet refreshInFlight: Promise<void> | null = null;\nlet periodicRefreshTimer: ReturnType<typeof setInterval> | null = null;\nconst ACTIVATED_KEY = '__alfeMcpBundlerPluginActivated';\nconst g = globalThis as Record<string, unknown>;\n\n// Pre-warm budget for `service.start`. The daemon's bundler may take\n// 100-500ms to do the first `mcp.list_tools` round-trip; spawning\n// children via npx can push this higher. 3s is generous enough to\n// cover a cold start without blocking gateway startup indefinitely\n// if the socket is missing. The warn log on timeout (NOT silent) is\n// how we noticed the v0.0.8-v0.0.11 onStartup regression after the\n// fact — keep it loud.\nconst PREWARM_TIMEOUT_MS = 3000;\n\n// Periodic refresh cadence. The bundler's `onConnect` refresh fires\n// ONCE; if the daemon's MCP children come online after that initial\n// `mcp.list_tools` returns (e.g. a slow npx-fetched provider proxy),\n// without polling the bundler's snapshot stays stuck on the partial\n// catalog it captured at startup. 10s is small enough for the LLM's\n// next-turn tool list to converge while staying well below the IPC\n// cost threshold (one `mcp.list_tools` call → tiny daemon-local\n// payload, no remote network).\nconst PERIODIC_REFRESH_INTERVAL_MS = 10_000;\n\nfunction resolveLiveConfig(api: OpenClawPluginApi, ctx?: ToolFactoryContext): ConfigSnapshot | undefined {\n // Prefer most-recent → ctx live → ctx snapshot → api.runtime live → api.config snapshot\n return (\n ctx?.runtimeConfig ??\n ctx?.getRuntimeConfig?.() ??\n api.runtime?.config?.current?.() ??\n ctx?.config ??\n api.config\n );\n}\n\nfunction buildAgentTool(api: OpenClawPluginApi, tool: McpToolDescriptor): AgentTool {\n return {\n name: tool.prefixed,\n label: tool.label,\n description: tool.description || `MCP tool ${tool.original} from server ${tool.server}`,\n // OpenClaw expects a JSON-Schema-shaped object. The MCP server already\n // gives us one in `tool.parameters`. Pass through unchanged.\n parameters: tool.parameters,\n async execute(_toolCallId, params) {\n // `ipcClient` is module-scoped and set by `startService` at runtime.\n // In tool-discovery mode (no service.start) `ipcClient` is\n // undefined here — return a graceful error so the LLM gets a\n // useful message rather than a hard crash. In full mode this\n // closure resolves at execute() time, after `service.start` has\n // wired the IPC client.\n if (!ipcClient?.isConnected()) {\n return errorResult(\n `mcp ${tool.prefixed}: bundler IPC not connected yet. Try again in a moment.`,\n );\n }\n const response = await ipcClient.call<McpToolCallResult>('mcp.call_tool', {\n name: tool.prefixed,\n args: params,\n });\n if (!response.ok || !response.payload) {\n api.logger.warn('[mcp-bundler] tool call failed', {\n tool: tool.prefixed,\n err: response.error?.message ?? 'unknown error',\n });\n return errorResult(\n response.error?.message\n ? `mcp ${tool.prefixed}: ${response.error.message}`\n : `mcp ${tool.prefixed}: call failed`,\n );\n }\n const result = response.payload;\n return {\n content: result.content,\n details: { isError: result.isError ?? false },\n };\n },\n };\n}\n\nfunction errorResult(message: string): AgentToolResult {\n return {\n content: [{ type: 'text', text: message }],\n details: { isError: true },\n };\n}\n\n/**\n * Async-aware shim around the module-scoped `ipcClient`. The management\n * tools and dynamic MCP tools registered in `activate()` execute LATER\n * (when the agent invokes them), but `ipcClient` is only set inside\n * `startService`. The `?.` chains return useful errors when the agent\n * tries to call a tool before the IPC is ready, instead of throwing.\n */\nasync function callIpc<T = unknown>(\n method: string,\n params: Record<string, unknown> = {},\n): Promise<IpcResponse<T>> {\n if (!ipcClient) {\n return {\n id: '',\n ok: false,\n error: { code: 'NOT_CONNECTED', message: 'Bundler IPC client not initialized yet' },\n };\n }\n return ipcClient.call<T>(method, params);\n}\n\n/**\n * Load the last-known tool catalogue from disk. Used at `activate()`\n * time so the agent's tool list contains real tools even in tool-\n * discovery mode (where `service.start` — and therefore the IPC\n * connection — never runs). Silently falls back to an empty list on\n * any read/parse failure so a first-run agent (no cache file yet)\n * still loads cleanly.\n */\nfunction isValidDescriptor(value: unknown): value is McpToolDescriptor {\n // Cheap shape check guarding against a corrupt or torn-write cache —\n // a half-written JSON array that parses but holds garbage entries\n // would otherwise reach `buildAgentTool` where `tool.prefixed` ends up\n // undefined and registers a tool with `name: undefined`. Catching the\n // shape here protects the agent's tool list from disk-level corruption.\n if (typeof value !== 'object' || value === null) return false;\n const v = value as Record<string, unknown>;\n return (\n typeof v.prefixed === 'string' &&\n typeof v.original === 'string' &&\n typeof v.server === 'string' &&\n typeof v.parameters === 'object' &&\n v.parameters !== null\n );\n}\n\nfunction loadCachedDescriptorsFromDisk(logger: Logger): McpToolDescriptor[] {\n try {\n const raw = readFileSync(CACHE_FILE, 'utf-8');\n const parsed = JSON.parse(raw) as unknown;\n if (!Array.isArray(parsed)) return [];\n return parsed.filter(isValidDescriptor);\n } catch (err) {\n if (err instanceof Error && (err as NodeJS.ErrnoException).code !== 'ENOENT') {\n logger.debug('[mcp-bundler] cache read failed', { err: err.message });\n }\n return [];\n }\n}\n\n/**\n * Persist the current catalogue to disk so the next tool-discovery\n * load sees the tools. Best-effort — disk failures are logged at\n * `warn` and otherwise swallowed.\n */\nfunction persistCachedDescriptorsToDisk(\n descriptors: McpToolDescriptor[],\n logger: Logger,\n): void {\n // Atomic write: write to a temp sibling then rename. `writeFileSync` is\n // NOT atomic on its own — two processes racing on the same path (a\n // daemon restart concurrent with a tool-discovery load's inline\n // refresh, say) can interleave bytes and corrupt the JSON. The\n // tool-discovery load then silently drops the catalogue until the\n // next successful refresh. `renameSync` is atomic on the same\n // filesystem on every platform we ship to.\n const tmp = `${CACHE_FILE}.${randomUUID()}.tmp`;\n try {\n mkdirSync(dirname(CACHE_FILE), { recursive: true });\n writeFileSync(tmp, JSON.stringify(descriptors));\n renameSync(tmp, CACHE_FILE);\n } catch (err) {\n logger.warn('[mcp-bundler] cache write failed', {\n err: err instanceof Error ? err.message : String(err),\n });\n }\n}\n\n/**\n * Self-service tools the agent can call to add/remove MCP servers in\n * the Alfe bundler store. The agent gets the same surface the CLI's\n * `alfe mcp add/remove/list` commands expose — same IPC methods, same\n * `'manual'` ownership semantics. Daemon-owned (`'cli'` /\n * `'integration:*'`) entries can't be clobbered.\n */\nfunction registerAgentMcpManagementTools(api: OpenClawPluginApi): void {\n // Tools are registered during `activate()` (whether or not the\n // service has started yet), so we can't capture a live `ipcClient`\n // here. Each tool's `execute()` reaches the module-scoped\n // `ipcClient` via `callIpc()` at invocation time — by then,\n // `service.start` has wired the IPC client in full mode. In tool-\n // discovery mode (where service.start never runs) these tools still\n // appear in the agent's tool list, but executing them returns a\n // `NOT_CONNECTED` error from `callIpc` rather than crashing.\n\n api.registerTool({\n name: 'alfe_mcp_list',\n label: 'List Alfe MCP servers',\n description:\n 'List every MCP server currently registered in the Alfe bundler store, with id, owner, and transport. Use this before adding a new server to avoid name collisions or to confirm what is currently available to call.',\n parameters: { type: 'object', properties: {}, additionalProperties: false },\n async execute() {\n const response = await callIpc<{ servers: unknown[] }>('mcp.list_servers', {});\n if (!response.ok) {\n return errorResult(response.error?.message ?? 'mcp_list failed');\n }\n return {\n content: [{ type: 'text', text: JSON.stringify(response.payload?.servers ?? [], null, 2) }],\n details: { isError: false },\n };\n },\n });\n\n api.registerTool({\n name: 'alfe_mcp_add',\n label: 'Register an MCP server with Alfe',\n description:\n 'Register a new MCP server in the Alfe bundler store. The Alfe daemon will spawn the child and its tools become available to the agent on the next turn (no runtime restart). Use a stdio command for local-process MCPs (most common) or url+transport for remote SSE / streamable-http MCPs.',\n parameters: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'Unique id under the bundler store. Letters, digits, hyphens. Becomes the `mcp__<id>__` prefix on every tool name from this server.',\n },\n command: {\n type: 'string',\n description: 'Executable to spawn for stdio transport (e.g. \"npx\", \"uvx\", \"/path/to/bin\"). Required for stdio mode.',\n },\n args: {\n type: 'array',\n items: { type: 'string' },\n description: 'Arguments to pass to the command. Optional.',\n },\n env: {\n type: 'object',\n additionalProperties: { type: 'string' },\n description: 'Environment variables for the child process. Optional.',\n },\n cwd: {\n type: 'string',\n description: 'Working directory for the child process. Optional.',\n },\n url: {\n type: 'string',\n description: 'Remote MCP endpoint URL. Required for sse / streamable-http transport.',\n },\n transport: {\n type: 'string',\n enum: ['sse', 'streamable-http'],\n description: 'Remote MCP transport. Defaults to \"sse\" when url is set.',\n },\n headers: {\n type: 'object',\n additionalProperties: { type: 'string' },\n description: 'HTTP headers for remote MCP transports. Optional.',\n },\n },\n required: ['id'],\n additionalProperties: false,\n },\n async execute(_toolCallId, params) {\n const response = await callIpc<{ id: string }>('mcp.add_server', (params ?? {}) as Record<string, unknown>);\n if (!response.ok) {\n return errorResult(response.error?.message ?? 'mcp_add failed');\n }\n return {\n content: [{ type: 'text', text: `Registered MCP server \"${response.payload?.id ?? '?'}\". Tools will appear on the next turn.` }],\n details: { isError: false },\n };\n },\n });\n\n api.registerTool({\n name: 'alfe_mcp_remove',\n label: 'Unregister an MCP server from Alfe',\n description:\n 'Remove an MCP server you previously registered with alfe_mcp_add. Only entries you registered (owner=manual) can be removed this way — integration-installed servers (atlassian, github, etc.) and the built-in alfe-platform server are owner-protected and must be removed via the dashboard or CLI.',\n parameters: {\n type: 'object',\n properties: {\n id: { type: 'string', description: 'Bundler store id of the entry to remove.' },\n },\n required: ['id'],\n additionalProperties: false,\n },\n async execute(_toolCallId, params) {\n const response = await callIpc<{ removed: boolean }>(\n 'mcp.remove_server',\n (params ?? {}) as Record<string, unknown>,\n );\n if (!response.ok) {\n return errorResult(response.error?.message ?? 'mcp_remove failed');\n }\n const removed = response.payload?.removed ?? false;\n return {\n content: [\n {\n type: 'text',\n text: removed\n ? `Removed MCP server. Its tools will disappear on the next turn.`\n : `No MCP server matched — nothing to remove.`,\n },\n ],\n details: { isError: false },\n };\n },\n });\n}\n\n/**\n * Pull the latest tool catalog from the daemon-hosted bundler. Coalesces\n * concurrent calls so the LLM hitting the factory many times in a tight\n * loop only triggers one in-flight IPC request.\n */\nfunction refreshCachedTools(api: OpenClawPluginApi): Promise<void> {\n if (!ipcClient) return Promise.resolve();\n if (refreshInFlight) return refreshInFlight;\n refreshInFlight = (async () => {\n try {\n const response = await callIpc<{ tools: McpToolDescriptor[] }>('mcp.list_tools', {});\n if (!response.ok || !response.payload) {\n // NOT_CONNECTED / TIMEOUT / etc — keep the prior cache so a\n // transient daemon hiccup doesn't drop tools mid-session.\n api.logger.debug('[mcp-bundler] list_tools failed', {\n err: response.error?.message ?? 'unknown',\n });\n return;\n }\n cachedDescriptors = response.payload.tools;\n // Persist for tool-discovery-mode loads — that's how this fix\n // lands tools on the agent's tool list. See CACHE_FILE comment.\n persistCachedDescriptorsToDisk(cachedDescriptors, api.logger);\n } finally {\n refreshInFlight = null;\n }\n })();\n return refreshInFlight;\n}\n\nconst plugin = {\n id: '@alfe.ai/openclaw-mcp-bundler',\n name: 'Alfe MCP Bundler',\n description: 'Proxies the daemon-hosted MCP bundler tools to the LLM',\n version: pkg.version,\n contracts: {\n // OpenClaw's `contracts.tools` matcher (in\n // `loader.js#registerTool` and `tools.js#resolvePluginTools`) uses\n // strict literal `Set.has()` — it does NOT honour glob patterns. So\n // the previous `'mcp__*'` entry was non-functional: every tool the\n // factory returned whose name started with anything else (e.g.\n // `atlassian-atlassian__atlassian_list_accounts`) was silently\n // rejected as \"undeclared\" at runtime, and never surfaced to the\n // agent. Diagnosed 2026-06-09 on QA Tester after the v0.0.9 IPC\n // fix proved correct but tools still didn't appear in chat\n // sessions. See CHANGELOG for the trace.\n //\n // This list enumerates every tool name the proxy can return today.\n // The three `alfe_mcp_*` entries are the agent self-service\n // surface. The `<provider>-<provider>__<provider>_*` entries are\n // the Pattern-A proxy meta-tools (list_accounts /\n // check_connection / refresh_token) each provider's MCP server\n // surfaces before a cloudId is selected. The `alfe-platform__*`\n // entries cover the CLI-managed alfe-platform MCP server.\n //\n // ADDING A NEW INTEGRATION: append its 3 Pattern-A meta-tools\n // here AND any post-cloudId tools the proxy exposes that you want\n // the agent to be able to invoke. There is no shortcut — until\n // OpenClaw learns glob patterns in `contracts.tools` (tracked\n // separately) every tool name must be listed literally.\n tools: [\n 'alfe_mcp_list',\n 'alfe_mcp_add',\n 'alfe_mcp_remove',\n 'atlassian-atlassian__atlassian_list_accounts',\n 'atlassian-atlassian__atlassian_check_connection',\n 'atlassian-atlassian__atlassian_refresh_token',\n 'notion-notion__notion_list_accounts',\n 'notion-notion__notion_check_connection',\n 'notion-notion__notion_refresh_token',\n 'github-github__github_list_accounts',\n 'github-github__github_check_connection',\n 'github-github__github_refresh_token',\n 'microsoft-microsoft__microsoft_list_accounts',\n 'microsoft-microsoft__microsoft_check_connection',\n 'microsoft-microsoft__microsoft_refresh_token',\n 'google-google__google_list_accounts',\n 'google-google__google_check_connection',\n 'google-google__google_refresh_token',\n 'slack-slack__slack_list_accounts',\n 'slack-slack__slack_check_connection',\n 'slack-slack__slack_refresh_token',\n 'discord-discord__discord_list_accounts',\n 'discord-discord__discord_check_connection',\n 'discord-discord__discord_refresh_token',\n 'mobile-mobile__mobile_list_accounts',\n 'mobile-mobile__mobile_check_connection',\n 'mobile-mobile__mobile_refresh_token',\n 'myob-myob__myob_list_accounts',\n 'myob-myob__myob_check_connection',\n 'myob-myob__myob_refresh_token',\n 'xero-xero__xero_list_accounts',\n 'xero-xero__xero_check_connection',\n 'xero-xero__xero_refresh_token',\n 'alfe-platform__session_status',\n 'alfe-platform__gateway_status',\n ],\n },\n\n activate(api: OpenClawPluginApi) {\n const log = api.logger;\n\n if (g[ACTIVATED_KEY] === true) {\n log.debug('[mcp-bundler] already activated — skipping duplicate');\n return;\n }\n g[ACTIVATED_KEY] = true;\n\n if (api.pluginConfig?.disabled) {\n g[ACTIVATED_KEY] = false;\n log.info('[mcp-bundler] disabled via plugin config — no-op');\n return;\n }\n\n if (!api.registerService) {\n g[ACTIVATED_KEY] = false;\n log.warn('[mcp-bundler] api.registerService unavailable — plugin is a no-op on this runtime');\n return;\n }\n\n const deferToNative = hasNativeOpenclawMcpServers(resolveLiveConfig(api));\n if (deferToNative) {\n log.info(\n '[mcp-bundler] openclaw.json#mcp.servers has entries — MCP-proxy factory will defer to native bundleMcp, but agent self-service tools (alfe_mcp_*) stay available',\n );\n }\n\n // ── Register tools synchronously in activate() ──────────────\n //\n // OpenClaw loads plugins in \"tool-discovery\" mode whenever it\n // builds the agent's tool list (see `resolvePluginTools` in\n // `loader.js`). The registration plan for that mode sets\n // `runtimeChannel: false`, which means `api.registerService(...)`\n // start/stop callbacks are NEVER invoked there — only `activate`\n // runs. v0.0.8 → v0.0.10 put tool registration inside\n // `service.start`, so tool-discovery loads contributed ZERO tools\n // to the agent's list (QA Tester repro traced 2026-06-09). We now\n // register every tool here in `activate` so they show up in both\n // modes. The IPC client + lifecycle stays in `registerService` —\n // each tool's `execute()` reaches the module-scoped `ipcClient`\n // lazily via `callIpc()`, so tools registered without a live IPC\n // still show up in the catalogue and return a graceful\n // NOT_CONNECTED error if invoked before `service.start` lands.\n\n // Seed the descriptor cache from disk so tool-discovery mode\n // (which has no IPC) can still return real MCP tools to the\n // agent. Refreshed and re-persisted by `refreshCachedTools` on\n // every IPC connect.\n cachedDescriptors = loadCachedDescriptorsFromDisk(log);\n\n // Agent self-service tools — always available, regardless of the\n // native-bundleMcp escape hatch. The alfe store is independent of\n // openclaw.json so agents can manage their MCP roster in either\n // mode.\n registerAgentMcpManagementTools(api);\n\n if (!deferToNative) {\n api.registerTool((ctx: ToolFactoryContext) => {\n const live = resolveLiveConfig(api, ctx);\n if (hasNativeOpenclawMcpServers(live)) return null;\n // Best-effort background refresh so each turn picks up any new\n // entries the daemon's bundler has discovered (only effective\n // when the IPC client is up — i.e. in full mode).\n void refreshCachedTools(api);\n return cachedDescriptors.map((d) => buildAgentTool(api, d));\n }, { optional: true });\n }\n\n // ── Service lifecycle: IPC client + cache freshness ────────\n //\n // Cache freshness is layered. Three independent tiers:\n //\n // 1. Disk cache (`~/.alfe/mcp/openclaw-bundler-cache.json`) —\n // the only source available in tool-discovery mode (no IPC).\n // Loaded in `activate()` BEFORE this service runs.\n // 2. Pre-warm — `service.start` awaits the first\n // `refreshCachedTools` (bounded by PREWARM_TIMEOUT_MS). The\n // first chat-turn after gateway boot then sees a fresh\n // catalog, NOT whatever stale snapshot the disk cache held.\n // 3. Periodic refresh — a setInterval picks up MCP children that\n // come online AFTER the pre-warm completed (slow npx fetches,\n // delayed integration provider spawns, late OAuth callbacks).\n //\n // Each tier is independent: a failure in one doesn't break the\n // others. The disk cache covers cold start in tool-discovery mode,\n // pre-warm covers turn-1 in agent-runtime mode, periodic refresh\n // covers steady-state staleness.\n const startService = async (): Promise<void> => {\n // Pre-warm deferred. Resolved either by `onConnect` finishing its\n // first refresh OR by a 3s timeout. We DO NOT throw on timeout —\n // a slow daemon shouldn't block gateway startup forever. We DO\n // log warn (not silent) so a consistently-timing-out daemon\n // shows up in CloudWatch instead of degrading quietly: the v0.0.8\n // - v0.0.11 onStartup regression took weeks to notice precisely\n // because there was no signal.\n const prewarmTimer: { handle: ReturnType<typeof setTimeout> | null } = { handle: null };\n const prewarm = new Promise<'completed' | 'timeout'>((resolve) => {\n prewarmTimer.handle = setTimeout(() => {\n // Clear the handle so the symmetric reset path in\n // `onConnect`'s `.finally` no-ops, instead of clearing an\n // already-fired timer.\n prewarmTimer.handle = null;\n resolve('timeout');\n }, PREWARM_TIMEOUT_MS);\n ipcClient = new McpIpcClient({\n logger: log,\n plugin: { name: plugin.id, version: plugin.version },\n // The onConnect path is the FAST path: IPC connects in\n // 50-300ms after `start()`, the refresh fires, and pre-warm\n // resolves. Only daemons that aren't reachable at all\n // (missing socket, dead daemon) hit the timeout branch.\n onConnect: () => {\n void refreshCachedTools(api).finally(() => {\n if (prewarmTimer.handle !== null) {\n clearTimeout(prewarmTimer.handle);\n prewarmTimer.handle = null;\n }\n resolve('completed');\n });\n },\n });\n ipcClient.start();\n });\n\n const prewarmResult = await prewarm;\n if (prewarmResult === 'timeout') {\n log.warn(\n `[mcp-bundler] pre-warm timed out after ${String(PREWARM_TIMEOUT_MS)}ms — first chat turn may see a stale tool list (likely a slow or unreachable daemon at ~/.alfe/gateway.sock)`,\n );\n }\n\n // Periodic refresh — see comment block above the function for\n // why this tier exists alongside pre-warm. Only fires when IPC\n // is connected; otherwise `refreshCachedTools` is a no-op.\n periodicRefreshTimer = setInterval(() => {\n if (ipcClient?.isConnected()) {\n void refreshCachedTools(api);\n }\n }, PERIODIC_REFRESH_INTERVAL_MS);\n // `unref()` lets the host process exit even if this timer is\n // still scheduled — important for the per-turn openclaw runtime\n // mode where a hanging interval would block process teardown.\n // Optional-chained because non-Node hosts (browsers, edge\n // runtimes) don't expose `.unref()` on Timeout handles.\n (periodicRefreshTimer as { unref?: () => void }).unref?.();\n\n if (deferToNative) {\n log.info(`[mcp-bundler] activated v${plugin.version} (management tools only — native bundleMcp owns runtime)`);\n return;\n }\n\n log.info(`[mcp-bundler] activated v${plugin.version} (thin IPC client, tools registered in activate)`);\n };\n\n const stopService = (): Promise<void> => {\n log.info('[mcp-bundler] stopping');\n if (periodicRefreshTimer !== null) {\n clearInterval(periodicRefreshTimer);\n periodicRefreshTimer = null;\n }\n if (ipcClient) {\n ipcClient.stop();\n ipcClient = undefined;\n }\n // Keep `cachedDescriptors` and the on-disk cache intact across\n // restarts — that's the WHOLE POINT of the disk cache. We only\n // tear down the live IPC reference here.\n refreshInFlight = null;\n // Reset the duplicate-activate guard so OpenClaw can re-run\n // `activate(api)` after a `service.stop` without going through a\n // full `deactivate`. Without this, a stop-then-reactivate path\n // (e.g. an OpenClaw reload that doesn't tear the plugin module\n // down) would silently skip ALL tool registration on the second\n // activate and the agent ends up with no bundler tools. The\n // guard's only job is preventing the same module from running\n // activate() twice concurrently — once the service is stopped,\n // that risk is gone.\n g[ACTIVATED_KEY] = false;\n return Promise.resolve();\n };\n\n api.registerService({\n id: 'alfe-mcp-bundler',\n start: async () => { await startService(); },\n stop: async () => { await stopService(); },\n });\n },\n\n deactivate(api: OpenClawPluginApi) {\n g[ACTIVATED_KEY] = false;\n api.logger.debug('[mcp-bundler] deactivating');\n },\n};\n\nexport { type IpcResponse };\nexport default plugin;\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alfe.ai/openclaw-mcp-bundler",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"description": "OpenClaw plugin: thin IPC client over the alfe-gateway daemon's hosted MCP bundler. Surfaces every MCP tool the daemon knows about to the agent and exposes self-service alfe_mcp_list / add / remove tools for agent-driven roster management.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|