UNPKG

it-tools-mcp

Version:

Full MCP 2025-06-18 compliant server with 121+ IT tools, logging, ping, progress tracking, cancellation, and sampling utilities

144 lines (143 loc) 6.34 kB
import { z } from "zod"; import fs from "fs"; import path from "path"; import os from "os"; function resolvePrivateKey(privateKeyArg) { // If not provided, try default keys if (!privateKeyArg) { const home = os.homedir(); const defaultKeys = [ path.join(home, '.ssh', 'id_rsa'), path.join(home, '.ssh', 'id_ed25519'), ]; for (const keyPath of defaultKeys) { if (fs.existsSync(keyPath)) { return fs.readFileSync(keyPath, 'utf8'); } } return undefined; } // If it looks like a path, try to read it if (privateKeyArg.startsWith('/') || privateKeyArg.startsWith('~') || privateKeyArg.endsWith('.pem') || privateKeyArg.endsWith('.key')) { let keyPath = privateKeyArg; if (keyPath.startsWith('~')) { keyPath = path.join(os.homedir(), keyPath.slice(1)); } if (fs.existsSync(keyPath)) { return fs.readFileSync(keyPath, 'utf8'); } else { throw new Error('Private key file not found: ' + keyPath); } } // Otherwise, assume it's the key content return privateKeyArg; } export function registerScp(server) { server.registerTool("scp", { description: "Copy files to or from a remote host using SFTP (SCP-like)", inputSchema: { target: z.string().describe("Target host"), user: z.string().describe("Username"), direction: z.enum(["upload", "download"]).describe("Direction: upload (local to remote) or download (remote to local)"), localPath: z.string().describe("Local file path (source for upload, destination for download)"), remotePath: z.string().describe("Remote file path (destination for upload, source for download)"), privateKey: z.string().optional().describe("Private key for authentication (PEM format, optional, or path to key file)") }, // VS Code compliance annotations annotations: { title: "Scp", description: "Copy files to or from a remote host using SFTP (SCP-like)", readOnlyHint: false } }, async ({ target, user, direction, localPath, remotePath, privateKey }) => { try { const { Client } = await import("ssh2"); const fs = await import("fs"); let resolvedKey; try { resolvedKey = resolvePrivateKey(privateKey); } catch (err) { return { content: [{ type: "text", text: `SCP key error: ${err.message}` }] }; } return await new Promise((resolve) => { const conn = new Client(); let finished = false; const finish = (msg) => { if (!finished) { finished = true; try { conn.end(); } catch { } resolve({ content: [{ type: "text", text: msg }] }); } }; // Connection timeout (20s) const timeout = setTimeout(() => { finish(`SCP connection timed out after 20 seconds`); }, 20000); conn.on("ready", () => { clearTimeout(timeout); conn.sftp((err, sftp) => { if (err) { finish(`SFTP error: ${err.message}`); return; } if (direction === "upload") { let readStream, writeStream; try { readStream = fs.createReadStream(localPath); writeStream = sftp.createWriteStream(remotePath); } catch (streamErr) { finish(`Upload failed: ${streamErr.message}`); return; } writeStream.on("close", () => finish(`Upload complete: ${localPath}${user}@${target}:${remotePath}`)); writeStream.on("error", (err) => finish(`Upload failed: ${err.message}`)); readStream.on("error", (err) => finish(`Upload failed: ${err.message}`)); readStream.pipe(writeStream); } else { let readStream, writeStream; try { readStream = sftp.createReadStream(remotePath); writeStream = fs.createWriteStream(localPath); } catch (streamErr) { finish(`Download failed: ${streamErr.message}`); return; } writeStream.on("close", () => finish(`Download complete: ${user}@${target}:${remotePath}${localPath}`)); writeStream.on("error", (err) => finish(`Download failed: ${err.message}`)); readStream.on("error", (err) => finish(`Download failed: ${err.message}`)); readStream.pipe(writeStream); } }); }).on("error", (err) => { clearTimeout(timeout); finish(`SCP connection error: ${err.message}`); }); try { conn.connect({ host: target, username: user, ...(resolvedKey ? { privateKey: resolvedKey } : {}) }); } catch (err) { clearTimeout(timeout); finish(`SCP connect threw: ${err.message}`); } }); } catch (fatalErr) { return { content: [{ type: "text", text: `SCP fatal error: ${fatalErr.message || fatalErr}` }] }; } }); }