WhatBroke

spawn npx ENOENT (or spawn uv ENOENT) when starting an MCP server

Claude Code Last checked 2026-10-15

ENOENT means the client could not find the npx or uv binary on the PATH it was given — it is not about your project files. Fix it by putting the absolute path to the binary in the MCP config, and on Windows by pointing at npx.cmd or going through cmd /c.

What you saw

Error: spawn npx ENOENT

The same error with any launcher in place of npx: spawn uv ENOENT, spawn uvx ENOENT, spawn bun ENOENT, spawn docker ENOENT, spawn python ENOENT.

In Claude Desktop the server never turns on and ~/Library/Logs/Claude/mcp-server-NAME.log contains the ENOENT line. In Claude Code the server shows as failed in claude mcp list. In Cursor the MCP entry shows a red dot. The client itself often reports only the downstream symptom — MCP error -32000: Connection closed — and hides ENOENT in the log.

The command works perfectly when you paste it into your own terminal. That contrast is the diagnostic, not a contradiction.

What it actually means

ENOENT is a POSIX error code: *Error NO ENTry* — the file does not exist. Node's child_process.spawn reports it with the name of the thing it failed to find, so spawn npx ENOENT literally means "I looked for an executable called npx and there was none on the PATH of this process."

It is not about your project, your config's file arguments, or the network. The failure happens before the MCP server runs a single line of its own code.

On macOS the cause is almost always the GUI PATH. An app launched from Finder, Dock or Spotlight inherits a minimal PATH from launchd — roughly /usr/bin:/bin:/usr/sbin:/sbin. It never reads ~/.zshrc, ~/.bash_profile or ~/.zprofile. So anything installed by nvm, Homebrew, pyenv, mise or the uv installer is invisible to it even though your terminal finds it instantly.

On Windows the cause is usually the .cmd shim. npx on Windows is npx.cmd, not a real executable. Node's spawn without shell: true does not apply PATHEXT, so it searches for a literal file named npx, finds nothing, and throws ENOENT. This is why the exact same config works on a colleague's Mac and fails on your PC.

With uv/uvx there is a third cause: the official installer drops the binary in ~/.local/bin (macOS/Linux) or %USERPROFILE%\.local\bin (Windows) and appends it to your shell profile only. Nothing outside your shell knows about it until you log out and back in — and GUI apps still will not see it.

The fix

  1. Get the absolute path to the binary. Run this in the same terminal where the command works.

    # macOS / Linux
    which npx
    # -> /Users/you/.nvm/versions/node/v22.14.0/bin/npx
    which uvx
    # -> /Users/you/.local/bin/uvx
    
    # Windows (PowerShell)
    (Get-Command npx).Source
    # -> C:\Program Files\nodejs\npx.cmd
  2. Paste that absolute path into the MCP config as command. Leave args alone. This single change fixes the large majority of ENOENT reports.

    {
      "mcpServers": {
        "filesystem": {
          "command": "/Users/you/.nvm/versions/node/v22.14.0/bin/npx",
          "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/you/projects"]
        }
      }
    }
  3. On Windows, point at the .cmd file explicitly — and escape the backslashes, because the config is JSON. A single unescaped backslash makes the file invalid and the server will fail for a second, unrelated reason.

    {
      "mcpServers": {
        "filesystem": {
          "command": "C:\\Program Files\\nodejs\\npx.cmd",
          "args": ["-y", "@modelcontextprotocol/server-filesystem", "C:\\Users\\you\\projects"]
        }
      }
    }
  4. If you would rather not hardcode a path on Windows, route through the shell instead. cmd /c resolves PATHEXT and the .cmd shim for you.

    {
      "command": "cmd",
      "args": ["/c", "npx", "-y", "@modelcontextprotocol/server-filesystem", "C:\\Users\\you\\projects"]
    }
  5. If the server still dies after it starts, give it a PATH of its own in the env block. Child processes the server spawns (a node, a git, a python) need the same treatment the parent did.

    {
      "command": "/Users/you/.local/bin/uvx",
      "args": ["mcp-server-fetch"],
      "env": {
        "PATH": "/Users/you/.local/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin"
      }
    }
  6. Confirm the fix from the client's side, not by eye. In Claude Code the server must read connected; in Claude Desktop the log file should stop producing ENOENT.

    claude mcp list
    claude mcp get filesystem
    
    # macOS Claude Desktop
    tail -n 50 ~/Library/Logs/Claude/mcp-server-filesystem.log
  7. Quit the client completely — Cmd-Q on macOS, or exit from the system tray on Windows — then relaunch. Closing the window leaves the old process holding the old config, and you will think the fix failed when it did not.

How to stop it happening again

Use absolute paths for command in every MCP config, on every machine. It costs one which call and permanently removes the most common startup failure.

Do not use an nvm-managed Node for MCP servers you care about. nvm paths contain the version number, so every Node upgrade silently breaks every config that points at the old one. Install a system Node, or symlink a stable path.

After editing claude_desktop_config.json, .mcp.json or .cursor/mcp.json, validate it before restarting: python3 -m json.tool <file>. Malformed JSON and a missing binary look identical from the UI.

Keep one known-good server in the config. If that one also throws ENOENT, the problem is the client's PATH, not the server you just added.

On Windows, standardise on the cmd /c form in anything you share with a team. It survives different Node installs; a hardcoded C:\Program Files\nodejs\npx.cmd does not.

Related questions

Why does the command work in my terminal but not in the app?

Because a GUI app does not inherit your shell's PATH. Terminal apps read ~/.zshrc or ~/.bash_profile at startup; apps launched from Finder, Dock or Spotlight get a minimal PATH from launchd and never read those files. Anything from nvm, Homebrew, pyenv, mise or uv is therefore invisible to the app.

Is `spawn npx ENOENT` the same as `MCP error -32000: Connection closed`?

They usually appear together. ENOENT is the cause (the binary was not found); -32000 is the client's downstream report that the process it was talking to is gone. If you only see -32000, open the server's log file — ENOENT is often the line above it.

Should I just install the server globally with `npm i -g` instead?

It helps, but only if the global bin directory is itself on the app's PATH — which is the exact thing that is broken. Installing globally and then pointing command at the resulting absolute path is reliable; installing globally and still writing "command": "npx" usually is not.

I get `spawn uv ENOENT` but `uv --version` works. What is different?

The uv installer writes the binary to ~/.local/bin and appends that directory to your shell profile only. Your shell picked it up; nothing else did. Use the absolute path from which uv, typically /Users/you/.local/bin/uv or /root/.local/bin/uv in containers.

Does this apply to Cursor, Windsurf and VS Code too?

Yes. They all spawn stdio MCP servers as child processes with the same Node API, so the PATH and .cmd-shim causes are identical. Only the config file location changes: .cursor/mcp.json, ~/.codeium/windsurf/mcp_config.json, or .vscode/mcp.json.

Can I set PATH globally for the app instead of per-server?

On macOS you can with launchctl setenv PATH "...", but it is machine-wide, does not survive reboot without a LaunchAgent, and is easy to forget when you debug this again in six months. Per-server absolute paths are more verbose and much easier to reason about.

Other things that break

What Broke This Week

One email, most Thursdays. New symptoms, tool updates that quietly break things, and one fix worth stealing. Unsubscribe in one click.