Como concluir o OAuth em um servidor remoto: túneis SSH, callbacks colados e device codes


Você instala o Hermes em uma VM na nuvem, ou em uma máquina de desenvolvimento atrás de um bastion host, e agora quer o Spotify no meio — ou um servidor MCP remoto como Linear, Sentry ou Atlassian. Você roda o comando, o Hermes imprime uma authorize URL, você abre no navegador do notebook e clica em Allow. O navegador então cai em um endereço 127.0.0.1 e mostra apenas “This site can’t be reached”. De volta ao servidor, o Hermes continua esperando em silêncio por um callback que nunca vai chegar.

Isso não é erro de configuração. É um desencontro físico de onde o handshake OAuth acontece: o 127.0.0.1 na redirect URI significa “a máquina onde está o seu navegador”, enquanto o listener está ligado ao host remoto. Abaixo estão os três caminhos que funcionam, além dos providers que nunca precisaram de túnel nenhum.

Por que quebra em um host remoto

Um minuto de teoria, porque ela deixa a correção óbvia. O Hermes usa dois tipos de login OAuth:

  • Redirect de loopback. O Hermes sobe um pequeno listener HTTP em 127.0.0.1 na máquina local. Quando você aprova, o authorization server redireciona o seu navegador para http://127.0.0.1:port/callback, e esse listener pega o code. O Spotify e a maioria dos servidores MCP remotos protegidos por OAuth funcionam assim.
  • Device code / paste-the-code. Nenhuma callback URI. O auth server te entrega um código curto e uma URL de verificação; você aprova em qualquer navegador, e o Hermes faz polling até o token aparecer.

Os dois são tranquilos quando o Hermes e o seu navegador compartilham a mesma máquina. O primeiro quebra no caso contrário: o navegador do seu notebook acessa o 127.0.0.1 do notebook, enquanto o listener está no 127.0.0.1 do servidor. Máquinas diferentes, endereço parecido.

Então por que não simplesmente ligar o listener em 0.0.0.0, ou usar o hostname público do servidor? Porque os auth servers validam a redirect URI contra uma allowlist fixa. O Spotify e a maioria dos authorization servers MCP exigem o formato de loopback http://127.0.0.1:port/callback; ligar em qualquer outro lugar, ou escolher outra porta, faz a requisição ser rejeitada como redirect_uri mismatch. A correção não é mover o listener — é levar essa porta de loopback até o seu notebook, que é exatamente o que um encaminhamento local SSH faz.

Caminho um: um encaminhamento local via SSH (Spotify como exemplo)

Abra um segundo terminal no seu notebook e dedique-o ao túnel:

# Terminal local: encaminhar notebook:43827 para remote-host:43827
ssh -N -L 43827:127.0.0.1:43827 user@remote-host

-N significa “não abra um shell remoto, apenas mantenha este encaminhamento aberto”. Deixe esse terminal rodando até o login concluir.

Depois, em uma sessão SSH separada, rode o comando de autenticação:

ssh user@remote-host
hermes auth spotify --no-browser

--no-browser pula o passo de abertura automática (não há navegador no servidor). O Hermes imprime uma authorize URL e, o que é crucial, a porta que ele de fato ligou:

Waiting for callback on http://127.0.0.1:43827/callback

Cole a authorize URL em um navegador no seu notebook e aprove. O auth server redireciona para 127.0.0.1:43827/callback — desta vez esse endereço é o seu notebook, ele bate no seu túnel, a requisição é encaminhada ao listener remoto e o terminal reporta sucesso. Depois você pode derrubar o túnel.

De onde vem a porta? Daquela linha Waiting for callback on .... O Spotify usa 43827 por padrão, mas se a porta estiver ocupada o Hermes pode incrementá-la automaticamente — sempre leia o número na saída em vez de copiar de um post de blog.

Através de um jump box, use o ProxyJump nativo do SSH para que a porta de loopback nunca pare no bastion:

ssh -N -L 43827:127.0.0.1:43827 -J jump-user@jump-host user@final-host

Em OpenSSH mais antigo, sem -J, a forma longa é:

ssh -N \
    -o "ProxyCommand=ssh -W %h:%p jump-user@jump-host" \
    -L 43827:127.0.0.1:43827 \
    user@final-host

Três armadilhas que vale conhecer de antemão. O roaming do Mosh não carrega encaminhamentos -L, então se você roda o Hermes dentro do tmux sobre mosh, abra uma sessão SSH comum separada só para o túnel — essa conexão precisa continuar viva durante todo o fluxo, enquanto a sua sessão interativa segue funcionando. Se você usa ssh -o ControlMaster=auto, os encaminhamentos compartilham o tempo de vida da conexão master; se o túnel não subir, mate o master com ssh -O exit user@remote-host e tente de novo.

Caminho dois: colar a URL de redirect de volta (servidores MCP)

Servidores MCP remotos usam o mesmo fluxo de loopback, mas em um terminal interativo o Hermes oferece um atalho que dispensa túnel: colar a callback URL de volta no prompt.

Depois de clicar em Allow no navegador, a página redireciona para um endereço 127.0.0.1 e não carrega — isso é esperado. Copie a URL completa da barra de endereços do navegador e cole no prompt do Hermes:

  MCP OAuth: authorization required.
  Open this URL in your browser:

    https://mcp.linear.app/authorize?response_type=code&...

  Or paste the redirect URL here (or the ?code=...&state=... portion) and press Enter:
> https://mcp.linear.app/callback?code=abc123&state=xyz
  Got authorization code from paste — completing flow.

Colar apenas a query string ?code=...&state=... também funciona. Esse caminho não exige configuração SSH nenhuma e vale para qualquer servidor MCP com auth: oauth.

Você só precisa do túnel do caminho um quando o fluxo tem de ser concluído sem ninguém presente — um re-auth em script, onde não há quem cole de forma interativa.

Uma diferença do lado desktop: no painel Skills → MCP do app desktop, o app nativo recebe o callback no seu computador e o repassa para a conexão e o profile selecionados, então não há túnel SSH nem dashboard.public_url envolvidos, e os tokens ficam com o backend profile dono. Sair da aba MCP ou mudar o escopo dela cancela um login pendente.

Caminho três: device code e paste-the-code (nenhum túnel)

Se o provider usa fluxo de device code, nada na sua rede precisa mudar — você aprova uma vez no navegador e o Hermes faz polling. Referência rápida para “preciso de túnel?”:

Provider Porta de loopback Precisa de túnel
Spotify 43827 por padrão Sim, quando o Hermes está remoto
Servidores MCP com o modo de auth oauth escolhida automaticamente por servidor Sim, ou cole a URL de redirect de volta
xai-oauth (Grok SuperGrok) n/a Não, device code
anthropic (Claude Pro/Max) n/a Não, cole o código
openai-codex (ChatGPT Plus/Pro) n/a Não, device code
minimax, nous-portal n/a Não, device code
openrouter só local Não: via SSH o Hermes muda para um fluxo headless e pede que você cole o código mostrado no navegador

O OpenRouter é o mais novo deles. hermes auth add openrouter --type oauth roda o fluxo PKCE de navegador do OpenRouter e, em caso de sucesso, cunha uma API key dentro do credential pool (a origem dela é registrada como manual:openrouter_pkce) — sem copiar e colar key de uma página web. Localmente ele usa um listener de loopback bind-first em uma porta atribuída pelo sistema operacional, o que evita a corrida probe-then-rebind ao nomear uma porta que ele já possui. Para sessões remotas, ele detecta o ambiente SSH e muda para o modo headless documentado do OpenRouter: sem callback_url, o código aparece na tela e você o cola. Omita o --type e ele ainda se comporta como login por API key, então o fluxo antigo hermes auth add openrouter --api-key ... continua igual.

Quatro falhas que você vai encontrar de verdade

  1. Editar a config em uma sessão ativa e depois tentar logar: 30 segundos e ele desiste. Quando você adiciona um servidor MCP OAuth ao config.yaml de dentro de uma sessão Hermes em execução, o CLI recarrega as conexões MCP automaticamente com timeout de 30 segundos. Isso está longe de ser suficiente para um fluxo OAuth interativo. Rode hermes mcp login <server> em um terminal novo — ele não tem esse limite e espera os cinco minutos completos pelo seu paste.
  2. Os tokens caem no ~/.hermes errado. As credenciais são gravadas sob o usuário que rodou o comando. Se o seu gateway ou serviço systemd roda como outra conta (digamos root, ou um usuário hermes dedicado), autentique como essa conta, senão os tokens ficam no seu diretório home e o serviço não os enxerga. sudo -u hermes -i ou equivalente.
  3. bind [127.0.0.1]:43827: Address already in use. Alguma coisa no seu notebook já ocupa a porta — um túnel morto, ou um listener local do Hermes. Encontre e mate com lsof -iTCP:43827 -sTCP:LISTEN, depois repita o encaminhamento.
  4. Authorization timed out waiting for the local callback. O redirect nunca chegou ao listener remoto. Confirme que o terminal do túnel ainda está vivo (uma sessão ssh -N não imprime nada, então olhe o próprio terminal), garanta que a porta bate com a última linha Waiting for callback on ..., reinicie o túnel e rode o comando de auth de novo.

Uma nota de segurança que vale repetir: tokens OAuth são credenciais de vida longa, então onde eles caem e quem consegue lê-los importa. Para a engrenagem em volta, veja credential pools e autofill do navegador e criptografia de segredos respaldada por keychain. Se o servidor que você está conectando é de terceiros, leia o guia de segurança do catálogo MCP e o apanhado do catálogo oficial de MCP remoto antes de ligá-lo a um gateway.

A versão curta

Faça uma pergunta: este provider usa callback ou código?

  • Fluxos com callback (Spotify, servidores MCP OAuth): em um terminal interativo, cole a URL de redirect de volta — é o mais simples. Para automação, encaminhe a porta de loopback com ssh -N -L, tirando a porta da própria saída do Hermes, e fique atento a mosh, tmux e ControlMaster.
  • Fluxos com código (xai-oauth, openai-codex, anthropic, minimax, nous-portal e o OpenRouter via SSH): não mude nada. Abra o link, informe o código, pressione Enter.

Um detalhe fácil de esquecer: depois de logar, reinicie qualquer gateway ou serviço de longa duração para que ele pegue a credencial no próprio processo. E se você está montando a máquina do zero, o guia de instalação cobre todo o caminho.