#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import {
sshConnect,
sshExec,
sshDisconnect,
sshListSessions,
sftpUpload,
sftpDownload,
sftpList,
sshShellStart,
sshShellSend,
sshShellRead,
sshShellClose,
} from './tools/index.js';
import { sessionManager } from './ssh/session.js';
const server = new Server(
{
name: 'ssh-mcp',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
// Tool definitions
const tools = [
{
name: 'ssh_connect',
description: 'Establish an SSH connection to a remote server. Returns a session ID for subsequent operations.',
inputSchema: {
type: 'object' as const,
properties: {
host: {
type: 'string',
description: 'Hostname or IP address of the SSH server',
},
port: {
type: 'number',
description: 'SSH port (default: 22)',
},
username: {
type: 'string',
description: 'Username for SSH authentication',
},
password: {
type: 'string',
description: 'Password for authentication (use this OR privateKey/privateKeyPath)',
},
privateKey: {
type: 'string',
description: 'Private key content as string (use this OR password OR privateKeyPath)',
},
privateKeyPath: {
type: 'string',
description: 'Path to private key file (use this OR password OR privateKey)',
},
passphrase: {
type: 'string',
description: 'Passphrase for encrypted private key',
},
},
required: ['host', 'username'],
},
},
{
name: 'ssh_exec',
description: 'Execute a command on a connected SSH server. Returns stdout, stderr, and exit code.',
inputSchema: {
type: 'object' as const,
properties: {
sessionId: {
type: 'string',
description: 'Session ID from ssh_connect',
},
command: {
type: 'string',
description: 'Command to execute on the remote server',
},
cwd: {
type: 'string',
description: 'Working directory for the command',
},
env: {
type: 'object',
description: 'Environment variables as key-value pairs',
additionalProperties: { type: 'string' },
},
pty: {
type: 'boolean',
description: 'Allocate a PTY for the command (useful for interactive commands)',
},
},
required: ['sessionId', 'command'],
},
},
{
name: 'ssh_disconnect',
description: 'Close an SSH session and clean up resources',
inputSchema: {
type: 'object' as const,
properties: {
sessionId: {
type: 'string',
description: 'Session ID to disconnect',
},
},
required: ['sessionId'],
},
},
{
name: 'ssh_list_sessions',
description: 'List all active SSH sessions',
inputSchema: {
type: 'object' as const,
properties: {},
},
},
{
name: 'sftp_upload',
description: 'Upload a local file to the remote server via SFTP',
inputSchema: {
type: 'object' as const,
properties: {
sessionId: {
type: 'string',
description: 'Session ID from ssh_connect',
},
localPath: {
type: 'string',
description: 'Path to the local file to upload',
},
remotePath: {
type: 'string',
description: 'Destination path on the remote server',
},
},
required: ['sessionId', 'localPath', 'remotePath'],
},
},
{
name: 'sftp_download',
description: 'Download a file from the remote server via SFTP',
inputSchema: {
type: 'object' as const,
properties: {
sessionId: {
type: 'string',
description: 'Session ID from ssh_connect',
},
remotePath: {
type: 'string',
description: 'Path to the file on the remote server',
},
localPath: {
type: 'string',
description: 'Local destination path for the downloaded file',
},
},
required: ['sessionId', 'remotePath', 'localPath'],
},
},
{
name: 'sftp_list',
description: 'List files and directories in a remote directory via SFTP',
inputSchema: {
type: 'object' as const,
properties: {
sessionId: {
type: 'string',
description: 'Session ID from ssh_connect',
},
remotePath: {
type: 'string',
description: 'Path to the remote directory to list',
},
},
required: ['sessionId', 'remotePath'],
},
},
{
name: 'ssh_shell_start',
description: 'Start an interactive shell session. Returns a shell ID for sending commands and reading output.',
inputSchema: {
type: 'object' as const,
properties: {
sessionId: {
type: 'string',
description: 'Session ID from ssh_connect',
},
cols: {
type: 'number',
description: 'Terminal width in columns (default: 80)',
},
rows: {
type: 'number',
description: 'Terminal height in rows (default: 24)',
},
},
required: ['sessionId'],
},
},
{
name: 'ssh_shell_send',
description: 'Send input to an interactive shell session',
inputSchema: {
type: 'object' as const,
properties: {
sessionId: {
type: 'string',
description: 'Session ID from ssh_connect',
},
shellId: {
type: 'string',
description: 'Shell ID from ssh_shell_start',
},
input: {
type: 'string',
description: 'Input to send to the shell (include \\n for Enter)',
},
},
required: ['sessionId', 'shellId', 'input'],
},
},
{
name: 'ssh_shell_read',
description: 'Read output from an interactive shell session',
inputSchema: {
type: 'object' as const,
properties: {
sessionId: {
type: 'string',
description: 'Session ID from ssh_connect',
},
shellId: {
type: 'string',
description: 'Shell ID from ssh_shell_start',
},
clear: {
type: 'boolean',
description: 'Clear the output buffer after reading (default: true)',
},
},
required: ['sessionId', 'shellId'],
},
},
{
name: 'ssh_shell_close',
description: 'Close an interactive shell session',
inputSchema: {
type: 'object' as const,
properties: {
sessionId: {
type: 'string',
description: 'Session ID from ssh_connect',
},
shellId: {
type: 'string',
description: 'Shell ID from ssh_shell_start',
},
},
required: ['sessionId', 'shellId'],
},
},
];
// Handle list tools request
server.setRequestHandler(ListToolsRequestSchema, async () => {
return { tools };
});
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
const params = args as Record<string, unknown>;
try {
let result;
switch (name) {
case 'ssh_connect':
result = await sshConnect({
host: params.host as string,
port: params.port as number | undefined,
username: params.username as string,
password: params.password as string | undefined,
privateKey: params.privateKey as string | undefined,
privateKeyPath: params.privateKeyPath as string | undefined,
passphrase: params.passphrase as string | undefined,
});
break;
case 'ssh_exec':
result = await sshExec({
sessionId: params.sessionId as string,
command: params.command as string,
cwd: params.cwd as string | undefined,
env: params.env as Record<string, string> | undefined,
pty: params.pty as boolean | undefined,
});
break;
case 'ssh_disconnect':
result = await sshDisconnect({
sessionId: params.sessionId as string,
});
break;
case 'ssh_list_sessions':
result = await sshListSessions();
break;
case 'sftp_upload':
result = await sftpUpload({
sessionId: params.sessionId as string,
localPath: params.localPath as string,
remotePath: params.remotePath as string,
});
break;
case 'sftp_download':
result = await sftpDownload({
sessionId: params.sessionId as string,
remotePath: params.remotePath as string,
localPath: params.localPath as string,
});
break;
case 'sftp_list':
result = await sftpList({
sessionId: params.sessionId as string,
remotePath: params.remotePath as string,
});
break;
case 'ssh_shell_start':
result = await sshShellStart({
sessionId: params.sessionId as string,
cols: params.cols as number | undefined,
rows: params.rows as number | undefined,
});
break;
case 'ssh_shell_send':
result = await sshShellSend({
sessionId: params.sessionId as string,
shellId: params.shellId as string,
input: params.input as string,
});
break;
case 'ssh_shell_read':
result = await sshShellRead({
sessionId: params.sessionId as string,
shellId: params.shellId as string,
clear: params.clear as boolean | undefined,
});
break;
case 'ssh_shell_close':
result = await sshShellClose({
sessionId: params.sessionId as string,
shellId: params.shellId as string,
});
break;
default:
return {
content: [
{
type: 'text' as const,
text: JSON.stringify({ success: false, error: `Unknown tool: ${name}` }),
},
],
};
}
return {
content: [
{
type: 'text' as const,
text: JSON.stringify(result, null, 2),
},
],
};
} catch (err) {
return {
content: [
{
type: 'text' as const,
text: JSON.stringify({
success: false,
error: err instanceof Error ? err.message : String(err),
}),
},
],
};
}
});
// Cleanup on exit
process.on('SIGINT', () => {
sessionManager.cleanup();
process.exit(0);
});
process.on('SIGTERM', () => {
sessionManager.cleanup();
process.exit(0);
});
// Start the server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('SSH MCP Server running on stdio');
}
main().catch((err) => {
console.error('Failed to start server:', err);
process.exit(1);
});