@tb.p/terminai
Version:
MCP (Model Context Protocol) server for secure SSH remote command execution. Enables AI assistants like Claude, Cursor, and VS Code to execute commands on remote servers via SSH with command validation, history tracking, and web-based configuration UI.
165 lines (137 loc) • 3.65 kB
JavaScript
import { saveConfig } from '../config/loader.js';
export function registerConnectionTools(server, config, configPath) {
server.setRequestHandler('tools/call', async (request) => {
const { name, arguments: args } = request.params;
switch (name) {
case 'terminai_list_connections':
return await listConnections(config);
case 'terminai_get_config':
return await getConfig(config);
case 'terminai_add_connection':
return await addConnection(config, configPath, args);
case 'terminai_update_connection':
return await updateConnection(config, configPath, args);
case 'terminai_remove_connection':
return await removeConnection(config, configPath, args);
default:
return null;
}
});
}
async function listConnections(config) {
const connections = Object.values(config.connections).map(conn => ({
name: conn.name,
description: conn.description,
host: conn.host,
username: conn.username,
allowedCommands: conn.allowedCommands,
disallowedCommands: conn.disallowedCommands,
}));
return {
content: [{
type: 'text',
text: JSON.stringify({ connections }, null, 2)
}]
};
}
async function getConfig(config) {
return {
content: [{
type: 'text',
text: JSON.stringify(config, null, 2)
}]
};
}
async function addConnection(config, configPath, args) {
try {
const { name, description = '', host, port = 22, username, identityFile, allowedCommands = [], disallowedCommands = [] } = args;
if (!name || !host || !username || !identityFile) {
throw new Error('Missing required fields: name, host, username, identityFile');
}
if (config.connections[name]) {
throw new Error(`Connection '${name}' already exists`);
}
config.connections[name] = {
name,
description,
host,
port,
username,
identityFile,
allowedCommands,
disallowedCommands,
};
saveConfig(config, configPath);
return {
content: [{
type: 'text',
text: `Successfully added connection '${name}'`
}]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `Error: ${error.message}`
}],
isError: true
};
}
}
async function updateConnection(config, configPath, args) {
try {
const { name, ...updates } = args;
if (!name) {
throw new Error('Missing required field: name');
}
if (!config.connections[name]) {
throw new Error(`Connection '${name}' not found`);
}
config.connections[name] = {
...config.connections[name],
...updates,
};
saveConfig(config, configPath);
return {
content: [{
type: 'text',
text: `Successfully updated connection '${name}'`
}]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `Error: ${error.message}`
}],
isError: true
};
}
}
async function removeConnection(config, configPath, args) {
try {
const { name } = args;
if (!name) {
throw new Error('Missing required field: name');
}
if (!config.connections[name]) {
throw new Error(`Connection '${name}' not found`);
}
delete config.connections[name];
saveConfig(config, configPath);
return {
content: [{
type: 'text',
text: `Successfully removed connection '${name}'`
}]
};
} catch (error) {
return {
content: [{
type: 'text',
text: `Error: ${error.message}`
}],
isError: true
};
}
}