mcp_ssh_connectors
An MCP server that enables AI to execute SSH commands on remote servers using the host's OpenSSH client, supporting both configured targets and dynamic connections with request-scoped credentials.
README
mcp_ssh_connectors
A host-side MCP server and local terminal tool that let an AI use the host machine's OpenSSH client to reach any SSH server with a request-scoped password or private key, even when the model sandbox has no direct network path. Configured named targets remain available for compatibility.
通过宿主机的 OpenSSH 客户端,让 AI 使用请求中提供的密码或私钥连接任意 SSH 服务器;支持本地 stdio,也支持带 API Key 鉴权的 Streamable HTTP MCP。
Dynamic MCP connections can execute any validated single-line command. HTTP clients still need a scoped, unexpired Bearer API key. Credentials are request-scoped and are never logged or returned.
Architecture
flowchart LR
AI[AI / MCP client] -->|stdio, local OS boundary| S[mcp-ssh-server]
AI -->|HTTP + Bearer API key| H[mcp-ssh-http]
H --> K[scrypt key store + scopes]
S --> P[dynamic connection or configured target]
H --> P
U[Human terminal] -->|mcp-ssh CLI| P
P -->|spawn, shell=false| O[Host OpenSSH client]
O --> R[configured SSH instances]
P --> A[JSONL audit log]
Node.js 20+ and an OpenSSH-compatible ssh executable are required.
Included
- MCP tools:
ssh_list_targets,ssh_preview,ssh_check,ssh_exec; check/exec accept configured targets or arbitrary dynamic connections - Local stdio MCP and authenticated Streamable HTTP MCP
- API key creation, listing, expiry, target scopes, operation scopes, and revocation
- Local CLI:
init,targets,preview,check,exec,connect,mcp,http,key - TOFU host-key checking for dynamic servers, request-scoped credentials, timeouts, output caps, and JSONL auditing
- ProxyJump, identity-file, port, and dedicated known-hosts support
- CI and unit tests
Install
git clone https://github.com/xtawa/mcp_ssh_connectors.git
cd mcp_ssh_connectors
npm install
npm run check
npm link
mcp-ssh init
$EDITOR ~/.config/mcp-ssh/config.json
After adding an optional configured target, it can still be tested through the CLI:
ssh example
mcp-ssh preview example -- uname -a
mcp-ssh check example
mcp-ssh exec example -- uname -a
Dynamic SSH connections
ssh_check and ssh_exec accept a connection object instead of a configured target. The connection requires a host, username, optional port, and exactly one authentication method.
Password example:
{
"connection": {
"host": "203.0.113.10",
"username": "deploy",
"port": 22,
"authentication": {
"type": "password",
"password": "request-scoped-password"
}
},
"command": "uname -a",
"reason": "diagnostics"
}
Private-key example:
{
"connection": {
"host": "server.example.com",
"username": "root",
"authentication": {
"type": "privateKey",
"privateKey": "-----BEGIN OPENSSH PRIVATE KEY-----\n...\n-----END OPENSSH PRIVATE KEY-----",
"passphrase": "optional-key-passphrase"
}
},
"command": "systemctl status example"
}
Dynamic commands are not checked against configured allow/deny expressions. They must be non-empty, single-line, NUL-free, and within policy.maxCommandLength. Dynamic calls use -F none, so local/system SSH configuration cannot inject a proxy or other options. New host keys use StrictHostKeyChecking=accept-new: the first key is recorded, while later key changes are rejected.
Passwords and key passphrases are supplied to OpenSSH through a forced SSH_ASKPASS helper. Private-key content is written to a per-request 0600 temporary file; Windows also receives an explicit user-only ACL. Temporary credential files are removed when the request finishes. Credentials are not included in process arguments, audit records, tool results, or error objects.
Local stdio MCP
{
"mcpServers": {
"ssh-connectors": {
"command": "mcp-ssh-server",
"env": { "MCP_SSH_CONFIG": "/absolute/path/to/config.json" }
}
}
}
Stdio relies on the local OS account boundary; no API key is sent through the model context.
HTTP MCP with API Key authentication
Create a read-only key restricted to staging:
mcp-ssh key create staging-observer \
--scopes mcp,ssh:read \
--expires 30d
Create an execution key for two targets:
mcp-ssh key create deploy-agent \
--scopes mcp,ssh:read,ssh:exec \
--expires 7d
The complete token is shown once. The key store contains only its salted scrypt hash. List metadata or revoke a key:
mcp-ssh key list
mcp-ssh key revoke KEY_ID
Start the authenticated endpoint:
mcp-ssh http
# or: mcp-ssh-http
# http://127.0.0.1:3000/mcp
Clients send:
Authorization: Bearer mcp_ssh.KEY_ID.SECRET
Example MCP client configuration:
{
"mcpServers": {
"ssh-connectors-http": {
"url": "http://127.0.0.1:3000/mcp",
"headers": { "Authorization": "Bearer ${MCP_SSH_API_KEY}" }
}
}
}
Keep the token in the client environment or secret manager. Do not commit it. For any non-loopback deployment, use TLS and set http.allowedHosts; preferably place the service behind a hardened reverse proxy.
Scopes
| Scope | Permission |
|---|---|
mcp |
Required to reach the MCP endpoint |
ssh:read |
List targets, preview commands, and check connectivity |
ssh:exec |
Execute commands that also pass host policy |
New API keys default to target *, which permits their SSH operation scopes to use dynamic connections. --targets remains available to restrict access to legacy configured targets; it does not restrict dynamic hosts.
Configuration
{
"version": 1,
"sshBinary": "ssh",
"auth": { "keyStore": "~/.config/mcp-ssh/keys.json" },
"http": {
"host": "127.0.0.1",
"port": 3000,
"allowedHosts": [],
"allowedOrigins": []
},
"audit": { "required": true, "logCommands": false },
"defaults": {
"timeoutMs": 30000,
"connectTimeoutSeconds": 10,
"maxOutputBytes": 1048576,
"knownHostsFile": "~/.ssh/known_hosts"
},
"policy": {
"maxCommandLength": 4096,
"deniedCommands": ["(?:^|\\s)sudo(?:\\s|$)"]
},
"targets": {
"staging": {
"destination": "deploy@10.0.20.15",
"identityFile": "~/.ssh/staging_ed25519",
"proxyJump": "bastion",
"tags": ["staging", "linux"],
"allowedCommands": [
"^uname -a$",
"^systemctl status [A-Za-z0-9_.@-]+$"
],
"deniedCommands": ["(?:^|[;&|]\\s*)rm(?:\\s|$)"],
"requireReason": true
}
}
}
The targets object is optional and may be empty. For configured targets, deny rules run before allow rules and a target with no allowedCommands is blocked. Dynamic connections bypass these configured command expressions and retain only the global length and single-line validation.
Terminal commands
mcp-ssh init [--config PATH]
mcp-ssh targets [--config PATH]
mcp-ssh preview TARGET [--reason TEXT] -- COMMAND
mcp-ssh check TARGET [--config PATH]
mcp-ssh exec TARGET [--reason TEXT] -- COMMAND
mcp-ssh connect TARGET [--config PATH]
mcp-ssh mcp [--config PATH]
mcp-ssh http [--host HOST] [--port PORT] [--config PATH]
mcp-ssh key create NAME [--targets LIST] [--scopes LIST] [--expires 30d]
mcp-ssh key list [--config PATH]
mcp-ssh key revoke KEY_ID [--config PATH]
connect is a human-only interactive shell and is not exposed as an MCP tool.
Request authorization order
For HTTP requests the connector applies four independent checks:
- validate the Bearer key hash, expiry, and revocation state;
- require operation scope (
ssh:readorssh:exec), plus target access only for a configured target; - validate dynamic single-line commands, or apply configured-target deny/allow rules;
- authenticate to the remote machine with the request password/private key or configured host SSH identity.
The API key id is recorded as the audit actor. Neither bearer tokens nor SSH key contents are logged.
Security
Read docs/security.md before exposing this server. Dynamic access deliberately gives an ssh:exec caller broad reach. Plain HTTP should stay on loopback or inside a trusted tunnel. Non-loopback deployments need TLS, explicit host/origin policy, short-lived scoped keys, and append-only audit storage.
Ideas and next steps
See docs/roadmap.md for human approvals, external identity providers, ephemeral SSH certificates, constrained SFTP, fleet blast-radius budgets, cached host facts, telemetry, and session recording.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.