UNPKG

mcp-ssh-tool

Version:

Model Context Protocol (MCP) SSH client server for remote automation

155 lines 5.9 kB
import { createSudoError, wrapError } from './errors.js'; import { logger, createTimer } from './logging.js'; import { sessionManager } from './session.js'; import { ErrorCode } from './types.js'; /** * Executes a command on the remote system */ export async function execCommand(sessionId, command, cwd, env) { logger.debug('Executing command', { sessionId, command, cwd }); const session = sessionManager.getSession(sessionId); if (!session) { throw new Error(`Session ${sessionId} not found or expired`); } const timer = createTimer(); try { // Build the command with environment and working directory let fullCommand = command; // Set environment variables if provided if (env && Object.keys(env).length > 0) { const envVars = Object.entries(env) .map(([key, value]) => `${key}=${JSON.stringify(value)}`) .join(' '); fullCommand = `${envVars} ${command}`; } // Change directory if provided if (cwd) { fullCommand = `cd ${JSON.stringify(cwd)} && ${fullCommand}`; } // Try bash first, fallback to sh if bash is not available const shellCommand = `if command -v bash >/dev/null 2>&1; then bash -lc ${JSON.stringify(fullCommand)}; else sh -lc ${JSON.stringify(fullCommand)}; fi`; const result = await session.ssh.execCommand(shellCommand); const execResult = { code: result.code || 0, stdout: result.stdout || '', stderr: result.stderr || '', durationMs: timer.elapsed() }; logger.debug('Command execution completed', { sessionId, code: execResult.code, durationMs: execResult.durationMs }); return execResult; } catch (error) { logger.error('Command execution failed', { sessionId, command, error }); throw wrapError(error, ErrorCode.ECONN, 'Failed to execute command on remote system'); } } /** * Executes a command with sudo privileges */ export async function execSudo(sessionId, command, password, cwd) { logger.debug('Executing sudo command', { sessionId, command, cwd }); const session = sessionManager.getSession(sessionId); if (!session) { throw new Error(`Session ${sessionId} not found or expired`); } const timer = createTimer(); try { // Build the sudo command let sudoCommand = command; // Change directory if provided if (cwd) { sudoCommand = `cd ${JSON.stringify(cwd)} && ${command}`; } let fullCommand; if (password) { // Use sudo -S -n to read password from stdin and never prompt fullCommand = `echo ${JSON.stringify(password)} | sudo -S -n ${sudoCommand}`; } else { // Try without password using -n (never prompt for password) fullCommand = `sudo -n ${sudoCommand}`; } const result = await session.ssh.execCommand(fullCommand); // Check if sudo failed due to password issues if (result.code !== 0 && result.stderr) { const stderrLower = result.stderr.toLowerCase(); if (stderrLower.includes('password') || stderrLower.includes('authentication') || stderrLower.includes('sorry')) { throw createSudoError('Sudo authentication failed', 'Provide a valid sudo password or ensure NOPASSWD is configured'); } } const execResult = { code: result.code || 0, stdout: result.stdout || '', stderr: result.stderr || '', durationMs: timer.elapsed() }; logger.debug('Sudo command execution completed', { sessionId, code: execResult.code, durationMs: execResult.durationMs }); return execResult; } catch (error) { if (error instanceof Error && error.message.includes('sudo')) { throw error; } logger.error('Sudo command execution failed', { sessionId, command, error }); throw wrapError(error, ErrorCode.ENOSUDO, 'Failed to execute sudo command on remote system'); } } /** * Checks if a command exists on the remote system */ export async function commandExists(sessionId, command) { try { const result = await execCommand(sessionId, `which ${command}`); return result.code === 0; } catch (error) { return false; } } /** * Gets the available shell on the remote system */ export async function getAvailableShell(sessionId) { const shells = ['bash', 'zsh', 'sh']; for (const shell of shells) { if (await commandExists(sessionId, shell)) { logger.debug('Found available shell', { sessionId, shell }); return shell; } } logger.warn('No standard shell found, defaulting to sh', { sessionId }); return 'sh'; } /** * Executes a command with proper shell detection */ export async function execWithShell(sessionId, command, cwd, env) { const shell = await getAvailableShell(sessionId); // Build the command with proper shell let fullCommand = command; // Set environment variables if provided if (env && Object.keys(env).length > 0) { const envVars = Object.entries(env) .map(([key, value]) => `${key}=${JSON.stringify(value)}`) .join(' '); fullCommand = `${envVars} ${command}`; } // Change directory if provided if (cwd) { fullCommand = `cd ${JSON.stringify(cwd)} && ${fullCommand}`; } // Use the detected shell with login shell behavior const shellCommand = `${shell} -lc ${JSON.stringify(fullCommand)}`; return execCommand(sessionId, shellCommand); } //# sourceMappingURL=process.js.map