/** Tests Claude-style bundle-MCP config-file overlays for CLI backends. */ import fs from "node:fs/promises"; import path from "node:path"; import { describe, expect, it } from "vitest"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { resolveBundlePluginRoot, writeBundleTextFiles, writeClaudeBundleManifest, } from "../../plugins/bundle-mcp.test-support.js"; import { clearPluginMetadataLifecycleCaches } from "../../plugins/plugin-metadata-lifecycle.js"; import { withEnvAsync } from "../../test-utils/env.js"; import { prepareCliBundleMcpCaptureAttempt, prepareCliBundleMcpConfig } from "./bundle-mcp.js"; import { cliBundleMcpHarness, cliNativeMcpPolicyContext, prepareBundleProbeCliConfig, requireMcpConfigPath, setupCliBundleMcpTestHarness, writeCliMcpPolicyProbeServer, } from "./bundle-mcp.test-support.js"; setupCliBundleMcpTestHarness(); describe("prepareCliBundleMcpConfig", () => { it("disables Claude native web search without bundle MCP", async () => { const prepared = await prepareCliBundleMcpConfig({ enabled: false, mode: "claude-config-file", backend: { command: "claude", args: ["--print"] }, workspaceDir: "/tmp/openclaw-cli-web-search-disabled", toolOverrides: { webSearch: false }, }); expect(prepared.backend.args).toEqual(["--print", "--disallowedTools", "WebSearch"]); expect(prepared.mcpConfigHash).toMatch(/^[0-9a-f]{64}$/); }); it("injects a strict empty --mcp-config overlay for bundle-MCP-enabled backends without servers", async () => { const workspaceDir = await cliBundleMcpHarness.tempHarness.createTempDir( "openclaw-cli-bundle-mcp-empty-", ); const prepared = await prepareCliBundleMcpConfig({ enabled: true, mode: "claude-config-file", backend: { command: "node", args: ["./fake-claude.mjs"], }, workspaceDir, config: { plugins: { enabled: false } }, }); expect(prepared.backend.args).toContain("--strict-mcp-config"); // Even empty overlays force Claude to ignore user/global MCP servers. const generatedConfigPath = requireMcpConfigPath(prepared.backend.args); const raw = JSON.parse(await fs.readFile(generatedConfigPath, "utf-8")) as { mcpServers?: Record; }; expect(raw.mcpServers).toStrictEqual({}); await prepared.cleanup?.(); }); it("serves only the exclusive config, ignoring user and plugin servers", async () => { const workspaceDir = await cliBundleMcpHarness.tempHarness.createTempDir( "openclaw-cli-bundle-mcp-exclusive-", ); const userConfig = path.join(workspaceDir, "user-mcp.json"); await fs.writeFile( userConfig, `${JSON.stringify({ mcpServers: { user: { command: "node", args: ["user.mjs"] } } })}\n`, "utf-8", ); const prepared = await prepareCliBundleMcpConfig({ enabled: true, mode: "claude-config-file", backend: { command: "node", args: ["./fake-claude.mjs", "--mcp-config", "user-mcp.json"], }, workspaceDir, config: { plugins: { enabled: false } }, exclusiveConfig: { mcpServers: { openclaw: { command: "node", args: ["openclaw.mjs"] } }, }, }); expect(prepared.backend.args).toContain("--strict-mcp-config"); const generatedConfigPath = requireMcpConfigPath(prepared.backend.args); const raw = JSON.parse(await fs.readFile(generatedConfigPath, "utf-8")) as { mcpServers?: Record; }; expect(Object.keys(raw.mcpServers ?? {})).toEqual(["openclaw"]); expect(raw.mcpServers?.openclaw?.args).toEqual(["openclaw.mjs"]); expect(prepared.mcpConfigHash).toMatch(/^[0-9a-f]{64}$/); await prepared.cleanup?.(); }); it("injects a merged --mcp-config overlay for bundle-MCP-enabled backends", async () => { const prepared = await prepareBundleProbeCliConfig(); expect(prepared.backend.args).toContain("--strict-mcp-config"); const generatedConfigPath = requireMcpConfigPath(prepared.backend.args); const raw = JSON.parse(await fs.readFile(generatedConfigPath, "utf-8")) as { mcpServers?: Record; }; expect(raw.mcpServers?.bundleProbe?.args).toEqual([ await fs.realpath(cliBundleMcpHarness.bundleProbeServerPath), ]); expect(prepared.mcpConfigHash).toMatch(/^[0-9a-f]{64}$/); expect(prepared.mcpResumeHash).toMatch(/^[0-9a-f]{64}$/); await prepared.cleanup?.(); }); it("carries Agent Plugins data-dir and transport contracts into external projections", async () => { const pluginId = "agent-cli-projection"; const pluginRoot = resolveBundlePluginRoot(cliBundleMcpHarness.bundleProbeHomeDir, pluginId); await writeBundleTextFiles(pluginRoot, { "plugin.json": JSON.stringify({ $schema: "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", name: pluginId, }), "mcp.json": JSON.stringify({ $schema: "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json", mcpServers: { local: { type: "stdio", command: "node" }, remote: { type: "streamable-http", url: "https://example.test/mcp" }, legacy: { type: "sse", url: "https://example.test/sse" }, }, }), }); const agentDataDir = path.join( cliBundleMcpHarness.bundleProbeHomeDir, ".openclaw", "plugin-data", pluginId, ); const userDataPath = path.join( cliBundleMcpHarness.bundleProbeHomeDir, "user-data-must-not-exist", ); clearPluginMetadataLifecycleCaches(); const prepared = await withEnvAsync( { HOME: cliBundleMcpHarness.bundleProbeHomeDir }, async () => await prepareCliBundleMcpConfig({ enabled: true, mode: "gemini-system-settings", backend: { command: "gemini" }, workspaceDir: cliBundleMcpHarness.bundleProbeWorkspaceDir, config: { plugins: { entries: { [pluginId]: { enabled: true } } }, mcp: { servers: { user: { command: "node", env: { PLUGIN_ROOT: "/user/plugin", PLUGIN_DATA: userDataPath }, }, }, }, }, }), ); expect((await fs.stat(agentDataDir)).isDirectory()).toBe(true); await expect(fs.stat(userDataPath)).rejects.toMatchObject({ code: "ENOENT" }); const raw = JSON.parse( await fs.readFile(prepared.env?.GEMINI_CLI_SYSTEM_SETTINGS_PATH as string, "utf8"), ) as { mcpServers?: Record; }; expect(raw.mcpServers?.remote).toMatchObject({ type: "http", url: "https://example.test/mcp", }); expect(raw.mcpServers?.legacy).toMatchObject({ type: "sse", url: "https://example.test/sse", }); expect(raw.mcpServers?.remote?.transport).toBeUndefined(); expect(raw.mcpServers?.legacy?.transport).toBeUndefined(); await prepared.cleanup?.(); await fs.rm(pluginRoot, { recursive: true, force: true }); clearPluginMetadataLifecycleCaches(); }); it("projects session MCP tool denials into Claude disallowed tools", async () => { const workspaceDir = await cliBundleMcpHarness.tempHarness.createTempDir( "openclaw-cli-bundle-mcp-deny-", ); const prepared = await prepareCliBundleMcpConfig({ enabled: true, mode: "claude-config-file", backend: { command: "claude", args: ["--disallowedTools", "Bash(rm *)"], }, workspaceDir, config: { plugins: { enabled: false }, mcp: { servers: { docs: { command: "node", args: ["docs.mjs"] } } }, }, toolOverrides: { mcpToolsDeny: { docs: ["delete_docs"] }, webSearch: false }, }); expect(prepared.backend.args).toContain("Bash(rm *),WebSearch,mcp__docs__delete_docs"); await prepared.cleanup?.(); }); it("matches Claude's normalized MCP permission identifiers", async () => { const workspaceDir = await cliBundleMcpHarness.tempHarness.createTempDir( "openclaw-cli-bundle-mcp-normalized-deny-", ); const prepared = await prepareCliBundleMcpConfig({ enabled: true, mode: "claude-config-file", backend: { command: "claude", args: ["--print"] }, workspaceDir, config: { plugins: { enabled: false } }, exclusiveConfig: { mcpServers: { "docs.prod": { command: "node" } } }, toolOverrides: { mcpToolsDeny: { "docs.prod": ["read.value", "read:value"] } }, }); const args = prepared.backend.args?.join(",") ?? ""; expect(args.match(/mcp__docs_prod__read_value/g) ?? []).toHaveLength(1); await prepared.cleanup?.(); }); it("projects durable policy into the first Claude process config and argv", async () => { const serverPath = await writeCliMcpPolicyProbeServer(); const config: OpenClawConfig = { plugins: { enabled: false }, tools: { allow: ["docs__*"], deny: ["docs__delete_docs"] }, mcp: { servers: { docs: { command: process.execPath, args: [serverPath] } } }, }; const prepared = await prepareCliBundleMcpConfig({ enabled: true, mode: "claude-config-file", backend: { command: "claude", args: ["--print"] }, workspaceDir: cliBundleMcpHarness.bundleProbeWorkspaceDir, config, additionalConfig: { mcpServers: { openclaw: { url: "http://127.0.0.1:31783/mcp" } }, }, toolOverrides: { mcpToolsDeny: { openclaw: ["message"] } }, nativeMcpPolicy: cliNativeMcpPolicyContext(config, "claude-policy"), }); const args = prepared.backend.args?.join(",") ?? ""; expect(args).toContain("mcp__docs__delete_docs"); expect(args).toContain("mcp__docs__task_docs"); expect(args).toContain("mcp__docs__app_docs"); expect(args).toContain("mcp__openclaw__message"); const raw = JSON.parse( await fs.readFile(requireMcpConfigPath(prepared.backend.args), "utf-8"), ) as { mcpServers?: Record }; expect(Object.keys(raw.mcpServers ?? {})).toEqual(["docs", "openclaw"]); expect(raw.mcpServers?.docs?.toolFilter).toBeUndefined(); await prepared.cleanup?.(); }); it("hides non-model MCP tools from Claude without an explicit policy", async () => { const serverPath = await writeCliMcpPolicyProbeServer(); const config: OpenClawConfig = { plugins: { enabled: false }, mcp: { servers: { docs: { command: process.execPath, args: [serverPath] } } }, }; const prepared = await prepareCliBundleMcpConfig({ enabled: true, mode: "claude-config-file", backend: { command: "claude", args: ["--print"] }, workspaceDir: cliBundleMcpHarness.bundleProbeWorkspaceDir, config, nativeMcpPolicy: cliNativeMcpPolicyContext(config, "claude-default-hidden"), }); const args = prepared.backend.args?.join(",") ?? ""; expect(args).toContain("mcp__docs__app_docs"); expect(args).toContain("mcp__docs__task_docs"); expect(args).not.toContain("mcp__docs__read_docs"); expect(args).not.toContain("mcp__docs__delete_docs"); await prepared.cleanup?.(); }); it("projects configured MCP filters into Claude denials without a global tool policy", async () => { const serverPath = await writeCliMcpPolicyProbeServer(); const config: OpenClawConfig = { plugins: { enabled: false }, mcp: { servers: { docs: { command: process.execPath, args: [serverPath], toolFilter: { include: ["read_*"] }, }, }, }, }; const prepared = await prepareCliBundleMcpConfig({ enabled: true, mode: "claude-config-file", backend: { command: "claude", args: ["--print"] }, workspaceDir: cliBundleMcpHarness.bundleProbeWorkspaceDir, config, nativeMcpPolicy: cliNativeMcpPolicyContext(config, "claude-configured-filter"), }); const args = prepared.backend.args?.join(",") ?? ""; expect(args).toContain("mcp__docs__delete_docs"); expect(args).toContain("mcp__docs__task_docs"); expect(args).toContain("mcp__docs__app_docs"); expect(args).not.toContain("mcp__docs__read_docs"); await prepared.cleanup?.(); }); it("projects an exact runtime cap into Claude before spawn", async () => { const serverPath = await writeCliMcpPolicyProbeServer(); const config: OpenClawConfig = { plugins: { enabled: false }, mcp: { servers: { docs: { command: process.execPath, args: [serverPath] } } }, }; const prepared = await prepareCliBundleMcpConfig({ enabled: true, mode: "claude-config-file", backend: { command: "claude", args: ["--print"] }, workspaceDir: cliBundleMcpHarness.bundleProbeWorkspaceDir, config, nativeMcpPolicy: { ...cliNativeMcpPolicyContext(config, "claude-runtime-cap"), runtimeToolsAllow: ["docs__read_docs"], }, }); const args = prepared.backend.args?.join(",") ?? ""; expect(args).toContain("mcp__docs__delete_docs"); expect(args).toContain("mcp__docs__task_docs"); expect(args).toContain("mcp__docs__app_docs"); expect(args).not.toContain("mcp__docs__read_docs"); await prepared.cleanup?.(); }); it("omits a fully excluded server while retaining an allowed sibling server", async () => { const serverPath = await writeCliMcpPolicyProbeServer(); const config: OpenClawConfig = { plugins: { enabled: false }, tools: { allow: ["docs__read_docs"] }, mcp: { servers: { docs: { command: process.execPath, args: [serverPath] }, admin: { command: process.execPath, args: [serverPath] }, }, }, }; const prepared = await prepareCliBundleMcpConfig({ enabled: true, mode: "claude-config-file", backend: { command: "claude", args: ["--print"] }, workspaceDir: cliBundleMcpHarness.bundleProbeWorkspaceDir, config, nativeMcpPolicy: cliNativeMcpPolicyContext(config, "claude-server-omission"), }); const raw = JSON.parse( await fs.readFile(requireMcpConfigPath(prepared.backend.args), "utf-8"), ) as { mcpServers?: Record }; expect(Object.keys(raw.mcpServers ?? {})).toEqual(["docs"]); await prepared.cleanup?.(); }); it("omits every configured MCP server removed by durable policy", async () => { const serverPath = await writeCliMcpPolicyProbeServer(); const config: OpenClawConfig = { plugins: { enabled: false }, tools: { allow: ["missing__tool"] }, mcp: { servers: { docs: { command: process.execPath, args: [serverPath] } } }, }; const prepared = await prepareCliBundleMcpConfig({ enabled: true, mode: "claude-config-file", backend: { command: "claude", args: ["--print"] }, workspaceDir: cliBundleMcpHarness.bundleProbeWorkspaceDir, config, nativeMcpPolicy: cliNativeMcpPolicyContext(config, "claude-policy-empty"), }); const raw = JSON.parse( await fs.readFile(requireMcpConfigPath(prepared.backend.args), "utf-8"), ) as { mcpServers?: Record }; expect(raw.mcpServers).toEqual({}); await prepared.cleanup?.(); }); it("omits a server whose restrictive policy catalog cannot be established", async () => { const config: OpenClawConfig = { plugins: { enabled: false }, tools: { deny: ["docs__delete_docs"] }, mcp: { servers: { docs: { command: process.execPath, args: ["-e", "process.exit(1)"] } }, }, }; const prepared = await prepareCliBundleMcpConfig({ enabled: true, mode: "claude-config-file", backend: { command: "claude", args: ["--print"] }, workspaceDir: cliBundleMcpHarness.bundleProbeWorkspaceDir, config, nativeMcpPolicy: cliNativeMcpPolicyContext(config, "claude-policy-catalog-failure"), }); const raw = JSON.parse( await fs.readFile(requireMcpConfigPath(prepared.backend.args), "utf-8"), ) as { mcpServers?: Record }; expect(raw.mcpServers).toEqual({}); await prepared.cleanup?.(); }); it("applies server disables to exclusive CLI MCP configs", async () => { const prepared = await prepareCliBundleMcpConfig({ enabled: true, mode: "claude-config-file", backend: { command: "claude" }, workspaceDir: "/tmp/openclaw-cli-bundle-mcp-exclusive-disable", exclusiveConfig: { mcpServers: { openclaw: { command: "node" } } }, toolOverrides: { mcpServers: { openclaw: false } }, }); const generatedConfigPath = requireMcpConfigPath(prepared.backend.args); const raw = JSON.parse(await fs.readFile(generatedConfigPath, "utf-8")) as { mcpServers?: Record; }; expect(raw.mcpServers).toEqual({}); await prepared.cleanup?.(); }); it("strips variadic Claude --mcp-config values and merges every listed config", async () => { const workspaceDir = await cliBundleMcpHarness.tempHarness.createTempDir( "openclaw-cli-bundle-mcp-variadic-", ); const firstConfig = path.join(workspaceDir, "first-mcp.json"); const secondConfig = path.join(workspaceDir, "second-mcp.json"); await fs.writeFile( firstConfig, `${JSON.stringify({ mcpServers: { first: { command: "node", args: ["first.mjs"] }, shared: { command: "node", args: ["old.mjs"] }, }, })}\n`, "utf-8", ); await fs.writeFile( secondConfig, `${JSON.stringify({ mcpServers: { second: { command: "node", args: ["second.mjs"] }, shared: { command: "node", args: ["new.mjs"] }, }, })}\n`, "utf-8", ); const prepared = await prepareCliBundleMcpConfig({ enabled: true, mode: "claude-config-file", backend: { command: "node", args: [ "./fake-claude.mjs", "--mcp-config", "first-mcp.json", "second-mcp.json", "--verbose", ], }, workspaceDir, config: { plugins: { enabled: false } }, }); expect(prepared.backend.args).not.toContain("first-mcp.json"); expect(prepared.backend.args).not.toContain("second-mcp.json"); expect(prepared.backend.args).toContain("--verbose"); const generatedConfigPath = requireMcpConfigPath(prepared.backend.args); const raw = JSON.parse(await fs.readFile(generatedConfigPath, "utf-8")) as { mcpServers?: Record; }; expect(raw.mcpServers?.first?.args).toEqual(["first.mjs"]); expect(raw.mcpServers?.second?.args).toEqual(["second.mjs"]); expect(raw.mcpServers?.shared?.args).toEqual(["new.mjs"]); await prepared.cleanup?.(); }); it("merges and strips Claude --mcp-config equals form", async () => { const workspaceDir = await cliBundleMcpHarness.tempHarness.createTempDir( "openclaw-cli-bundle-mcp-equals-", ); const configPath = path.join(workspaceDir, "equals-mcp.json"); await fs.writeFile( configPath, `${JSON.stringify({ mcpServers: { equals: { command: "node", args: ["equals.mjs"] }, }, })}\n`, "utf-8", ); const prepared = await prepareCliBundleMcpConfig({ enabled: true, mode: "claude-config-file", backend: { command: "node", args: ["./fake-claude.mjs", "--mcp-config=equals-mcp.json"], }, workspaceDir, config: { plugins: { enabled: false } }, }); expect(prepared.backend.args).not.toContain("--mcp-config=equals-mcp.json"); const generatedConfigPath = requireMcpConfigPath(prepared.backend.args); const raw = JSON.parse(await fs.readFile(generatedConfigPath, "utf-8")) as { mcpServers?: Record; }; expect(raw.mcpServers?.equals?.args).toEqual(["equals.mjs"]); await prepared.cleanup?.(); }); it("keeps dash-prefixed args after Claude --mcp-config because they terminate variadic values", async () => { const workspaceDir = await cliBundleMcpHarness.tempHarness.createTempDir( "openclaw-cli-bundle-mcp-dash-", ); const prepared = await prepareCliBundleMcpConfig({ enabled: true, mode: "claude-config-file", backend: { command: "node", args: ["./fake-claude.mjs", "--mcp-config", "--verbose", "prompt"], }, workspaceDir, config: { plugins: { enabled: false } }, }); expect(prepared.backend.args).toContain("--verbose"); expect(prepared.backend.args).toContain("prompt"); const generatedConfigPath = requireMcpConfigPath(prepared.backend.args); const raw = JSON.parse(await fs.readFile(generatedConfigPath, "utf-8")) as { mcpServers?: Record; }; expect(raw.mcpServers).toStrictEqual({}); await prepared.cleanup?.(); }); it("loads workspace bundle MCP plugins from the configured workspace root", async () => { const workspaceDir = await cliBundleMcpHarness.tempHarness.createTempDir( "openclaw-cli-bundle-mcp-workspace-root-", ); const pluginRoot = path.join(workspaceDir, ".openclaw", "extensions", "workspace-probe"); // Workspace-local plugins should be resolved relative to workspaceDir, not HOME. const serverPath = path.join(pluginRoot, "servers", "probe.mjs"); await fs.mkdir(path.dirname(serverPath), { recursive: true }); await fs.writeFile(serverPath, "export {};\n", "utf-8"); await writeClaudeBundleManifest({ homeDir: workspaceDir, pluginId: "workspace-probe", manifest: { name: "workspace-probe" }, }); await fs.writeFile( path.join(pluginRoot, ".mcp.json"), `${JSON.stringify( { mcpServers: { workspaceProbe: { command: "node", args: ["./servers/probe.mjs"], }, }, }, null, 2, )}\n`, "utf-8", ); const prepared = await prepareCliBundleMcpConfig({ enabled: true, mode: "claude-config-file", backend: { command: "node", args: ["./fake-claude.mjs"], }, workspaceDir, config: { plugins: { entries: { "workspace-probe": { enabled: true }, }, }, }, }); const generatedConfigPath = requireMcpConfigPath(prepared.backend.args); const raw = JSON.parse(await fs.readFile(generatedConfigPath, "utf-8")) as { mcpServers?: Record; }; expect(raw.mcpServers?.workspaceProbe?.args).toEqual([await fs.realpath(serverPath)]); await prepared.cleanup?.(); }); it("merges loopback overlay config with bundle MCP servers", async () => { const additionalConfig = { mcpServers: { openclaw: { type: "http", url: "http://127.0.0.1:23119/mcp", headers: { Authorization: "Bearer ${OPENCLAW_MCP_TOKEN}", "x-openclaw-cli-capture-key": "${OPENCLAW_MCP_CLI_CAPTURE_KEY}", }, }, }, }; const prepared = await prepareBundleProbeCliConfig({ additionalConfig, env: { OPENCLAW_MCP_TOKEN: "lb-tk-123", OPENCLAW_MCP_CLI_CAPTURE_KEY: "", }, }); const otherEnvPrepared = await prepareBundleProbeCliConfig({ additionalConfig, env: { OPENCLAW_MCP_TOKEN: "other-loopback-token", OPENCLAW_MCP_CLI_CAPTURE_KEY: "", }, }); const generatedConfigPath = requireMcpConfigPath(prepared.backend.args); const raw = JSON.parse(await fs.readFile(generatedConfigPath, "utf-8")) as { mcpServers?: Record }>; }; expect(Object.keys(raw.mcpServers ?? {}).toSorted()).toEqual(["bundleProbe", "openclaw"]); expect(raw.mcpServers?.openclaw?.url).toBe("http://127.0.0.1:23119/mcp"); expect(raw.mcpServers?.openclaw?.headers?.Authorization).toBe("Bearer lb-tk-123"); expect(raw.mcpServers?.openclaw?.headers?.["x-openclaw-cli-capture-key"]).toBe(""); await prepareCliBundleMcpCaptureAttempt({ mode: "claude-config-file", backend: prepared.backend, env: prepared.env, captureKey: "attempt-123", }); const attemptRaw = JSON.parse(await fs.readFile(generatedConfigPath, "utf-8")) as { mcpServers?: Record }>; }; expect(attemptRaw.mcpServers?.openclaw?.headers?.Authorization).toBe("Bearer lb-tk-123"); expect(attemptRaw.mcpServers?.openclaw?.headers?.["x-openclaw-cli-capture-key"]).toBe( "attempt-123", ); expect(prepared.mcpConfigHash).toBe(otherEnvPrepared.mcpConfigHash); expect(prepared.mcpResumeHash).toBe(otherEnvPrepared.mcpResumeHash); await prepared.cleanup?.(); await otherEnvPrepared.cleanup?.(); }); it("preserves extra env values alongside generated MCP config", async () => { const workspaceDir = await cliBundleMcpHarness.tempHarness.createTempDir( "openclaw-cli-bundle-mcp-env-", ); const prepared = await prepareCliBundleMcpConfig({ enabled: true, mode: "claude-config-file", backend: { command: "node", args: ["./fake-claude.mjs"], }, workspaceDir, config: { plugins: { enabled: false } }, env: { OPENCLAW_MCP_TOKEN: "lb-tk-123", OPENCLAW_MCP_SESSION_KEY: "agent:main:telegram:group:chat123", }, }); expect(prepared.env).toEqual({ OPENCLAW_MCP_TOKEN: "lb-tk-123", OPENCLAW_MCP_SESSION_KEY: "agent:main:telegram:group:chat123", }); await prepared.cleanup?.(); }); it("leaves args untouched when bundle MCP is disabled", async () => { const prepared = await prepareCliBundleMcpConfig({ enabled: false, backend: { command: "node", args: ["./fake-cli.mjs"], }, workspaceDir: "/tmp/openclaw-bundle-mcp-disabled", }); expect(prepared.backend.args).toEqual(["./fake-cli.mjs"]); expect(prepared.cleanup).toBeUndefined(); }); });