Terminal MCP Server

by weidwonder
Verified
import { Client } from 'ssh2'; import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; export class SSHManager { private client: Client | null = null; private connection: Promise<void> | null = null; private timeout: NodeJS.Timeout | null = null; private sessionTimeout: number = 20 * 60 * 1000; // 20 minutes constructor() { this.client = new Client(); } async connect(host: string): Promise<void> { if (this.connection) { return this.connection; } const privateKey = fs.readFileSync(path.join(os.homedir(), '.ssh', 'id_rsa')); this.connection = new Promise((resolve, reject) => { this.client = new Client(); this.client .on('ready', () => { this.resetTimeout(); resolve(); }) .on('error', (err) => { reject(err); }) .connect({ host: host, username: 'weidwonder', privateKey: privateKey, }); }); return this.connection; } private resetTimeout(): void { if (this.timeout) { clearTimeout(this.timeout); } this.timeout = setTimeout(async () => { console.log('SSH session timeout, disconnecting'); await this.disconnect(); }, this.sessionTimeout); } async executeCommand(host: string, command: string, env: Record<string, string> | {} = {}): Promise<{stdout: string; stderr: string}> { if (!this.client) { await this.connect(host); } this.resetTimeout(); return new Promise((resolve, reject) => { // 使用完整的shell路径,以交互式登录模式执行命令 // 这更接近于用户直接SSH登录的体验 // 构建环境变量设置命令 const envSetup = Object.entries(env as Record<string, string>) .map(([key, value]) => `export ${key}="${String(value).replace(/"/g, '\\"')}"`) .join(' && '); // 如果有环境变量,先设置环境变量,再执行命令 const fullCommand = envSetup ? `${envSetup} && ${command}` : command; this.client?.exec(`/bin/bash --login -c "${fullCommand}"`, (err, stream) => { if (err) { reject(err); return } let stdout = "" let stderr = ''; stream .on("data", (data: Buffer) => { this.resetTimeout(); stdout += data.toString(); }) .stderr.on('data', (data: Buffer) => { stderr += data.toString(); }) .on('close', () => { resolve({ stdout, stderr }); }) .on('error', (err) => { reject(err); }); }); }); } async disconnect(): Promise<void> { if (this.client) { this.client.end(); this.client = null; this.connection = null; } } }