fix(cli-runtime): replace overlapping user mcp servers

This commit is contained in:
Kei Shingu
2026-04-25 08:45:49 +09:00
committed by Peter Steinberger
parent d8ae63e7a2
commit b86a04262d
2 changed files with 93 additions and 2 deletions

View File

@@ -263,6 +263,7 @@ describe("prepareCliBundleMcpConfig", () => {
});
const configFlagIndex = prepared.backend.args?.indexOf("--mcp-config") ?? -1;
expect(configFlagIndex).toBeGreaterThanOrEqual(0);
const generatedConfigPath = prepared.backend.args?.[configFlagIndex + 1];
const raw = JSON.parse(await fs.readFile(generatedConfigPath as string, "utf-8")) as {
mcpServers?: Record<string, { url?: string }>;
@@ -272,6 +273,90 @@ describe("prepareCliBundleMcpConfig", () => {
await prepared.cleanup?.();
});
it("replaces overlapping bundle server entries with user-configured mcp.servers", async () => {
const workspaceDir = await tempHarness.createTempDir(
"openclaw-cli-bundle-mcp-user-servers-replace-",
);
await writeClaudeBundleManifest({
homeDir: bundleProbeHomeDir,
pluginId: "omi",
manifest: { name: "omi" },
});
const pluginDir = path.join(bundleProbeHomeDir, ".openclaw", "extensions", "omi");
await fs.writeFile(
path.join(pluginDir, ".mcp.json"),
`${JSON.stringify(
{
mcpServers: {
omi: {
command: process.execPath,
args: [bundleProbeServerPath],
env: { BUNDLE_ONLY: "true" },
},
},
},
null,
2,
)}\n`,
"utf-8",
);
const env = captureEnv(["HOME"]);
try {
process.env.HOME = bundleProbeHomeDir;
const prepared = await prepareCliBundleMcpConfig({
enabled: true,
mode: "claude-config-file",
backend: {
command: "node",
args: ["./fake-claude.mjs"],
},
workspaceDir,
config: {
plugins: {
entries: {
omi: { enabled: true, path: pluginDir },
},
},
mcp: {
servers: {
omi: {
type: "sse",
url: "https://api.omi.me/v1/mcp/sse",
headers: { Authorization: "Bearer test-token" },
},
},
},
},
});
const configFlagIndex = prepared.backend.args?.indexOf("--mcp-config") ?? -1;
expect(configFlagIndex).toBeGreaterThanOrEqual(0);
const generatedConfigPath = prepared.backend.args?.[configFlagIndex + 1];
const raw = JSON.parse(await fs.readFile(generatedConfigPath as string, "utf-8")) as {
mcpServers?: Record<
string,
{
type?: string;
url?: string;
command?: string;
args?: string[];
env?: Record<string, string>;
}
>;
};
expect(raw.mcpServers?.omi?.type).toBe("sse");
expect(raw.mcpServers?.omi?.url).toBe("https://api.omi.me/v1/mcp/sse");
expect(raw.mcpServers?.omi?.command).toBeUndefined();
expect(raw.mcpServers?.omi?.args).toBeUndefined();
expect(raw.mcpServers?.omi?.env).toBeUndefined();
await prepared.cleanup?.();
} finally {
env.restore();
}
});
it("stabilizes the resume hash when only the OpenClaw loopback port changes", async () => {
const first = await prepareBundleProbeCliConfig({
additionalConfig: {

View File

@@ -2,8 +2,8 @@ import crypto from "node:crypto";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { applyMergePatch } from "../../config/merge-patch.js";
import { normalizeConfiguredMcpServers } from "../../config/mcp-config.js";
import { applyMergePatch } from "../../config/merge-patch.js";
import type { CliBackendConfig } from "../../config/types.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import {
@@ -418,7 +418,13 @@ export async function prepareCliBundleMcpConfig(params: {
mergedConfig = applyMergePatch(mergedConfig, bundleConfig.config) as BundleMcpConfig;
const configuredMcp = normalizeConfiguredMcpServers(params.config?.mcp?.servers);
if (Object.keys(configuredMcp).length > 0) {
mergedConfig = applyMergePatch(mergedConfig, { mcpServers: configuredMcp }) as BundleMcpConfig;
mergedConfig = {
...mergedConfig,
mcpServers: {
...(mergedConfig.mcpServers ?? {}),
...configuredMcp,
},
} satisfies BundleMcpConfig;
}
if (params.additionalConfig) {
mergedConfig = applyMergePatch(mergedConfig, params.additionalConfig) as BundleMcpConfig;