runCommandBatch
Executes multiple shell commands sequentially on an SSH host via the MCP SSH Agent, enabling automated and secure remote task execution.
Instructions
Executes multiple shell commands sequentially on an SSH host
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| commands | Yes | List of shell commands to execute | |
| hostAlias | Yes | Alias or hostname of the SSH host |
Implementation Reference
- src/ssh-client.ts:122-161 (handler)The core handler function implementing the 'runCommandBatch' MCP tool. It connects to an SSH host, runs each command in the provided batch, collects stdout/stderr/code for each, determines if all succeeded, and returns structured results.async runCommandBatch(hostAlias: string, commands: string[]): Promise<BatchCommandResult> { try { await this.connectToHost(hostAlias); const results: CommandResult[] = []; let success = true; for (const command of commands) { const result = await this.ssh.execCommand(command); const cmdResult: CommandResult = { stdout: result.stdout, stderr: result.stderr, code: result.code || 0 }; results.push(cmdResult); if (cmdResult.code !== 0) { success = false; // We don't abort, execute all commands } } this.ssh.dispose(); return { results, success }; } catch (error) { console.error(`Error during batch execution on ${hostAlias}:`, error); return { results: [{ stdout: '', stderr: error instanceof Error ? error.message : String(error), code: 1 }], success: false }; } }
- src/types.ts:11-28 (schema)TypeScript type definitions serving as the schema for the tool's input (hostAlias: string, commands: string[]) and output (BatchCommandResult containing array of CommandResult and success boolean).// Result of a remote command export interface CommandResult { stdout: string; stderr: string; code: number; } // SSH connection status export interface ConnectionStatus { connected: boolean; message: string; } // Batch result of remote commands export interface BatchCommandResult { results: CommandResult[]; success: boolean; }
- src/ssh-client.ts:166-187 (helper)Private helper method used by runCommandBatch to connect to the SSH host by parsing host info from ~/.ssh/config and establishing the NodeSSH connection.private async connectToHost(hostAlias: string): Promise<void> { // Get host information const hostInfo = await this.getHostInfo(hostAlias); if (!hostInfo) { throw new Error(`Host ${hostAlias} not found`); } // Create connection configuration const connectionConfig = { host: hostInfo.hostname, username: hostInfo.user, port: hostInfo.port || 22, privateKeyPath: hostInfo.identityFile }; try { await this.ssh.connect(connectionConfig); } catch (error) { throw new Error(`Connection to ${hostAlias} failed: ${error instanceof Error ? error.message : String(error)}`); } }
- src/ssh-config-parser.ts:7-118 (helper)Supporting class that parses ~/.ssh/config and known_hosts files to provide host information required for connecting in runCommandBatch.export class SSHConfigParser { private configPath: string; private knownHostsPath: string; constructor() { const homeDir = homedir(); this.configPath = join(homeDir, '.ssh', 'config'); this.knownHostsPath = join(homeDir, '.ssh', 'known_hosts'); } /** * Reads and parses the SSH config file */ async parseConfig(): Promise<SSHHostInfo[]> { try { const content = await readFile(this.configPath, 'utf-8'); const config = sshConfig.parse(content); return this.extractHostsFromConfig(config); } catch (error) { console.error('Error reading SSH config:', error); return []; } } /** * Extracts host information from SSH Config */ private extractHostsFromConfig(config: any[]): SSHHostInfo[] { const hosts: SSHHostInfo[] = []; for (const section of config) { if (section.param === 'Host' && section.value !== '*') { const hostInfo: SSHHostInfo = { hostname: '', alias: section.value, }; // Search all entries for this host for (const param of section.config) { switch (param.param.toLowerCase()) { case 'hostname': hostInfo.hostname = param.value; break; case 'user': hostInfo.user = param.value; break; case 'port': hostInfo.port = parseInt(param.value, 10); break; case 'identityfile': hostInfo.identityFile = param.value; break; default: // Store other parameters hostInfo[param.param.toLowerCase()] = param.value; } } // Only add hosts with complete information if (hostInfo.hostname) { hosts.push(hostInfo); } } } return hosts; } /** * Reads the known_hosts file and extracts hostnames */ async parseKnownHosts(): Promise<string[]> { try { const content = await readFile(this.knownHostsPath, 'utf-8'); const knownHosts = content .split('\n') .filter(line => line.trim() !== '') .map(line => { // Format: hostname[,hostname2...] key-type public-key const parts = line.split(' ')[0]; return parts.split(',')[0]; }); return knownHosts; } catch (error) { console.error('Error reading known_hosts file:', error); return []; } } /** * Consolidates information from config and known_hosts */ async getAllKnownHosts(): Promise<SSHHostInfo[]> { const configHosts = await this.parseConfig(); const knownHostnames = await this.parseKnownHosts(); // Add hosts from known_hosts that aren't in the config for (const hostname of knownHostnames) { if (!configHosts.some(host => host.hostname === hostname || host.alias === hostname)) { configHosts.push({ hostname: hostname }); } } return configHosts; } }