SSH MCP Server
公式エージェントからSSH経由でコマンド実行、ファイル移動、ログ検索、マシン監査を行います。
SSH MCPで何ができますか?
- 安全ガード付きでコマンドを実行 — アシスタントに
ssh_exec経由で単一またはバッチのコマンドを実行させ、破壊的な操作をサーバーに到達する前にブロックする保護機能を備えています。 - リモートファイルの読み取り、書き込み、一覧表示 —
ssh_file_read、ssh_file_write、ssh_file_listを使用してファイルを検査または変更し、アトミック書き込みとオプションの SHA-256 検証をサポートします。 - ログの検索とサーバーヘルスの確認 — ファイルやコンテナを横断して
ssh_log_searchまたはssh_log_tailをクエリするか、ssh_snapshotとssh_audit_baselineで構造化されたヘルススナップショットを取得します。 - 整合性チェック付きでファイルを転送 —
ssh_uploadとssh_downloadを使用してファイルやディレクトリをアップロードまたはダウンロードし、古いデバイス向けの自動レガシー scp フォールバックを備えています。 - 長時間実行されるバックグラウンドジョブを管理 —
ssh_execで遅い操作を切り離し、ssh_job_status、ssh_job_output、ssh_job_killで追跡し、切断後も維持されます。
ドキュメント
SSH MCP Server — AIエージェント向けリモートサーバーツール
|
|
SSH MCPサーバー — デバッグ、開発、サーバーメンテナンスにおいて、あなたとAIエージェントの時間とトークンを節約するマルチツールです。 SSH経由でコマンドを実行し、ファイルを移動し、ログを読み、マシンを監査します — クラウドVPS、ベアメタルマシン、あるいはクローゼットに眠るBusyBoxルーターでも対応します。 |
お使いのマシンに既にあるOpenSSHクライアントを使用します:あなたのキー、~/.ssh/config、ジャンプホスト、エージェント転送。バンドルされたものはなく、コンパイル不要、ネイティブバインディングもありません。
Claude Code、Codex CLI、Cline、opencode、Gemini CLI、Qwen Code、Hermes、その他のMCPクライアントで動作します。
インストール · ツール · セットアップ · セキュリティ · ロードマップ · ドキュメント · 変更履歴
30秒でインストール
グローバルインストールは不要です。npx が初回使用時にパッケージをダウンロードします:
npx -y @hypnosis/ssh-mcp-server
MCPクライアントに追加します — 例えば Claude Code — すべてのプロジェクトで:
claude mcp add ssh -s user \
-e SSH_PROFILES_FILE="$HOME/.claude/ssh-profiles.json" \
-- npx -y @hypnosis/ssh-mcp-server
または手動で記述します — ほとんどのクライアントが共有する設定形式で同じサーバーを:
{
"mcpServers": {
"ssh": {
"command": "npx",
"args": ["-y", "@hypnosis/ssh-mcp-server"],
"env": {
"SSH_PROFILES_FILE": "~/.claude/ssh-profiles.json"
}
}
}
}
次に、少なくとも1台のマシンを指定して ~/.claude/ssh-profiles.json を作成します:
{
"profiles": {
"production": {
"host": "server.example.com",
"username": "admin",
"privateKeyPath": "~/.ssh/your_private_key"
}
}
}
これで接続するのに十分です。
Codex、opencode、Qwen Code、その他のクライアントについては、 SSH MCPサーバーのセットアップ で説明しています。
プラグインとしてインストール
一部のクライアント — 例えば Claude Code — は、全体をプラグインとして扱うことができます:
/plugin marketplace add hypnosis/ssh-mcp-server
/plugin install ssh-mcp-server@ssh-mcp-server
プラグインは SSH_PROFILES_FILE が別の指定をしない限り ~/.claude/ssh-profiles.json を読み取るため、
そのファイルを先に作成すれば、サーバーはマシンがすでに読み込まれた状態で起動します。
要件
Node.js 18+ と、PATH 上のシステム ssh クライアントが必要です。Windowsでは、キーベースのプロファイルを使用してください。
パスワードおよびパスフレーズプロファイルは現在利用できません。
固定バージョンを希望する場合、オフライン作業、または起動ごとのレジストリチェックを減らしたい場合は:
npm install -g @hypnosis/ssh-mcp-server を使用し、npx の代わりにコマンドとして ssh-mcp-server を使用します。
対象読者
- DevOpsおよびSRE — より迅速な監査、インシデントチェック、日常のサーバー作業を求める方。
- Vibeコーダーやインディービルダー — AIアシスタントで開発し、自分たちのサーバーで実行する方。
- システム管理者およびプラットフォームエンジニア — 無制限の生シェルではなく、構造化されたツールを求める方。
- 専任の運用チームを持たずに独自のVPSを運用する開発者や小規模チーム。
- ホームラボ、NAS、ルーターの所有者 — 現代のプロトコルを超えて使える便利なハードウェアをお持ちの方。
生シェルではなくSSH MCPサーバーを使う理由
トークン削減、AIコスト削減
生シェルはAIエージェントに情報の奔流を与えます:繰り返されるコマンド、ASCIIテーブル、ログのダンプ。 そのノイズをサーバーの状態に変換するためにトークンを消費します — それはあなたのお金です。
より高速なサーバーデバッグ
目的に特化したツールは、日常的なチェックを一括処理し、ノイズの多い出力を制限し、重要な部分だけを返します。 エージェントはターミナル出力の解釈に費やす時間が減り、修正に早く到達します。
推測の減少、AIのミス削減
構造化された回答は、何が見つかったか、何を測定できなかったか、何が切り詰められたかを示します。 これにより、エージェントが幻覚でギャップを埋める余地が減り — 悪い修正が減り、 落ち着いたデプロイとより信頼性の高いコードが得られます。
SSH互換性:最新サーバー、レガシー機器、Windows
既存のOpenSSH設定を使用
バンドルされたSSH実装なし、ネイティブバインディングなし、プラットフォームごとの再ビルドなし。コマンドは
システムの ssh クライアントを使用するため、あなたのキー、~/.ssh/config、ジャンプホスト、エージェント
転送はすべてターミナルと同じように機能し続けます。サポートされている場合、宛先ごとに1つの共有
多重化接続を使用するため、コマンドごとではなく1回だけ認証します。
レガシーサーバー、ルーター、NASデバイスへのSSHサポート
最新の scp を持つルーターにファイルを送信すると、次のようになります:
scp app.conf router:/etc/
# scp: subsystem request failed on channel 0
何も壊れていません — 現在の scp は新しいプロトコルを話し、ルーターはそれを知りません。
ターミナルでは、フォーラムのスレッドを読んで追加のフラグを持ち帰る必要があります。ここでは何も
する必要はありません:転送が試行され、拒否が認識され、古いプロトコルが代わりに使用され、
そのマシンは記憶されるため、次のファイルは直接そこに送信されます。
古いSSHクライアントや不足しているツールのためのフォールバック
古い機器には行き止まりではなくフォールバックがあります。最新の機能がない場合、サーバーは 可能な場合は古い経路を取ります:
| お使いのマシン | 得られるもの |
|---|---|
| 最新のファイル転送には小さすぎるルーターやNAS | ファイルは依然として届きます — 古いプロトコルが自動的に使用されます |
| 10年前のサーバー | ワークフローは依然として機能します。コマンドごとに新しい接続を開くだけで、再利用はしません |
| ファイルのハッシュ化手段がない最小限のイメージ | アップロードは「検証できませんでした」と表示され、誰も確認していない一致を主張しません |
| ツールが単にインストールされていないボックス | 回答は「未測定」と表示されます — 「何もない」と読めるゼロではありません |
Model Context Protocolのために構築
公式MCP SDK上に構築され、TypeScriptで統一され、2500以上のユニットテストに加えて、 モックではなく実際のコンテナに対して実行されるライブスイートを備えています。
生SSH vs SSH MCPサーバー:同じ仕事、両方の方法
SSHサーバーヘルスチェック
状況: デプロイが完了したばかりです。サーバーが遅く感じられ、ディスク、メモリ、サービス、コンテナ、エラーのどれが原因かわかりません。
質問: 「このボックスは健全ですか?」
生SSH
$ uptime
10:42:17 up 18 days, 3:21, 2 users, load average: 0.42, 0.31, 0.28
$ df -hT
Filesystem Type Size Used Avail Use% Mounted on
/dev/sda1 ext4 40G 35G 5.0G 87% /
overlay overlay 40G 35G 5.0G 87% /var/lib/docker/overlay2/...
$ free -h
total used free shared buff/cache available
Mem: 7.7Gi 4.9Gi 612Mi 121Mi 2.2Gi 2.5Gi
$ systemctl --failed
UNIT LOAD ACTIVE SUB DESCRIPTION
● api-worker.service loaded failed failed API background worker
$ docker ps -a
CONTAINER ID IMAGE STATUS PORTS
8e14d0b41c2a api:latest Up 3 minutes 0.0.0.0:8080->8080/tcp
65b894af2430 worker:latest Exited (1) 2 minutes ago
$ ss -tulpn
Netid State Local Address:Port Process
tcp LISTEN 0.0.0.0:22 users:(("sshd",pid=842,fd=3))
tcp LISTEN 0.0.0.0:8080 users:(("docker-proxy",pid=1942,fd=4))
$ journalctl -p err --since -1h | tail -50
Aug 20 10:39:14 prod api-worker[22104]: database connection timed out
Aug 20 10:39:14 prod systemd[1]: api-worker.service: Failed with result 'exit-code'.
これでも省略された結果です。完全なチェックには、CPU、サービス状態、コンテナ数、最近のエラーを確認するための追加コマンドが必要で、それぞれ独自の出力形式があります。さらに悪いことに、ss のないボックスは、ポートチェックが実行されなかった場合にリスナーがゼロのように見えることがあります。
構造化されたMCP結果
ssh_snapshot({ "profile": "production" })
{
"disk_pct": 87,
"mem_pct": 64,
"cpu_pct": 12,
"load": "0.42 0.31 0.28",
"containers": 7,
"ports": 14,
"services_running": 3,
"recent_errors": 21,
"unavailable": []
}
エージェントが得るもの
| 生SSH | 構造化MCP | あなたの利点 |
|---|---|---|
| 複数のコマンドとASCIIテーブル | 1つの結果の名前付きフィールド | 1回の呼び出し、名前付きフィールド、ラウンドトリップ削減 |
| 不足しているツールが空の出力に見えることがある | unavailable が測定されなかったものを明示 | 推測の減少と悪い修正の削減 |
| ディスク、サービス、エラーを自分で整理 | 問題のシグナルがすでに表面化 | より高速なデバッグ |
完全な ssh_audit_baseline 結果は、いくつかの生コマンド出力よりも長くなることがあります —
当社のラボ測定では約1,077トークン対765トークン。節約は完全な
ワークフローから来ており、1つの応答を短くすることからではありません。
実際のトラブルシューティングセッションでは、目的に特化したツールにより49の個別コマンド呼び出しが 4つのMCP呼び出しに削減されました。追加の呼び出しごとに、蓄積された 会話で別のモデルターンが始まります。プロンプトキャッシングは繰り返し入力のコストを削減できますが、新しいコマンドと その出力は依然としてコンテキストを消費します。ラウンドトリップが少ないほど、セッション全体のトークンが減り、 繰り返しの分析が減り、回答への道のりが速くなります。
全体像ではなく脈拍が必要ですか? ssh_audit_baseline はシステム、ディスク、
メモリ、ポート、sshd、失敗したユニット、Docker、ファイアウォール、アップデートを一括処理します。結果は
CRITICAL / WARNING / OK として届きます。未測定のセクションは、黙ってゼロとして読まれる代わりに明示されます。
Linuxサーバーログ検索
状況: APIがタイムアウトしていますが、同じメッセージがnginx、syslog、 journald、または通常のユーザーでは読めないアプリケーションログにある可能性があります。
質問: 「そのエラーはどこから来たのですか?」
生SSH
$ grep -i "timeout" /var/log/nginx/error.log
2026/08/20 10:38:54 [error] upstream timed out while reading response header
$ grep -i "timeout" /var/log/syslog
Aug 20 10:39:14 prod api-worker[22104]: database connection timed out
$ grep -i "timeout" /var/log/app/*.log 2>/dev/null
$ journalctl -u api --since "1 hour ago" | grep -i timeout
Aug 20 10:39:14 prod api[22104]: database connection timed out after 30000ms
3番目のコマンドはきれいに見えますが、2>/dev/null は権限エラーも隠していました。「一致なし」
と「読み取られなかった」が同じに見えるようになりました。ビジーなログは数千行を返し、
エージェントのコンテキストからインシデントの残りを押し出すこともあります。
構造化されたMCP結果
ssh_log_search({ "profile": "production",
"path": ["/var/log/nginx/error.log", "/var/log/syslog", "/var/log/app/*.log"],
"query": "timeout", "context": 2, "since": "1h" })
{
"matches": 34,
"lines": [
{ "file": "/var/log/nginx/error.log", "line": 4821,
"text": "upstream timed out while reading response header", "context": false },
{ "file": "/var/log/nginx/error.log", "line": 4822,
"text": "client closed connection", "context": true }
],
"files_searched": 6,
"files_unreadable": ["/var/log/app/private"],
"files_skipped": 12,
"files_undated": [],
"limited": false,
"truncated": false
}
エージェントが得るもの
| 生SSH | 構造化MCP | あなたの利点 |
|---|---|---|
| 4つの検索と4つの出力 | ファイルとグロブを横断する1つの検索 | トークンとラウンドトリップの削減 |
| 権限エラーが消えることがある | files_unreadable がすべての見逃したパスを明示 | 誤った「ログはクリーン」という結論なし |
| 出力が有用な上限なしに増えることがある | limited と truncated がすべての切り詰めを明示 | 部分的な結果からのより安全な判断 |
since はサーバーのクロックを使用し、namesOnly: true は一致するパスのみを返し、
ssh_log_tail は複数のログから最後のN行を1回の呼び出しで読み取ります。
安全なリモート設定編集
状況: 稼働中のサーバーでnginx設定を置き換える必要があります。接続の切断、 誤ったモード、未チェックのコピーにより、サービスが壊れたファイルのままになる可能性があります。
質問: 「部分的なファイルを残さずにこの設定を置き換えられますか?」
生SSH
$ sudo sh -c 'cat > /etc/nginx/conf.d/api.conf' <<'EOF'
server {
listen 80;
location / { proxy_pass http://127.0.0.1:8080; }
}
EOF
$ echo $?
0
終了コードゼロはシェルが完了したことを示します。どのバイトが着地したかは証明されず、>
は新しいファイルの最初のバイトが到着する前に古いファイルを切り詰めました。書き込み中に接続が
切断されると、サービスは部分的な設定のままになります。
構造化されたMCP結果
ssh_file_write({ "profile": "production",
"files": [{ "path": "/etc/nginx/conf.d/api.conf",
"content": "server {\n listen 80;\n location / { proxy_pass http://127.0.0.1:8080; }\n}\n",
"mode": "644", "sudo": true, "verify": true }] })
{
"files": [{ "path": "/etc/nginx/conf.d/api.conf", "written": true,
"verified": "verified", "reason": null, "bytes": 79 }]
}
エージェントが得るもの
| 生SSH | 構造化MCP | あなたの利点 |
|---|---|---|
| コピー完了前にターゲットが切り詰められる | 完全な一時ファイルが1回のリネームで置き換える | 半分書き込まれた設定なし |
| 終了コードのみ | バイト数と検証結果が明示される | 実際に何が着地したかがわかる |
| 権限はシェルテキスト内にある | sudo、mode、verify はファイルごとのフィールド | 予測可能な所有権と引用ミスの削減 |
verified には3つの正直な結果があります:verified、サーバーにハッシュツールがない場合の unavailable、
検証が要求されなかった場合の skipped。読み取りの場合、ssh_file_read は
パスのリストを受け入れます。ssh_file_list はグロブ、再帰、サイズ、モードを処理します。
sudoでバッチSSHコマンドを実行
状況: デプロイの準備はできていますが、トラフィックを移動する前にnginx構文、サービス状態、最近のエラーをすべて チェックする必要があります。1つの失敗したチェックが結合ダンプ内に消えてはいけません。
質問: 「すべての事前チェックが合格しましたか?」
生SSH
$ ssh admin@server.example.com 'sudo nginx -t'
nginx: configuration file /etc/nginx/nginx.conf test is successful
$ ssh admin@server.example.com 'sudo systemctl is-active nginx'
active
$ ssh admin@server.example.com 'sudo tail -5 /var/log/nginx/error.log'
2026/08/20 10:38:54 [error] upstream timed out while reading response header
3つの接続が3つの無関係な出力を返します。コマンドが ; で結合されている場合、
シェルは最後の終了コードのみを報告します。&& で結合されている場合、最初の失敗の後に
後のチェックが消えます。
構造化されたMCP結果
ssh_exec({ "profile": "production",
"command": ["nginx -t", "systemctl is-active nginx",
"tail -5 /var/log/nginx/error.log"],
"sudo": true })
{
"commands": [
{ "command": "nginx -t", "exit_code": 0, "truncated": false, "clipped_bytes": 0,
"stdout": "", "stderr": "nginx: configuration file /etc/nginx/nginx.conf test is successful\n" },
{ "command": "systemctl is-active nginx", "exit_code": 0, "truncated": false,
"clipped_bytes": 0, "stdout": "active\n", "stderr": "" },
{ "command": "tail -5 /var/log/nginx/error.log", "exit_code": 0, "truncated": false,
"clipped_bytes": 0, "stdout": "2026/08/21 09:14:02 [error] upstream timed out\n", "stderr": "" }
],
"job_id": null
}
エージェントが得るもの
| 生SSH | 構造化MCP | あなたの利点 |
|---|---|---|
| 3つの呼び出しと無関係な出力 | 1つの順序付けられたコマンドリスト | ラウンドトリップの削減 |
| 結合シェルは中間ステータスを隠すことができる | すべてのコマンドが独自の exit_code を保持 | 失敗したチェックの見逃しなし |
sudo と引用がコマンドテキストで繰り返される | sudo がバッチ全体に適用 | 引用ミスの削減 |
破壊的コマンドガードは、最初のコマンドが実行される前に完全なリストをチェックします。1つの
エントリが拒否された場合、他のすべてのエントリは未実行としてマークされ、サーバーには何も送信されません。
各コマンドには独自のstdoutとstderrが付与されます。実行されて何も出力しなかったコマンドは空文字列を持ち、実行されなかったコマンドにはそのようなフィールドが一切存在しないため、両者を混同することはありません。コマンドごとに128 KBを超える出力は両端を保持します——表用の先頭とログ用の末尾——間に継ぎ目があり、その量を示し、clipped_bytesはどれだけ切り落とされたかを示します。切り落としはバイト境界で行われ、文字の端まで戻るため、切り詰められた回答に置換マークが付くことはありません。
sudoは端末なしでサーバーに到達します。プロファイルの応答は標準入力でsudoに渡されます。どのシークレットを使うかは、プロファイルが名前を指定している場合はsudoPasswordから、それ以外の場合はpasswordから決まります——鍵でログインするプロファイルにはログインパスワードが一切なく、マシンが両者を分けている場合、ログイン用のものは誤った答えになります。応答するものがない場合は、sudo自身の-Sやaskpassヘルパーに関する助言を残す代わりに、その旨と脱出方法を返答します。自身の標準入力を読み取るコマンドにはパスワードが渡されることはなく、そうでなければデータに混入してしまうからです。
長時間実行されるSSHジョブの実行
状況: バックアップや移行がエージェントセッションより長く実行されます。接続は 閉じられるかもしれませんが、後でその状態、出力、終了コードが引き続き必要です。
質問: 「このジョブは会話を生き延びますか?」
生のSSH
$ ssh admin@server.example.com 'pg_dump app | gzip > /srv/backups/app.sql.gz'
client_loop: send disconnect: Broken pipe
端末は消えています。再接続し、プロセスを見つけ、対象ファイルを検査し、バックアップが完了したのか途中で止まったのかを推測する必要があります。
構造化されたMCP結果
ssh_exec({ "profile": "production",
"command": "pg_dump app | gzip > /srv/backups/app.sql.gz",
"detach": true })
{
"commands": [{
"command": "pg_dump app | gzip > /srv/backups/app.sql.gz",
"exit_code": null,
"truncated": false,
"timed_out": false,
"blocked": false,
"blocked_reason": null,
"not_run": false,
"warning": null
}],
"job_id": "mst0f2q1-9ab3c4d5"
}
エージェントが得られるもの
| 生のSSH | 構造化されたMCP | 得られる利点 |
|---|---|---|
| ジョブは1つのSSHセッションに結び付けられる | リモートジョブには永続的なIDがある | 安全な切断と再起動 |
| 再接続はプロセスとファイルの検索を意味する | ステータスと終了コードには名前付きの状態がある | 完了したかどうかの推測が不要 |
| 出力の再読み取りは古いテキストを繰り返す | 出力はバイトオフセットから継続する | 長時間ジョブでのトークン使用量が減る |
ジョブの状態はこのサーバーのメモリではなくリモートディスクに保存されます。ssh_job_statusはrunning、finished、lostを区別し、ssh_job_outputは最後のバイトオフセットから継続し、ssh_job_killはシェルだけでなくプロセスグループ全体にシグナルを送ります。
レガシールーターやNASデバイスへのファイル転送
状況: 現在のOpenSSHクライアントがSFTPを試みますが、ルーターやNASが クラシックなscpプロトコルしか理解しません。ファイルはそれでも無傷で到着し、 対象を安全に置き換える必要があります。
質問: 「この古いデバイスは検証済みファイルをまだ受け取れますか?」
生のSSH
$ scp app.conf operator@router:/etc/app.conf
subsystem request failed on channel 0
scp: Connection closed
通常の次のステップは、レガシーフラグを思い出し、コピーを再試行し、その後別のハッシュコマンドを実行することです——デバイスにハッシュツールがまったくない場合もあります。
構造化されたMCP結果
ssh_upload({ "profile": "router", "local_path": "./app.conf",
"remote_path": "/etc/app.conf", "sudo": true,
"mode": "644", "owner": "root:root", "verify": true })
{
"files": [{
"path": "/etc/app.conf",
"written": true,
"verified": "verified",
"reason": null,
"bytes": 1284
}]
}
エージェントが得られるもの
| 生のSSH | 構造化されたMCP | 得られる利点 |
|---|---|---|
| 最新のSFTPモードは最初のエラーで停止する | クラシックなscpフォールバックは自動で記憶される | 古い機器でも動作する |
| コピーが成功しても完全性は証明されない | SHA-256検証には名前付きの結果がある | 破損が成功と誤認されない |
| 直接置換は部分的な対象を残すことがある | 一時ファイルが転送後に所定の位置へ移動される | 作業ファイルは中断を生き延びる |
デバイスにsha256sumもopensslもない場合、結果はunavailableとその理由を示し、誤った一致を報告しません。ディレクトリ全体はrecursive: trueを使用し、ハッシュを一括で検証します。
AIエージェント向け破壊的コマンド保護
ガードはコマンドがSSHに到達する前にローカルで実行されます。データを保持するコンテナを破壊する操作と回復可能な操作を区別し、チェーンやバッチ内のコマンド順序も検査します。
破壊的なチェーンが始まる前に停止する
安全なバックアップと置換のシーケンス:
cp -r /srv/app /srv/app.bak && mv /srv/app /srv/app-old && rm -rf /srv/app
同じ操作でも順序が間違っている場合:
rm -rf /srv/app && cp -r /srv/app /srv/app.bak && mv /srv/app /srv/app-old
# REFUSED before the first command runs
シェルはディレクトリを削除し、その後で初めてバックアップ元が消えていることに気づくでしょう。ガードは、後のステップが前のステップで既に破壊された対象を読み取ることを検出するため、呼び出し全体があなたのマシンに留まります。同じ検査がdropdb app && pg_dump app > backup.sqlも捕捉します。
取り返しのつかない損失は拒否し、回復可能な変更は警告する
| 拒否 — コンテナ自体 | 警告のみ — その内容 |
|---|---|
DROP DATABASE、dropdb | DROP TABLE、TRUNCATE、DELETE FROM |
docker volume rm、docker compose down -v | docker rm -f <name> |
crontab -r | 1つのジョブの編集 |
mkfs、wipefs -a、lvremove、zfs destroy | chmod 777 |
reboot、shutdown、halt | git reset --hard |
docker compose down -vは拒否されます。なぜなら-vはデータベースボリュームを含む名前付きDockerボリュームを削除するからです。-vがない場合、サービスの停止は同じ取り返しのつかない操作とは見なされません。
ファイルシステムルート、ホームディレクトリ、または/etc、/var、/usrなどのシステムツリーの再帰的削除も拒否されます。シンボリックリンクがそこに導く場合も同様です。rm -rf "$DIR"/*のような未解決の対象も拒否されます。「確認できなかった」は「安全」とは扱われません。
停止するものを名前で指定する
対象を名前で指定する代わりに見つけるコマンドは送信されません。サーバーはそれを展開し、対象の背後にあるものを答えます:
docker kill $(docker ps -q --filter ancestor=web)
# BLOCKED — would stop:
# edge — web:latest, Up 34 days, 0.0.0.0:8443->8443/tcp
プロセスの場合、回答には使用中の兆候が追加されます: 実行時間、接続を受け入れているポート、運んでいる接続数。名前付きの対象は追加コストなしで静かに通過します——docker kill web-1、kill 4871、systemctl stop app。
先に進むには、停止するものを名前で指定します。名前はコマンドが実際に到達するものと照合されるため、別のものにずれたマスクは確認される代わりに拒否されます:
docker kill $(docker ps -q --filter ancestor=web) # CONFIRMED-KILL: edge
コマンドラインに対するパターンは独自のケースです。それはそれを運ぶコマンド自体に一致するため、それを実行するシェルは対象より先にシグナルを受け、応答は途中で途切れます。そのような攻撃は確認されるのではなく書き換えられます——番号によって、または1文字をクラスとして書くことでパターンが自分自身に一致しなくなります:
pkill -f relay
# BLOCKED — two ways through:
# kill 4871
# pkill -f '[r]elay' # CONFIRMED-KILL: 4871
3つの結果は区別されます: 対象が見つかった、展開が何にも到達しなかった、尋ねるものがない——マシンにエンジンがない、切り詰められた回答、失敗した接続。最後の2つも拒否です: 知らないことは進める理由にはなりません。
意図的な破壊的コマンドの確認
何も永久に禁止されているわけではありません。レビュー済みコマンドに# CONFIRMED-DESTRUCTIVEを追加すると許可されます。ガードがバッチ内の1つのエントリを拒否すると、バッチ全体が実行前に停止するため、サーバーが半分実行された操作の後に残されることはありません。
ガードは単一の呼び出し内で機能します。ある呼び出しの削除を次の呼び出しの読み取りと結び付けたり、認識しないツールについて推論したりすることはできません。これはシートベルトであり、ポリシーエンジンではありません: 回復可能な操作はあなたの判断に委ねられます。パス制限と引用規則は**docs/security.md**に文書化されています。
ツール
サーバー操作用の18のSSH MCPツール。完全なパラメータと例は**docs/tools.md**にあります。
| ツール | 機能 |
|---|---|
ssh_exec | 破壊的コマンドガードとオプションのデタッチ付きで、1つのコマンドまたはバッチを実行 |
ssh_file_read | 1つまたは複数のファイルをテキストまたはバイナリで読み取り |
ssh_file_write | アトミックな名前変更とオプションのSHA-256検証付きでファイルを書き込み |
ssh_file_list | オプションのグロブと再帰付きでディレクトリを一覧表示 |
ssh_upload | SSH経由でファイルまたはディレクトリをアップロード、完全性チェック付きのバイナリセーフ; ディレクトリは対象を置換またはマージ |
ssh_download | SSH経由でファイルまたはディレクトリをダウンロード、完全性チェック付きのバイナリセーフ |
ssh_job_status | バックグラウンドジョブの状態: 実行中、完了、または消失 |
ssh_job_output | バイトオフセットから蓄積された出力を読み取り |
ssh_job_list | ジョブを一覧表示し、TTLを超えた完了ジョブを掃引 |
ssh_job_kill | ジョブのプロセスグループ全体にシグナルを送信 |
ssh_log_tail | 1つまたは複数のログの最後のN行、グロブ対応; 名前によるコンテナ |
ssh_log_search | ログ全体またはコンテナのログを横断するパターン検索 |
ssh_snapshot | ワンショットのヘルススナップショット: サービス、リソース、Docker、ネットワーク、エラー |
ssh_monitor | トランスポート制御: 統計、リロード、テスト、一覧、クローズ |
ssh_audit_baseline | システム、ディスク、メモリ、ネットワーク、ssh、サービス、Docker、ファイアウォール、更新 |
ssh_tls_check | ドメインの証明書有効期限、SAN、チェーン、更新フック |
ssh_disk_breakdown | ディスクの行き先: duトップN、Docker、journald、キャッシュ |
ssh_service_status | systemctl statusに1ユニット分のjournalctlテールを加えたもの |
MCPツールの安全性アノテーション
標準のMCPアノテーションは、どのツールが読み取り専用、破壊的、冪等、またはオープンワールドかをクライアントに伝えます。完全な表を参照してください。
SSHコマンドの実行とリモートファイルの管理
コマンド、ファイルの読み書き、ディレクトリ一覧——マシン上の通常の作業で、各回答は既に解析されています。
長時間実行されるSSHジョブの監視
遅い作業はデタッチされ、待つのではなく追跡されます: 各確認でどこまで進んだかがわかります。
ログの検索とサーバーヘルスの確認
ファイルとコンテナのログ、およびマシンのワンショット画像。出力は上限付きで、テールがコンテキストウィンドウを消費しないようにします。
SSH経由でのファイルのアップロードとダウンロード
完全性チェック付きのバイナリセーフ転送。詳細はdocs/transfer.mdにあります。
バイナリと大きなファイルには
ssh_upload/ssh_downloadを使用してください — base64チャンクと heredocはバイナリセーフでもアトミックでもありません。
SSH経由でのLinuxサーバー監査
読み取り専用で1回のラウンドトリップにバッチ化されます。詳細はdocs/audit.mdにあります。
Windows SSH互換モード
Windowsは互換モードを自動的に使用します。接続多重化が利用できない場合、サーバーはコマンドごとに1つの接続に切り替えます。同じツールが鍵ベースのSSH経由で引き続き利用可能です——別途設定やWindows固有の実装は不要です。
破壊的コマンドガードはAIエージェント向け破壊的コマンド保護で説明されています。
SSH MCPサーバーのセットアップ
まず30秒でインストールからパッケージを実行し、その後プロファイルファイルを作成します。
SSH接続プロファイルの作成
好きな場所に置いてください——エージェント自身の設定の隣が一般的な選択です。以下の例では~/.claude/ssh-profiles.jsonを使用しています。他のエージェントではディレクトリを交換してください(~/.codex/、~/.qwen/、~/.config/opencode/):
{
"profiles": {
"production": {
"host": "server.example.com",
"username": "admin",
"port": 22,
"privateKeyPath": "~/.ssh/your_private_key"
}
}
}
SSHプロファイルを明示的に選択する
サーバーがフォールバックするプロファイルはありません。各プロファイルは異なるマシンであり、間違ったマシンに送信されたコマンドは、後でエラーメッセージで元に戻せるものではありません。名前なしで尋ねると、回答は選択する名前の一覧を示します:
ssh_exec({ command: "uptime" })
→ No profile specified. Name one explicitly: production
サーバーがSSHに使用できないプロファイル——hostがない、usernameがない、またはmode: "local"——は文句なしにスキップされ、認識されないフィールドはそのまま残されるため、ファイルを他のツールと共有できます。壊れたフィールドを持つプロファイルは別のケースです: フィールドと値とともに名前が示され、健全な隣接プロファイルは動作し続けます。
各プロファイルはオプションでpathSecurityブロックを取り、ファイルツールが触れられるパスをホワイトリストまたはブラックリストにできます——docs/security.mdを参照してください。
鍵でログインするが、向こう側でsudoが必要なプロファイルはsudoPasswordを取ります——sudoで応答されるシークレットで、多くのマシンではログインパスワードではありません。ここではなくシークレットファイルに保管してください。
SSHパスワードとパスフレーズをプロファイルから遠ざける
パスワードや暗号化キーのパスフレーズが避けられない場合は、プロファイル自体ではなく、別のシークレットファイルに保管してください:
{
"secretsFile": "~/.config/ssh-mcp/secrets.json",
"profiles": {
"production": {
"host": "server.example.com",
"username": "admin"
}
}
}
シークレットファイルはプロファイル名でキー付けされます — secrets.json.example を参照してください:
{
"production": { "password": "..." },
"buildbox": { "sudoPassword": "..." }
}
sudoPassword は、そのマシン上で sudo が返す応答です。キーでログインするプロファイルにはログインパスワードを提供するものはなく、両者が異なる場合、ログイン用のパスワードは誤った答えです。それがなければ、password が使用されます。
シークレットファイルは自分だけが読み取れる必要があります (chmod 600)。相対パスはプロファイルファイルから解決されます。シークレットは argv の外に置かれ、ログではマスクされます。認証情報のセキュリティ を参照してください。
Claude Code、Codex、その他のMCPクライアントを設定する
使用するクライアントを選択し、同じプロファイルファイルを指定してください。
Claude Code
1つのコマンドで、-s user によりサーバーがすべてのプロジェクトで利用可能になります:
claude mcp add ssh -s user \
-e SSH_PROFILES_FILE="$HOME/.claude/ssh-profiles.json" \
-- npx -y @hypnosis/ssh-mcp-server
Codex CLI
codex mcp add ssh \
--env SSH_PROFILES_FILE="$HOME/.codex/ssh-profiles.json" \
-- npx -y @hypnosis/ssh-mcp-server
opencode
~/.config/opencode/opencode.json に配置します:
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"ssh": {
"type": "local",
"command": ["npx", "-y", "@hypnosis/ssh-mcp-server"],
"enabled": true,
"environment": {
"SSH_PROFILES_FILE": "~/.config/opencode/ssh-profiles.json"
}
}
}
}
Qwen Code
他のものと同じ、1つのコマンドです:
qwen mcp add ssh \
-e SSH_PROFILES_FILE="$HOME/.qwen/ssh-profiles.json" \
npx -y @hypnosis/ssh-mcp-server
その他のMCPクライアント
Gemini CLI、Hermes、Cline、エディタプラグイン、または独自のエージェントも同じように動作します。必要なのは、実行するコマンドと1つの環境変数だけです。
MCPクライアントを再起動する
クライアントを再起動し、ssh_monitor({ action: "list" }) を実行してプロファイルが読み込まれたことを確認してください。
SSH MCPサーバー設定
| 変数 | 機能 | デフォルト |
|---|---|---|
SSH_PROFILES_FILE | プロファイルJSONへのパス — 必須 | — |
SSH_MCP_LOG_LEVEL | debug、info、warn、error | info |
LOG_LEVEL | フォールバック。SSH_MCP_LOG_LEVEL が未設定の場合のみ使用 | info |
SSH_MCP_LOG_TIMESTAMP | ログ行のタイムスタンプ | true |
SSH_MCP_CONTROL_PERSIST | 最後のコマンド後、共有接続が維持される秒数。0 は即座に閉じます | 600 |
SSH_MCP_CONTROL_DIR | コントロールソケットの場所 | ~/.ssh/ssh-mcp |
SSH_MCP_PROFILES_CACHE_TTL | プロファイルキャッシュのTTL(ミリ秒) | 60000 |
SSH_MCP_PROFILES_WATCH | プロファイルファイルが変更されたときに再読み込み | true |
共有接続は意図的にこのプロセスより長く存続します。終了時に閉じると、同じマシン上の別のウィンドウが使用しているチャネルが切断されるためです。
SSH MCPサーバーの制限事項
すべての制限は回避方法を示しています。 何かできないツールはその旨を伝え、ssh_exec を指定します。これはマシン上で直接コマンドを実行します — サポートされていないログドライバー、マシンにないユーティリティ、このサーバーが対応していないエンジンなどです。ツールの限界を事前に知っておく必要はありません。重要な瞬間に拒否がそれを伝えます。
3つの拒否は意図的にシェルについて沈黙します。そこではそれが誤った答えだからです: プロファイルが禁止するパス(自分のルールを迂回するのは修正ではありません)、不正な呼び出し(修正は呼び出し側にあります)、そして ssh_exec 自体からの拒否です。
- キャンセル: キャンセルされた呼び出しは、同じ接続上の2番目の呼び出しとして送信され、サーバー上のコマンドも停止します。サーバーに
/procがない場合、コマンドは代わりにpsを通じて検出されます。FreeBSDは検証されていません。そこでの正しい動作は保証されません。ファイル転送とssh_snapshotはキャンセルを受け付けません。 - アトミック書き込み: BSDとmacOSは、ファイルシステム間のリネームを事前にチェックできません。
SSH MCPサーバーのロードマップ
-
macOS SSHホストに対する完全なテスト実行
-
Windowsでのエンドツーエンド互換性実行
-
マルチホスト監査 — 1回の呼び出しで複数のSSHプロファイルの健全性を比較
-
既存の
~/.ssh/configからプロファイルをインポート -
大容量ファイルと不安定な接続のための再開可能な転送
-
リモート操作タイムライン — コマンド、転送、ガード決定を1つの監査証跡に
-
すぐに使えるSSHトラブルシューティングプレイブック
-
シェルに落とさずにコンテナログを取得— 完了:ssh_log_tailとssh_log_searchはコンテナ名を受け取り、dockerに書き込み先を尋ね、他のログと同じ仕組みでそのファイルを読み取ります -
行き詰まる拒否— 完了: すべての制限は現在ssh_execを通過方法として指定しているため、ツールの限界に達すると、推測ゲームではなく1文で済みます -
モデルに届く回答— 完了: コマンド出力、一致したログ行、マシン名、スナップショットセクションは、テキストだけでなくフィールドで送信されます -
より小さなMCPツールスキーマ— 完了: ツールリストが10%軽量化され、デタッチされたジョブは盲目的にポーリングされる代わりに、書き込んだ最後の行を表示します -
rootでの長時間作業— 完了: デタッチされたジョブはsudoで実行され、rootとして追跡されます。キーのみのプロファイルはsudoに独自のsudoPasswordで応答します
SSH MCPサーバーの開発とテスト
npm install
npm run build # tsc
npx tsc --noEmit # types, plus dead declarations
npm run test:unit # unit tests
npm run lab:up # start the two test containers
npm run test:live # live suite against those containers
ライブスイートは実際のコンテナ(BusyBox 1つ、coreutils 1つ)に対して実行されます。この2つは静かに意見が異なり、モックはそれを書いた人に同意するためです。レイアウトについては docs/architecture.md を参照してください。
SSH MCPサーバーが気に入りましたか? ⭐
このツールが気に入ったら、GitHubでスターを付けてください — より多くの人がプロジェクトを発見するのに役立ちます。
SSH MCPサーバーへの貢献
Issueとプルリクエストは github.com/hypnosis/ssh-mcp-server で歓迎します。
ライセンス
MIT — LICENSE を参照してください。