Skip to main content
Glama
smithery.ts3.82 kB
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; import { Client as SSHClient, type ConnectConfig, type ClientChannel } from "ssh2"; export const configSchema = z.object({ sshHost: z.string().describe("Target SSH IP/hostname"), sshPort: z.number().int().positive().default(22).describe("SSH port"), sshUsername: z.string().describe("SSH username"), sshPassword: z.string().describe("SSH password"), }); type SshConfig = z.infer<typeof configSchema>; async function createSshConnection(cfg: SshConfig): Promise<SSHClient> { const ssh = new SSHClient(); const connectConfig: ConnectConfig = { host: cfg.sshHost, port: cfg.sshPort, username: cfg.sshUsername, password: cfg.sshPassword, readyTimeout: 15000, tryKeyboard: false, }; await new Promise<void>((resolve, reject) => { ssh .on("ready", () => resolve()) .on("error", (err: Error) => reject(err)) .connect(connectConfig); }); 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(); }); }); }); } export default function ({ config }: { config: SshConfig }) { 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 and returns the remote hostname", inputSchema: {}, }, async () => { try { const ssh = await createSshConnection(config); try { const { stdout, stderr, exitCode } = await runRemoteCommand(ssh, "hostname"); ssh.end(); return { content: [ { type: "text", text: JSON.stringify({ exitCode, stdout: stdout.trim(), stderr: stderr.trim() }, null, 2), }, ], }; } finally { 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 on the remote host", inputSchema: { command: z.string().min(1).describe("Command to run remotely") }, }, async ({ command }) => { try { const ssh = await createSshConnection(config); try { const result = await runRemoteCommand(ssh, command); ssh.end(); return { content: [ { type: "text", text: JSON.stringify( { command, ...result, stdout: result.stdout.trim(), stderr: result.stderr.trim() }, null, 2 ), }, ], }; } 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 }; } } ); return server.server; }

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