14 KiB
forge-tools-ssh
MCP-сервер, который даёт AI-ассистенту аккуратный доступ к серверам по SSH.
forge-tools-ssh — это небольшая отдельная программа, которая превращает
обычный SSH в набор инструментов, понятных AI-ассистенту. Ассистент вызывает
инструмент по имени — run, disk_usage, docker_ps, journal_read — и
получает готовый результат, не выдумывая каждый раз синтаксис ssh, grep,
docker и df.
Типичные задачи: посмотреть состояние сервера, найти причину падения сервиса, прочитать лог, проверить занятое место, разобраться с контейнерами или БД, а при явном разрешении — отредактировать конфиг и перезапустить службу.
Коротко про MCP
MCP — стандарт подключения AI-ассистентов к инструментам. Сервер — это программа, которая читает запросы ассистента через стандартные ввод/вывод и возвращает результат. Один и тот же сервер работает с любым MCP-клиентом.
Модуль намеренно не содержит адресов, логинов и ключей: всё, к чему можно подключиться, объявляет оператор. Ассистент лишь выбирает из разрешённого.
Возможности
- 45 инструментов — от запуска команды до разбора SIP-трафика.
- Подключение через джамп-хост — доступ к закрытому контуру через bastion.
- PAM-шлюзы — двухстадийная аутентификация через корпоративный шлюз.
- Проверка синтаксиса JSON/YAML/TOML/XML/INI/ENV/Dockerfile до записи файла — на самом сервере MCP, без зависимостей на удалённой машине.
- Политика подключений (
ssh.json): именованные профили и allowlist хостов, которые оператор задаёт декларативно. Ошибка конфига — отказ, а не тихий обход. - Live-reload: правки
ssh.jsonподхватываются без перезапуска. - Устойчивость: перезапуск связи при обрыве, таймауты, лимиты на размер и длину вывода, корректная отмена задач.
Архитектура
flowchart LR
A["AI-ассистент<br/>(MCP-клиент)"] -- "stdio · JSON-RPC" --> B["forge-tools-ssh"]
B -- "SSH / SFTP" --> C["Сервер"]
B -- "SSH → bastion" --> D["Сервер в закрытом контуре"]
B -- "SSH → PAM-шлюз" --> E["Целевой сервер"]
style B fill:#6E56CF,color:#fff
Сервер — stdio-процесс: он не открывает портов и не слушает сеть. Общение с AI-клиентом идёт через потоки ввода/вывода, а наружу он ходит только по SSH.
Быстрый старт
Сборка
export GOPRIVATE=git.totmin.ru
go build -trimpath -o forge-tools-ssh .
Проверить, что бинарник живой:
./forge-tools-ssh --health # → ok
Подключение к AI-клиенту
Сервер запускается клиентом как дочерний процесс. Пример конфигурации:
{
"mcp": {
"servers": {
"ssh": {
"command": "/opt/forge-tools/ssh/forge-tools-ssh",
"env": {
"FORGE_TENANT_CONFIG": "/etc/forge/agents/my-agent",
"SSH_MCP_KEY_PATH": "/etc/forge/agents/my-agent/ssh/id_ed25519"
}
}
}
}
}
Дальше достаточно попросить ассистента, например: «посмотри, почему упал nginx на
prod-web, и покажи последние 100 строк журнала».
Инструменты (45)
Ядро — 5
| Инструмент | Назначение |
|---|---|
connect |
Установить SSH-соединение (по профилю или ad-hoc в рамках allowlist) |
disconnect |
Закрыть соединение (или все сразу) |
run |
Выполнить команду на удалённом хосте |
identity |
Показать публичный ключ сервера для authorized_keys |
info |
ОС, ядро, hostname, архитектура |
Файлы — 6
| Инструмент | Назначение |
|---|---|
read |
Прочитать файл (лимит 10 МБ) |
write |
Записать файл с валидацией синтаксиса перед записью |
edit |
Правка файла: replace / regex / insert / append / prepend / delete / replace_line |
validate |
Проверить синтаксис файла на стороне MCP-сервера |
list_dir |
Список каталога |
sync |
Потоковая передача файла между двумя хостами |
Мониторинг — 7
| Инструмент | Назначение |
|---|---|
usage |
Загрузка, память, диски |
ps |
Топ процессов по CPU или памяти |
logs |
Хвост лог-файла (опционально с фильтром) |
journal_read |
journalctl / syslog |
dmesg_read |
Кольцевой буфер ядра |
diagnose_system |
Экспресс-диагностика: load, OOM, диски, упавшие службы |
list_services |
Список служб (systemd / OpenRC и др.) |
Диск — 2
| Инструмент | Назначение |
|---|---|
disk_usage |
Место на томе, содержащем указанный путь |
disk_usage_all |
Все точки монтирования, разделы ≥ 80% помечены |
Сеть и поиск — 4
| Инструмент | Назначение |
|---|---|
net_stat |
Слушающие порты (ss / netstat) |
search_files |
Поиск файлов (find) |
search_text |
Поиск текста (grep) |
package_manage |
Пакеты: apt / apk / dnf / yum |
Docker — 8
| Инструмент | Назначение |
|---|---|
docker_ps |
Список контейнеров |
docker_logs |
Логи контейнера |
docker_op |
start / stop / restart |
docker_ip |
IP-адреса контейнера |
docker_find_by_ip |
Найти контейнер по IP |
docker_networks |
Сети Docker |
docker_cp_from |
Копировать файл из контейнера на хост |
docker_cp_to |
Копировать файл с хоста в контейнер |
Базы данных — 3
| Инструмент | Назначение |
|---|---|
db_query |
SQL/CQL/Mongo-запрос внутри контейнера (postgres, mysql, scylladb, cassandra, mongodb) |
db_schema |
Схема: таблицы / коллекции |
list_db_containers |
Найти контейнеры, похожие на БД |
VoIP — 10
| Инструмент | Назначение |
|---|---|
voip_discover_containers |
Найти VoIP-контейнеры по имени/образу |
voip_sip_capture |
Захват SIP-сигналинга в PCAP (sngrep) |
voip_call_flow |
Разбор SIP call flow из PCAP |
voip_registrations |
REGISTER-диалоги и их исход |
voip_call_stats |
Агрегированная статистика вызовов |
voip_extract_sdp |
Кодеки и RTP-порты из SDP |
voip_packet_check |
Быстрая проверка наличия SIP-пакетов |
voip_network_capture |
Захват SIP через tcpdump |
voip_rtp_capture |
Захват RTP для проверки медиа-потока |
voip_network_diagnostics |
ping / traceroute / проверка TCP-портов |
Конфигурация
Доступы задаются декларативно в <FORGE_TENANT_CONFIG>/ssh.json. Оператор
описывает профили (что и как подключать) и allowlist хостов; ассистент работает
только с этими именами. Файл перечитывается на лету при изменении.
{
// Ключ по умолчанию, если профиль или вызов не задали свой.
"default_key_path": "${SSH_KEY_PATH}",
// Glob-паттерны разрешённых хостов. Наличие списка включает проверку,
// пустой список — ad-hoc без ограничений. Держите список непустым.
"allowed_hosts": ["10.0.*", "*.internal.example.com"],
"profiles": [
{
"alias": "prod-web",
"host": "10.0.1.10",
"username": "deploy",
"port": 22,
"key": "${PROD_SSH_KEY}"
},
{
"alias": "bastion",
"host": "bastion.example.com",
"username": "deploy"
},
{
"alias": "db-via-pam",
"host": "pam-gateway.example.com",
"username": "operator",
"target": "10.0.2.20",
"target_password": "${DB_TARGET_PASSWORD}"
}
]
}
Готовый шаблон — ssh.json.example.
| Поле профиля | Смысл |
|---|---|
alias |
Имя профиля, на которое ссылается ассистент |
host |
Хост или IP |
username |
Пользователь SSH |
port |
Порт (по умолчанию 22) |
key |
Приватный ключ (иначе default_key_path, иначе системный ключ) |
via |
Алиас jump-хоста, через который подключаться |
target |
Хост за PAM-шлюзом (SafeInspect): шлюзу отправляется username@target |
target_password |
Пароль целевого аккаунта для второй стадии аутентификации PAM |
Переменные окружения
| Переменная | Назначение |
|---|---|
FORGE_TENANT_CONFIG |
Каталог с ssh.json. Если не задан — режим без политики. |
SSH_MCP_KEY_PATH |
Путь к системному приватному ключу Ed25519. |
Значения в конфиге поддерживают подстановку ${VAR} из окружения — реальные
адреса и ключи в репозиторий не попадают.
Безопасность
- Проваливается закрытым (fail-closed). Если задан
allowed_hosts, любой хост вне списка получает отказ. Битыйssh.json— тоже отказ. - Креды задаёт оператор, а не модель. Ассистент выбирает профиль по имени и не может придумать произвольный хост, ключ или адрес.
- Валидация вместо порчи.
writeпроверяет синтаксис перед записью,edit— после правки, и предупреждает, если файл сломан. - Экранирование. Все пользовательские строки, попадающие в shell-команды, проходят кавычивание и санитизацию.
- Лимиты. Таймауты команд, ограничение размера файла и длины вывода, отдельный лимит на чтение.
- Ключи. Каталог ключей создаётся с правами
0700, приватный ключ —0600. - Осторожно: host key удалённого сервера не проверяется
(
InsecureIgnoreHostKey). Это осознанный компромисс для работы с динамической инфраструктурой — за целостность канала отвечает сеть.
Перед публикацией убедитесь, что реальные ключи и конфиги с секретами не попали
в репозиторий (см. .gitignore).
Разработка
go build ./... && go vet ./...
go test -race ./...
gofmt -l . # должно быть пусто
Требования: Go 1.27+ и доступ к приватному Go-модулю
forge-toolkit
(export GOPRIVATE=git.totmin.ru).
Лицензия
Apache-2.0 — см. LICENSE.