CCD/Cowork requires system Node.js for local MCP extensions while Chat uses built-in UtilityProcess
Bug Description
Local MCP extensions that use Node.js (e.g., server.type: "node", command: "node") work in Chat but fail silently in CCD (Claude Code Desktop) and Cowork on machines without a system Node.js installation.
Root Cause
The Chat path and CCD path use completely different mechanisms to spawn Node.js MCP servers:
Chat path (pDe) — uses Electron's built-in UtilityProcess:
const u = await wNe(t);
if (u.type === "built-in-node") {
l = new zMt(h, g, {...i.env});
// zMt uses xe.utilityProcess.fork(nodeHostPath, ...)
// nodeHostPath = "app.asar/.vite/build/mcp-runtime/nodeHost.js"
// env: {...process.env, ...this.env} — full environment
// No system node binary needed — runs JS inside Electron's Node.js runtime
}
CCD path (LocalMcpServerManager.createConnection) — always uses StdioClientTransport (bNe):
const {cmd, args, cwd} = await iDe(e, s);
// iDe() calls wNe() which returns {type: "built-in-node"}
// But then iDe() just calls Go("node", args, pathDirs) — searching for system "node" binary
// The "built-in-node" result is effectively ignored
const f = new bNe({command: l.cmd, args: l.args, env: ...});
// Spawns via child_process.spawn() — requires real "node" on PATH
The CCD path calls wNe() and correctly identifies the extension as "built-in-node", but then iDe() resolves "node" as a system command via Go() anyway. The "built-in-node" type only triggers the UtilityProcess branch in the Chat path — the CCD path has no such branch.
Additionally, the CCD path uses a stripped environment ($Z() — only HOME, LOGNAME, PATH, SHELL, TERM, USER on macOS) vs Chat's full process.env.
Compatibility Checker Says "All Good" Even When CCD Will Fail
The extension compatibility checker (NPt) evaluates whether an extension's node requirements are met. For extensions where command === "node", it checks if builtInNodeVersion (Electron's embedded Node.js) satisfies the version requirement. If yes, it returns null — no compatibility issue. This means extensions appear fully compatible in the UI even on machines without system Node.js, because the checker correctly knows Electron has built-in Node. But CCD never actually uses the built-in Node — it spawns node as a system command. So the compatibility checker is giving a green light for a configuration that will silently fail in CCD/Cowork.
function NPt(t, e) {
const r = t.server.mcp_config.command;
const {builtInNodeVersion: n, nodeVersions: i} = e;
const s = t.compatibility?.runtimes?.node;
// ...
if (n) { // builtInNodeVersion always exists in Electron
const c = r && /^node(\.exe)?$/.test(r); // command is "node"
const l = !s || $a.satisfies(n, s); // built-in node meets version req
return l ? null : "builtin-node-version-mismatch";
// Returns null (no issue) — but CCD doesn't use built-in node!
}
}
Impact
- Affects all users without system Node.js installed (common for non-developer users)
- Chat tab works perfectly for these users (UtilityProcess needs no system node)
- CCD shows
local: 0plugins and MCP connections fail withMCP error -32000: Connection closed - The failure is silent from the user's perspective — tools just don't appear in CCD/Cowork
- The extension compatibility UI shows no warnings, since it checks built-in node (which CCD doesn't use)
Reproduction
- Install a Node.js-based MCP extension (any extension with
server.type: "node") - Verify it works in Chat tab
- Uninstall system Node.js (
brew uninstall node, remove nvm, etc.) - Restart Claude Desktop
- Chat tab still works (UtilityProcess)
- CCD/Cowork fails — extension tools are missing,
main.logshows connection failure
Suggested Fix
Have LocalMcpServerManager.createConnection() use UtilityProcess (via zMt) for extensions where wNe() returns {type: "built-in-node"}, matching the Chat path's behavior. The nodeHost.js and zMt class already exist and work — they just need to be used in the CCD path as well.
Environment
- Claude Desktop on macOS (likely affects Windows too if system node isn't installed)
- Tested with Node.js MCP extension (JupyterHub MCP, server.type: "node")
- Multiple affected users confirmed across an organization
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗