Skip to main content
Glama
index.ts4.57 kB
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import { Client as SSHClient, ConnectConfig, type ClientChannel } from "ssh2"; type SshEnv = { SSH_HOST: string | undefined; SSH_PORT: string | undefined; SSH_USERNAME: string | undefined; SSH_PASSWORD: string | undefined; }; function getEnv(): Required<SshEnv> { const env: SshEnv = { SSH_HOST: process.env.SSH_HOST, SSH_PORT: process.env.SSH_PORT ?? "22", SSH_USERNAME: process.env.SSH_USERNAME, SSH_PASSWORD: process.env.SSH_PASSWORD, }; const missing: string[] = []; if (!env.SSH_HOST) missing.push("SSH_HOST"); if (!env.SSH_USERNAME) missing.push("SSH_USERNAME"); if (!env.SSH_PASSWORD) missing.push("SSH_PASSWORD"); if (missing.length > 0) { throw new Error( `Missing required environment variables: ${missing.join(", ")}. Configure them in your MCP server config.` ); } return env as Required<SshEnv>; } async function createSshConnection(): Promise<SSHClient> { const env = getEnv(); const ssh = new SSHClient(); const config: ConnectConfig = { host: env.SSH_HOST, port: Number(env.SSH_PORT), username: env.SSH_USERNAME, password: env.SSH_PASSWORD, readyTimeout: 15000, tryKeyboard: false, algorithms: { // Keep defaults; allow host key algo negotiation modern-first }, }; await new Promise<void>((resolve, reject) => { ssh .on("ready", () => resolve()) .on("error", (err: Error) => reject(err)) .connect(config); }); return ssh; } async function runRemoteCommand(ssh: SSHClient, command: string): Promise<{ exitCode: number; stdout: string; stderr: string }>{ return await new Promise((resolve, reject) => { ssh.exec(command, (err: Error | undefined, stream: ClientChannel) => { if (err) return reject(err); let stdout = ""; let stderr = ""; stream .on("close", (code: number) => { resolve({ exitCode: code ?? 0, stdout, stderr }); }) .on("data", (data: Buffer) => { stdout += data.toString(); }) .stderr.on("data", (data: Buffer) => { stderr += data.toString(); }); }); }); } async function main(): Promise<void> { const server = new McpServer({ name: "ssh-mcp-server", version: "1.0.0", }); server.registerTool( "ssh_test_connection", { title: "Test SSH Connection", description: "Attempts to connect to the configured SSH host and returns the remote hostname", inputSchema: {}, }, async () => { try { const ssh = await createSshConnection(); try { const { stdout, stderr, exitCode } = await runRemoteCommand(ssh, "hostname"); ssh.end(); const details = JSON.stringify( { exitCode, stderr: stderr.trim(), stdout: stdout.trim() }, null, 2 ); return { content: [{ type: "text", text: details }] }; } finally { // Ensure connection is closed if not already ssh.end(); } } catch (error) { const message = error instanceof Error ? error.message : String(error); return { content: [{ type: "text", text: `SSH connection failed: ${message}` }], isError: true }; } } ); server.registerTool( "ssh_run", { title: "Run Remote Command", description: "Runs a non-interactive command remotely over SSH and returns stdout, stderr, and exit code", inputSchema: { command: z.string().min(1).describe("Command to run remotely") }, }, async ({ command }) => { try { const ssh = await createSshConnection(); try { const result = await runRemoteCommand(ssh, command); ssh.end(); const text = JSON.stringify( { command, ...result, stdout: result.stdout.trim(), stderr: result.stderr.trim() }, null, 2 ); return { content: [{ type: "text", text }] }; } finally { ssh.end(); } } catch (error) { const message = error instanceof Error ? error.message : String(error); return { content: [{ type: "text", text: `SSH command failed: ${message}` }], isError: true }; } } ); const transport = new StdioServerTransport(); await server.connect(transport); } main().catch((err) => { console.error(err); process.exit(1); });

Implementation Reference

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/lgariv/ssh-mcp'

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