Files
mcp-ssh/AGENTS.md
2026-05-24 21:18:09 +00:00

9.4 KiB

AGENTS.md

Project

MCP SSH Server

Self-contained MCP server binary that provides SSH command execution, persistent terminal sessions, and file transfer support with minimal user configuration.

The implementation must rely on the user's existing OpenSSH configuration and SSH environment.


Core Product Requirements

Long-Term Goals

  • SSH command execution
  • Persistent interactive terminal sessions
  • Remote file transfer support
  • Minimal user-side MCP configuration
  • Self-contained binary distribution
  • Use existing ~/.ssh/config
  • Use existing SSH keys and SSH agent
  • Support SSH aliases exactly as users use them in terminal

Current Vertical Slice

The first implementation pass intentionally narrows the MVP:

  • .NET 10 stdio MCP server
  • ssh_exec
  • terminal_start, terminal_write, terminal_read, and terminal_stop
  • sftp_list, sftp_get, and sftp_put
  • SSH.NET-based SSH connections
  • Tool-supplied host, username, optional port, optional keyPath, and optional keyPassphrase
  • Default key discovery from ~/.ssh/id_ed25519, ~/.ssh/id_ecdsa, then ~/.ssh/id_rsa when keyPath is omitted
  • SFTP first for file transfer; sftp_get and sftp_put silently fall back to SCP if SFTP is unavailable
  • No SSH agent, OpenSSH alias, ssh -G, ProxyJump, or ProxyCommand support yet
  • Basic audit logging and timeout enforcement

Non-Goals

  • Do not replace OpenSSH
  • Do not store SSH private keys
  • Do not expose arbitrary local shell execution
  • Do not require duplicate SSH configuration inside MCP settings
  • Do not build a web UI for MVP

Distribution Requirements

The server must ship as a single self-contained executable.

Target platforms:

  • Windows x64
  • Linux x64
  • Linux arm64
  • macOS x64
  • macOS arm64

Expected binary names:

mcp-ssh
mcp-ssh.exe

Example .NET publish command:

dotnet publish \
  -c Release \
  -r linux-x64 \
  --self-contained true \
  /p:PublishSingleFile=true \
  /p:PublishTrimmed=true

MCP Transport

Use stdio transport for MVP.

Example MCP config:

{
  "mcpServers": {
    "ssh": {
      "command": "/path/to/mcp-ssh",
      "args": []
    }
  }
}

No SSH-specific configuration should be required in MCP config.


SSH Configuration Resolution

The long-term implementation should use OpenSSH configuration resolution.

Primary mechanism:

ssh -G <host-alias>

The implementation should parse the resolved output.

Required resolved fields:

  • hostname
  • user
  • port
  • identityfile
  • proxyjump
  • proxycommand
  • stricthostkeychecking
  • userknownhostsfile
  • identitiesonly

The implementation must support:

  • SSH aliases
  • bastion hosts
  • proxy jump
  • existing SSH agent
  • known hosts validation

Fallback parsing of ~/.ssh/config may be implemented if ssh -G is unavailable.

This is not part of the current vertical slice. The current implementation treats host as a hostname or IP address supplied directly to the MCP tool.


Authentication Requirements

Authentication should reuse the user's existing SSH environment.

Supported:

  • SSH agent
  • identity files
  • OpenSSH config
  • known hosts
  • optional password prompt support

The implementation must never persist private keys.


File Transfer Strategy

SFTP is the primary transfer mechanism.

Rationale:

  • More stable across SSH implementations
  • Better metadata support
  • Better directory traversal semantics
  • Avoids SCP shell parsing issues
  • Modern OpenSSH increasingly routes SCP over SFTP internally

Priority order:

1. SFTP
2. SCP fallback for get/put

The implementation must:

  • Use SFTP for directory listing
  • Use SFTP first for file get/put
  • Silently fall back to SCP for file get/put if SFTP is unavailable

Example error:

{
  "error": "sftp_unavailable",
  "message": "Remote host does not expose the SFTP subsystem for directory listing.",
  "scpFallbackAvailable": false
}

MCP Tools

ssh_exec

Execute a single SSH command.

Input:

{
  "host": "prod-api.example.com",
  "username": "deploy",
  "command": "systemctl status nginx",
  "cwd": "/var/www",
  "port": 22,
  "keyPath": "~/.ssh/id_ed25519",
  "keyPassphrase": "optional-passphrase",
  "timeoutSeconds": 30
}

Output:

{
  "exitCode": 0,
  "stdout": "...",
  "stderr": "...",
  "durationMs": 421
}

Requirements:

  • Enforce timeout
  • Capture stdout separately
  • Capture stderr separately
  • Preserve non-zero exit codes
  • Authenticate with explicit keyPath, or the first available default private key from ~/.ssh/id_ed25519, ~/.ssh/id_ecdsa, then ~/.ssh/id_rsa
  • Support optional keyPassphrase for encrypted private keys

terminal_start

Start a persistent PTY shell session.

Input:

{
  "host": "prod-api.example.com",
  "username": "deploy",
  "cols": 120,
  "rows": 40,
  "port": 22,
  "keyPath": "~/.ssh/id_ed25519",
  "keyPassphrase": "optional-passphrase",
  "idleTimeoutSeconds": 900
}

Output:

{
  "sessionId": "term_abc123"
}

Requirements:

  • Allocate PTY
  • Maintain server-side session state
  • Support idle timeout cleanup
  • Use the same key-auth inputs and default key discovery as ssh_exec
  • Use the remote account's default shell; do not write shell setup commands into the PTY after startup

terminal_write

Write to an active terminal session.

Input:

{
  "sessionId": "term_abc123",
  "input": "tail -f /var/log/nginx/error.log\\n"
}

Output:

{
  "accepted": true
}

terminal_read

Read buffered output from a terminal session.

Input:

{
  "sessionId": "term_abc123",
  "maxBytes": 12000
}

Output:

{
  "output": "...",
  "truncated": false
}

terminal_stop

Stop and remove a terminal session.

Input:

{
  "sessionId": "term_abc123"
}

Output:

{
  "stopped": true
}

sftp_list

List remote directory contents.

Input:

{
  "host": "prod-api",
  "remotePath": "/var/www"
}

Output:

{
  "entries": [
    {
      "name": "app",
      "path": "/var/www/app",
      "type": "directory",
      "size": 4096,
      "modifiedUtc": "2026-05-24T12:00:00Z"
    }
  ]
}

sftp_get

Download a remote file.

Input:

{
  "host": "prod-api",
  "remotePath": "/var/log/app.log",
  "localPath": "./downloads/app.log"
}

Output:

{
  "bytesTransferred": 123456,
  "localPath": "./downloads/app.log"
}

Requirements:

  • Enforce max download size
  • Prevent unsafe local path traversal
  • Fail on overwrite unless explicitly enabled

sftp_put

Upload a local file.

Input:

{
  "host": "prod-api",
  "localPath": "./dist/app.tar.gz",
  "remotePath": "/tmp/app.tar.gz",
  "overwrite": false
}

Output:

{
  "bytesTransferred": 123456,
  "remotePath": "/tmp/app.tar.gz"
}

Requirements:

  • Enforce max upload size
  • Fail if remote file exists and overwrite is false

Session Management

Terminal sessions should be maintained in memory.

Example session state:

{
  "sessionId": "term_abc123",
  "host": "prod-api",
  "createdUtc": "...",
  "lastActivityUtc": "...",
  "idleTimeoutSeconds": 900
}

Requirements:

  • Cryptographically random session IDs
  • Idle timeout cleanup
  • Graceful cleanup on shutdown
  • Output buffering with maximum limits

Security Requirements

The implementation must default to safe behavior.

Required safeguards:

  • Command timeout enforcement
  • Upload size limits
  • Download size limits
  • Audit logging
  • No private key persistence
  • No arbitrary local command execution
  • No command-content or host blocking; access control is delegated to SSH users, SSH keys, and remote-side permissions

Audit Logging

Every tool call should be logged.

Example:

{
  "timestampUtc": "2026-05-24T12:00:00Z",
  "tool": "ssh_exec",
  "host": "prod-api",
  "command": "systemctl status nginx",
  "success": true,
  "durationMs": 421
}

Sensitive values must be redacted.


Recommended Implementation Stack

Language:

  • C#
  • .NET 10+

Recommended libraries:

  • Official MCP SDK
  • SSH.NET
  • OpenSSH ssh -G

Recommended Internal Interfaces

public interface ISshConfigResolver
{
    /// <summary>
    /// Resolves an SSH host alias into effective connection settings.
    /// </summary>
    /// <param name="hostAlias">The SSH host alias.</param>
    /// <returns>The resolved SSH configuration.</returns>
    ResolvedSshConfig Resolve(string hostAlias);
}

public interface ISshSessionFactory
{
    /// <summary>
    /// Creates a connected SSH client.
    /// </summary>
    /// <param name="hostAlias">The SSH host alias.</param>
    /// <returns>A connected SSH client.</returns>
    SshClient CreateSshClient(string hostAlias);

    /// <summary>
    /// Creates a connected SFTP client.
    /// </summary>
    /// <param name="hostAlias">The SSH host alias.</param>
    /// <returns>A connected SFTP client.</returns>
    SftpClient CreateSftpClient(string hostAlias);
}

MVP Scope

The MVP must include:

  • Self-contained binary
  • stdio transport
  • ssh_exec
  • terminal_start
  • terminal_write
  • terminal_read
  • terminal_stop
  • sftp_list
  • sftp_get
  • sftp_put
  • basic audit logging

Future Enhancements

Potential future work:

  • Streamable HTTP transport
  • SCP compatibility fallback
  • Per-host policy configuration
  • File checksum validation
  • Directory upload/download
  • Remote command templates
  • Resource-based remote file browsing
  • Multi-hop SSH validation
  • Secret redaction improvements
  • Per-tool and per-host authorization policy