Skip to main content
Glama
bvisible

MCP SSH Manager

ssh_deploy

Deploy files to remote servers with automatic permission handling, backup options, and service restart capabilities for secure file transfers.

Instructions

Deploy files to remote server with automatic permission handling

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
serverYesServer name or alias
filesYesArray of files to deploy
optionsNoDeployment options

Implementation Reference

  • Registration of the 'ssh_deploy' tool within the 'advanced' TOOL_GROUPS array. This is where the tool name is listed for conditional registration based on configuration.
    advanced: [
      'ssh_deploy',
      'ssh_execute_sudo',
      'ssh_alias',
      'ssh_command_alias',
      'ssh_hooks',
      'ssh_profile',
      'ssh_connection_status',
      'ssh_tunnel_create',
      'ssh_tunnel_list',
      'ssh_tunnel_close',
      'ssh_key_manage',
      'ssh_execute_group',
      'ssh_group_manage',
      'ssh_history'
    ]
  • Complete helper module providing functions for ssh_deploy tool: getTempFilename for safe temp files, buildDeploymentStrategy to generate deployment steps (backup, copy, chown, chmod, restart, cleanup), detectDeploymentNeeds for auto-detecting sudo/owner/perms requirements, and createBatchDeployScript for multi-file deployments.
    import path from 'path';
    import crypto from 'crypto';
    
    /**
     * Deploy helper functions for secure file deployment
     */
    
    /**
     * Generate a unique temporary filename
     */
    export function getTempFilename(originalName) {
      const timestamp = Date.now();
      const random = crypto.randomBytes(4).toString('hex');
      const ext = path.extname(originalName);
      const base = path.basename(originalName, ext);
      return `/tmp/${base}_${timestamp}_${random}${ext}`;
    }
    
    /**
     * Build deployment strategy based on target path and permissions
     */
    export function buildDeploymentStrategy(remotePath, options = {}) {
      const {
        sudoPassword = null,
        owner = null,
        permissions = null,
        backup = true,
        restart = null
      } = options;
    
      const strategy = {
        steps: [],
        requiresSudo: false
      };
    
      // Step 1: Backup existing file if requested
      if (backup) {
        strategy.steps.push({
          type: 'backup',
          command: `if [ -f "${remotePath}" ]; then cp "${remotePath}" "${remotePath}.bak.$(date +%Y%m%d_%H%M%S)"; fi`
        });
      }
    
      // Step 2: Determine if we need sudo
      const needsSudo = remotePath.startsWith('/etc/') ||
                        remotePath.startsWith('/var/') ||
                        remotePath.startsWith('/usr/') ||
                        owner || permissions;
    
      if (needsSudo) {
        strategy.requiresSudo = true;
      }
    
      // Step 3: Copy from temp to final location
      const copyCmd = needsSudo && sudoPassword ?
        `echo "${sudoPassword}" | sudo -S cp {{tempFile}} "${remotePath}"` :
        needsSudo ?
          `sudo cp {{tempFile}} "${remotePath}"` :
          `cp {{tempFile}} "${remotePath}"`;
    
      strategy.steps.push({
        type: 'copy',
        command: copyCmd
      });
    
      // Step 4: Set ownership if specified
      if (owner) {
        const chownCmd = sudoPassword ?
          `echo "${sudoPassword}" | sudo -S chown ${owner} "${remotePath}"` :
          `sudo chown ${owner} "${remotePath}"`;
    
        strategy.steps.push({
          type: 'chown',
          command: chownCmd
        });
      }
    
      // Step 5: Set permissions if specified
      if (permissions) {
        const chmodCmd = sudoPassword ?
          `echo "${sudoPassword}" | sudo -S chmod ${permissions} "${remotePath}"` :
          `sudo chmod ${permissions} "${remotePath}"`;
    
        strategy.steps.push({
          type: 'chmod',
          command: chmodCmd
        });
      }
    
      // Step 6: Restart service if specified
      if (restart) {
        strategy.steps.push({
          type: 'restart',
          command: restart
        });
      }
    
      // Step 7: Cleanup temp file
      strategy.steps.push({
        type: 'cleanup',
        command: 'rm -f {{tempFile}}'
      });
    
      return strategy;
    }
    
    /**
     * Parse deployment configuration from file path patterns
     * Examples:
     *   /home/user/app/file.js -> normal deploy
     *   /etc/nginx/sites-available/site -> needs sudo
     *   /var/www/html/index.html -> needs sudo
     */
    export function detectDeploymentNeeds(remotePath) {
      const needs = {
        sudo: false,
        suggestedOwner: null,
        suggestedPerms: null
      };
    
      // System directories that typically need sudo
      if (remotePath.startsWith('/etc/')) {
        needs.sudo = true;
        needs.suggestedOwner = 'root:root';
        needs.suggestedPerms = '644';
      } else if (remotePath.startsWith('/var/www/')) {
        needs.sudo = true;
        needs.suggestedOwner = 'www-data:www-data';
        needs.suggestedPerms = '644';
      } else if (remotePath.includes('/nginx/')) {
        needs.sudo = true;
        needs.suggestedOwner = 'root:root';
        needs.suggestedPerms = '644';
      } else if (remotePath.includes('/apache/') || remotePath.includes('/httpd/')) {
        needs.sudo = true;
        needs.suggestedOwner = 'www-data:www-data';
        needs.suggestedPerms = '644';
      } else if (remotePath.includes('/frappe-bench/')) {
        // For ERPNext/Frappe deployments
        needs.sudo = false;
        needs.suggestedOwner = null; // Will be handled by the app
        needs.suggestedPerms = '644';
      }
    
      return needs;
    }
    
    /**
     * Create batch deployment script for multiple files
     */
    export function createBatchDeployScript(deployments) {
      const script = ['#!/bin/bash', 'set -e', ''];
    
      script.push('# Batch deployment script');
      script.push(`# Generated at ${new Date().toISOString()}`);
      script.push('');
    
      deployments.forEach((deploy, index) => {
        script.push(`# File ${index + 1}: ${deploy.localPath} -> ${deploy.remotePath}`);
        deploy.strategy.steps.forEach(step => {
          if (step.type !== 'cleanup') {
            script.push(step.command.replace('{{tempFile}}', deploy.tempFile));
          }
        });
        script.push('');
      });
    
      // Cleanup all temp files at the end
      script.push('# Cleanup temporary files');
      deployments.forEach(deploy => {
        script.push(`rm -f ${deploy.tempFile}`);
      });
    
      return script.join('\n');
    }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/bvisible/mcp-ssh-manager'

If you have feedback or need assistance with the MCP directory API, please join our Discord server