Skip to main content
Glama
client.ts5.38 kB
import { Client, ConnectConfig } from 'ssh2'; export type MikrotikConnection = { host: string; port?: number; username: string; password?: string; privateKey?: string | Buffer; timeoutMs?: number; }; export type ExecResult = { stdout: string; stderr: string; exitCode: number | null; signal?: string | null; durationMs?: number; }; /** * Promise-based SSH wrapper using ssh2. * Designed for running MikroTik RouterOS commands over SSH. */ export class MikrotikSSH { private conn: Client | null = null; private connected = false; async connect(opts: MikrotikConnection): Promise<void> { if (this.connected) return; this.conn = new Client(); const cfg: ConnectConfig = { host: opts.host, port: opts.port ?? 22, username: opts.username, readyTimeout: opts.timeoutMs ?? 20000, } as ConnectConfig; if (opts.privateKey) cfg.privateKey = opts.privateKey; if (opts.password) cfg.password = opts.password; await new Promise<void>((resolve, reject) => { const connection = this.conn; if (!connection) return reject(new Error('SSH client not initialized')); connection.on('ready', () => { this.connected = true; resolve(); }); connection.on('error', (err) => { this.connected = false; reject(err); }); connection.on('end', () => (this.connected = false)); connection.on('close', () => (this.connected = false)); connection.connect(cfg); }); } async disconnect(): Promise<void> { if (!this.conn) return; try { this.conn.end(); } finally { this.connected = false; this.conn = null; } } isConnected(): boolean { return this.connected; } /** * Execute a single command and return result with stdout/stderr/exit code. */ async exec(command: string, timeoutMs = 30000): Promise<ExecResult> { if (!this.conn || !this.connected) throw new Error('Not connected to MikroTik device'); const connection = this.conn; const start = Date.now(); return new Promise<ExecResult>((resolve, reject) => { let stdout = ''; let stderr = ''; let exitCode: number | null = null; let signal: string | null = null; const timer = setTimeout(() => { reject(new Error(`Command timeout after ${timeoutMs}ms: ${command}`)); }, timeoutMs); connection.exec(command, (err, stream) => { if (err) { clearTimeout(timer); return reject(err); } stream.on('close', (code: number, sig: string) => { clearTimeout(timer); exitCode = code; signal = sig; resolve({ stdout, stderr, exitCode, signal, durationMs: Date.now() - start }); }); stream.on('data', (data: Buffer) => { stdout += data.toString(); }); stream.stderr.on('data', (data: Buffer) => { stderr += data.toString(); }); }); }); } /** * Execute multiple commands sequentially and return results. * Stops on first error by default. */ async execMulti( commands: string[], opts?: { perCommandTimeoutMs?: number; stopOnError?: boolean } ): Promise<Record<string, ExecResult>> { const perCommandTimeoutMs = opts?.perCommandTimeoutMs ?? 30000; const stopOnError = opts?.stopOnError !== false; const results: Record<string, ExecResult> = {}; for (const cmd of commands) { try { results[cmd] = await this.exec(cmd, perCommandTimeoutMs); } catch (err: unknown) { const message = err instanceof Error ? err.message : String(err); results[cmd] = { stdout: '', stderr: message, exitCode: 1, durationMs: 0 }; if (stopOnError) break; } } return results; } /** * Execute commands in parallel (non-blocking) and return results. * Useful for independent operations. */ async execParallel( commands: string[], timeoutMs = 30000 ): Promise<Record<string, ExecResult>> { const promises = commands.map(cmd => this.exec(cmd, timeoutMs) .catch((err: unknown) => ({ stdout: '', stderr: err instanceof Error ? err.message : String(err), exitCode: 1, durationMs: 0 })) ); const results = await Promise.all(promises); return Object.fromEntries( commands.map((cmd, i) => [cmd, results[i]]) ); } } export default MikrotikSSH;

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/babasida246/ai-mcp-gateway'

If you have feedback or need assistance with the MCP directory API, please join our Discord server