Skip to main content
Glama
README.md
# mcp-ssh-tool

[![npm version](https://img.shields.io/npm/v/mcp-ssh-tool.svg)](https://www.npmjs.com/package/mcp-ssh-tool)
[![CI](https://github.com/oaslananka-lab/mcp-ssh-tool/actions/workflows/ci.yml/badge.svg)](https://github.com/oaslananka-lab/mcp-ssh-tool/actions/workflows/ci.yml)
[![Security](https://github.com/oaslananka-lab/mcp-ssh-tool/actions/workflows/security.yml/badge.svg)](https://github.com/oaslananka-lab/mcp-ssh-tool/actions/workflows/security.yml)
[![Official MCP Registry](https://img.shields.io/badge/MCP%20Registry-active-green.svg)](https://registry.modelcontextprotocol.io/v0.1/servers/io.github.oaslananka%2Fmcp-ssh-tool/versions/latest)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![npm downloads](https://img.shields.io/npm/dm/mcp-ssh-tool.svg)](https://www.npmjs.com/package/mcp-ssh-tool)

Production-grade MCP SSH automation for operators, developers, and AI clients. `mcp-ssh-tool` opens persistent SSH sessions and exposes safe, structured tools for command execution, file operations, transfers, tunnels, package/service management, metrics, resources, and guided prompts.

v2 is secure by default: strict host-key verification is on, root login is off, raw sudo is policy-gated, destructive commands and filesystem mutations are denied unless policy allows them, and remote HTTP starts on loopback only unless bearer auth and allowed origins are configured.

## Why This Server

- **Trust:** central policy engine, structured audit events, redacted logs, strict host keys, and machine-readable errors.
- **MCP quality:** stdio for local clients, Streamable HTTP for remote clients, legacy SSE only behind an explicit compatibility flag.
- **AI-friendly tools:** stable output schemas, `structuredContent`, annotations for read-only/destructive/idempotent behavior, resources, and curated prompts.
- **Operations:** session TTL/eviction, command timeouts, transfer checksum verification, real SSH forwarding, Prometheus metrics, and OpenTelemetry hooks.
- **Portability:** SFTP first, POSIX/BusyBox-aware shell fallbacks for basic file operations, and explicit support boundaries.

## Quick Start

Run without installing:

```bash
pnpm dlx mcp-ssh-tool --version
```

Or install globally:

```bash
pnpm add --global mcp-ssh-tool
```

Add a stdio MCP server to your client:

```json
{
  "servers": {
    "ssh-mcp": {
      "type": "stdio",
      "command": "mcp-ssh-tool",
      "args": []
    }
  }
}
```

Use it from your MCP client:

```text
Open a safe SSH session to prod-1 as deploy, inspect host capabilities, then show disk usage.
```

## Requirements

- Node.js `22.22.2+` or `24.15.0+` (LTS only)
- SSH access to target hosts
- A populated `known_hosts` file for strict host verification, or an explicit per-session host-key policy

## Transports

| Mode | Command | Use When |
|------|---------|----------|
| stdio | `mcp-ssh-tool` | Local desktop clients such as ChatGPT, Claude Desktop, VS Code, Cursor, or Codex. |
| Streamable HTTP | `mcp-ssh-tool --transport=http --host 127.0.0.1 --port 3000` | Remote MCP clients, reverse proxies, or Inspector sessions. |
| legacy SSE | `mcp-ssh-tool --transport=http --enable-legacy-sse` | Temporary v1 compatibility only. Prefer Streamable HTTP. |

Non-loopback HTTP startup is refused unless `--bearer-token-file`, allowed origins, and `SSH_MCP_HTTP_PUBLIC_URL` are configured.

## No-Custody Remote Agent Mode

For public ChatGPT connector deployments, enable the remote-agent control plane instead of asking users for SSH credentials:

```bash
SSHAUTOMATOR_REMOTE_AGENT_CONTROL_PLANE=true \
PUBLIC_BASE_URL=https://sshautomator.example.com \
MCP_RESOURCE_URL=https://sshautomator.example.com/mcp \
mcp-ssh-tool http --host 0.0.0.0 --port 3000
```

Remote mode adds OAuth/DCR endpoints, a protected `/mcp` endpoint, agent enrollment APIs, and outbound WebSocket agent connections. ChatGPT receives OAuth-scoped MCP access. The platform stores identity, policy, audit records, OAuth metadata, hashed one-time tokens, and agent public keys; it does not store user SSH private keys, SSH passwords, root passwords, or cloud login credentials.

Enroll an agent on the user's own host:

```bash
npx --yes --package mcp-ssh-tool@latest mcp-ssh-agent enroll --server https://sshautomator.example.com --token <one-time-token> --alias prod-1
npx --yes --package mcp-ssh-tool@latest mcp-ssh-agent run
```

The agent verifies signed control-plane action envelopes, enforces local policy, executes bounded actions, signs results, and returns them over the outbound connection. See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md), [docs/CHATGPT_CONNECTOR.md](docs/CHATGPT_CONNECTOR.md), and [docs/AGENT_INSTALL.md](docs/AGENT_INSTALL.md).

## Secure Defaults

| Area | v2 Default |
|------|------------|
| Host keys | `hostKeyPolicy=strict`, `knownHostsPath=~/.ssh/known_hosts` |
| Root SSH login | denied |
| Raw `proc_sudo` | denied unless `allowRawSudo=true` |
| Destructive commands | denied unless `allowDestructiveCommands=true` |
| Destructive fs operations | allowed only under policy prefixes, denied elsewhere |
| Local transfer paths | `file_upload` and `file_download` limited to OS temp unless policy allows more |
| HTTP bind | `127.0.0.1` |
| Legacy SSE | disabled |
| File reads | size-limited by `SSH_MCP_MAX_FILE_SIZE` |
| Command output | bounded by `SSH_MCP_MAX_COMMAND_OUTPUT_BYTES` |
| Transfers | bounded by `SSH_MCP_MAX_TRANSFER_BYTES` |

Per-session `policyMode: "explain"` returns a plan/verdict without executing. Use it before mutations when an AI client needs to summarize risk.

## Policy Example

Set `SSH_MCP_POLICY_FILE=/etc/mcp-ssh-tool/policy.json`:

```json
{
  "mode": "enforce",
  "allowRootLogin": false,
  "allowRawSudo": false,
  "allowDestructiveCommands": false,
  "allowDestructiveFs": false,
  "allowedHosts": ["^prod-[0-9]+\\.example\\.com$"],
  "commandAllow": ["^(uname|df|uptime|systemctl status)\\b"],
  "commandDeny": ["rm\\s+-rf\\s+/", "shutdown", "reboot"],
  "pathAllowPrefixes": ["/tmp", "/var/tmp", "/home/deploy"],
  "pathDenyPrefixes": ["/etc/shadow", "/etc/sudoers", "/boot", "/dev", "/proc"],
  "localPathAllowPrefixes": ["/var/tmp/mcp-ssh-tool"],
  "localPathDenyPrefixes": []
}
```

Simple deploys can use environment overrides such as `SSH_MCP_ALLOW_RAW_SUDO=true`, `SSH_MCP_ALLOWED_HOSTS=prod-1.example.com`, `SSH_MCP_PATH_ALLOW_PREFIXES=/tmp,/home/deploy`, or `SSH_MCP_LOCAL_PATH_ALLOW_PREFIXES=/var/tmp/mcp-ssh-tool`.

## Core Tools

- `ssh_open_session`, `ssh_close_session`, `ssh_list_sessions`, `ssh_ping`, `ssh_list_configured_hosts`, `ssh_resolve_host`
- `proc_exec`, `proc_sudo`, `proc_exec_stream`
- `fs_read`, `fs_write`, `fs_list`, `fs_stat`, `fs_mkdirp`, `fs_rmrf`, `fs_rename`
- `file_upload`, `file_download`
- `ensure_package`, `ensure_service`, `ensure_lines_in_file`, `patch_apply`
- `os_detect`, `get_metrics`
- `tunnel_local_forward`, `tunnel_remote_forward`, `tunnel_list`, `tunnel_close`

All tools return text plus stable `structuredContent`. Tool metadata includes titles, output schemas, and annotations that disclose read-only, destructive, idempotent, and external side-effect behavior.

## Resources And Prompts

Resources:

- `mcp-ssh-tool://sessions/active`
- `mcp-ssh-tool://metrics/json`
- `mcp-ssh-tool://metrics/prometheus`
- `mcp-ssh-tool://ssh-config/hosts`
- `mcp-ssh-tool://policy/effective`
- `mcp-ssh-tool://audit/recent`
- `mcp-ssh-tool://capabilities/support-matrix`

Prompts:

- `safe-connect`
- `inspect-host-capabilities`
- `plan-mutation`
- `managed-config-change`

## Support Matrix

| Target | Status |
|--------|--------|
| Linux | Full support. |
| macOS/BSD | Session, process, fs, and transfer supported; package/service helpers only where tested. |
| BusyBox/dropbear | Experimental for session, process, and basic fs fallbacks. |
| Windows SSH targets | Experimental for session, process, fs, and transfer; no `proc_sudo` or `ensure_*`. |

## Client Examples

ChatGPT or Claude Desktop:

```json
{
  "mcpServers": {
    "ssh-mcp": {
      "command": "pnpm",
      "args": ["dlx", "mcp-ssh-tool"]
    }
  }
}
```

VS Code or Cursor:

```json
{
  "servers": {
    "ssh-mcp": {
      "type": "stdio",
      "command": "mcp-ssh-tool"
    }
  }
}
```

MCP Inspector over HTTP:

```bash
printf '%s' 'super-secret-token' > .mcp-token
mcp-ssh-tool --transport=http --host 127.0.0.1 --port 3000 --bearer-token-file .mcp-token
```

## Configuration

High-value environment variables:

| Variable | Default | Purpose |
|----------|---------|---------|
| `SSH_MCP_POLICY_FILE` | unset | Canonical JSON policy source. |
| `SSH_MCP_HOST_KEY_POLICY` | `strict` | `strict`, `accept-new`, or `insecure`. |
| `SSH_MCP_KNOWN_HOSTS_PATH` | `~/.ssh/known_hosts` | Known-hosts file for strict verification. |
| `SSH_MCP_MAX_FILE_SIZE` | `10485760` | Max bytes for `fs_read`. |
| `SSH_MCP_MAX_FILE_WRITE_BYTES` | `10485760` | Max text payload bytes accepted by `fs_write` before buffering. |
| `SSH_MCP_MAX_COMMAND_OUTPUT_BYTES` | `1048576` | Max retained stdout/stderr bytes per command or stream. |
| `SSH_MCP_MAX_STREAM_CHUNKS` | `4096` | Max retained streaming chunks before truncation metadata is returned. |
| `SSH_MCP_MAX_TRANSFER_BYTES` | `52428800` | Max bytes for `file_upload` and `file_download`. |
| `SSH_MCP_COMMAND_TIMEOUT` | `30000` | Default command timeout. |
| `SSH_MCP_HTTP_HOST` | `127.0.0.1` | Streamable HTTP bind host. |
| `SSH_MCP_HTTP_PORT` / `PORT` | `3000` | Streamable HTTP port. |
| `SSH_MCP_HTTP_MAX_REQUEST_BODY_BYTES` | `1048576` | Max JSON request bytes accepted by Streamable HTTP. |
| `SSH_MCP_HTTP_MAX_SESSIONS` | `20` | Max concurrent Streamable HTTP/SSE MCP sessions. |
| `SSH_MCP_HTTP_SESSION_IDLE_TTL_MS` | `900000` | Idle TTL before abandoned HTTP sessions are closed. |
| `SSH_MCP_HTTP_PUBLIC_URL` | unset | Required for non-loopback HTTP to publish stable protected-resource metadata. |
| `SSH_MCP_HTTP_TRUST_PROXY` | `false` | Trust `X-Forwarded-Proto` only when explicitly set. |
| `SSH_MCP_LOCAL_PATH_ALLOW_PREFIXES` | OS temp directory | Local transfer allow-list for `file_upload` and `file_download`. |
| `SSH_MCP_TUNNEL_ALLOW_BIND_HOSTS` | `127.0.0.1,localhost,::1` | Tunnel bind-host allow-list. |
| `SSH_MCP_TUNNEL_DENY_BIND_HOSTS` | `0.0.0.0,::` | Tunnel bind-host deny-list. |
| `SSH_MCP_TUNNEL_ALLOW_REMOTE_HOSTS` | unset | Optional tunnel destination host allow-list. |
| `SSH_MCP_TUNNEL_DENY_REMOTE_HOSTS` | unset | Tunnel destination host deny-list. |
| `SSH_MCP_TUNNEL_ALLOW_PORTS` | unset | Optional tunnel port allow-list, including ranges such as `1024-65535`. |
| `SSH_MCP_TUNNEL_DENY_PORTS` | unset | Tunnel port deny-list. |
| `SSH_MCP_HTTP_BEARER_TOKEN_FILE` | unset | Required for non-loopback HTTP. |
| `SSH_MCP_HTTP_ALLOWED_ORIGINS` | loopback origins | Comma-separated allowed origins. |

Deprecated aliases `STRICT_HOST_KEY_CHECKING` and `SSH_MCP_STRICT_HOST_KEY` are still accepted for one v2 compatibility cycle. Prefer `SSH_MCP_HOST_KEY_POLICY`.

## Development

Use the exact local runtime from `.nvmrc` / `.node-version`, then run:

```bash
pnpm install --frozen-lockfile
pnpm run check
```

Live SSH suites are opt-in:

```bash
RUN_SSH_INTEGRATION=1 pnpm run test:integration
RUN_SSH_E2E=1 pnpm run test:e2e
```

Local quality gates are layered:

- `pre-commit`: formats staged files and lints staged TypeScript only
- `pre-push`: runs `pnpm run check:push`
- `task hooks`: runs tracked pnpm hooks plus `.pre-commit-config.yaml` hooks when `pre-commit` is installed
- manual/full parity: `task ci` or `pnpm run check`

## CI/CD Ownership

The personal repository `https://github.com/oaslananka/mcp-ssh-tool` is the source repository. The organization repository `https://github.com/oaslananka-lab/mcp-ssh-tool` is the GitHub Actions, CI/CD, release, security, and provenance boundary.

Automatic CI/CD, supply-chain security checks, trusted npm publishing, MCP Registry publishing, GitHub Releases, Docker image validation, SBOMs, attestations, and release decisions run only from the org repository. Personal-repo Actions are intentionally not required gates.

The two repositories must stay content-identical for `main`, release tags, releases, labels, milestones, and active collaboration state. Org release-generated refs are backfilled to the personal source repository.

The npm package `repository.url` intentionally points at the org automation repository so npm provenance can verify that the published artifact came from the same GitHub Actions repository that built it. The MCP Registry server name remains `io.github.oaslananka/mcp-ssh-tool` because it is already published and changing it would break existing users.

See [docs/ci-cd-topology.md](docs/ci-cd-topology.md) for mirror, release, dry-run, and manual fallback guidance.

## Documentation

- [ARCHITECTURE.md](ARCHITECTURE.md)
- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)
- [docs/SECURITY.md](docs/SECURITY.md)
- [docs/CHATGPT_CONNECTOR.md](docs/CHATGPT_CONNECTOR.md)
- [docs/AGENT_INSTALL.md](docs/AGENT_INSTALL.md)
- [docs/MIGRATION.md](docs/MIGRATION.md)
- [docs/API.md](docs/API.md)
- [SECURITY_DECISIONS.md](SECURITY_DECISIONS.md)
- [MIGRATION.md](MIGRATION.md)
- [docs/ci-cd-topology.md](docs/ci-cd-topology.md)
- [docs/development.md](docs/development.md)
- [docs/release.md](docs/release.md)
- [docs/publishing.md](docs/publishing.md)
- [docs/npm-provenance.md](docs/npm-provenance.md)
- [docs/mcp-registry.md](docs/mcp-registry.md)
- [docs/chatgpt-app.md](docs/chatgpt-app.md)
- [docs/doppler.md](docs/doppler.md)
- [docs/operations.md](docs/operations.md)
- [docs/repository-operations.md](docs/repository-operations.md)
- [docs/docker.md](docs/docker.md)
- [docs/client-configs.md](docs/client-configs.md)
- [docs/security/release-integrity.md](docs/security/release-integrity.md)
- [docs/security/chatgpt-app-threat-model.md](docs/security/chatgpt-app-threat-model.md)
- [docs/security/ssh-threat-model.md](docs/security/ssh-threat-model.md)
- [docs/branch-protection.md](docs/branch-protection.md)
- [docs/maintenance-policy.md](docs/maintenance-policy.md)
- [docs/api-stability.md](docs/api-stability.md)
- [docs/threat-model.md](docs/threat-model.md)
- [docs/configuration.md](docs/configuration.md)
- [docs/security-model.md](docs/security-model.md)
- [docs/troubleshooting.md](docs/troubleshooting.md)
- [docs/enterprise-deployment.md](docs/enterprise-deployment.md)

## License

MIT License. See [LICENSE](LICENSE).

TDQS

A3.6/5.0

Scored across 33 tools

Disambiguation5/5

Each tool targets a distinct operation or resource type (file, command, session, tunnel, package, service, inspection). There is no significant overlap; even similar tools like proc_exec and proc_exec_stream are differentiated by execution mode.

Naming Consistency4/5

Tools are grouped by domain (fs_, proc_, tunnel_, ssh_, ensure_) with consistent patterns within groups. However, the overall naming convention mixes prefixes (fs_list vs file_download) and some tools use long phrasal names (ensure_lines_in_file).

Tool Count3/5

At 33 tools, the server is heavily featured. While most tools serve a clear purpose, the count is above the typical sweet spot and includes several niche utilities (get_metrics, os_detect, ssh_mutation_plan) that could be consolidated.

Completeness5/5

The tool surface covers the full lifecycle of SSH interactions: session management, file operations, command execution, package/service management, tunneling, and safety/planning. No obvious gaps exist for a generic SSH administration server.

Maintenance

ActivityNo data
ResponsivenessSyncing