Files
mcp-ssh/AGENTS.md
2026-05-24 20:45:12 +00:00

574 lines
9.2 KiB
Markdown

# 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`
* 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
* No SSH agent, OpenSSH alias, `ssh -G`, ProxyJump, ProxyCommand, or SFTP 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:
```text
mcp-ssh
mcp-ssh.exe
```
Example .NET publish command:
```bash
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:
```json
{
"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:
```bash
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:
```text
1. SFTP
2. SCP fallback (optional future enhancement)
```
The implementation must:
* Validate SFTP subsystem availability during connection
* Return structured capability errors if unavailable
* Not silently downgrade to SCP
Example error:
```json
{
"error": "sftp_unavailable",
"message": "Remote host does not expose the SFTP subsystem.",
"scpFallbackAvailable": false
}
```
---
# MCP Tools
## ssh_exec
Execute a single SSH command.
Input:
```json
{
"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:
```json
{
"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:
```json
{
"host": "prod-api.example.com",
"username": "deploy",
"shell": "bash",
"cols": 120,
"rows": 40,
"port": 22,
"keyPath": "~/.ssh/id_ed25519",
"keyPassphrase": "optional-passphrase",
"idleTimeoutSeconds": 900
}
```
Output:
```json
{
"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`
---
## terminal_write
Write to an active terminal session.
Input:
```json
{
"sessionId": "term_abc123",
"input": "tail -f /var/log/nginx/error.log\\n"
}
```
Output:
```json
{
"accepted": true
}
```
---
## terminal_read
Read buffered output from a terminal session.
Input:
```json
{
"sessionId": "term_abc123",
"maxBytes": 12000
}
```
Output:
```json
{
"output": "...",
"truncated": false
}
```
---
## terminal_stop
Stop and remove a terminal session.
Input:
```json
{
"sessionId": "term_abc123"
}
```
Output:
```json
{
"stopped": true
}
```
---
## sftp_list
List remote directory contents.
Input:
```json
{
"host": "prod-api",
"remotePath": "/var/www"
}
```
Output:
```json
{
"entries": [
{
"name": "app",
"path": "/var/www/app",
"type": "directory",
"size": 4096,
"modifiedUtc": "2026-05-24T12:00:00Z"
}
]
}
```
---
## sftp_get
Download a remote file.
Input:
```json
{
"host": "prod-api",
"remotePath": "/var/log/app.log",
"localPath": "./downloads/app.log"
}
```
Output:
```json
{
"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:
```json
{
"host": "prod-api",
"localPath": "./dist/app.tar.gz",
"remotePath": "/tmp/app.tar.gz",
"overwrite": false
}
```
Output:
```json
{
"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:
```json
{
"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:
```json
{
"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
```csharp
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
* 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