SSH MCP Server
oficialExecute comandos, mova arquivos, pesquise logs e audite máquinas via SSH a partir do seu agente.
O que você pode fazer com SSH MCP?
- Executar comandos com proteções de segurança — Peça ao seu assistente para executar comandos únicos ou em lote via
ssh_exec, com proteção contra comandos destrutivos que bloqueia operações irreversíveis antes que cheguem ao servidor. - Ler, escrever e listar arquivos remotos — Use
ssh_file_read,ssh_file_writeessh_file_listpara inspecionar ou modificar arquivos, com gravações atômicas e verificação opcional de SHA-256. - Pesquisar logs e verificar a saúde do servidor — Consulte
ssh_log_searchoussh_log_tailem arquivos e contêineres, ou obtenha um instantâneo estruturado da saúde comssh_snapshotessh_audit_baseline. - Transferir arquivos com verificações de integridade — Envie ou baixe arquivos e diretórios via
ssh_uploadessh_download, com fallback automático parascplegado em dispositivos mais antigos. - Gerenciar jobs em segundo plano de longa duração — Desanexe operações lentas com
ssh_exece acompanhe-as viassh_job_status,ssh_job_outputessh_job_kill, sobrevivendo a desconexões.
Documentação
SSH MCP Server — Ferramentas de servidor remoto para agentes de IA
|
|
Um servidor MCP SSH — uma multiferramenta que economiza tempo e tokens para você e seu agente de IA em depuração, desenvolvimento e manutenção de servidores. Execute comandos, mova arquivos, leia logs e audite máquinas via SSH — uma VPS na nuvem, um servidor físico, ou o roteador BusyBox escondido no seu armário. |
Ele usa o cliente OpenSSH já presente na sua máquina: suas chaves, seu ~/.ssh/config, seus hosts de salto, seu encaminhamento de agente. Nada embutido, nada para compilar, sem bindings nativos.
Funciona com Claude Code, Codex CLI, Cline, opencode, Gemini CLI, Qwen Code, Hermes e outros clientes MCP.
Instalar · Ferramentas · Configuração · Segurança · Roadmap · Documentação · Changelog
Instale em 30 segundos
Nenhuma instalação global é necessária. npx baixa o pacote no primeiro uso:
npx -y @hypnosis/ssh-mcp-server
Adicione-o ao seu cliente MCP — Claude Code, por exemplo — para cada projeto:
claude mcp add ssh -s user \
-e SSH_PROFILES_FILE="$HOME/.claude/ssh-profiles.json" \
-- npx -y @hypnosis/ssh-mcp-server
Ou escreva manualmente — o mesmo servidor no formato de configuração que a maioria dos clientes compartilha:
{
"mcpServers": {
"ssh": {
"command": "npx",
"args": ["-y", "@hypnosis/ssh-mcp-server"],
"env": {
"SSH_PROFILES_FILE": "~/.claude/ssh-profiles.json"
}
}
}
}
Depois crie ~/.claude/ssh-profiles.json com pelo menos uma máquina:
{
"profiles": {
"production": {
"host": "server.example.com",
"username": "admin",
"privateKeyPath": "~/.ssh/your_private_key"
}
}
}
Isso é suficiente para conectar.
Codex, opencode, Qwen Code e outros clientes são abordados em Configurar o servidor MCP SSH.
Instalar como plugin
Alguns clientes — Claude Code, por exemplo — podem receber tudo como um plugin:
/plugin marketplace add hypnosis/ssh-mcp-server
/plugin install ssh-mcp-server@ssh-mcp-server
O plugin lê ~/.claude/ssh-profiles.json a menos que SSH_PROFILES_FILE diga o contrário, então
crie esse arquivo primeiro e o servidor sobe com suas máquinas já carregadas.
Requisitos
Node.js 18+ e um cliente ssh de sistema no PATH. No Windows, use um perfil baseado em chave;
perfis com senha e frase secreta não estão disponíveis no momento.
Prefere uma versão fixada, trabalho offline ou uma verificação de registro a menos por execução:
npm install -g @hypnosis/ssh-mcp-server, depois use ssh-mcp-server como comando em vez
de npx.
Para quem é isso
- DevOps e SREs que querem auditorias mais rápidas, verificações de incidentes e trabalho rotineiro de servidor.
- Codificadores por vibração e construtores independentes que publicam com um assistente de IA e executam o que constroem em seus próprios servidores.
- Administradores de sistemas e engenheiros de plataforma que querem ferramentas estruturadas em vez de um shell bruto sem restrições.
- Desenvolvedores e pequenas equipes que administram sua própria VPS sem uma equipe de operações dedicada.
- Proprietários de homelab, NAS e roteadores cujo hardware útil sobreviveu aos seus protocolos modernos.
Por que um servidor MCP SSH em vez de um shell bruto
Menos tokens, custos de IA mais baixos
Um shell bruto dá a um agente de IA um fluxo torrencial: comandos repetidos, tabelas ASCII e despejos de log. Isso queima tokens transformando esse ruído em uma imagem do servidor — seu dinheiro.
Depuração de servidor mais rápida
Ferramentas projetadas para propósito específico agrupam verificações rotineiras, limitam saídas ruidosas e retornam a parte que importa. O agente gasta menos tempo traduzindo saída de terminal e chega à correção mais cedo.
Menos suposições, menos erros de IA
Respostas estruturadas dizem o que foi encontrado, o que não pôde ser medido e o que foi truncado. Isso deixa menos espaço para o agente preencher lacunas com uma alucinação — e dá a você menos correções ruins, deploys mais calmos e código mais confiável.
Compatibilidade SSH: servidores modernos, equipamentos legados e Windows
Use sua configuração OpenSSH existente
Nenhuma implementação SSH embutida, nenhum binding nativo, nenhuma recompilação por plataforma. Comandos usam o
cliente ssh do sistema, então suas chaves, seu ~/.ssh/config, seus hosts de salto e seu encaminhamento
de agente continuam funcionando exatamente como em um terminal. Quando suportado, uma única conexão
multiplexada compartilhada por destino significa que você autentica uma vez, não uma vez por comando.
Suporte SSH para servidores legados, roteadores e dispositivos NAS
Envie um arquivo para um roteador com um scp moderno e você recebe isto:
scp app.conf router:/etc/
# scp: subsystem request failed on channel 0
Nada está quebrado — um scp atual fala o novo protocolo, e o roteador não o conhece.
Em um terminal, você agora vai ler um tópico de fórum e volta com uma flag extra. Aqui você não faz
nada: a transferência é tentada, a recusa é reconhecida, o protocolo antigo é usado em vez disso,
e essa máquina é lembrada para que o próximo arquivo vá direto para lá.
Alternativas para clientes SSH mais antigos e ferramentas ausentes
Equipamentos antigos recebem uma alternativa, não um beco sem saída. Quando um recurso moderno está ausente, o servidor segue o caminho mais antigo onde puder:
| Sua máquina | O que você recebe |
|---|---|
| Um roteador ou NAS pequeno demais para transferência de arquivos moderna | O arquivo ainda chega — o protocolo antigo é usado automaticamente |
| Um servidor de dez anos atrás | O fluxo de trabalho ainda funciona; apenas abre uma conexão nova por comando em vez de reutilizar uma |
| Uma imagem enxuta sem como gerar hash de arquivo | O upload diz "não foi possível verificar" em vez de afirmar uma correspondência que ninguém verificou |
| Uma máquina onde uma ferramenta simplesmente não está instalada | A resposta diz "não medido" — nunca um zero que pareça "nada aqui" |
Construído para o Model Context Protocol
Construído no SDK MCP oficial, TypeScript em todo o código, mais de 2500 testes unitários além de uma suíte ao vivo que roda contra contêineres reais em vez de mocks.
SSH bruto vs um servidor MCP SSH: o mesmo trabalho, de ambas as formas
Verificação de saúde do servidor SSH
Situação: Um deploy acabou de sair. O servidor parece lento, e você não sabe se disco, memória, serviços, contêineres ou erros são os culpados.
Pergunta: "Esta máquina está saudável?"
SSH bruto
$ 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'.
Isso ainda é um resultado resumido. Uma verificação completa precisa de mais comandos para CPU, estados
de serviço, contagens de contêineres e erros recentes, cada um com seu próprio formato de saída. Pior, uma máquina
sem ss pode parecer que tem zero listeners quando a verificação de porta nunca rodou.
Resultado MCP estruturado
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": []
}
O que o agente ganha
| SSH bruto | MCP estruturado | Seu ganho |
|---|---|---|
| Vários comandos e tabelas ASCII | Campos nomeados em um resultado | Uma chamada, campos nomeados e menos idas e voltas |
| Uma ferramenta ausente pode parecer saída vazia | unavailable nomeia o que não foi medido | Menos suposições e menos correções ruins |
| Você vasculha discos, serviços e erros | Os sinais do problema já estão na superfície | Depuração mais rápida |
Um resultado completo de ssh_audit_baseline pode ser mais longo que um punhado de saídas de comandos brutos —
cerca de 1.077 tokens contra 765 em nossa medição de laboratório. A economia vem do fluxo de trabalho
completo, não de tornar uma resposta mais curta.
Em uma sessão real de solução de problemas, ferramentas de propósito específico reduziram 49 chamadas de comando separadas para 4 chamadas MCP. Cada chamada adicional inicia outra rodada do modelo com a conversa acumulada. O cache de prompt pode reduzir o custo de entrada repetida, mas novos comandos e suas saídas ainda consomem contexto. Menos idas e voltas significam menos tokens na sessão, menos análise repetida e um caminho mais rápido para a resposta.
Precisa do quadro completo em vez do pulso? ssh_audit_baseline agrupa sistema, disco,
memória, portas, sshd, unidades com falha, Docker, firewall e atualizações. Os resultados chegam como
CRÍTICO / AVISO / OK; seções não medidas são nomeadas em vez de silenciosamente lidas como zero.
Busca de log em servidor Linux
Situação: A API está expirando, mas a mesma mensagem pode estar no nginx, syslog, journald ou em um log de aplicação que você não consegue ler com seu usuário normal.
Pergunta: "De onde veio esse erro?"
SSH bruto
$ 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
O terceiro comando parece limpo, mas 2>/dev/null também escondeu um erro de permissão. "Nada
correspondeu" e "nada foi lido" agora parecem idênticos. Um log movimentado também pode retornar milhares de
linhas e empurrar o resto do incidente para fora do contexto do agente.
Resultado MCP estruturado
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
}
O que o agente ganha
| SSH bruto | MCP estruturado | Seu ganho |
|---|---|---|
| Quatro buscas e quatro saídas | Uma busca em arquivos e globs | Menos tokens e idas e voltas |
| Erros de permissão podem desaparecer | files_unreadable nomeia cada caminho perdido | Sem conclusão falsa de "logs limpos" |
| A saída pode crescer sem um teto útil | limited e truncated expõem cada corte | Decisões mais seguras a partir de resultados parciais |
since usa o relógio do servidor, namesOnly: true retorna apenas caminhos correspondentes, e
ssh_log_tail lê as últimas N linhas de vários logs em uma chamada.
Edições seguras de configuração remota
Situação: Você precisa substituir uma configuração do nginx em um servidor ativo. Uma conexão perdida, modo errado ou cópia não verificada pode deixar o serviço com um arquivo quebrado.
Pergunta: "Posso substituir esta configuração sem deixar um arquivo parcial?"
SSH bruto
$ 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
Código de saída zero diz que o shell terminou. Não prova quais bytes chegaram, e >
truncou o arquivo antigo antes do primeiro byte do novo chegar. Se a conexão cair
no meio da escrita, o serviço fica com uma configuração parcial.
Resultado MCP estruturado
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 }]
}
O que o agente ganha
| SSH bruto | MCP estruturado | Seu ganho |
|---|---|---|
| O destino é truncado antes da cópia completar | Um arquivo temporário completo o substitui com um rename | Sem configuração meio escrita |
| Apenas código de saída | Bytes e resultado de verificação são nomeados | Você sabe o que realmente chegou |
| Permissões vivem dentro do texto do shell | sudo, mode e verify são campos por arquivo | Propriedade previsível e menos erros de aspas |
verified tem três resultados honestos: verified, unavailable quando o servidor não tem ferramenta de hash,
e skipped quando a verificação não foi solicitada. Para leituras, ssh_file_read aceita uma
lista de caminhos; ssh_file_list lida com globs, recursão, tamanhos e modos.
Execute comandos SSH em lote com sudo
Situação: Um deploy está pronto, mas a sintaxe do nginx, o estado do serviço e erros recentes devem todos ser verificados antes que o tráfego mude. Uma verificação com falha não deve desaparecer dentro de um despejo combinado.
Pergunta: "Todas as verificações de pré-flight passaram?"
SSH bruto
$ 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
Três conexões retornam três saídas não relacionadas. Se os comandos são unidos com ;, o
shell relata apenas o último código de saída; se são unidos com &&, verificações posteriores desaparecem
após a primeira falha.
Resultado MCP estruturado
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
}
O que o agente ganha
| SSH bruto | MCP estruturado | Seu ganho |
|---|---|---|
| Três chamadas e saídas não relacionadas | Uma lista de comandos ordenada | Menos idas e voltas |
| Um shell combinado pode esconder status intermediário | Cada comando mantém seu próprio exit_code | Nenhuma verificação com falha perdida |
sudo e aspas são repetidos no texto do comando | sudo se aplica ao lote inteiro | Menos erros de aspas |
A proteção de comandos destrutivos verifica a lista completa antes do primeiro comando rodar. Se uma
entrada for recusada, todas as outras são marcadas como não executadas e nada é enviado ao servidor.
Cada comando carrega seu próprio stdout e stderr. Um comando que foi executado e não imprimiu nada
tem uma string vazia; um comando que nunca foi executado não tem esse campo de forma alguma, então os dois não podem ser
confundidos. Saída acima de 128 KB por comando mantém ambas as extremidades — o cabeçalho para tabelas, o final para
logs — com uma costura no meio nomeando a quantidade, e clipped_bytes diz quanto foi cortado.
O corte acontece em limites de bytes e recua até a borda de um caractere, então uma
resposta truncada nunca carrega uma marca de substituição.
sudo alcança o servidor sem um terminal: a resposta do perfil é entregue ao sudo na
entrada padrão. Qual segredo é esse vem de sudoPassword quando o perfil nomeia um e
de password caso contrário — um perfil que faz login por chave não tem senha de login alguma, e
onde uma máquina mantém os dois separados, o de login é a resposta errada. Quando não há nada
com que responder, a resposta diz isso e nomeia as saídas, em vez de deixar o próprio conselho do sudo
sobre -S e auxiliares askpass. Um comando que lê sua própria entrada padrão nunca recebe a
senha, que de outra forma acabaria misturada nos dados.
Executar trabalhos SSH de longa duração
Situação: Um backup ou migração vai rodar por mais tempo que a sessão do agente. A conexão pode fechar, mas você ainda precisa do estado, da saída e do código de saída depois.
Pergunta: "Este trabalho vai sobreviver à conversa?"
SSH bruto
$ ssh admin@server.example.com 'pg_dump app | gzip > /srv/backups/app.sql.gz'
client_loop: send disconnect: Broken pipe
O terminal se foi. Agora você precisa reconectar, encontrar o processo, inspecionar o arquivo de destino e adivinhar se o backup terminou ou parou no meio do caminho.
Resultado MCP estruturado
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"
}
O que o agente ganha
| SSH bruto | MCP estruturado | Seu ganho |
|---|---|---|
| O trabalho está preso a uma sessão SSH | O trabalho remoto tem um id persistente | Desconexões e reinícios seguros |
| Reconectar significa procurar processos e arquivos | Status e código de saída têm estados nomeados | Sem adivinhação se terminou |
| Ler a saída novamente repete texto antigo | A saída continua de um deslocamento de bytes | Menor uso de tokens em trabalhos longos |
O estado do trabalho vive no disco remoto, não na memória deste servidor. ssh_job_status distingue
running, finished e lost; ssh_job_output continua do último deslocamento de bytes; e
ssh_job_kill sinaliza o grupo de processos inteiro em vez de apenas seu shell.
Transferir arquivos para roteadores e NAS legados
Situação: Um cliente OpenSSH atual tenta SFTP, mas o roteador ou NAS só entende o protocolo scp clássico. O arquivo ainda deve chegar intacto e substituir seu destino com segurança.
Pergunta: "Este dispositivo antigo ainda consegue receber um arquivo verificado?"
SSH bruto
$ scp app.conf operator@router:/etc/app.conf
subsystem request failed on channel 0
scp: Connection closed
O próximo passo usual é lembrar do sinalizador legado, tentar a cópia novamente e então executar um comando de hash separado — se o dispositivo tiver uma ferramenta de hash.
Resultado MCP estruturado
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
}]
}
O que o agente ganha
| SSH bruto | MCP estruturado | Seu ganho |
|---|---|---|
| O modo SFTP moderno para no primeiro erro | O fallback scp clássico é automático e lembrado | Equipamento antigo ainda funciona |
| Uma cópia bem-sucedida não prova integridade | A verificação SHA-256 tem um resultado nomeado | Corrupção não é confundida com sucesso |
| Substituição direta pode deixar um destino parcial | Um arquivo temporário é movido para o lugar após a transferência | O arquivo de trabalho sobrevive a interrupções |
Se o dispositivo não tiver nem sha256sum nem openssl, o resultado diz unavailable e nomeia
o motivo em vez de relatar uma correspondência falsa. Diretórios inteiros usam recursive: true e
verificam seus hashes em um único lote.
Proteção contra comandos destrutivos para agentes de IA
A proteção roda localmente, antes de um comando chegar ao SSH. Ela separa operações que podem ser recuperadas daquelas que destroem o contêiner que contém os dados, e verifica a ordem dos comandos dentro de cadeias e lotes.
Parar uma cadeia destrutiva antes que ela comece
Uma sequência segura de backup-e-substituição:
cp -r /srv/app /srv/app.bak && mv /srv/app /srv/app-old && rm -rf /srv/app
As mesmas operações na ordem errada:
rm -rf /srv/app && cp -r /srv/app /srv/app.bak && mv /srv/app /srv/app-old
# REFUSED before the first command runs
O shell excluiria o diretório e só então descobriria que a fonte do backup se foi.
A proteção vê que passos posteriores leem um destino já destruído por um passo anterior, então a
chamada inteira fica na sua máquina. A mesma verificação pega
dropdb app && pg_dump app > backup.sql.
Recusar perda irreversível, avisar sobre mudanças recuperáveis
| Recusado — o próprio contêiner | Apenas avisado — seu conteúdo |
|---|---|
DROP DATABASE, dropdb | DROP TABLE, TRUNCATE, DELETE FROM |
docker volume rm, docker compose down -v | docker rm -f <name> |
crontab -r | editar um trabalho |
mkfs, wipefs -a, lvremove, zfs destroy | chmod 777 |
reboot, shutdown, halt | git reset --hard |
docker compose down -v é recusado porque -v remove volumes Docker nomeados, incluindo um volume
de banco de dados. Sem -v, parar os serviços não é tratado como a mesma ação irreversível.
Exclusão recursiva da raiz do sistema de arquivos, de um diretório home ou de árvores do sistema como /etc,
/var e /usr também é recusada, inclusive quando um link simbólico leva até lá. Um destino não resolvido
como rm -rf "$DIR"/* também é recusado: "não foi possível verificar" não é tratado como "seguro".
Nomeie o que você está parando
Um comando que encontra seu destino em vez de nomeá-lo não é enviado. O servidor o expande e responde com o que está por trás do destino:
docker kill $(docker ps -q --filter ancestor=web)
# BLOCKED — would stop:
# edge — web:latest, Up 34 days, 0.0.0.0:8443->8443/tcp
Para um processo, a resposta adiciona os sinais de que está em uso: há quanto tempo está rodando,
em quais portas aceita conexões, quantas conexões está carregando. Destinos nomeados
não custam nada extra e passam em silêncio — docker kill web-1, kill 4871,
systemctl stop app.
Para prosseguir, nomeie o que está sendo parado. Os nomes são verificados contra o que o comando realmente alcança, então uma máscara que se desviou para outra coisa é recusada em vez de confirmada:
docker kill $(docker ps -q --filter ancestor=web) # CONFIRMED-KILL: edge
Um padrão sobre linhas de comando é um caso à parte. Ele corresponde ao próprio comando que o carrega, então o shell que o executa é sinalizado antes do destino e a resposta se interrompe no meio. Tal ataque não é confirmado, mas reescrito — por número, ou com um caractere escrito como uma classe para que o padrão pare de corresponder a si mesmo:
pkill -f relay
# BLOCKED — two ways through:
# kill 4871
# pkill -f '[r]elay' # CONFIRMED-KILL: 4871
Três resultados permanecem separados: destinos encontrados, a expansão não alcançou nada e nada com que perguntar — sem mecanismo na máquina, uma resposta truncada, uma conexão que falhou. Os dois últimos são recusas também: não saber não é motivo para prosseguir.
Confirmar um comando destrutivo intencional
Nada é proibido permanentemente. Adicione # CONFIRMED-DESTRUCTIVE a um comando revisado e ele
é permitido. Quando a proteção recusa uma entrada em um lote, o lote completo para
antes da execução, então o servidor nunca fica após uma operação meio executada.
A proteção funciona dentro de uma única chamada. Ela não pode conectar uma exclusão em uma invocação com uma leitura na próxima, ou raciocinar sobre ferramentas que não reconhece. É um cinto de segurança, não um mecanismo de política: operações recuperáveis continuam sendo sua decisão. Restrições de caminho e regras de citação estão documentadas em docs/security.md.
Ferramentas
18 ferramentas MCP SSH para operações de servidor. Parâmetros completos e exemplos estão em docs/tools.md.
| Ferramenta | O que faz |
|---|---|
ssh_exec | Executa um comando ou um lote, com a proteção de comando destrutivo e desconexão opcional |
ssh_file_read | Lê um ou vários arquivos, texto ou binário |
ssh_file_write | Escreve arquivos com renomeação atômica e verificação SHA-256 opcional |
ssh_file_list | Lista um diretório, com glob e recursão opcionais |
ssh_upload | Envia um arquivo ou diretório via SSH, seguro para binários com verificações de integridade; um diretório substitui o destino ou mescla nele |
ssh_download | Baixa um arquivo ou diretório via SSH, seguro para binários com verificações de integridade |
ssh_job_status | Estado de um trabalho em segundo plano: rodando, concluído ou perdido |
ssh_job_output | Lê a saída acumulada a partir de um deslocamento de bytes |
ssh_job_list | Lista trabalhos, removendo os concluídos além do TTL |
ssh_job_kill | Sinaliza o grupo de processos inteiro de um trabalho |
ssh_log_tail | Últimas N linhas de um ou vários logs, com suporte a glob; um contêiner por nome |
ssh_log_search | Busca por padrão em logs, ou através do log de um contêiner |
ssh_snapshot | Instantâneo de saúde único: serviços, recursos, Docker, rede, erros |
ssh_monitor | Controle de transporte: estatísticas, recarregar, testar, listar, fechar |
ssh_audit_baseline | Sistema, disco, memória, rede, ssh, serviços, Docker, firewall, atualizações |
ssh_tls_check | Expiração de certificado, SAN, cadeia e gancho de renovação para um domínio |
ssh_disk_breakdown | Para onde o disco foi: du top-N, Docker, journald, caches |
ssh_service_status | systemctl status mais um final journalctl para uma unidade |
Anotações de segurança de ferramentas MCP
Anotações MCP padrão informam aos clientes quais ferramentas são somente leitura, destrutivas, idempotentes ou de mundo aberto. Veja a tabela completa.
Executar comandos SSH e gerenciar arquivos remotos
Comandos, leituras e escritas de arquivos, listagens de diretórios — o trabalho comum em uma máquina, cada resposta já analisada.
Monitorar trabalhos SSH de longa duração
Trabalhos lentos são desconectados e acompanhados em vez de aguardados: cada olhada diz até onde chegou.
Buscar logs e verificar a saúde do servidor
Logs de arquivos e contêineres, e uma imagem única da máquina, com saída limitada para que um final não consuma a janela de contexto.
Enviar e baixar arquivos via SSH
Transferências seguras para binários com verificações de integridade. Detalhes em docs/transfer.md.
Para binários e arquivos grandes use
ssh_upload/ssh_download— blocos base64 e heredocs não são seguros para binários nem atômicos.
Auditar servidores Linux via SSH
Somente leitura e em lote em uma única ida e volta. Detalhes em docs/audit.md.
Modo de compatibilidade SSH Windows
O Windows usa o modo de compatibilidade automaticamente. Quando a multiplexação de conexão está indisponível, o servidor alterna para uma conexão por comando. As mesmas ferramentas permanecem disponíveis via SSH baseado em chave — sem configuração separada ou implementação específica para Windows.
A proteção de comando destrutivo é coberta em Proteção contra comandos destrutivos para agentes de IA.
Configurar o servidor SSH MCP
Execute o pacote de Instalar em 30 segundos primeiro, depois crie um arquivo de perfil.
Criar perfis de conexão SSH
Coloque-o onde quiser — próximo à configuração do seu próprio agente é a escolha usual. Os exemplos abaixo usam ~/.claude/ssh-profiles.json; para outros agentes troque o diretório (~/.codex/, ~/.qwen/, ~/.config/opencode/):
{
"profiles": {
"production": {
"host": "server.example.com",
"username": "admin",
"port": 22,
"privateKeyPath": "~/.ssh/your_private_key"
}
}
}
Escolha um perfil SSH explicitamente
Não há perfil para o qual o servidor recaia: cada um é uma máquina diferente, e um comando enviado para a máquina errada não é algo que uma mensagem de erro possa desfazer depois. Pergunte sem um nome e a resposta lista os nomes para escolher:
ssh_exec({ command: "uptime" })
→ No profile specified. Name one explicitly: production
Um perfil que o servidor não pode usar para SSH — sem host, sem username, ou mode: "local" — é ignorado sem reclamação, e campos que ele não reconhece são deixados de lado, então o arquivo pode ser compartilhado com outras ferramentas. Um perfil com um campo quebrado é um caso diferente: ele é nomeado junto com o campo e o valor, e seus vizinhos saudáveis continuam funcionando.
Cada perfil opcionalmente aceita um bloco pathSecurity que permite ou bloqueia os caminhos que as ferramentas de arquivo podem tocar — veja docs/security.md.
Um perfil que faz login por chave mas precisa de sudo no lado remoto aceita um sudoPassword — o segredo sudo com o qual se responde, que em muitas máquinas não é a senha de login. Mantenha-o no arquivo de segredos em vez de aqui.
Manter senhas e frases secretas SSH fora dos perfis
Preferir chaves. Se uma senha ou frase secreta de chave criptografada for inevitável, mantenha-a em um arquivo de segredos separado, nunca no próprio perfil:
{
"secretsFile": "~/.config/ssh-mcp/secrets.json",
"profiles": {
"production": {
"host": "server.example.com",
"username": "admin"
}
}
}
O arquivo de segredos é indexado pelo nome do perfil — veja secrets.json.example:
{
"production": { "password": "..." },
"buildbox": { "sudoPassword": "..." }
}
sudoPassword é o que sudo responde naquela máquina. Um perfil que faz login por chave não tem senha de login para oferecer, e quando os dois diferem, a senha de login é a resposta errada; sem ela, password é usado.
O arquivo de segredos deve ser legível apenas por você (chmod 600). Caminhos relativos são resolvidos a partir do arquivo de perfis; segredos ficam fora de argv e são mascarados nos logs. Veja segurança de credenciais.
Configurar Claude Code, Codex e outros clientes MCP
Escolha o cliente que você usa e aponte-o para o mesmo arquivo de perfis.
Claude Code
Um comando; -s user disponibiliza o servidor em todos os projetos:
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
Coloque em ~/.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
Um comando, igual aos outros:
qwen mcp add ssh \
-e SSH_PROFILES_FILE="$HOME/.qwen/ssh-profiles.json" \
npx -y @hypnosis/ssh-mcp-server
Outros clientes MCP
Gemini CLI, Hermes, Cline, um plugin de editor ou seu próprio agente funcionam da mesma forma. Tudo o que precisam é de um comando para executar e uma variável de ambiente.
Reinicie seu cliente MCP
Reinicie o cliente e execute ssh_monitor({ action: "list" }) para confirmar que o perfil foi carregado.
Configuração do SSH MCP server
| Variável | O que faz | Padrão |
|---|---|---|
SSH_PROFILES_FILE | Caminho para o JSON de perfis — obrigatório | — |
SSH_MCP_LOG_LEVEL | debug, info, warn, error | info |
LOG_LEVEL | Alternativa, usada apenas quando SSH_MCP_LOG_LEVEL não está definida | info |
SSH_MCP_LOG_TIMESTAMP | Carimbos de data/hora nas linhas de log | true |
SSH_MCP_CONTROL_PERSIST | Segundos que uma conexão compartilhada permanece ativa após o último comando; 0 a encerra imediatamente | 600 |
SSH_MCP_CONTROL_DIR | Onde os sockets de controle ficam | ~/.ssh/ssh-mcp |
SSH_MCP_PROFILES_CACHE_TTL | TTL do cache de perfis, em ms | 60000 |
SSH_MCP_PROFILES_WATCH | Recarregar o arquivo de perfis quando ele mudar | true |
A conexão compartilhada sobrevive a este processo de propósito: encerrá-la na saída cortaria o canal que outra janela na mesma máquina está usando.
Limitações do SSH MCP server
Cada limite indica o caminho para contorná-lo. Uma ferramenta que não pode fazer algo diz isso e nomeia ssh_exec, que executa comandos diretamente na máquina — um driver de log não suportado, um utilitário que a máquina não possui, um mecanismo que este servidor não fala. Você não precisa saber antecipadamente onde as ferramentas terminam: a recusa diz isso, no momento em que importa.
Três recusas permanecem deliberadamente silenciosas sobre o shell, porque ali essa é a resposta errada: um caminho que seu perfil proíbe (contornar sua própria regra não é uma correção), uma chamada malformada (a correção está na chamada) e uma recusa do próprio ssh_exec.
- Cancelamento: uma chamada cancelada agora também interrompe o comando no servidor, enviada como uma segunda chamada pela mesma conexão. Onde o servidor não tem
/proc, o comando é encontrado por meio deps. FreeBSD não é verificado: o comportamento correto lá não é garantido. Transferências de arquivos essh_snapshotnão aceitam cancelamento. - Gravações atômicas: BSD e macOS não podem pré-verificar renomeações entre sistemas de arquivos.
Roteiro do SSH MCP server
-
Execução completa de testes contra hosts SSH macOS
-
Execução de compatibilidade de ponta a ponta no Windows
-
Auditorias multi-host — comparar a saúde de vários perfis SSH em uma única chamada
-
Importar perfis do
~/.ssh/configexistente -
Transferências retomáveis para arquivos grandes e conexões instáveis
-
Linha do tempo de operações remotas — comandos, transferências e decisões de proteção em uma trilha de auditoria
-
Playbooks prontos de solução de problemas SSH
-
Logs de contêineres sem recorrer ao shell— CONCLUÍDO:ssh_log_tailessh_log_searchaceitam um nome de contêiner, perguntam ao docker onde ele grava e leem esse arquivo com o mesmo mecanismo de qualquer outro log -
Uma recusa que deixa você travado— CONCLUÍDO: cada limite agora nomeiassh_execcomo o caminho, então atingir o limite de uma ferramenta custa uma frase em vez de um jogo de adivinhação -
Respostas que chegam ao modelo— CONCLUÍDO: saída de comandos, linhas de log correspondentes, nomes de máquinas e seções de snapshot viajam nos campos, não apenas no texto -
Esquemas de ferramentas MCP menores— CONCLUÍDO: a lista de ferramentas ficou 10% mais leve, e um trabalho destacado agora mostra as últimas linhas que escreveu em vez de ser consultado às cegas -
Trabalho longo sob root— CONCLUÍDO: um trabalho destacado é executado comsudoe acompanhado como root, e um perfil somente com chave respondesudocom seu própriosudoPassword
Desenvolver e testar o 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
A suíte ao vivo é executada contra contêineres reais — um BusyBox, um coreutils — porque os dois discordam silenciosamente, e um mock concorda com quem o escreveu. Veja docs/architecture.md para a estrutura.
Gostou do SSH MCP Server? ⭐
Se você gostou da ferramenta, dê uma estrela no GitHub — isso ajuda mais pessoas a descobrirem o projeto.
Contribua com o SSH MCP server
Issues e pull requests são bem-vindos em github.com/hypnosis/ssh-mcp-server.
Licença
MIT — veja LICENSE.