From b6232472a74627f0aa3f95d149cbe5fcdaa51c4f Mon Sep 17 00:00:00 2001 From: dengmik-commits <270912164+dengmik-commits@users.noreply.github.com> Date: Fri, 29 May 2026 16:37:41 +0800 Subject: [PATCH] fix(mcp): fix Windows MCP spawn double-quoting that breaks all MCP servers The quoteWindowsShellArg function wrapped every argument in double quotes unconditionally, producing command strings like '"npx" "-y" "@playwright/mcp"'. When passed to spawn() with shell:true, Node.js wraps the entire string again for cmd.exe, causing double-quoting that breaks command parsing. All MCP servers (playwright, fetch, memory, github) failed to start on Windows. Fix: only quote arguments that contain spaces or double-quotes, leaving simple args unquoted. --- src/mcp/mcp-client.ts | 15 ++++++++++----- src/tests/mcp-client.test.ts | 4 ++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/mcp/mcp-client.ts b/src/mcp/mcp-client.ts index 26a7a321..d2ef1c88 100644 --- a/src/mcp/mcp-client.ts +++ b/src/mcp/mcp-client.ts @@ -425,9 +425,11 @@ export function createMcpSpawnSpec( if (platform === "win32") { return { // On Windows, shell: true lets cmd.exe resolve the command via PATHEXT - // (npx -> npx.cmd, etc.). Pass one quoted command line with no spawn - // args to avoid Node 24 DEP0190. - command: [command, ...args].map(quoteWindowsShellArg).join(" "), + // (npx -> npx.cmd, etc.). Join command and args into a single string + // with empty spawn args to avoid Node 24 DEP0190. + // Only quote arguments that contain spaces or double-quotes to prevent + // double-wrapping by Node.js's own shell quoting. + command: [command, ...args].map(quoteWindowsArgIfNeeded).join(" "), args: [], shell: true, windowsHide: true, @@ -441,6 +443,9 @@ export function createMcpSpawnSpec( }; } -function quoteWindowsShellArg(arg: string): string { - return `"${arg.replace(/(\\*)"/g, '$1$1\\"').replace(/\\+$/g, "$&$&")}"`; +function quoteWindowsArgIfNeeded(arg: string): string { + if (arg.includes(" ") || arg.includes('"')) { + return `"${arg.replace(/(\\*)"/g, '$1$1\\"').replace(/\\+$/g, "$&$&")}"`; + } + return arg; } diff --git a/src/tests/mcp-client.test.ts b/src/tests/mcp-client.test.ts index e161aad3..29151d3a 100644 --- a/src/tests/mcp-client.test.ts +++ b/src/tests/mcp-client.test.ts @@ -10,9 +10,9 @@ test("createMcpSpawnSpec keeps non-Windows MCP launches shell-free", () => { }); }); -test("createMcpSpawnSpec avoids Windows shell args for Node 24", () => { +test("createMcpSpawnSpec joins args without quoting when spaces are absent (Windows)", () => { assert.deepEqual(createMcpSpawnSpec("npx", ["-y", "@playwright/mcp@latest"], "win32"), { - command: '"npx" "-y" "@playwright/mcp@latest"', + command: "npx -y @playwright/mcp@latest", args: [], shell: true, windowsHide: true,