Skip to main content
Glama
bvisible

MCP SSH Manager

ssh_profile

Manage SSH connection profiles for different project types, enabling users to list, switch between, or check current profiles to organize remote server access.

Instructions

Manage SSH Manager profiles for different project types

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
actionYesAction to perform
profileNoProfile name (for switch)

Implementation Reference

  • 'ssh_profile' tool is listed/registered in the 'advanced' tool group in the central tool registry used for configuration and conditional registration of MCP tools.
    'ssh_profile',
  • Helper module for profile management. Provides all functions needed for ssh_profile tool: loading profiles from JSON files, listing available profiles, setting active profile, creating new profiles, and merging profiles. Profiles contain command aliases and hooks.
    /**
     * Profile Loader for SSH Manager
     * Loads configuration profiles for different project types
     */
    
    import fs from 'fs';
    import path from 'path';
    import { fileURLToPath } from 'url';
    
    const __filename = fileURLToPath(import.meta.url);
    const __dirname = path.dirname(__filename);
    
    const PROFILES_DIR = path.join(__dirname, '..', 'profiles');
    const PROFILE_CONFIG_FILE = path.join(__dirname, '..', '.ssh-manager-profile');
    
    /**
     * Get the active profile name
     */
    export function getActiveProfileName() {
      // 1. Check environment variable
      if (process.env.SSH_MANAGER_PROFILE) {
        return process.env.SSH_MANAGER_PROFILE;
      }
    
      // 2. Check configuration file
      if (fs.existsSync(PROFILE_CONFIG_FILE)) {
        try {
          const profileName = fs.readFileSync(PROFILE_CONFIG_FILE, 'utf8').trim();
          if (profileName) {
            return profileName;
          }
        } catch (error) {
          console.error(`Error reading profile config: ${error.message}`);
        }
      }
    
      // 3. Default to 'default' profile
      return 'default';
    }
    
    /**
     * Load a profile by name
     */
    export function loadProfile(profileName = null) {
      const name = profileName || getActiveProfileName();
      const profilePath = path.join(PROFILES_DIR, `${name}.json`);
    
      try {
        if (fs.existsSync(profilePath)) {
          const profileData = fs.readFileSync(profilePath, 'utf8');
          const profile = JSON.parse(profileData);
    
          console.error(`📦 Loaded profile: ${profile.name} - ${profile.description}`);
          return profile;
        } else {
          console.error(`⚠️  Profile '${name}' not found, using default profile`);
          return loadDefaultProfile();
        }
      } catch (error) {
        console.error(`❌ Error loading profile '${name}': ${error.message}`);
        return loadDefaultProfile();
      }
    }
    
    /**
     * Load the default profile
     */
    function loadDefaultProfile() {
      const defaultPath = path.join(PROFILES_DIR, 'default.json');
    
      try {
        if (fs.existsSync(defaultPath)) {
          const profileData = fs.readFileSync(defaultPath, 'utf8');
          return JSON.parse(profileData);
        }
      } catch (error) {
        console.error(`Error loading default profile: ${error.message}`);
      }
    
      // Return minimal profile if default doesn't exist
      return {
        name: 'minimal',
        description: 'Minimal profile',
        commandAliases: {},
        hooks: {}
      };
    }
    
    /**
     * List all available profiles
     */
    export function listProfiles() {
      try {
        const files = fs.readdirSync(PROFILES_DIR);
        const profiles = [];
    
        for (const file of files) {
          if (file.endsWith('.json')) {
            const profilePath = path.join(PROFILES_DIR, file);
            try {
              const data = fs.readFileSync(profilePath, 'utf8');
              const profile = JSON.parse(data);
              profiles.push({
                name: profile.name || file.replace('.json', ''),
                description: profile.description || 'No description',
                file: file,
                aliasCount: Object.keys(profile.commandAliases || {}).length,
                hookCount: Object.keys(profile.hooks || {}).length
              });
            } catch (error) {
              console.error(`Error reading profile ${file}: ${error.message}`);
            }
          }
        }
    
        return profiles;
      } catch (error) {
        console.error(`Error listing profiles: ${error.message}`);
        return [];
      }
    }
    
    /**
     * Set the active profile
     */
    export function setActiveProfile(profileName) {
      try {
        // Verify profile exists
        const profilePath = path.join(PROFILES_DIR, `${profileName}.json`);
        if (!fs.existsSync(profilePath)) {
          throw new Error(`Profile '${profileName}' does not exist`);
        }
    
        // Write to config file
        fs.writeFileSync(PROFILE_CONFIG_FILE, profileName);
        return true;
      } catch (error) {
        console.error(`Error setting active profile: ${error.message}`);
        return false;
      }
    }
    
    /**
     * Create a custom profile
     */
    export function createProfile(name, config) {
      try {
        const profilePath = path.join(PROFILES_DIR, `${name}.json`);
    
        // Check if profile already exists
        if (fs.existsSync(profilePath)) {
          throw new Error(`Profile '${name}' already exists`);
        }
    
        const profile = {
          name: name,
          description: config.description || `Custom profile: ${name}`,
          commandAliases: config.commandAliases || {},
          hooks: config.hooks || {}
        };
    
        fs.writeFileSync(profilePath, JSON.stringify(profile, null, 2));
        return true;
      } catch (error) {
        console.error(`Error creating profile: ${error.message}`);
        return false;
      }
    }
    
    /**
     * Merge profiles (useful for extending base profiles)
     */
    export function mergeProfiles(baseProfileName, extensions) {
      const baseProfile = loadProfile(baseProfileName);
    
      return {
        ...baseProfile,
        commandAliases: {
          ...baseProfile.commandAliases,
          ...extensions.commandAliases
        },
        hooks: {
          ...baseProfile.hooks,
          ...extensions.hooks
        }
      };
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden of behavioral disclosure. 'Manage' implies both read and write operations, but the description doesn't specify what 'manage' includes (listing, switching, viewing current), whether changes persist, what permissions are required, or what happens when switching profiles. For a tool with multiple action types and no annotation coverage, this is a significant gap.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that gets straight to the point. There's no wasted verbiage or unnecessary elaboration. However, it could be more front-loaded with specific actions rather than the generic 'manage' term.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with no annotations, no output schema, and multiple action types (list, switch, current), the description is insufficient. It doesn't explain what a 'profile' represents in this context, what happens when switching profiles, what information is returned for different actions, or how this integrates with other SSH tools. The description leaves too many behavioral questions unanswered.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, with clear parameter documentation in the schema itself. The description adds no parameter-specific information beyond the generic 'manage' concept. The schema already documents the action enum values and profile parameter purpose, so baseline 3 is appropriate when the schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose3/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description 'Manage SSH Manager profiles for different project types' states the general purpose (managing profiles) but is vague about what 'manage' entails. It mentions 'different project types' which adds some specificity, but doesn't clearly distinguish this tool from sibling SSH tools like ssh_key_manage or ssh_group_manage that also manage SSH-related resources.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives. With many sibling tools for SSH operations (ssh_key_manage, ssh_session_list, etc.), there's no indication of when profile management is appropriate versus other SSH configuration approaches. The description only states what the tool does, not when to use it.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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