@@ -71,6 +71,14 @@ function shellEscapeSingleQuoted(value: string): string {
7171 return String ( value ) . replace ( / ' / g, `'\\''` ) ;
7272}
7373
74+ function shellEscapeDoubleQuoted ( value : string ) : string {
75+ return String ( value )
76+ . replace ( / \\ / g, "\\\\" )
77+ . replace ( / \$ / g, "\\$" )
78+ . replace ( / ` / g, "\\`" )
79+ . replace ( / " / g, '\\"' ) ;
80+ }
81+
7482function isPlainObject ( value : unknown ) : value is Record < string , unknown > {
7583 return ! ! value && typeof value === "object" && ! Array . isArray ( value ) ;
7684}
@@ -179,6 +187,19 @@ function getPosixShellCandidates(runtime: NodeResolutionRuntime): string[] {
179187 ) ;
180188}
181189
190+ export function buildNodeShellExecutionCommand ( launcherPath : string ) : string {
191+ const escapedLauncherPath = shellEscapeDoubleQuoted ( launcherPath ) ;
192+ return [
193+ 'NODE_BIN="$(command -v node 2>/dev/null || true)"' ,
194+ 'if [ -z "$NODE_BIN" ] && [ -n "$NVM_BIN" ] && [ -x "$NVM_BIN/node" ]; then NODE_BIN="$NVM_BIN/node"; fi' ,
195+ 'if [ -z "$NODE_BIN" ] && [ -d "$HOME/.nvm/versions/node" ]; then NODE_BIN="$(find "$HOME/.nvm/versions/node" -type f -path "*/bin/node" 2>/dev/null | sort | tail -n 1)"; fi' ,
196+ 'if [ -z "$NODE_BIN" ] && [ -d "$HOME/.asdf/installs/nodejs" ]; then NODE_BIN="$(find "$HOME/.asdf/installs/nodejs" -type f -path "*/bin/node" 2>/dev/null | sort | tail -n 1)"; fi' ,
197+ 'if [ -z "$NODE_BIN" ]; then for candidate in /opt/homebrew/bin/node /usr/local/bin/node /usr/bin/node; do if [ -x "$candidate" ]; then NODE_BIN="$candidate"; break; fi; done; fi' ,
198+ 'if [ -z "$NODE_BIN" ]; then echo "Copilot Cockpit MCP launcher could not find node. Install Node.js or expose it in your shell startup." >&2; exit 127; fi' ,
199+ `exec "$NODE_BIN" "${ escapedLauncherPath } "` ,
200+ ] . join ( "; " ) ;
201+ }
202+
182203function resolvePosixShellCommand ( runtime : NodeResolutionRuntime ) : NodeLaunchCommand | undefined {
183204 for ( const shellPath of getPosixShellCandidates ( runtime ) ) {
184205 if ( runtime . fileExists ( shellPath ) ) {
@@ -244,7 +265,7 @@ export function buildSchedulerMcpServerEntry(
244265 command : nodeLaunch . command ,
245266 args :
246267 nodeLaunch . argsPrefix . length > 0
247- ? [ ...nodeLaunch . argsPrefix , `node ' ${ shellEscapeSingleQuoted ( launcherPath ) } '` ]
268+ ? [ ...nodeLaunch . argsPrefix , buildNodeShellExecutionCommand ( launcherPath ) ]
248269 : [ launcherPath ] ,
249270 } ;
250271}
@@ -272,7 +293,7 @@ function buildSchedulerCodexServerTable(workspaceRoot: string): string {
272293 const nodeLaunch = resolveNodeLaunchCommand ( ) ;
273294 const args =
274295 nodeLaunch . argsPrefix . length > 0
275- ? [ ...nodeLaunch . argsPrefix , `node ' ${ shellEscapeSingleQuoted ( launcherPath ) } '` ]
296+ ? [ ...nodeLaunch . argsPrefix , buildNodeShellExecutionCommand ( launcherPath ) ]
276297 : [ launcherPath ] ;
277298 return [
278299 "[mcp_servers.scheduler]" ,
0 commit comments