Skip to content

Instantly share code, notes, and snippets.

@cognivator
Last active April 3, 2025 06:39
Show Gist options
  • Save cognivator/ceca5a68d696b0575962ff3d18c31aa5 to your computer and use it in GitHub Desktop.
Save cognivator/ceca5a68d696b0575962ff3d18c31aa5 to your computer and use it in GitHub Desktop.
Configure Claude MCP for use with NVM

Claude MCP - with NVM

As documented by Jonathan Gastón Löwenstern (hereafter, JGL), loading Claude Desktop MCP servers while using Node Version Manager (NVM) can be fraught, and there is little assistance in the community. Thankfully, Jonathon describes and solves the problems.

My modification is the ability to configure and use an NVM alias for use with Claude MCPs, without hardcoding the node version.

Caveats: This is tested and known to work on MacOS Monterey with ZSH. I have not tried other flavors of MacOS, Linux, or shells.

The Strategy

Like JGL's solution, we still use an npx wrapper to load MCPs within the NVM context. The change is in how we specify and control that context.

We do this in four parts:

  1. Move NVM initialization to a location that is sure to run for all four kinds of shells - [Non]Login-[Non]Interactive. In the case of ZSH (and probably all? others), this means initializing NVM in one of the zshenv files.
  2. Create an NVM alias to control which node version Claude uses for MCP loads.
  3. Invoke the NVM alias in the npx wrapper script.
  4. Use the npx wrapper in place of npx in the MCP configuration.

The Configuration

1. zshenv files

ZSH uses two zshenv files, one at /etc/zshenv (system), and one at ~/.zshenv (personal). We use both, primarily to avoid PATH configuration conflicts.

First, /etc/zshenv. This runs for all shells, all users. MacOS puts some stuff in /etc/zprofile that needs to move to the system zshenv.

/etc/zshenv

## /etc/zshenv

# MacOS puts this in /etc/zprofile originally, but it clobbers certain PATH-dependent utilities, namely NVM
if [ -x /usr/libexec/path_helper ]; then
  eval `/usr/libexec/path_helper -s`
fi

This path_helper utility configures the initial system PATH using /etc/paths, and /etc/paths.d. It's not clear why this would be in a zprofile which only runs for login shells. In any case, it will now run for all shells.

Importantly, it runs before the NVM initializer, coming next.

~/.zshenv

## ~/.zshenv

function run() {
  ## normal .zshenv content... ##

  # This is the standard NVM initialization. It's what enables you to use NVM in general, and an alias in npx-for-claude in particular.
  export NVM_DIR="$HOME/.nvm"
  [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm

  ## It's possible you have other NVM shims here, like the nvmrc auto changer on directory changes... ##

  ## ... other shell settings for interactive and non-interactive uses ... ##
}

## This next bit shuts off unwanted STDOUT output when running a non-interactive shell, such as MCPs in Claude Desktop.
## Without this redirection, any messages hitting STDOUT will foul the MCP server loads.
## Ref: https://stackoverflow.com/a/44661168/4346960

OUTPUT=1

if [[ ! -o interactive ]]; then
  OUTPUT=3
  eval "exec $OUTPUT<>/dev/null"
fi

run >& $OUTPUT

if [[ ! -o interactive ]]; then
  eval "exec $OUTPUT>&-"
fi

The important bit of magic is the redirection for NonInteractive shells:

  • Create a temporary file descriptor equivalent to /dev/null
  • Redirect the output of run function to it
  • Remove the temporary file descriptor

Interactive shells output to STDOUT like normal.

2. NVM Aliases

NVM lets you set aliases for the versions it manages. Some are standard (or at least common,) like "default", "stable", or one of the "lts..." monikers.

We simply create our own alias for the node version Claude Desktop should use to load MCPs. You can do this at a terminal any time before running Claude Desktop, and you only have to do it once - NVM remembers aliases across shells, initializations, and reboots.

> nvm alias claude default

In the above command, "claude" is the alias you want to use in npx-for-claude, and "default" is whatever version (or alias) you want the claude alias to be. In my case, "default" is aliased to the "stable" release, and "stable" is further aliased to the most recent lts.

3. NPX Wrapper

The next bit is a very simple change to npx-for-claude script. Instead of hardcoding a path to an NVM-controlled npx, we use NVM itself to activate it.

npx-for-claude

#!/usr/bin/env zsh

nvm use claude > /dev/null 2>&1
npx "$@"

The "shebang," #!/usr/bin/env zsh, ensures the zshenv files (system and user) are both run - so PATH is configured by path_helper, and NVM is intitialized.

Then nvm uses our "claude" alias to set the right node/npm/npx version.

Note the output is sent to /dev/null directly, to avoid poluting STDOUT and fouling the MCP load.

4. MCP Configuration

This is already well-documented, both in the community and by JGL, but to quickly recap...

{
  "mcpServers": {
    "filesystem": {
      "command": "npx-for-claude",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "/Users/slhenty/Documents"
      ]
    }        
  }
}

Note the command used - npx-for-claude in lieu of npx. This runs our wrapper script, with NVM and node properly configured.

Wrap-up

At this point, any MCP that suggests using npx should use npx-for-claude. Similarly, if you choose to load MCP modules onto your system, you may need to create a similar wrapper for whatever node/npm command is used. Just include the same nvm use claude... command as in npx-for-claude.

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