SSH Nexus MCP
by faridarjmand
README.md
# SSH Nexus MCP
SSH Nexus is a local-first dashboard and [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server for an existing OpenSSH inventory. It reads explicit aliases from `~/.ssh/config`, shows live reachability and Linux resource usage, opens an interactive browser terminal, indexes a project for agent context, and exposes the same safe operations to Codex, Claude, and other MCP clients.
  
## What it includes
- OpenSSH inventory with `Host`, `Include`, effective hostname, user, port, and `ProxyJump` support
- Structured dashboard add/edit using a separate managed SSH config with atomic backups
- Green/red reachability status with text labels and connection latency
- Interactive xterm.js terminal backed by the local `ssh` executable
- CPU, RAM, root-disk, load, uptime, and disk-I/O metrics over authenticated SSH
- Provider-neutral MCP tools, resources, and prompts for Codex, Claude Code, Claude Desktop, and compatible clients
- Bounded project indexing and text search for other AI agents
- Local-only binding by default, optional bearer authentication, alias allowlisting, and no private keys in the browser
- Non-overlapping fleet refreshes capped at eight concurrent host probes
- Responsive, keyboard-accessible dashboard
## Requirements
- Node.js 22 or newer
- npm 10 or newer
- OpenSSH client (`ssh` in `PATH`)
- A working `~/.ssh/config`
- Key-based/non-interactive SSH authentication for automatic metrics
- Linux remote hosts for the full metrics set (non-Linux hosts show the fields they support)
## Quick start
```bash
git clone https://github.com/YOUR_USERNAME/ssh-nexus-mcp.git
cd ssh-nexus-mcp
npm install
cp .env.example .env
npm run dev
```
Open <http://127.0.0.1:5173>. Vite serves the development UI and proxies the API/WebSocket to port `3100`.
For a production-style local run:
```bash
npm run build
npm start
```
Open <http://127.0.0.1:3100>.
The default configuration already reads `~/.ssh/config`. A minimal SSH entry looks like:
```sshconfig
Host production-web
HostName 203.0.113.10
User deploy
Port 22
IdentityFile ~/.ssh/id_ed25519
```
SSH Nexus lists explicit aliases only. Wildcard blocks such as `Host *` contribute OpenSSH defaults but do not become dashboard cards.
## Managed server editing
Dashboard editing is disabled by default. It never rewrites `~/.ssh/config`; changes go to `SSH_NEXUS_MANAGED_CONFIG` (default `~/.ssh/ssh-nexus/hosts.conf`) and the previous managed file is backed up as `hosts.conf.bak`.
Enable editing with a bearer token:
```bash
export SSH_NEXUS_TOKEN="$(openssl rand -hex 32)"
export ALLOW_SSH_CONFIG_WRITES=true
npm run dev
```
The editor supports alias, hostname/IP, user, port, and explicit `ProxyJump` aliases. Editing an existing source alias creates a managed override while leaving the original entry untouched. Private-key paths and arbitrary OpenSSH directives are intentionally not editable.
## Connect an AI agent
Build the project first:
```bash
npm run build
```
### Codex
```bash
codex mcp add ssh-nexus -- node "$(pwd)/dist/server/mcp-stdio.js"
codex mcp list
```
Codex also supports project-scoped `.codex/config.toml`:
```toml
[mcp_servers.ssh_nexus]
command = "node"
args = ["/absolute/path/to/ssh-nexus-mcp/dist/server/mcp-stdio.js"]
default_tools_approval_mode = "writes"
```
See the [official Codex MCP documentation](https://learn.chatgpt.com/docs/extend/mcp?surface=cli).
### Claude Code
```bash
claude mcp add ssh-nexus -- node "$(pwd)/dist/server/mcp-stdio.js"
claude mcp get ssh-nexus
```
Use `--scope user` before `--` if you want the integration in all projects. See the [official Claude Code MCP documentation](https://docs.anthropic.com/en/docs/claude-code/mcp).
### Claude Desktop or another MCP host
Add this stdio definition, replacing the absolute path:
```json
{
"mcpServers": {
"ssh-nexus": {
"command": "node",
"args": ["/absolute/path/to/ssh-nexus-mcp/dist/server/mcp-stdio.js"]
}
}
}
```
The dashboard's **Connect AI** dialog generates copy-ready commands using the current project path.
### Streamable HTTP
While the dashboard is running, MCP is also available at:
```text
http://127.0.0.1:3100/mcp
```
If `SSH_NEXUS_TOKEN` is set, clients must send `Authorization: Bearer <token>`. Do not expose this endpoint publicly without TLS and a proper identity-aware proxy.
## MCP capabilities
| Capability | Purpose | Default safety |
| --- | --- | --- |
| `list_ssh_hosts` | List configured aliases | Read-only |
| `check_ssh_host` | Test reachability | Read-only/network |
| `get_host_metrics` | Run a fixed metrics probe | Read-only/network |
| `get_project_index` | Read or initialize the project index | Writes only generated index |
| `refresh_project_index` | Rebuild project metadata | Writes only generated index |
| `search_project` | Search bounded indexed source | Read-only |
| `run_ssh_command` | Run an arbitrary remote command | **Disabled by default** |
| `ssh-nexus://hosts` | SSH inventory resource | Read-only |
| `ssh-nexus://project-index` | Project index resource | Read-only |
| `server_health_review` | Safe health-review prompt | Read-only workflow |
To deliberately enable arbitrary MCP remote commands:
```bash
ALLOW_REMOTE_COMMANDS=true npm start
```
This changes the trust boundary. Keep agent approval enabled and review commands before execution.
## Project indexing
Press **Build index** in the dashboard or call `refresh_project_index`. The generated `.ssh-nexus/index.json` contains paths, sizes, line counts, modification times, SHA-256 hashes, and language counts. It does not copy file content into the index.
The indexer skips `.git`, dependencies, build output, its own generated directory, symlinks, `.env*`, private-key-style names, and common certificate/keystore extensions. Allowed roots are controlled by `PROJECT_ROOTS`.
## Configuration
| Variable | Default | Description |
| --- | --- | --- |
| `SSH_NEXUS_CONFIG` | `~/.ssh/config` | OpenSSH config path |
| `SSH_NEXUS_MANAGED_CONFIG` | `~/.ssh/ssh-nexus/hosts.conf` | Dashboard-owned structured host entries |
| `DASHBOARD_HOST` | `127.0.0.1` | Bind address |
| `DASHBOARD_PORT` | `3100` | API/dashboard port |
| `SSH_NEXUS_TOKEN` | unset | Bearer token; required for non-loopback bind |
| `PROJECT_ROOTS` | current directory | Allowed index roots, separated by OS path delimiter |
| `SSH_CHECK_TIMEOUT_MS` | `3500` | Reachability timeout |
| `SSH_COMMAND_TIMEOUT_MS` | `12000` | Fixed metrics/MCP command timeout |
| `METRICS_CACHE_MS` | `10000` | Metrics cache lifetime |
| `MAX_INDEX_FILES` | `5000` | Per-index file limit |
| `ALLOW_SSH_CONFIG_WRITES` | `false` | Enable managed host writes; also requires a token |
| `ALLOW_REMOTE_COMMANDS` | `false` | Enable dangerous MCP command tool |
The dashboard never reads private-key content. OpenSSH itself resolves identities, agents, proxies, host keys, and authentication.
## Docker
Native installation is recommended because it naturally uses your SSH agent and filesystem permissions. For Docker:
```bash
export SSH_NEXUS_TOKEN="$(openssl rand -hex 32)"
export ALLOW_SSH_CONFIG_WRITES=true
docker compose up --build
```
The browser asks for this token. Compose mounts `${HOME}/.ssh` read-only, keeps managed entries in the persistent `ssh-nexus-data` volume, and leaves config editing disabled unless `ALLOW_SSH_CONFIG_WRITES=true`. The repository is mounted at `/workspace`. On Linux, make sure mounted key files are readable by the container's `node` user and that host keys are already present in `known_hosts`.
## Validate before pushing
```bash
npm run check
```
Then create and push your GitHub repository:
```bash
git init
git add .
git commit -m "feat: initial SSH Nexus MCP dashboard"
gh repo create ssh-nexus-mcp --source=. --private --push
```
Change `--private` to `--public` only after reviewing the repository for personal hostnames or local configuration. SSH Nexus does not commit your SSH config or generated index.
## Documentation
- [Installation and operations](docs/INSTALLATION.md)
- [Architecture](docs/ARCHITECTURE.md)
- [Security model](docs/SECURITY.md)
- [AI agent integration](docs/AI_AGENTS.md)
- [HTTP and WebSocket API](docs/API.md)
- [Project map](docs/PROJECT_INDEX.md)
- [Contributing](CONTRIBUTING.md)
## License
[MIT](LICENSE)
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues