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, ваши jump-хосты, ваш агентский форвардинг. Ничего встроенного, ничего компилировать, никаких нативных привязок.
Работает с 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"
}
}
}
}
Затем создайте ~/.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
Плагин читает ~/.claude/ssh-profiles.json, если SSH_PROFILES_FILE не указывает иное, поэтому
создайте этот файл сначала, и сервер запустится с уже загруженными машинами.
Требования
Node.js 18+ и системный ssh клиент на PATH. В Windows используйте профиль на основе ключей;
профили с паролем и парольной фразой в настоящее время недоступны.
Предпочитаете закреплённую версию, офлайн-работу или одну лишнюю проверку реестра при каждом запуске:
npm install -g @hypnosis/ssh-mcp-server, затем используйте ssh-mcp-server в качестве команды вместо
npx.
Для кого это
- DevOps и SRE-инженеры, которым нужны более быстрые аудиты, проверки инцидентов и рутинная работа с серверами.
- Vibe-кодеры и независимые разработчики, которые создают продукты с помощью AI-ассистента и запускают их на собственных серверах.
- Системные администраторы и платформенные инженеры, которым нужны структурированные инструменты вместо неограниченной сырой оболочки.
- Разработчики и небольшие команды, управляющие собственным VPS без выделенной команды эксплуатации.
- Владельцы домашних серверов, NAS и роутеров, чьё полезное оборудование пережило свои современные протоколы.
Почему SSH MCP сервер вместо сырой оболочки
Меньше токенов, ниже затраты на AI
Сырая оболочка даёт AI-агенту поток данных: повторяющиеся команды, ASCII-таблицы и дампы журналов. Он тратит токены, превращая этот шум в картину состояния сервера — ваши деньги.
Более быстрая отладка серверов
Специализированные инструменты группируют рутинные проверки, ограничивают шумный вывод и возвращают только важное. Агент тратит меньше времени на перевод вывода терминала и быстрее добирается до исправления.
Меньше догадок, меньше ошибок AI
Структурированные ответы сообщают, что было найдено, что не удалось измерить и что было обрезано. Это оставляет агенту меньше места для заполнения пробелов галлюцинациями — и даёт вам меньше плохих исправлений, спокойнее деплои и более надёжный код.
SSH-совместимость: современные серверы, устаревшее оборудование и Windows
Используйте вашу существующую настройку OpenSSH
Никакой встроенной реализации SSH, никаких нативных привязок, никакой пересборки под каждую платформу. Команды используют
системный ssh клиент, поэтому ваши ключи, ваш ~/.ssh/config, ваши jump-хосты и ваш агентский
форвардинг продолжают работать точно так же, как в терминале. Когда поддерживается, одно общее
мультиплексированное соединение на пункт назначения означает, что вы аутентифицируетесь один раз, а не для каждой команды.
Поддержка SSH для устаревших серверов, роутеров и NAS-устройств
Отправьте файл на роутер с современным scp и получите вот это:
scp app.conf router:/etc/
# scp: subsystem request failed on channel 0
Ничего не сломано — текущий scp говорит на новом протоколе, а роутер его не знает.
В терминале вам пришлось бы читать форумный тред и возвращаться с дополнительным флагом. Здесь вы не делаете
ничего: передача пробуется, отказ распознаётся, вместо него используется старый протокол,
и эта машина запоминается, чтобы следующий файл пошёл туда напрямую.
Запасные варианты для старых SSH-клиентов и отсутствующих инструментов
Старое оборудование получает запасной путь, а не тупик. Когда современная функция отсутствует, сервер идёт более старым путём, где может:
| Ваша машина | Что вы получаете |
|---|---|
| Роутер или NAS слишком маленький для современной передачи файлов | Файл всё равно доставляется — старый протокол используется автоматически |
| Сервер десятилетней давности | Рабочий процесс всё равно работает; просто открывается новое соединение на команду вместо повторного использования |
| Урезанный образ без возможности хеширования файла | Загрузка сообщает «не удалось проверить» вместо заявления о совпадении, которое никто не проверял |
| Машина, где инструмент просто не установлен | Ответ сообщает «не измерено» — никогда не ноль, который читается как «ничего нет» |
Создан для Model Context Protocol
Построен на официальном MCP SDK, полностью на TypeScript, более 2500 модульных тестов плюс живой набор, который работает с реальными контейнерами, а не с моками.
Сырой SSH против 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-таблицы | Именованные поля в одном результате | Один вызов, именованные поля и меньше往返 |
| Отсутствующий инструмент может выглядеть как пустой вывод | unavailable называет, что не было измерено | Меньше догадок и меньше плохих исправлений |
| Вы разбираетесь с дисками, службами и ошибками | Сигналы проблем уже выведены на поверхность | Более быстрая отладка |
Полный результат ssh_audit_baseline может быть длиннее, чем несколько сырых выводов команд —
около 1077 токенов против 765 в наших лабораторных измерениях. Экономия приходит от полного
рабочего процесса, а не от сокращения одного ответа.
В реальной сессии устранения неполадок специализированные инструменты сократили 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
Третья команда выглядит чистой, но 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 | Ваша выгода |
|---|---|---|
| Четыре поиска и четыре вывода | Один поиск по файлам и глобам | Меньше токенов и往返 |
| Ошибки прав могут исчезать | files_unreadable называет каждый пропущенный путь | Нет ложного вывода «журналы чистые» |
| Вывод может расти без полезного предела | limited и truncated раскрывают каждый обрыв | Более безопасные решения на основе частичных результатов |
since использует часы сервера, namesOnly: true возвращает только совпадающие пути, а
ssh_log_tail читает последние N строк из нескольких журналов за один вызов.
Безопасное удалённое редактирование конфигураций
Ситуация: Вам нужно заменить конфиг 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 | Ваша выгода |
|---|---|---|
| Целевой файл обрезается до завершения копирования | Полный временный файл заменяет его одним переименованием | Нет наполовину записанного конфига |
| Только код выхода | Байты и результат проверки названы | Вы знаете, что реально приземлилось |
| Права живут внутри текста оболочки | sudo, mode и verify — поля на файл | Предсказуемое владение и меньше ошибок кавычек |
verified имеет три честных исхода: verified, unavailable, когда на сервере нет инструмента хеширования,
и skipped, когда проверка не была запрошена. Для чтения ssh_file_read принимает
список путей; ssh_file_list обрабатывает глобы, рекурсию, размеры и режимы.
Пакетные SSH-команды с sudo
Ситуация: Деплой готов, но синтаксис nginx, состояние службы и недавние ошибки должны быть проверены до переключения трафика. Одна проваленная проверка не должна исчезнуть внутри объединённого дампа.
Вопрос: «Прошла ли каждая предварительная проверка?»
Сырой 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
Три соединения возвращают три несвязанных вывода. Если команды объединены с ;, оболочка
сообщает только последний код выхода; если они объединены с &&, более поздние проверки исчезают
после первого сбоя.
Структурированный результат 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 | Ваша выгода |
|---|---|---|
| Три вызова и несвязанные выводы | Один упорядоченный список команд | Меньше往返 |
| Объединённая оболочка может скрыть промежуточный статус | Каждая команда сохраняет свой exit_code | Ни одна проваленная проверка не пропущена |
sudo и кавычки повторяются в тексте команды | sudo применяется ко всему пакету | Меньше ошибок кавычек |
Защита от разрушительных команд проверяет полный список до запуска первой команды. Если одна
запись отклонена, каждая другая запись помечается как не выполненная, и на сервер ничего не отправляется.
Каждая команда несёт собственные stdout и stderr. Команда, которая выполнилась и ничего не вывела, имеет пустую строку; команда, которая никогда не запускалась, вообще не имеет такого поля, так что их невозможно перепутать. Вывод объёмом более 128 КБ на команду сохраняет оба конца — начало для таблиц, хвост для журналов — со швом посередине, указывающим объём, а 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 | Ваша выгода |
|---|---|---|
| Задача привязана к одной SSH-сессии | Удалённая задача имеет постоянный идентификатор | Безопасные обрывы и перезапуски |
| Переподключение означает поиск процессов и файлов | Статус и код завершения имеют именованные состояния | Не нужно гадать, завершилось ли |
| Повторное чтение вывода повторяет старый текст | Вывод продолжается с байтового смещения | Меньше токенов на длинных задачах |
Состояние задачи хранится на удалённом диске, а не в памяти этого сервера. 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 и проверяют свои хеши одним пакетом.
Защита от разрушительных команд для ИИ-агентов
Защита работает локально, до того как команда достигнет 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 | редактирование одной задачи |
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
Шаблон по командным строкам — особый случай. Он совпадает с самой командой, которая его несёт, поэтому оболочка, выполняющая его, получает сигнал раньше цели, и ответ обрывается на середине. Такой удар не подтверждается, а переписывается — по номеру или с одним символом, записанным как класс, чтобы шаблон перестал совпадать с самим собой:
pkill -f relay
# BLOCKED — two ways through:
# kill 4871
# pkill -f '[r]elay' # CONFIRMED-KILL: 4871
Три исхода остаются раздельными: цели найдены, разворачивание ничего не достигло, и нечем спрашивать — нет движка на машине, усечённый ответ, неудачное соединение. Последние два также являются отказами: незнание — не причина продолжать.
Подтверждение намеренной разрушительной команды
Ничто не запрещено навсегда. Добавьте # CONFIRMED-DESTRUCTIVE к проверенной команде, и она будет пропущена. Когда защита отклоняет одну запись в пакете, весь пакет останавливается до выполнения, поэтому сервер никогда не остаётся после наполовину выполненной операции.
Защита работает в пределах одного вызова. Она не может связать удаление в одном вызове с чтением в следующем или рассуждать об инструментах, которых не распознаёт. Это ремень безопасности, а не механизм политик: восстановимые операции остаются вашим решением. Ограничения путей и правила кавычек задокументированы в docs/security.md.
Инструменты
18 SSH MCP-инструментов для операций на сервере. Полные параметры и примеры находятся в docs/tools.md.
| Инструмент | Что делает |
|---|---|
ssh_exec | Выполняет одну команду или пакет, с защитой от разрушительных команд и опциональным отсоединением |
ssh_file_read | Читает один или несколько файлов, текст или бинарные данные |
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 | Последние N строк одного или нескольких журналов, с поддержкой глоба; контейнер по имени |
ssh_log_search | Поиск по шаблону в журналах или через журнал контейнера |
ssh_snapshot | Разовый снимок здоровья: сервисы, ресурсы, Docker, сеть, ошибки |
ssh_monitor | Управление транспортом: статистика, перезагрузка, тест, список, закрытие |
ssh_audit_baseline | Система, диск, память, сеть, ssh, сервисы, Docker, брандмауэр, обновления |
ssh_tls_check | Срок действия сертификата, SAN, цепочка и хук обновления для домена |
ssh_disk_breakdown | Куда ушёл диск: du top-N, Docker, journald, кеши |
ssh_service_status | systemctl status плюс хвост journalctl для одного юнита |
Аннотации безопасности MCP-инструментов
Стандартные аннотации MCP сообщают клиентам, какие инструменты доступны только для чтения, разрушительны, идемпотентны или открыты для мира. См. полную таблицу.
Выполнение SSH-команд и управление удалёнными файлами
Команды, чтение и запись файлов, списки каталогов — обычная работа на машине, каждый ответ уже разобран.
Мониторинг длительных SSH-задач
Медленная работа отсоединяется и отслеживается, а не ожидается: каждый взгляд сообщает, как далеко она продвинулась.
Поиск по журналам и проверка здоровья сервера
Журналы файлов и контейнеров, а также разовый снимок машины, с ограничением вывода, чтобы хвост не съедал контекстное окно.
Загрузка и скачивание файлов по SSH
Бинарно-безопасные передачи с проверками целостности. Подробности в docs/transfer.md.
Для бинарных файлов и больших файлов используйте
ssh_upload/ssh_download— фрагменты base64 и heredoc не являются бинарно-безопасными или атомарными.
Аудит Linux-серверов по SSH
Только чтение и пакетная обработка за один往返. Подробности в docs/audit.md.
Режим совместимости с Windows SSH
Windows автоматически использует режим совместимости. Когда мультиплексирование соединений недоступно, сервер переключается на одно соединение на команду. Те же инструменты остаются доступными через SSH с ключами — без отдельной настройки или реализации, специфичной для Windows.
Защита от разрушительных команд описана в Защита от разрушительных команд для ИИ-агентов.
Настройка 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
Одна команда; -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
Одна команда, как и у остальных:
qwen mcp add ssh \
-e SSH_PROFILES_FILE="$HOME/.qwen/ssh-profiles.json" \
npx -y @hypnosis/ssh-mcp-server
Другие MCP-клиенты
Gemini CLI, Hermes, Cline, плагин редактора или ваш собственный агент работают так же. Всё, что им нужно, — это команда для запуска и одна переменная окружения.
Перезапустите ваш MCP-клиент
Перезапустите клиент, затем выполните ssh_monitor({ action: "list" }), чтобы убедиться, что профиль загружен.
Конфигурация SSH MCP server
| Переменная | Что она делает | По умолчанию |
|---|---|---|
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 | Время жизни кэша профилей, мс | 60000 |
SSH_MCP_PROFILES_WATCH | Перезагружать файл профилей при его изменении | true |
Общее соединение намеренно переживает этот процесс: закрытие его при выходе оборвало бы канал, который использует другое окно на той же машине.
Ограничения SSH MCP server
Каждое ограничение подсказывает, как его обойти. Инструмент, который не может что-то сделать, сообщает об этом и
называет ssh_exec, который выполняет команды на машине напрямую — неподдерживаемый драйвер журнала,
утилита, которой нет на машине, движок, с которым этот сервер не говорит. Вам не нужно
знать заранее, где заканчиваются инструменты: отказ сообщает об этом в тот момент, когда это важно.
Три отказа намеренно молчат о оболочке, потому что там это неверный ответ:
путь, запрещённый вашим профилем (обход собственного правила — не решение),
некорректный вызов (исправление — в самом вызове) и отказ от самого ssh_exec.
- Отмена: отменённый вызов теперь также останавливает команду на сервере, отправляясь как второй вызов по тому же соединению. Если на сервере нет
/proc, команда находится черезpsвместо этого. FreeBSD не проверялся: корректное поведение там не гарантируется. Передача файлов иssh_snapshotвообще не поддерживают отмену. - Атомарные записи: BSD и macOS не могут предварительно проверять переименования между файловыми системами.
Дорожная карта SSH MCP server
-
Полный прогон тестов на macOS SSH-хостах
-
Сквозная проверка совместимости на Windows
-
Аудиты нескольких хостов — сравнение состояния нескольких SSH-профилей одним вызовом
-
Импорт профилей из существующего
~/.ssh/config -
Возобновляемые передачи для больших файлов и нестабильных соединений
-
Хронология удалённых операций — команды, передачи и решения защиты в едином журнале аудита
-
Готовые плейбуки для устранения неполадок SSH
-
Журналы контейнеров без перехода в оболочку— ГОТОВО:ssh_log_tailиssh_log_searchпринимают имя контейнера, спрашивают docker, куда он пишет, и читают этот файл тем же механизмом, что и любой другой журнал -
Отказ, оставляющий вас в тупике— ГОТОВО: каждое ограничение теперь называетssh_execкак путь вперёд, так что достижение границы инструмента стоит одного предложения вместо игры в угадайку -
Ответы, доходящие до модели— ГОТОВО: вывод команд, совпавшие строки журналов, имена машин и разделы снимков передаются в полях, а не только в тексте -
Меньше схем MCP-инструментов— ГОТОВО: список инструментов стал легче на 10%, а отсоединённая задача теперь показывает последние записанные строки вместо слепого опроса -
Долгая работа под root— ГОТОВО: отсоединённая задача выполняется сsudoи отслеживается как root, а профиль только с ключом отвечает наsudoсвоим собственнымsudoPassword
Разработка и тестирование SSH MCP server
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, один coreutils — потому что они тихо расходятся во мнениях, а макет соглашается с тем, кто его написал. См. docs/architecture.md для описания структуры.
Нравится SSH MCP Server? ⭐
Если вам нравится инструмент, поставьте ему звезду на GitHub — это поможет большему числу людей узнать о проекте.
Внесите вклад в SSH MCP server
Вопросы и запросы на включение изменений приветствуются на github.com/hypnosis/ssh-mcp-server.
Лицензия
MIT — см. LICENSE.