Fix stdio MCP server launches when Node is managed via nvm or fnm — works with VS Code, Cursor, Claude Code, Claude Desktop, and any host reading the standard
{"mcpServers": { ... }}JSON config.
GUI-launched applications (VS Code, Cursor, Claude Desktop, etc.) inherit their environment from launchd, not from your interactive shell. That means ~/.zshrc / ~/.bash_profile are never sourced, and any tool installed under a Node version manager (nvm, fnm) is missing from PATH.
When one of those apps tries to spawn an MCP server, it exec()s the command directly — and gets back spawn pnpm ENOENT, command not found, or — if you "fixed" it with an absolute path — silent breakage after you upgrade Node and the absolute path stops existing.
- macOS intentionally keeps GUI app environments minimal for reproducibility.
- VS Code's MCP server docs note the requirement but don't provide a fix for version-manager users.
- The absolute path of
pnpm/node/npxchanges every time you switch Node versions (~/.nvm/versions/node/v20.x.x/bin/pnpm→~/.nvm/versions/node/v22.x.x/bin/pnpm). - One-time hardcoding goes stale. You need a script that re-resolves the real path each time you switch.
mcp-mac-setup does this:
- Detects your Node version manager (fnm, nvm, or PNPM_HOME /
whichfallback). - Resolves the absolute paths to the active
node,npx,pnpm,npmbinaries. - Rewrites every
stdioMCP server entry in the target config so that:commandis the absolute path to the right binary for that serverenv.PATHis hardcoded with the activebin/firstenv.PNPM_HOME/env.NVM_DIR/env.NVM_BINare set when relevant
Any host that reads {"mcpServers": { ... }} JSON. Includes:
| Host | User-level config | Workspace / project config |
|---|---|---|
| VS Code | ~/Library/Application Support/Code/User/mcp.json |
.vscode/mcp.json |
| Cursor | (via project root) | ./.mcp.json |
| Claude Code | ~/.claude.json |
./.mcp.json |
| Claude Desktop | ~/Library/Application Support/Claude/claude_desktop_config.json |
(none — user-level only) |
Pass --target <path> to target a specific file.
# rewrite every stdio server in the user-level mcp.json with absolute paths
pnpm dlx @kylebrodeur/mcp-mac-setup --binary auto
# verify everything matches the current Node version (CI-friendly)
pnpm dlx @kylebrodeur/mcp-mac-setup --check
# then reload the host
# VS Code: Cmd+Shift+P → Developer: Reload Window
# Claude Code: restart MCP servers
# Cursor: restart MCP serversDifferent MCP servers use different launchers:
--binary auto rewrites each server's command to the matching absolute path under the resolved Node bin directory, based on what the server actually uses. --binary pnpm (the v1.0 default) still works and forces every server through pnpm.
| Flag | Description |
|---|---|
--binary <mode> |
auto (per-server) | pnpm (legacy default) | node | npx | npm | all |
--server <name> |
Update a single named server only |
--target <path> |
Write to <path> instead of the user-level mcp.json |
--workspace <path> |
Read workspace .vscode/mcp.json from <path> |
--prefer <v> |
Detection preference: auto | fnm | nvm |
--check |
Exit 0 if config matches current runtime; 1 if rewrite is needed |
--install-task |
Write a .vscode/tasks.json entry into the project |
--task-dir <path> |
With --install-task: target <path>/.vscode |
# Fix everything in the user-level VS Code mcp.json
pnpm dlx @kylebrodeur/mcp-mac-setup --binary auto
# Fix a single server in a project-root .mcp.json
pnpm dlx @kylebrodeur/mcp-mac-setup --binary auto --server codegraph --target ./.mcp.json
# Force every server through pnpm (v1.0 behavior)
pnpm dlx @kylebrodeur/mcp-mac-setup --binary pnpm
# CI guard: fail the build if the config is stale
pnpm dlx @kylebrodeur/mcp-mac-setup --binary auto --check
# Use fnm even if both nvm and fnm are installed
pnpm dlx @kylebrodeur/mcp-mac-setup --binary auto --prefer fnmRe-run mcp-mac-setup after:
- Upgrading your Node version (
nvm use/fnm use) - Switching Node version managers
- Adding or removing MCP servers that depend on
node/npx/pnpm/npm - Any change that moves the Node-managed binaries to new absolute paths
The script only rewrites fields that need to change:
- ✅ Absolute paths you wrote yourself (e.g.
/opt/custom/binary) → command preserved; onlyenv.PATHis injected so the server can find its own helpers - ✅ HTTP / SSE servers (
type: "http"ortype: "sse") → skipped entirely - ✅
argsarrays → unchanged (the gallery "exec" → "dlx" typo fix still applies under--binary pnpm) - ✅ User-defined env vars → preserved, only
PATH/PNPM_HOME/NVM_*are merged in
Install a one-keystroke task into any project:
pnpm dlx @kylebrodeur/mcp-mac-setup --install-taskThen: Cmd+Shift+P → Tasks: Run Task → Fix MCP runtime path.
The repo's own .vscode/tasks.json exposes the same tasks for contributors.
- fnm —
~/.local/share/fnm/aliases/default/bin/ - nvm — reads
~/.nvm/alias/default(the active version) - PNPM_HOME /
which pnpm— last-resort fallback (warns; path may be unstable)
Override priority with --prefer fnm or --prefer nvm.
- macOS or Linux
- Node ≥ 18
- nvm or fnm managing your Node install (PNPM_HOME fallback also works)
Issues and PRs welcome at github.com/kylebrodeur/mcp-mac-setup.
Run the test suite locally:
pnpm test
{ "mcpServers": { "playwright": { "command": "pnpm", "args": ["dlx", "@playwright/mcp"] }, "codegraph": { "command": "npx", "args": ["-y", "@colbymchenry/codegraph", "serve", "--mcp"] }, "custom-shim": { "command": "node", "args": ["./bin/my-mcp-server.js"] } } }