fix(mcp): resolve managed Windows Node launchers (npx.cmd/npm.cmd) for stdio MCP servers
On Windows a stdio MCP server configured with `command: npx|npm|node` failed
with WinError 2 whenever the desktop/gateway PATH lacked the managed Node dir:
`_node_fallback` probed only the POSIX shape `<HERMES_HOME>/node/bin/<cmd>`
with no extension, while `scripts/install.ps1` unpacks Node directly into
`<HERMES_HOME>\node` as `npx.cmd`/`npm.cmd`/`node.exe`. It also derived the
home from raw `os.getenv("HERMES_HOME")`, so a context-local profile home
(multiplexed gateway) was ignored.
Reuse the platform-aware helpers instead of a second hand-rolled layout:
`hermes_constants.iter_hermes_node_dirs(get_hermes_home())` supplies both
managed shapes in the right order, and the module's own `_npx_bin_candidates`
supplies the `.cmd` -> `.exe` precedence (same injectable `windows=` seam the
npx-cache shortcut already uses, so the branch is testable on Linux CI).
POSIX candidates (`node/bin`, `~/.local/bin`, `/usr/local/bin`) are unchanged.
Slimmer redo of #111941 by @KoNit-K, which re-derived the Windows shape
in-place and kept the raw env read.
Fixes #111937
Co-authored-by: KoNit-K <124019182+KoNit-K@users.noreply.github.com>
This commit is contained in:
@@ -149,14 +149,19 @@ def _which_with_config_pathext(command: str, path_arg, env: dict):
|
||||
os.environ["PATHEXT"] = saved
|
||||
|
||||
|
||||
def _node_fallback(command: str) -> str:
|
||||
"""Well-known Node install locations for bare ``npx``/``npm``/``node``; *command* unchanged when none exists."""
|
||||
def _node_fallback(command: str, *, windows: Optional[bool] = None) -> str:
|
||||
"""Well-known Node install locations for bare ``npx``/``npm``/``node``; *command* unchanged when none exists.
|
||||
|
||||
The managed tree comes from ``iter_hermes_node_dirs`` (Windows unpacks into ``<home>\\node``, POSIX into
|
||||
``<home>/node/bin``) under the active profile's ``get_hermes_home()``; on Windows the real files are
|
||||
``npx.cmd``/``node.exe`` (``windows`` injectable, as for ``_npx_bin_candidates``)."""
|
||||
from hermes_constants import get_hermes_home, iter_hermes_node_dirs
|
||||
home = os.path.expanduser("~")
|
||||
hermes_home = os.path.expanduser(os.getenv("HERMES_HOME", os.path.join(home, ".hermes")))
|
||||
# /usr/local/bin: canonical Node location (from-source Linux, Hermes Docker image, Intel Homebrew),
|
||||
# needed when a hand-authored env.PATH omits it — npx's shebang re-execs /usr/bin/env node.
|
||||
candidates = (os.path.join(hermes_home, "node", "bin", command), os.path.join(home, ".local", "bin", command),
|
||||
os.path.join(os.sep, "usr", "local", "bin", command))
|
||||
directories = [*map(str, iter_hermes_node_dirs(get_hermes_home())), os.path.join(home, ".local", "bin"),
|
||||
os.path.join(os.sep, "usr", "local", "bin")]
|
||||
candidates = (c for d in directories for c in _npx_bin_candidates(d, command, windows=windows))
|
||||
return next((c for c in candidates if os.path.isfile(c) and os.access(c, os.X_OK)), command)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user