From 4587ef903ca6afa6ca58b4c02a656e59a6ca0656 Mon Sep 17 00:00:00 2001 From: Patrick Erichsen Date: Thu, 1 Oct 2026 16:43:44 -0700 Subject: [PATCH] fix: remote MCP plugins fail to start in native agent sessions (#162376) * fix: remote MCP plugins fail to start in native agent sessions * test: align remote MCP assertions with normalized transports --- docs/plugins/bundles.md | 2 ++ src/plugins/bundle-mcp.test.ts | 39 ++++++++++++++++++++++++++++++++++ src/plugins/bundle-mcp.ts | 7 +++--- 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/docs/plugins/bundles.md b/docs/plugins/bundles.md index 9ccf0c8ecf64..7fa3856a2b91 100644 --- a/docs/plugins/bundles.md +++ b/docs/plugins/bundles.md @@ -141,6 +141,8 @@ These are recognized and shown in diagnostics, but OpenClaw does not run them: ## MCP for embedded OpenClaw - Enabled bundles can contribute MCP server config. +- Stdio commands default their working directory to the plugin config directory. + Remote HTTP servers do not receive an implicit working directory. - OpenClaw merges bundle MCP config into the effective embedded OpenClaw settings as `mcpServers`. - OpenClaw exposes supported bundle MCP tools during embedded OpenClaw agent diff --git a/src/plugins/bundle-mcp.test.ts b/src/plugins/bundle-mcp.test.ts index 22bd230bff4a..d9df8dbbdab0 100644 --- a/src/plugins/bundle-mcp.test.ts +++ b/src/plugins/bundle-mcp.test.ts @@ -157,6 +157,35 @@ describe("loadEnabledBundleMcpConfig", () => { }); }); + it("keeps file-backed HTTP bundle servers free of implicit stdio fields", async () => { + await withBundleFixture(async ({ homeDir, workspaceDir }) => { + const pluginRoot = await writeClaudeBundleManifest({ + homeDir, + pluginId: "remote-bundle", + manifest: { name: "remote-bundle" }, + }); + await writeBundleTextFiles(pluginRoot, { + ".mcp.json": JSON.stringify({ + mcpServers: { + http: { type: "http", url: "https://example.test/mcp" }, + sse: { type: "sse", url: "https://example.test/sse" }, + }, + }), + }); + + const loaded = loadEnabledBundleMcpConfig({ + workspaceDir, + cfg: createEnabledBundleConfig(["remote-bundle"]), + }); + + expectNoDiagnostics(loaded.diagnostics); + expect(loaded.config.mcpServers).toEqual({ + http: { transport: "streamable-http", url: "https://example.test/mcp" }, + sse: { transport: "sse", url: "https://example.test/sse" }, + }); + }); + }); + it("uses a provided manifest registry instead of rediscovering bundle plugins", async () => { const homeDir = await tempHarness.createTempDir("openclaw-bundle-mcp-home-"); const workspaceDir = await tempHarness.createTempDir("openclaw-bundle-mcp-workspace-"); @@ -217,6 +246,8 @@ describe("loadEnabledBundleMcpConfig", () => { command: "node", args: ["./mcp-server.js"], }, + remote: { transport: "streamable-http", url: "https://example.test/mcp" }, + events: { transport: "sse", url: "https://example.test/sse" }, }, }, ], @@ -230,6 +261,14 @@ describe("loadEnabledBundleMcpConfig", () => { args: [path.join(pluginRoot, "mcp-server.js")], cwd: pluginRoot, }); + expect(loaded.config.mcpServers.remote).toEqual({ + transport: "streamable-http", + url: "https://example.test/mcp", + }); + expect(loaded.config.mcpServers.events).toEqual({ + transport: "sse", + url: "https://example.test/sse", + }); }); it("skips MCP servers declared by a disabled native plugin", async () => { diff --git a/src/plugins/bundle-mcp.ts b/src/plugins/bundle-mcp.ts index be16e0de405c..3e627e111405 100644 --- a/src/plugins/bundle-mcp.ts +++ b/src/plugins/bundle-mcp.ts @@ -149,7 +149,6 @@ function absolutizeBundleMcpServer(params: { baseDir: string; server: BundleMcpServerConfig; pluginDataDir?: string; - agentFormat?: boolean; }): BundleMcpServerConfig { const next: BundleMcpServerConfig = { ...params.server }; const expand = (value: string) => @@ -168,10 +167,11 @@ function absolutizeBundleMcpServer(params: { : normalizeExpandedAbsolutePath(expanded); }; + // Remote transports have no process cwd; native runners reject that stdio-only field. if ( + typeof next.command === "string" && typeof next.cwd !== "string" && - typeof next.workingDirectory !== "string" && - (!params.agentFormat || typeof next.command === "string") + typeof next.workingDirectory !== "string" ) { next.cwd = params.baseDir; } @@ -418,7 +418,6 @@ function loadBundleFileBackedMcpConfig(params: { baseDir, server, pluginDataDir: agentLoaded?.pluginDataDir, - agentFormat: params.bundleFormat === "agent", }), ]), ),