This reverts commit 79dbb1450e (#124792).
_resolve_stdio_command passes _prepend_path the directory the command
resolved into, not the managed Node directory. After 79dbb145 that
directory moved to PATH[0] even when it was already on PATH, so:
- the reported layout (system Node with npx first, managed dir behind)
was unchanged: which() finds the system npx, whose dir is already first;
- a command found later on PATH now shadowed every earlier entry for the
child's other bare lookups. With the store-first PATH pm.activate()
gives the Hermes process, a brew-installed MCP command hands its
children brew's node/python3/git instead of the pinned store copies.
Restore the prepend-only-when-absent behaviour.
`_prepend_path` inserted the resolved command's directory only when it was
absent from the child's PATH. The Hermes installer appends its managed Node
dir to the user PATH, so for anyone with a system Node (<22.12) earlier on
PATH the check no-oped and the managed dir stayed behind it. npm lifecycle
children (`node install.js`) then resolved the older system Node and failed
with ERR_REQUIRE_ESM even though Hermes had provisioned a compatible runtime.
Strip every existing case/trailing-separator variant of the directory first,
then prepend it, so the canonical entry is the one that wins and PATH does
not grow duplicates.
Fixes#82309
The `enabled` key had four parsers. The MCP client (`_parse_boolish`) read
`enabled: 0` as on; the toolset resolver and editor (`_parse_enabled_flag`)
read it as off. The server list (`summarize_server`, `/api/mcp/servers`) read
any non-`False` value as on, so `enabled: "false"` showed on while the agent
skipped it. The catalog and `hermes mcp list` accepted only true/1/yes, so
`enabled: on` showed off while the server ran.
`tools/mcp_tool_common.py::mcp_server_enabled` is now the only reader, and
every surface calls it. `_parse_boolish` treats YAML numbers by truthiness
(0 off, other numbers on). Everything else keeps the client's semantics:
the off words are off, absent / null / junk stay on, with the existing
warning for junk.
The desktop MCP page mirrors the rule in `serverEnabled`
(`apps/desktop/src/lib/mcp-servers.ts`). One case table
(`mcp-enabled-cases.json`) drives the Python invariant test and the vitest
test, so the page and the runtime cannot drift apart again.
Review follow-up (minor): `_CREDENTIAL_PATTERN` used `sk-[A-Za-z0-9_]{1,255}`,
so a dotted `sk-sp-…`/`sk-ws-…` key inside an MCP error message leaked its
tail. Same dot-aware body shape as agent/redact; one assertion.