Skip to main content
Glama
YawLabs

SSH MCP Server

by YawLabs

ssh_key_load

Load an SSH private key into the running agent for authentication. Use after listing keys to load an unloaded key.

Instructions

Load an SSH private key into the running agent. Ensures the agent is running first. Use this after ssh_key_list shows a key that is not loaded.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
keyPathYesPath to the SSH private key to load (e.g. ~/.ssh/id_ed25519)

Implementation Reference

  • src/tools.ts:223-233 (registration)
    Registration of the ssh_key_load tool with its schema (keyPath param) and handler that delegates to the loadKey function.
    server.tool(
      "ssh_key_load",
      "Load an SSH private key into the running agent. Ensures the agent is running first. Use this after ssh_key_list shows a key that is not loaded.",
      {
        keyPath: z.string().describe("Path to the SSH private key to load (e.g. ~/.ssh/id_ed25519)"),
      },
      async ({ keyPath }) => {
        const result = loadKey(keyPath);
        return { content: [{ type: "text", text: result.message }], isError: result.status === "error" };
      },
    );
  • Core handler for loading an SSH key: ensures the agent is running, resolves the key path, calls ssh-add, and returns a structured result with descriptive error messages for missing keys, passphrase-protected keys, and permission issues.
    export function loadKey(keyPath: string): { status: "ok" | "error"; message: string } {
      // Ensure agent is running first
      const agent = ensureAgent();
      if (!agent.reachable) {
        return { status: "error", message: agent.message };
      }
    
      // Resolve ~ to home directory
      const resolved = keyPath.startsWith("~") ? join(homedir(), keyPath.slice(1)) : keyPath;
    
      if (!existsSync(resolved)) {
        return { status: "error", message: `Key not found: ${resolved}` };
      }
    
      const { stdout, ok } = runArgs("ssh-add", [resolved]);
      if (ok) {
        return { status: "ok", message: `Key loaded: ${resolved}` };
      }
    
      if (stdout.includes("passphrase") || stdout.includes("incorrect") || stdout.includes("bad permissions")) {
        if (stdout.includes("UNPROTECTED PRIVATE KEY")) {
          return { status: "error", message: `Key ${resolved} has too-open permissions. Fix: chmod 600 ${resolved}` };
        }
        return { status: "error", message: `Key ${resolved} requires a passphrase. Add it manually: ssh-add ${resolved}` };
      }
    
      return { status: "error", message: `Failed to load key: ${stdout}` };
    }
  • Type interface for the agent status result, used by ensureAgent() which is called by loadKey to verify the agent is reachable before loading a key.
    export interface AgentResult {
      running: boolean;
      reachable: boolean;
      socket?: string;
      keys: string[];
      started: boolean;
      env?: { SSH_AUTH_SOCK?: string; SSH_AGENT_PID?: string };
      message: string;
    }
  • Helper that ensures ssh-agent is running (used by loadKey before attempting to add a key). Probes existing agent, falls back to Windows OpenSSH agent, or starts a new agent scoped to the MCP server process.
    export function ensureAgent(): AgentResult {
      const sock = process.env.SSH_AUTH_SOCK;
      if (sock) {
        const result = probeAgent(sock, "ssh-agent");
        if (result) return result;
      }
    
      // On Windows, try the OpenSSH agent service (uses named pipe, not SSH_AUTH_SOCK)
      if (!sock && process.platform === "win32") {
        const result = probeAgent("\\\\.\\pipe\\openssh-ssh-agent", "Windows OpenSSH agent");
        if (result) return result;
      }
    
      // Try to start a new agent (Unix)
      const { stdout, ok } = runArgs("ssh-agent", ["-s"]);
      if (ok) {
        const sockMatch = stdout.match(/SSH_AUTH_SOCK=([^;]+)/);
        const pidMatch = stdout.match(/SSH_AGENT_PID=([^;]+)/);
        if (sockMatch) {
          process.env.SSH_AUTH_SOCK = sockMatch[1];
          if (pidMatch) {
            process.env.SSH_AGENT_PID = pidMatch[1];
            startedAgentPid = Number.parseInt(pidMatch[1], 10);
          }
          return {
            running: true,
            reachable: true,
            socket: sockMatch[1],
            keys: [],
            started: true,
            env: { SSH_AUTH_SOCK: sockMatch[1], SSH_AGENT_PID: pidMatch?.[1] },
            message:
              "Started new ssh-agent scoped to the ssh-mcp server process. " +
              "Your shell's environment is NOT modified — this agent is only visible " +
              "to this MCP server and will terminate when the server exits. " +
              "No keys loaded yet — use ssh_key_load to add one.",
          };
        }
      }
    
      return {
        running: false,
        reachable: false,
        keys: [],
        started: false,
        message:
          process.platform === "win32"
            ? "Windows OpenSSH agent not running. Start it: Get-Service ssh-agent | Set-Service -StartupType Automatic; Start-Service ssh-agent"
            : 'Could not start ssh-agent. Run manually: eval "$(ssh-agent -s)"',
      };
    }
Behavior3/5

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

With no annotations, the description must disclose behavioral traits. It mentions 'ensures the agent is running first', which suggests an automatic check or start. However, it does not detail what happens if the key is already loaded, error conditions (e.g., invalid key path), or side effects beyond loading. This leaves some ambiguity.

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

Conciseness5/5

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

Two sentences: first states the action, second provides usage guidance. Every sentence is essential; no padding or redundancy.

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

Completeness4/5

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

For a simple 1-parameter tool with no output schema, the description covers the key aspects: purpose, prerequisite, and when to use. It does not explain the return behavior or long-term effects, but these are less critical given the tool's straightforward nature.

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

Parameters4/5

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

The input schema covers 100% of parameters with a description for keyPath. The description adds an example path (~/.ssh/id_ed25519) and context from the tool description (loading into running agent), enhancing clarity beyond the schema alone.

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

Purpose5/5

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

The description clearly states the action 'Load an SSH private key into the running agent', specifying both the verb and the resource. It distinguishes from siblings by referencing ssh_key_list, indicating this tool is for loading a key that is not yet loaded.

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

Usage Guidelines4/5

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

The description provides a clear usage context: 'Use this after ssh_key_list shows a key that is not loaded.' It also implies a prerequisite: ensuring the agent is running first. However, it does not explicitly state when not to use or offer alternatives, though the guidance is sufficient.

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/YawLabs/ssh-mcp'

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