
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
@agent-remote/ssh
Advanced tools
A TypeScript library for executing commands and managing files on remote systems via SSH. Designed for integration with the Claude Agent SDK to provide AI agents with remote system access.
pnpm add @agent-remote/ssh
The package includes a standalone MCP server executable that can be run from the command line and communicates over stdio transport. The server is self-contained with all dependencies bundled (except Node.js builtins), making it easy to distribute and run without installing node_modules.
Installation:
npm install -g @agent-remote/ssh
Usage with command line arguments:
# With password authentication
remote-ssh-mcp --host example.com --username user --password secret
# With private key authentication
remote-ssh-mcp --host example.com --username user --private-key ~/.ssh/id_rsa
# With SSH agent
remote-ssh-mcp --host example.com --username user --agent $SSH_AUTH_SOCK
Usage with environment variables:
export SSH_HOST=example.com
export SSH_USERNAME=user
export SSH_PRIVATE_KEY=~/.ssh/id_rsa
remote-ssh-mcp
Available options:
--host, -h - SSH host (or SSH_HOST env var)--port, -p - SSH port, default 22 (or SSH_PORT env var)--username, -u - SSH username (or SSH_USERNAME env var)--password - SSH password (or SSH_PASSWORD env var)--private-key - Path to private key file (or SSH_PRIVATE_KEY env var)--passphrase - Passphrase for encrypted private key (or SSH_PASSPHRASE env
var)--agent - SSH agent socket path (or SSH_AUTH_SOCK env var)--default-keys - Automatically try default SSH keys from ~/.ssh, enabled
by default (or SSH_DEFAULT_KEYS env var). Use --no-try-default-keys to
disable.--timeout, -t - SSH connection timeout in milliseconds (or SSH_TIMEOUT env
var)The server exposes all remote tools (bash, read, write, edit, grep, glob, etc.) through the MCP protocol.
Authentication behavior:
The MCP server mimics OpenSSH's authentication behavior:
--password or --private-key, it uses that exclusively--agent or SSH_AUTH_SOCK)~/.ssh/ (id_ed25519,
id_ecdsa, id_rsa, id_dsa)This means you can connect just like OpenSSH without specifying any auth options:
# Just like: ssh user@example.com
remote-ssh-mcp --host example.com --username user
To disable the automatic default key fallback:
remote-ssh-mcp --host example.com --username user --no-default-keys
import { Remote } from '@agent-remote/ssh';
// Connect to remote server
const remote = await Remote.connect({
host: 'example.com',
username: 'user',
privateKey: fs.readFileSync('/path/to/id_rsa'),
});
// Execute commands
const result = await remote.bash.handler({
command: 'ls -la',
});
console.log(result.content[0].text);
// Read files
const fileContent = await remote.read.handler({
file_path: '/etc/hosts',
});
// Write files
await remote.write.handler({
file_path: '/tmp/test.txt',
content: 'Hello, world!',
});
// Clean up
await remote.disconnect();
import { Remote } from '@agent-remote/ssh';
const remote = await Remote.connect({
host: 'example.com',
username: 'user',
privateKey: fs.readFileSync('/path/to/id_rsa'),
});
// Create an MCP server with all tools
const server = remote.createSdkMcpServer();
// Use with Agent SDK...
const remote = await Remote.connect(config);
// Each tool is accessible as a property with a handler
const tools = [
remote.bash,
remote.bashOutput,
remote.killBash,
remote.grep,
remote.read,
remote.write,
remote.edit,
remote.glob,
];
// You can use individual tools in your own MCP server
import { createSdkMcpServer, tool } from '@anthropic-ai/claude-agent-sdk';
const customServer = createSdkMcpServer({
name: 'custom-remote-ssh',
version: '1.0.0',
tools: [
tool(
remote.bash.name,
remote.bash.description,
remote.bash.inputSchema,
remote.bash.handler,
),
tool(
remote.read.name,
remote.read.description,
remote.read.inputSchema,
remote.read.handler,
),
tool(
remote.write.name,
remote.write.description,
remote.write.inputSchema,
remote.write.handler,
),
],
});
The main class for managing SSH connections and accessing tools.
Remote.connect(config: ConnectConfig): Promise<Remote>Establishes a connection to a remote SSH server.
Parameters:
config - SSH connection configuration (see SSH Configuration below)Returns: A connected Remote instance
Throws: If the SSH connection fails
Note: SFTP is attempted but not required. If SFTP is unavailable, the connection will still succeed but file operations (read, write, edit) will return error messages when called. Bash, grep, and glob tools work without SFTP.
Example:
const remote = await Remote.connect({
host: 'example.com',
username: 'user',
password: 'secret',
});
disconnect(): Promise<void>Closes the SSH connection and SFTP session (if present).
Example:
await remote.disconnect();
createSdkMcpServer()Creates an MCP server with all remote tools from this Remote instance.
Returns: An MCP server instance from the Claude Agent SDK
Example:
const server = remote.createSdkMcpServer();
Each tool is accessed via a getter property that returns a tool definition with
name, description, inputSchema, and handler properties.
bash: BashToolDefinitionExecutes commands in a persistent shell session.
Input:
{
command: string; // The command to execute
timeout?: number; // Optional timeout in milliseconds (max 600000)
description?: string; // Description of what the command does
run_in_background?: boolean; // Run in background
}
Output:
{
output: string; // Combined stdout and stderr
exitCode?: number; // Exit code (optional)
signal?: string; // Signal used to terminate the command
killed?: boolean; // Whether the command was killed due to timeout
shellId?: string; // Shell ID if background execution
}
bashOutput: BashOutputToolDefinitionRetrieves output from a running or completed background bash shell.
Input:
{
shell_id: string; // Shell ID to retrieve output from
}
Output:
{
output: string; // New output since last check
status: 'running' | 'completed'; // Shell status
exitCode?: number; // Exit code (when completed)
signal?: string; // Signal (when completed)
}
killBash: KillBashToolDefinitionKills a running background shell by its ID.
Input:
{
shell_id: string; // Shell ID to kill
signal?: string; // Signal to send (e.g., 'SIGTERM', 'SIGKILL')
}
Output:
{
killed: boolean; // Whether the shell was killed
}
grep: GrepToolDefinitionSearches for patterns in files or directories.
Input:
{
pattern: string; // Regular expression pattern
path: string; // File or directory to search
glob?: string; // Glob pattern to filter files
output_mode?: 'content' | 'files_with_matches' | 'count';
'-B'?: number; // Lines of context before match
'-A'?: number; // Lines of context after match
'-C'?: number; // Lines of context before and after
'-n'?: boolean; // Show line numbers
'-i'?: boolean; // Case insensitive
head_limit?: number; // Limit output lines
}
Output:
{
mode: 'content' | 'files_with_matches' | 'count';
content?: string; // Matching lines (if mode is 'content')
filenames?: string[]; // Matching files (if mode is 'files_with_matches')
numFiles?: number; // Number of files (if mode is 'files_with_matches')
numMatches?: number; // Number of matches (if mode is 'count')
}
read: ReadToolDefinitionReads files from the remote filesystem. Automatically uses SFTP when available,
or falls back to cat command.
Input:
{
file_path: string; // Absolute path to file
offset?: number; // Line number to start reading from
limit?: number; // Number of lines to read
}
Output:
{
content: string; // File content
numLines: number; // Number of lines read
startLine: number; // Starting line number
totalLines: number; // Total lines in file
}
Implementation:
sftp.readFile() for direct file accesscat command to read file contentswrite: WriteToolDefinitionWrites content to files on the remote filesystem. Automatically uses SFTP when
available, or falls back to printf command with proper escaping.
Input:
{
file_path: string; // Absolute path to file
content: string; // Content to write
}
Output:
{
content: string; // Content that was written
}
Implementation:
sftp.writeFile() for direct file accessprintf '%s' '...' with single-quote escaping to
safely handle special characters, newlines, and unicodeedit: EditToolDefinitionEdits files by replacing text on the remote filesystem. Automatically uses SFTP when available, or falls back to shell commands.
Input:
{
file_path: string; // Absolute path to file
old_string: string; // Text to find
new_string: string; // Text to replace with
replace_all?: boolean; // Replace all occurrences
}
Output:
{
replacements: number; // Number of replacements made
diff: StructuredPatch; // Unified diff of changes
}
Implementation:
sftp.readFile() and sftp.writeFile() for file accesscat to read, performs replacement locally, then
uses printf to write backglob: GlobToolDefinitionSearches for files matching glob patterns.
Input:
{
base_path: string; // Absolute base path to search from
pattern: string; // Glob pattern (e.g., '**/*.ts')
include_hidden?: boolean; // Include hidden files
}
Output:
{
matches: string[]; // List of matching file paths
count: number; // Number of matches
}
The ConnectConfig type from the ssh2
library supports various authentication methods:
const remote = await Remote.connect({
host: 'example.com',
port: 22,
username: 'user',
password: 'secret',
});
import fs from 'fs';
const remote = await Remote.connect({
host: 'example.com',
port: 22,
username: 'user',
privateKey: fs.readFileSync('/path/to/id_rsa'),
passphrase: 'optional-passphrase', // If key is encrypted
});
const remote = await Remote.connect({
host: 'example.com',
port: 22,
username: 'user',
agent: process.env.SSH_AUTH_SOCK,
});
When using the MCP server (not the library directly), if you don't provide
explicit credentials, the server automatically tries default SSH keys from
~/.ssh/ as a fallback. This mimics OpenSSH behavior:
# MCP server will try SSH agent, then fall back to ~/.ssh/id_ed25519, etc.
remote-ssh-mcp --host example.com --username user
This automatic fallback can be disabled with --no-default-keys.
const remote = await Remote.connect({
host: 'example.com',
port: 22,
username: 'user',
privateKey: fs.readFileSync('/path/to/id_rsa'),
keepaliveInterval: 10000,
readyTimeout: 20000,
algorithms: {
kex: ['ecdh-sha2-nistp256'],
cipher: ['aes128-ctr'],
},
});
For all available configuration options, see the ssh2 documentation.
try {
const remote = await Remote.connect(config);
try {
const result = await remote.bash.handler({
command: 'some-command',
});
if (result.isError) {
console.error('Command failed:', result.content[0].text);
} else {
console.log('Success:', result.content[0].text);
}
} finally {
await remote.disconnect();
}
} catch (error) {
console.error('Connection failed:', error);
}
const remote = await Remote.connect(config);
// Start a long-running command in the background
const startResult = await remote.bash.handler({
command: 'npm run build',
run_in_background: true,
description: 'Building project',
});
const { shellId } = startResult.structuredContent;
// Check output periodically
const checkOutput = async () => {
const output = await remote.bashOutput.handler({ shell_id: shellId });
console.log(output.content[0].text);
if (output.structuredContent.status === 'running') {
setTimeout(checkOutput, 1000);
}
};
checkOutput();
const remote = await Remote.connect(config);
// Find all TypeScript files
const files = await remote.glob.handler({
base_path: '/home/user/project',
pattern: '**/*.ts',
});
// Search for a pattern
const matches = await remote.grep.handler({
pattern: 'TODO',
path: '/home/user/project',
glob: '*.ts',
output_mode: 'content',
'-n': true,
});
// Edit a file
await remote.edit.handler({
file_path: '/home/user/project/config.ts',
old_string: 'localhost',
new_string: 'example.com',
replace_all: true,
});
Apache-2.0
FAQs
SSH remote tools for AI agents
The npm package @agent-remote/ssh receives a total of 4 weekly downloads. As such, @agent-remote/ssh popularity was classified as not popular.
We found that @agent-remote/ssh demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.