@thesashadev/ssh-mcp-server 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,216 @@
1
+ import { readFileSync, createReadStream, statSync } from 'fs';
2
+ import { basename } from 'path';
3
+ import { getSftp, getConnection } from '../ssh-manager.js';
4
+ import { escapeRemotePath, normalizeRemotePath, ensureRemoteDir, remoteDir } from '../utils/path-utils.js';
5
+ /**
6
+ * Upload a local file to a remote server with 5 fallback strategies.
7
+ *
8
+ * Strategies (tried in order):
9
+ * 1. SFTP fastPut — high-speed parallel transfer
10
+ * 2. SFTP createWriteStream — streamed transfer
11
+ * 3. SCP via exec + stdin pipe — cat > remotePath
12
+ * 4. Base64 via exec — encode/decode
13
+ * 5. Chunked echo via exec — small base64 chunks appended
14
+ */
15
+ export async function uploadFile(params) {
16
+ const { serverId, localPath, remotePath: rawRemotePath, overwrite = true } = params;
17
+ const remotePath = normalizeRemotePath(rawRemotePath);
18
+ // Validate local file exists
19
+ let fileSize;
20
+ try {
21
+ const stat = statSync(localPath);
22
+ if (!stat.isFile()) {
23
+ return `Error: "${localPath}" is not a file.`;
24
+ }
25
+ fileSize = stat.size;
26
+ }
27
+ catch (err) {
28
+ return `Error: Cannot access local file "${localPath}": ${err.message}`;
29
+ }
30
+ const errors = [];
31
+ const fileName = basename(localPath);
32
+ const startTime = Date.now();
33
+ // Strategy 1: SFTP fastPut
34
+ try {
35
+ const result = await strategy1_sftpFastPut(serverId, localPath, remotePath, overwrite);
36
+ const duration = ((Date.now() - startTime) / 1000).toFixed(1);
37
+ return `SUCCESS: uploaded via SFTP fastPut\nfile: ${fileName} (${formatSize(fileSize)})\nremote: ${remotePath}\nduration: ${duration}s`;
38
+ }
39
+ catch (err) {
40
+ errors.push(`Strategy 1 (SFTP fastPut): ${err.message}`);
41
+ }
42
+ // Strategy 2: SFTP stream
43
+ try {
44
+ const result = await strategy2_sftpStream(serverId, localPath, remotePath, overwrite);
45
+ const duration = ((Date.now() - startTime) / 1000).toFixed(1);
46
+ return `SUCCESS: uploaded via SFTP stream\nfile: ${fileName} (${formatSize(fileSize)})\nremote: ${remotePath}\nduration: ${duration}s`;
47
+ }
48
+ catch (err) {
49
+ errors.push(`Strategy 2 (SFTP stream): ${err.message}`);
50
+ }
51
+ // Strategy 3: SCP via stdin pipe
52
+ try {
53
+ await strategy3_scpPipe(serverId, localPath, remotePath);
54
+ const duration = ((Date.now() - startTime) / 1000).toFixed(1);
55
+ return `SUCCESS: uploaded via SCP pipe\nfile: ${fileName} (${formatSize(fileSize)})\nremote: ${remotePath}\nduration: ${duration}s`;
56
+ }
57
+ catch (err) {
58
+ errors.push(`Strategy 3 (SCP pipe): ${err.message}`);
59
+ }
60
+ // Strategy 4: Base64
61
+ try {
62
+ await strategy4_base64(serverId, localPath, remotePath);
63
+ const duration = ((Date.now() - startTime) / 1000).toFixed(1);
64
+ return `SUCCESS: uploaded via base64\nfile: ${fileName} (${formatSize(fileSize)})\nremote: ${remotePath}\nduration: ${duration}s`;
65
+ }
66
+ catch (err) {
67
+ errors.push(`Strategy 4 (Base64): ${err.message}`);
68
+ }
69
+ // Strategy 5: Chunked echo
70
+ try {
71
+ await strategy5_chunkedEcho(serverId, localPath, remotePath);
72
+ const duration = ((Date.now() - startTime) / 1000).toFixed(1);
73
+ return `SUCCESS: uploaded via chunked base64\nfile: ${fileName} (${formatSize(fileSize)})\nremote: ${remotePath}\nduration: ${duration}s`;
74
+ }
75
+ catch (err) {
76
+ errors.push(`Strategy 5 (chunked echo): ${err.message}`);
77
+ }
78
+ return `FAILED: upload failed, all 5 strategies exhausted\nlocal: ${localPath}\nremote: ${remotePath}\n\nerrors:\n${errors.map((e, i) => ` ${i + 1}. ${e}`).join('\n')}`;
79
+ }
80
+ // --- Strategy Implementations ---
81
+ async function strategy1_sftpFastPut(serverId, localPath, remotePath, overwrite) {
82
+ const sftp = await getSftp(serverId);
83
+ await ensureRemoteDir(sftp, remoteDir(remotePath));
84
+ if (!overwrite) {
85
+ const exists = await new Promise(resolve => {
86
+ sftp.stat(remotePath, (err) => resolve(!err));
87
+ });
88
+ if (exists)
89
+ throw new Error('File already exists and overwrite=false');
90
+ }
91
+ return new Promise((resolve, reject) => {
92
+ sftp.fastPut(localPath, remotePath, {
93
+ concurrency: 64,
94
+ chunkSize: 65536,
95
+ step: (transferred, chunk, total) => {
96
+ // Progress tracking available if needed
97
+ },
98
+ }, (err) => {
99
+ if (err)
100
+ reject(err);
101
+ else
102
+ resolve();
103
+ });
104
+ });
105
+ }
106
+ async function strategy2_sftpStream(serverId, localPath, remotePath, overwrite) {
107
+ const sftp = await getSftp(serverId);
108
+ await ensureRemoteDir(sftp, remoteDir(remotePath));
109
+ if (!overwrite) {
110
+ const exists = await new Promise(resolve => {
111
+ sftp.stat(remotePath, (err) => resolve(!err));
112
+ });
113
+ if (exists)
114
+ throw new Error('File already exists and overwrite=false');
115
+ }
116
+ return new Promise((resolve, reject) => {
117
+ const readStream = createReadStream(localPath);
118
+ const writeStream = sftp.createWriteStream(remotePath);
119
+ writeStream.on('close', () => resolve());
120
+ writeStream.on('error', (err) => reject(err));
121
+ readStream.on('error', (err) => reject(err));
122
+ readStream.pipe(writeStream);
123
+ });
124
+ }
125
+ async function strategy3_scpPipe(serverId, localPath, remotePath) {
126
+ const client = await getConnection(serverId);
127
+ const escaped = escapeRemotePath(remotePath);
128
+ // Ensure parent directory exists
129
+ const parentDir = escapeRemotePath(remoteDir(remotePath));
130
+ await execSimple(client, `mkdir -p ${parentDir}`);
131
+ return new Promise((resolve, reject) => {
132
+ client.exec(`cat > ${escaped}`, (err, stream) => {
133
+ if (err)
134
+ return reject(err);
135
+ let error = null;
136
+ stream.on('close', () => {
137
+ if (error)
138
+ reject(error);
139
+ else
140
+ resolve();
141
+ });
142
+ stream.on('error', (e) => { error = e; });
143
+ const readStream = createReadStream(localPath);
144
+ readStream.on('error', (e) => {
145
+ error = e;
146
+ stream.close();
147
+ });
148
+ readStream.pipe(stream);
149
+ });
150
+ });
151
+ }
152
+ async function strategy4_base64(serverId, localPath, remotePath) {
153
+ const client = await getConnection(serverId);
154
+ const fileData = readFileSync(localPath);
155
+ const b64 = fileData.toString('base64');
156
+ const escaped = escapeRemotePath(remotePath);
157
+ const parentDir = escapeRemotePath(remoteDir(remotePath));
158
+ // Ensure parent dir and decode base64
159
+ const cmd = `mkdir -p ${parentDir} && echo '${b64}' | base64 -d > ${escaped}`;
160
+ // If command is too long (>100KB), fall through to chunked
161
+ if (cmd.length > 100000) {
162
+ throw new Error('File too large for single base64 transfer, use chunked strategy');
163
+ }
164
+ await execSimple(client, cmd);
165
+ }
166
+ async function strategy5_chunkedEcho(serverId, localPath, remotePath) {
167
+ const client = await getConnection(serverId);
168
+ const fileData = readFileSync(localPath);
169
+ const b64 = fileData.toString('base64');
170
+ const escaped = escapeRemotePath(remotePath);
171
+ const parentDir = escapeRemotePath(remoteDir(remotePath));
172
+ await execSimple(client, `mkdir -p ${parentDir}`);
173
+ const CHUNK_SIZE = 100000; // ~100KB per chunk
174
+ const tempFile = `${remotePath}.b64tmp`;
175
+ const escapedTemp = escapeRemotePath(tempFile);
176
+ // Clear temp file
177
+ await execSimple(client, `> ${escapedTemp}`);
178
+ // Write chunks
179
+ for (let i = 0; i < b64.length; i += CHUNK_SIZE) {
180
+ const chunk = b64.slice(i, i + CHUNK_SIZE);
181
+ await execSimple(client, `printf '%s' '${chunk}' >> ${escapedTemp}`);
182
+ }
183
+ // Decode and move
184
+ await execSimple(client, `base64 -d ${escapedTemp} > ${escaped} && rm -f ${escapedTemp}`);
185
+ }
186
+ // --- Helpers ---
187
+ function execSimple(client, command) {
188
+ return new Promise((resolve, reject) => {
189
+ client.exec(command, (err, stream) => {
190
+ if (err)
191
+ return reject(err);
192
+ let output = '';
193
+ let stderr = '';
194
+ stream.on('data', (data) => { output += data.toString(); });
195
+ stream.stderr.on('data', (data) => { stderr += data.toString(); });
196
+ stream.on('close', (code) => {
197
+ if (code !== 0) {
198
+ reject(new Error(`Command failed (exit ${code}): ${stderr || output}`));
199
+ }
200
+ else {
201
+ resolve(output);
202
+ }
203
+ });
204
+ });
205
+ });
206
+ }
207
+ function formatSize(bytes) {
208
+ if (bytes < 1024)
209
+ return `${bytes}B`;
210
+ if (bytes < 1024 * 1024)
211
+ return `${(bytes / 1024).toFixed(1)}KB`;
212
+ if (bytes < 1024 * 1024 * 1024)
213
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
214
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)}GB`;
215
+ }
216
+ //# sourceMappingURL=upload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload.js","sourceRoot":"","sources":["../../src/tools/upload.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Cleans SSH output for AI readability:
3
+ * - Strips ANSI escape codes
4
+ * - Removes control characters
5
+ * - Collapses excessive blank lines
6
+ * - Truncates very long lines
7
+ * - Detects and replaces binary data
8
+ * - Converts tab-separated data to aligned columns
9
+ */
10
+ export declare function cleanOutput(raw: string | Buffer): string;
11
+ /**
12
+ * Format command result for AI consumption
13
+ */
14
+ export declare function formatCommandResult(opts: {
15
+ stdout: string;
16
+ stderr: string;
17
+ exitCode: number | null;
18
+ signal?: string | null;
19
+ timedOut?: boolean;
20
+ duration?: number;
21
+ }): string;
22
+ /**
23
+ * Format directory listing for AI
24
+ */
25
+ export declare function formatLsOutput(raw: string): string;
26
+ //# sourceMappingURL=output-format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-format.d.ts","sourceRoot":"","sources":["../../src/utils/output-format.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAUH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CA+CxD;AAgBD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,MAAM,CAmCT;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAYlD"}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Cleans SSH output for AI readability:
3
+ * - Strips ANSI escape codes
4
+ * - Removes control characters
5
+ * - Collapses excessive blank lines
6
+ * - Truncates very long lines
7
+ * - Detects and replaces binary data
8
+ * - Converts tab-separated data to aligned columns
9
+ */
10
+ const ANSI_REGEX = /\x1B\[[0-9;]*[A-Za-z]|\x1B\][^\x07]*\x07|\x1B[()][AB012]|\x1B\[?[0-9;]*[hlm]/g;
11
+ const CONTROL_CHARS_REGEX = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g;
12
+ const CARRIAGE_RETURN_REGEX = /\r\n?/g;
13
+ const MAX_LINE_LENGTH = 2000;
14
+ const MAX_CONSECUTIVE_BLANKS = 2;
15
+ const BINARY_THRESHOLD = 0.15; // If more than 15% non-text chars, consider binary
16
+ export function cleanOutput(raw) {
17
+ if (Buffer.isBuffer(raw)) {
18
+ if (isBinaryData(raw)) {
19
+ return `[binary data, ${raw.length} bytes]`;
20
+ }
21
+ raw = raw.toString('utf-8');
22
+ }
23
+ if (typeof raw !== 'string')
24
+ return '';
25
+ // Strip ANSI escape codes
26
+ let output = raw.replace(ANSI_REGEX, '');
27
+ // Normalize line endings
28
+ output = output.replace(CARRIAGE_RETURN_REGEX, '\n');
29
+ // Remove control characters (keep \n and \t)
30
+ output = output.replace(CONTROL_CHARS_REGEX, '');
31
+ // Process line by line
32
+ const lines = output.split('\n');
33
+ const processedLines = [];
34
+ let consecutiveBlanks = 0;
35
+ for (let line of lines) {
36
+ // Truncate very long lines
37
+ if (line.length > MAX_LINE_LENGTH) {
38
+ line = line.substring(0, MAX_LINE_LENGTH) + ' [...truncated]';
39
+ }
40
+ // Collapse excessive blank lines
41
+ if (line.trim() === '') {
42
+ consecutiveBlanks++;
43
+ if (consecutiveBlanks > MAX_CONSECUTIVE_BLANKS)
44
+ continue;
45
+ }
46
+ else {
47
+ consecutiveBlanks = 0;
48
+ }
49
+ processedLines.push(line);
50
+ }
51
+ // Trim trailing blank lines
52
+ while (processedLines.length > 0 && processedLines[processedLines.length - 1].trim() === '') {
53
+ processedLines.pop();
54
+ }
55
+ return processedLines.join('\n');
56
+ }
57
+ function isBinaryData(buf) {
58
+ if (buf.length === 0)
59
+ return false;
60
+ const sampleSize = Math.min(buf.length, 8192);
61
+ let nonTextCount = 0;
62
+ for (let i = 0; i < sampleSize; i++) {
63
+ const byte = buf[i];
64
+ // Non-text: anything outside printable ASCII + common whitespace
65
+ if (byte < 0x09 || (byte > 0x0D && byte < 0x20) || byte === 0x7F) {
66
+ nonTextCount++;
67
+ }
68
+ }
69
+ return nonTextCount / sampleSize > BINARY_THRESHOLD;
70
+ }
71
+ /**
72
+ * Format command result for AI consumption
73
+ */
74
+ export function formatCommandResult(opts) {
75
+ const parts = [];
76
+ if (opts.timedOut) {
77
+ parts.push('TIMED_OUT: command exceeded timeout limit');
78
+ }
79
+ if (opts.duration !== undefined) {
80
+ parts.push(`Duration: ${(opts.duration / 1000).toFixed(1)}s`);
81
+ }
82
+ if (opts.exitCode !== null && opts.exitCode !== undefined) {
83
+ parts.push(`Exit code: ${opts.exitCode}${opts.exitCode !== 0 ? ' (FAILURE)' : ''}`);
84
+ }
85
+ if (opts.signal) {
86
+ parts.push(`Signal: ${opts.signal}`);
87
+ }
88
+ const cleanStdout = cleanOutput(opts.stdout);
89
+ const cleanStderr = cleanOutput(opts.stderr);
90
+ if (cleanStdout) {
91
+ parts.push(`\n--- STDOUT ---\n${cleanStdout}`);
92
+ }
93
+ if (cleanStderr) {
94
+ parts.push(`\n--- STDERR ---\n${cleanStderr}`);
95
+ }
96
+ if (!cleanStdout && !cleanStderr) {
97
+ parts.push('(no output)');
98
+ }
99
+ return parts.join('\n');
100
+ }
101
+ /**
102
+ * Format directory listing for AI
103
+ */
104
+ export function formatLsOutput(raw) {
105
+ const cleaned = cleanOutput(raw);
106
+ // If it looks like `ls -la` output, try to clean up the alignment
107
+ const lines = cleaned.split('\n');
108
+ if (lines.length < 2)
109
+ return cleaned;
110
+ // Check if it looks like ls -la (starts with "total" or permission string)
111
+ const hasLsHeader = /^total\s+\d+/.test(lines[0]);
112
+ if (!hasLsHeader)
113
+ return cleaned;
114
+ // Collapse multiple spaces in ls output for cleaner display
115
+ return lines.map(line => line.replace(/\s{2,}/g, ' ')).join('\n');
116
+ }
117
+ //# sourceMappingURL=output-format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-format.js","sourceRoot":"","sources":["../../src/utils/output-format.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Escape a remote path for safe use in shell commands.
3
+ * Handles spaces, quotes, special characters.
4
+ */
5
+ export declare function escapeRemotePath(remotePath: string): string;
6
+ /**
7
+ * Normalize a local path — handle Windows/Unix differences.
8
+ */
9
+ export declare function normalizeLocalPath(localPath: string): string;
10
+ /**
11
+ * Normalize a remote path — ensure Unix-style.
12
+ */
13
+ export declare function normalizeRemotePath(remotePath: string): string;
14
+ /**
15
+ * Get parent directory of a remote path.
16
+ */
17
+ export declare function remoteDir(remotePath: string): string;
18
+ /**
19
+ * Join remote path segments (Unix-style).
20
+ */
21
+ export declare function remoteJoin(...segments: string[]): string;
22
+ /**
23
+ * Create remote directory recursively via SFTP.
24
+ */
25
+ export declare function ensureRemoteDir(sftp: any, dirPath: string): Promise<void>;
26
+ /**
27
+ * Create local directory recursively.
28
+ */
29
+ export declare function ensureLocalDir(dirPath: string): Promise<void>;
30
+ /**
31
+ * Check if a remote path exists.
32
+ */
33
+ export declare function remoteExists(sftp: any, remotePath: string): Promise<boolean>;
34
+ //# sourceMappingURL=path-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-utils.d.ts","sourceRoot":"","sources":["../../src/utils/path-utils.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAI3D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAG5D;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAG9D;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,GAAG,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAExD;AAED;;GAEG;AACH,wBAAsB,eAAe,CACjC,IAAI,EAAE,GAAG,EACT,OAAO,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CA2Bf;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGnE;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAMlF"}
@@ -0,0 +1,87 @@
1
+ import { posix, win32 } from 'path';
2
+ /**
3
+ * Escape a remote path for safe use in shell commands.
4
+ * Handles spaces, quotes, special characters.
5
+ */
6
+ export function escapeRemotePath(remotePath) {
7
+ // Use single quotes with escaped single quotes inside
8
+ // This is the safest way to handle paths with spaces, $, etc.
9
+ return "'" + remotePath.replace(/'/g, "'\\''") + "'";
10
+ }
11
+ /**
12
+ * Normalize a local path — handle Windows/Unix differences.
13
+ */
14
+ export function normalizeLocalPath(localPath) {
15
+ // Replace forward slashes with OS-appropriate separator on Windows
16
+ return localPath.replace(/\//g, win32.sep);
17
+ }
18
+ /**
19
+ * Normalize a remote path — ensure Unix-style.
20
+ */
21
+ export function normalizeRemotePath(remotePath) {
22
+ // Remote paths are always Unix-style
23
+ return remotePath.replace(/\\/g, '/');
24
+ }
25
+ /**
26
+ * Get parent directory of a remote path.
27
+ */
28
+ export function remoteDir(remotePath) {
29
+ return posix.dirname(normalizeRemotePath(remotePath));
30
+ }
31
+ /**
32
+ * Join remote path segments (Unix-style).
33
+ */
34
+ export function remoteJoin(...segments) {
35
+ return posix.join(...segments.map(s => normalizeRemotePath(s)));
36
+ }
37
+ /**
38
+ * Create remote directory recursively via SFTP.
39
+ */
40
+ export async function ensureRemoteDir(sftp, dirPath) {
41
+ const normalized = normalizeRemotePath(dirPath);
42
+ const parts = normalized.split('/').filter(Boolean);
43
+ let current = normalized.startsWith('/') ? '/' : '';
44
+ for (const part of parts) {
45
+ current = current ? posix.join(current, part) : part;
46
+ try {
47
+ await new Promise((resolve, reject) => {
48
+ sftp.stat(current, (err) => {
49
+ if (err) {
50
+ sftp.mkdir(current, (mkErr) => {
51
+ if (mkErr && mkErr.code !== 4) { // 4 = already exists
52
+ reject(mkErr);
53
+ }
54
+ else {
55
+ resolve();
56
+ }
57
+ });
58
+ }
59
+ else {
60
+ resolve();
61
+ }
62
+ });
63
+ });
64
+ }
65
+ catch (err) {
66
+ throw new Error(`Failed to create remote directory "${current}": ${err.message}`);
67
+ }
68
+ }
69
+ }
70
+ /**
71
+ * Create local directory recursively.
72
+ */
73
+ export async function ensureLocalDir(dirPath) {
74
+ const { mkdir } = await import('fs/promises');
75
+ await mkdir(dirPath, { recursive: true });
76
+ }
77
+ /**
78
+ * Check if a remote path exists.
79
+ */
80
+ export async function remoteExists(sftp, remotePath) {
81
+ return new Promise((resolve) => {
82
+ sftp.stat(remotePath, (err) => {
83
+ resolve(!err);
84
+ });
85
+ });
86
+ }
87
+ //# sourceMappingURL=path-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-utils.js","sourceRoot":"","sources":["../../src/utils/path-utils.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@thesashadev/ssh-mcp-server",
3
+ "version": "1.0.0",
4
+ "description": "Professional SSH MCP server for AI agents. Supports sync/async commands, high-performance file transfers with 5 fallbacks.",
5
+ "license": "AGPL-3.0",
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "bin": {
9
+ "@thesashadev/ssh-mcp-server": "./dist/index.js",
10
+ "ssh-mcp-server": "./dist/index.js"
11
+ },
12
+ "files": [
13
+ "dist",
14
+ "README.md",
15
+ "LICENSE",
16
+ "ssh-servers.example.json"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsc",
20
+ "start": "node dist/index.js",
21
+ "dev": "tsx src/index.ts"
22
+ },
23
+ "dependencies": {
24
+ "@modelcontextprotocol/sdk": "^1.12.1",
25
+ "ssh2": "^1.16.0",
26
+ "zod": "^3.25.23"
27
+ },
28
+ "devDependencies": {
29
+ "@types/node": "^22.15.0",
30
+ "@types/ssh2": "^1.15.4",
31
+ "tsx": "^4.19.0",
32
+ "typescript": "^5.8.3"
33
+ }
34
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "servers": [
3
+ {
4
+ "id": "dev",
5
+ "name": "Dev Server",
6
+ "host": "192.168.1.100",
7
+ "port": 22,
8
+ "username": "ubuntu",
9
+ "password": "your-password",
10
+ "defaultRemoteDir": "/home/ubuntu",
11
+ "workspaces": [
12
+ "D:\\projects\\my-app"
13
+ ]
14
+ },
15
+ {
16
+ "id": "prod",
17
+ "name": "Production",
18
+ "host": "10.0.0.5",
19
+ "port": 22,
20
+ "username": "deploy",
21
+ "privateKeyPath": "C:\\Users\\you\\.ssh\\id_rsa",
22
+ "passphrase": "",
23
+ "defaultRemoteDir": "/var/www",
24
+ "workspaces": [
25
+ "D:\\projects\\production-app"
26
+ ]
27
+ }
28
+ ]
29
+ }