Skip to content

Windows: stdio MCP servers fail to spawn (spawn npx ENOENT / EINVAL) in 1.0.56-1 #3576

Description

@jamesdooleymsft

Summary

On Windows, all stdio MCP servers whose command is npx (or any other .cmd / .ps1 / extensionless shell-script launcher on PATH) fail to start in Copilot CLI 1.0.56-1. The same configuration worked in 1.0.51.

Failure modes observed by reproducing what Copilot CLI does under the hood:

Spawn form Copilot CLI may be using Result on Windows + Node ≥ 20.12.2
spawn("npx", [...]) Error: spawn npx ENOENT
spawn("npx.cmd", [...]) Error: spawn EINVAL
spawn("npx.cmd", [...], { shell: true }) ✅ works
spawn("cmd", ["/c", "npx", ...]) ✅ works

EINVAL is by design — it's the Node mitigation for CVE‑2024‑27980, which blocks direct spawning of .bat/.cmd files unless shell: true is passed.

Environment

  • Copilot CLI: 1.0.56-1 (also reproduces with 1.0.56-0 in the local pkg cache)
  • Last known-good Copilot CLI version: 1.0.51
  • OS: Windows 11 (Windows_NT, x64)
  • Node: v24.12.0
  • npm / npx: 11.6.2
  • where npx →
    C:\Program Files\nodejs\npx
    C:\Program Files\nodejs\npx.cmd
    

Affected MCP servers in my setup

All use npx -y ...:

  • @azure-devops/mcp <tenant>
  • workiq → @microsoft/workiq mcp
  • kusto → @azure/mcp@latest server start

HTTP-transport and uv-based servers in the same workspace (microsoft-learn, bluebird, godot-api-docs, etc.) are unaffected.

Reproduction

Reduces to a plain child_process.spawn test — no MCP server install needed:

// test-spawn-matrix.js
const { spawn } = require("child_process");
const cases = [
  { label: "spawn('npx', ...)",                   cmd: "npx",     args: ["--version"], opts: {} },
  { label: "spawn('npx.cmd', ...)",               cmd: "npx.cmd", args: ["--version"], opts: {} },
  { label: "spawn('npx.cmd', ..., {shell:true})", cmd: "npx.cmd", args: ["--version"], opts: { shell: true } },
  { label: "spawn('cmd', ['/c','npx',...])",      cmd: "cmd",     args: ["/c", "npx", "--version"], opts: {} },
];
(async () => {
  for (const c of cases) {
    await new Promise(res => {
      try {
        const p = spawn(c.cmd, c.args, { ...c.opts, stdio: ["ignore", "pipe", "pipe"] });
        let out = "";
        p.stdout.on("data", d => out += d);
        p.on("error", e => { console.log(c.label.padEnd(46), "ERROR:", e.code, e.message); res(); });
        p.on("exit", code => { console.log(c.label.padEnd(46), "exit=" + code, "stdout=" + out.trim()); res(); });
      } catch (e) {
        console.log(c.label.padEnd(46), "THROW:", e.code, e.message);
        res();
      }
    });
  }
})();

Output on this machine:

spawn('npx', ...)                              ERROR: ENOENT spawn npx ENOENT
spawn('npx.cmd', ...)                          THROW: EINVAL spawn EINVAL
spawn('npx.cmd', ..., {shell:true})            exit=0 stdout=11.6.2
spawn('cmd', ['/c','npx',...])                 exit=0 stdout=11.6.2

Reproduction inside Copilot CLI

~/.copilot/mcp-config.json:

{
  "mcpServers": {
    "ado-microsoft": {
      "type": "local",
      "command": "npx",
      "args": ["-y", "@azure-devops/mcp", "microsoft"],
      "tools": ["*"]
    }
  }
}

Start Copilot CLI → the ado-microsoft server fails to start; tool list is empty.

Workaround

Wrap every npx (or other .cmd) command with cmd /c:

{
  "mcpServers": {
    "ado-microsoft": {
      "type": "local",
      "command": "cmd",
      "args": ["/c", "npx", "-y", "@azure-devops/mcp", "microsoft"],
      "tools": ["*"]
    }
  }
}

After this change, copilot mcp get ado-microsoft reports Command: cmd /c npx -y @azure-devops/mcp microsoft and the server starts correctly.

Suggested fix

In the StdioClientTransport (or equivalent) spawn path, on process.platform === "win32":

  1. Easy: always pass { shell: true } to spawn. Caveat: requires escaping args since CVE‑2024‑27980 mitigation no longer applies.
  2. Better: use cross-spawn, which already wraps .cmd/.bat invocations via cmd /c and handles arg-escaping. This is what most cross-platform Node CLIs use.
  3. Manual: detect when command resolves to .cmd/.bat/.ps1 (or has no extension and matches a .cmd/.bat on PATHEXT) and rewrite the spawn as spawn("cmd", ["/c", command, ...args]) with proper quoting.

VS Code's MCP client launches the same npx-based servers from the same workspace .vscode/mcp.json without issue, which strongly suggests it uses one of these strategies.

Related (not duplicates)

References

Activity

  1. added
    area:mcpMCP server configuration, discovery, connectivity, OAuth, policy, and registry
    area:platform-windowsWindows-specific: PowerShell, cmd, Git Bash, WSL, Windows Terminal
    on May 30, 2026
  2. added theissue type on May 30, 2026
  3. jamesdooleymsft commented on Jul 28, 2026

    @jamesdooleymsft
    Author

    Still broken in Copilot CLI 1.0.76-0 on Windows with Node v24.12.0
    Not blocked, since all my uses of NPX are wrapped in 'cmd /d' calls

  4. self-assigned this
    on Aug 27, 2026
  5. kerobbi commented on Aug 27, 2026

    @kerobbi

    Hey @jamesdooleymsft, thanks for the detailed report, and sorry it sat open for so long.

    This is fixed in the latest stable release - stdio MCP servers configured with command: "npx" should now start normally on Windows without the workarounds you described. If you still see the same problem please let us know, happy to take another look!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:mcpMCP server configuration, discovery, connectivity, OAuth, policy, and registryarea:platform-windowsWindows-specific: PowerShell, cmd, Git Bash, WSL, Windows Terminal

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions