From fc03ef874853128a47af73af3681454bae3368ba Mon Sep 17 00:00:00 2001 From: Jordan Lee Date: Tue, 11 Aug 2026 14:49:59 -0700 Subject: [PATCH 1/2] Fix Windows MCP build pipeline: spawn npm/npx through a shell with quoted args On Windows the whole MCP pipeline (gen:mcp -> build:mcp -> pack:mcp) failed with a misleading "npm install failed in mcp/." Two stacked bugs: 1. npm/npx are .cmd shims that CreateProcess cannot launch directly, so a bare spawnSync('npm', ...) throws ENOENT. Naming npm.cmd explicitly is not a fix either -- that throws EINVAL on Node 20+ (CVE-2024-27980 hardening), so shell: true is required. 2. shell: true alone then breaks any path containing a space: Node hands the args to the shell as a command string and the shell re-splits on whitespace. pack:mcp stages under os.tmpdir(), so every user whose profile name has a space (C:\Users\Sony User\...) had the stage/output paths split into extra argv entries and mcpb rejected the arg count. Route all npm/npx spawns through one runTool() helper that sets shell and quotes whitespace-bearing args on Windows only. On macOS and Linux needsShell is false, so args pass through untouched with shell: false -- byte-identical to the previous behavior. Verified on Windows 11 / Node 22: gen:mcp, build:mcp, and pack:mcp all complete, producing mcp/alpha-camera.mcpb. Refs #31 Co-Authored-By: Claude Opus 5 --- cli/src/lib/mcp-build.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/cli/src/lib/mcp-build.ts b/cli/src/lib/mcp-build.ts index a5643a6..2df0d9e 100644 --- a/cli/src/lib/mcp-build.ts +++ b/cli/src/lib/mcp-build.ts @@ -39,10 +39,19 @@ function mcpDir(root: string | null): string | null { return dir; } +// On Windows npm/npx are .cmd shims, which CreateProcess cannot launch directly +// (ENOENT), so they need shell: true — but the shell re-splits arguments on +// spaces, so paths must be quoted before they reach it. +const needsShell = process.platform === 'win32'; + +function runTool(cmd: string, args: string[], opts: { cwd?: string } = {}) { + const finalArgs = needsShell ? args.map((a) => (/\s/.test(a) ? `"${a}"` : a)) : args; + return spawnSync(cmd, finalArgs, { ...opts, stdio: 'inherit', shell: needsShell }); +} + /** Run an npm script in a directory, streaming output. Returns true on success. */ function npm(cwd: string, args: string[]): boolean { - const r = spawnSync('npm', args, { cwd, stdio: 'inherit' }); - return r.status === 0; + return runTool('npm', args, { cwd }).status === 0; } /** Ensure node_modules is present in mcp/ before a codegen/build that needs it. */ @@ -212,14 +221,14 @@ export async function packMcp(root: string | null): Promise { console.log(`${symbols.arrow} Installing production dependencies…`); const install = existsSync(join(stage, 'package-lock.json')) - ? spawnSync('npm', ['ci', '--omit=dev'], { cwd: stage, stdio: 'inherit' }) - : spawnSync('npm', ['install', '--omit=dev'], { cwd: stage, stdio: 'inherit' }); + ? runTool('npm', ['ci', '--omit=dev'], { cwd: stage }) + : runTool('npm', ['install', '--omit=dev'], { cwd: stage }); if (install.status !== 0) { fail('Production install failed (see output above).'); return; } const out = join(dir, 'alpha-camera.mcpb'); if (existsSync(out)) rmSync(out); console.log(`${symbols.arrow} Packing the MCP bundle…`); - const r = spawnSync('npx', ['--yes', '@anthropic-ai/mcpb', 'pack', stage, out], { stdio: 'inherit' }); + const r = runTool('npx', ['--yes', '@anthropic-ai/mcpb', 'pack', stage, out]); if (r.status !== 0 || !existsSync(out)) { fail('mcpb pack failed (see output above).'); return; } printBlank(); From 0ad7d26e09ab5f2e1a02131921835608ae9745b1 Mon Sep 17 00:00:00 2001 From: Jordan Lee Date: Tue, 11 Aug 2026 14:55:52 -0700 Subject: [PATCH 2/2] Fix Windows OpenCV bundling: match the layout extract.ts actually writes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pack:mcp reported "OpenCV libs not found — assuming static/linked" on Windows and shipped a bundle that could not start. opencvLibDir() looked for shared/opencv/Windows/{Release/x64,x64/vc17}/bin, but extract.ts writes shared/opencv/Windows/x86_64/Release/bin. Neither candidate matched, so all four OpenCV DLLs were silently omitted. CameraWebApp.exe load-time imports opencv_core480.dll, opencv_imgcodecs480.dll and opencv_imgproc480.dll, so the packed server died instantly with STATUS_DLL_NOT_FOUND before it could log anything -- which surfaced to users as the MCP server simply failing to auto-start on connect. Adds the correct path as the first candidate and keeps the previous two as fallbacks for older SDK layouts. The darwin and linux candidates are untouched; the macOS entry already matches what extract.ts writes. Verified on Windows 11: pack:mcp now reports "OpenCV libs 4", and the bundled server extracted to a clean directory starts and enumerates a camera (ILCE-1M2) over USB. Refs #31 Co-Authored-By: Claude Opus 5 --- cli/src/lib/mcp-build.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cli/src/lib/mcp-build.ts b/cli/src/lib/mcp-build.ts index 2df0d9e..6968201 100644 --- a/cli/src/lib/mcp-build.ts +++ b/cli/src/lib/mcp-build.ts @@ -82,7 +82,8 @@ function libExt(): string { function opencvLibDir(root: string): string | null { const candidates = process.platform === 'darwin' ? [['Darwin', 'Release', 'macos', 'bin']] : - process.platform === 'win32' ? [['Windows', 'Release', 'x64', 'bin'], ['Windows', 'x64', 'vc17', 'bin']] : + // Must match the layout extract.ts writes: shared/opencv/Windows/x86_64/Release/bin + process.platform === 'win32' ? [['Windows', 'x86_64', 'Release', 'bin'], ['Windows', 'Release', 'x64', 'bin'], ['Windows', 'x64', 'vc17', 'bin']] : [['Linux', 'Release', 'x64', 'lib'], ['Linux', 'Release', 'aarch64', 'lib']]; for (const rel of candidates) { const p = join(root, 'shared', 'opencv', ...rel);