Skip to main content
Glama

Infer MCP Server

by jackyxhb
sshService.ts8.62 kB
import { Client as SshClient, type ConnectConfig } from "ssh2"; import { getConfig } from "../config/index.js"; import { ConcurrencyLimiter } from "../utils/concurrency.js"; import { createAbortError } from "../utils/abort.js"; import type { ProgressUpdate } from "../utils/progress.js"; import { logger } from "../utils/logger.js"; export interface SshCommandOptions { timeoutMs?: number; cwd?: string; env?: Record<string, string>; maxOutputBytes?: number; signal?: AbortSignal; onProgress?: (update: ProgressUpdate) => void; } export interface SshExecutionMetadata { requestId?: string; tool?: string; } export interface SshCommandResult { stdout: string; stderr: string; truncated: { stdout: boolean; stderr: boolean; }; exitCode: number | null; signal?: string; durationMs: number; } interface TruncatingBuffer { push(chunk: Buffer): void; toString(): string; isTruncated(): boolean; size(): number; } const sshConcurrency = new ConcurrencyLimiter(); function createTruncatingBuffer(limit: number): TruncatingBuffer { const chunks: Buffer[] = []; let byteLength = 0; let truncated = false; return { push(chunk: Buffer) { if (truncated || limit <= 0) { truncated = limit <= 0 ? true : truncated; return; } const available = limit - byteLength; if (available <= 0) { truncated = true; return; } if (chunk.length > available) { chunks.push(chunk.subarray(0, available)); byteLength += available; truncated = true; } else { chunks.push(chunk); byteLength += chunk.length; } }, toString() { if (chunks.length === 0) { return ""; } return Buffer.concat(chunks, byteLength).toString(); }, isTruncated() { return truncated; }, size() { return byteLength; } }; } function ensureCommandAllowed(command: string, patterns?: RegExp[]): void { if (!patterns || patterns.length === 0) { return; } const allowed = patterns.some((regex) => regex.test(command)); if (!allowed) { throw new Error("Command is not permitted by policy"); } } export async function executeSshCommand( profileName: string, command: string, options: SshCommandOptions = {}, metadata: SshExecutionMetadata = {} ): Promise<SshCommandResult> { const config = getConfig(); const profile = config.sshProfiles[profileName]; if (!profile) { throw new Error(`SSH profile '${profileName}' not found`); } const skipAuthorization = config.localTestMode && (profileName === "local-test" || profile.host === "127.0.0.1" || profile.host === "localhost"); if (!skipAuthorization) { ensureCommandAllowed(command, profile.policy.allowedCommandPatterns); } const timeoutLimit = Math.min(options.timeoutMs ?? profile.policy.maxExecutionMs, profile.policy.maxExecutionMs); const outputLimit = Math.min(options.maxOutputBytes ?? profile.policy.maxOutputBytes, profile.policy.maxOutputBytes); options.onProgress?.({ progress: 0, message: "Waiting for SSH availability" }); const release = await sshConcurrency.acquire(profileName, profile.policy.maxConcurrent, options.signal); const connectionConfig: ConnectConfig = { host: profile.host, port: profile.port, username: profile.username, readyTimeout: timeoutLimit }; if (profile.privateKey) { connectionConfig.privateKey = profile.privateKey; if (profile.passphrase) { connectionConfig.passphrase = profile.passphrase; } } else if (profile.password) { connectionConfig.password = profile.password; } else { throw new Error(`SSH profile '${profileName}' is missing authentication details`); } const start = Date.now(); logger.info("Executing SSH command", { profile: profileName, requestId: metadata.requestId, tool: metadata.tool, command }); options.onProgress?.({ progress: 0.1, message: "Connecting to remote host" }); try { return await new Promise((resolve, reject) => { const sshClient = new SshClient(); let timeoutHandle: NodeJS.Timeout | undefined; let aborted = false; let settled = false; let abortListener: (() => void) | undefined; const stdoutBuffer = createTruncatingBuffer(outputLimit); const stderrBuffer = createTruncatingBuffer(outputLimit); const finalize = (executor: () => void) => { if (settled) { return; } settled = true; executor(); }; const cleanup = () => { if (timeoutHandle) { clearTimeout(timeoutHandle); timeoutHandle = undefined; } if (abortListener && options.signal) { options.signal.removeEventListener("abort", abortListener); } sshClient.end(); }; const rejectWithError = (error: Error) => { cleanup(); finalize(() => reject(error)); }; const onTimeout = () => { options.onProgress?.({ progress: 1, message: "SSH command timed out" }); logger.warn("SSH command timed out", { profile: profileName, requestId: metadata.requestId, tool: metadata.tool, timeoutMs: timeoutLimit }); rejectWithError(new Error(`SSH command timed out after ${timeoutLimit} ms`)); }; if (options.signal) { if (options.signal.aborted) { rejectedDueToAbort(); return; } abortListener = () => rejectedDueToAbort(); options.signal.addEventListener("abort", abortListener, { once: true }); } function rejectedDueToAbort(): void { aborted = true; options.onProgress?.({ progress: 1, message: "SSH command cancelled" }); sshClient.destroy(); rejectWithError(createAbortError("SSH command cancelled")); } sshClient .on("ready", () => { if (aborted) { rejectWithError(createAbortError("SSH command cancelled")); return; } options.onProgress?.({ progress: 0.4, message: "Executing remote command" }); const execOptions = { cwd: options.cwd, env: options.env }; sshClient.exec(command, execOptions, (err, stream) => { if (err) { rejectWithError(err); return; } let exitCode: number | null = null; let signal: string | undefined; stream .on("close", (code: number | null, signalName?: string) => { exitCode = code; signal = signalName ?? undefined; cleanup(); const durationMs = Date.now() - start; const result: SshCommandResult = { stdout: stdoutBuffer.toString(), stderr: stderrBuffer.toString(), truncated: { stdout: stdoutBuffer.isTruncated(), stderr: stderrBuffer.isTruncated() }, exitCode, signal, durationMs }; logger.info("SSH command completed", { profile: profileName, requestId: metadata.requestId, tool: metadata.tool, exitCode, signal, durationMs, stdoutBytes: stdoutBuffer.size(), stderrBytes: stderrBuffer.size(), stdoutTruncated: result.truncated.stdout, stderrTruncated: result.truncated.stderr }); options.onProgress?.({ progress: 1, message: "SSH command completed" }); finalize(() => resolve(result)); }) .on("data", (chunk: Buffer) => { stdoutBuffer.push(chunk); }) .stderr.on("data", (chunk: Buffer) => { stderrBuffer.push(chunk); }); }); }) .on("error", (err) => { options.onProgress?.({ progress: 1, message: "SSH command failed" }); logger.error("SSH command failed", { profile: profileName, requestId: metadata.requestId, tool: metadata.tool, error: err.message }); rejectWithError(err); }) .connect(connectionConfig); timeoutHandle = setTimeout(onTimeout, timeoutLimit); }); } finally { release(); } }

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/jackyxhb/InferMCPServer'

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