Por que suas ferramentas MCP vivem pedindo login de novo: dentro do ciclo de vida dos tokens OAuth


Você conectou Linear, Figma e Notion ao Hermes no mês passado. O hermes mcp list mostrava três servidores saudáveis e toda chamada de ferramenta simplesmente funcionava. Hoje de manhã, toda ferramenta MCP responde com um erro de autorização — o Desktop piscou um toast na noite passada, você estava em uma reunião e o dispensou. Você reautentica o Linear e, à tarde, o Figma também cai. O padrão se repete quase toda semana sempre que o Hermes roda no seu notebook e na máquina do escritório ao mesmo tempo, e você começa a se perguntar o que configurou errado. Provavelmente nada: a falha está no longo ciclo de vida dos tokens por trás dessas conexões, e o Hermes acabou de lançar quatro correções voltadas exatamente para ele.

O handshake por trás dessas ferramentas: access tokens, refresh tokens e o “uso único”

MCP (Model Context Protocol) é o protocolo que permite ao Hermes chamar ferramentas que um serviço externo expõe; Linear, Figma e Notion não aceitam uma API key estática, eles entregam credenciais via OAuth. Quando você conclui um login OAuth, recebe duas coisas:

  • Um access token — de vida curta, geralmente de trinta a sessenta minutos. Ele viaja junto com cada chamada de ferramenta.
  • Um refresh token — de vida longa. O único trabalho dele é comprar um novo access token para você.

Para que um refresh token roubado deixe de ser útil rapidamente, authorization servers modernos habilitam a rotação: cada vez que você troca seu refresh token por um novo access token, o refresh token antigo é invalidado na hora e um novo é emitido. Design sensato — e que vem com uma rede de segurança chamada detecção de reuso: se o mesmo refresh token aparecer uma segunda vez, o servidor não consegue distinguir um retry de rede instável de uma credencial roubada, então escolhe o caminho seguro, revoga o grant inteiro (todos os tokens daquele client e daquele usuário) e pede que você faça login de novo.

É aí que uma instalação normal encontra problema. O Desktop, o dashboard e um gateway em segundo plano podem estar todos rodando na mesma máquina, ou você tem dois profiles, e eles compartilham o mesmo arquivo de tokens em ~/.hermes/mcp-tokens/linear.json. Quando o access token expira, dois processos percebem quase no mesmo instante e cada um apresenta o mesmo refresh token:

  1. O processo A chega primeiro, recebe um par novo e o refresh token antigo é anulado.
  2. O processo B está algumas centenas de milissegundos atrás e apresenta aquele refresh token já anulado.
  3. O servidor chama isso de replay e revoga o grant inteiro — incluindo os tokens saudáveis e novíssimos que o processo A acabou de receber.

Os dois processos apagam, por um motivo que de fora parece absurdo: funcionava um segundo atrás. Fechar essa janela é o que a PR #111186 resolve.

Correção um: uma cerca entre processos para que um refresh token rotacionado seja apresentado exatamente uma vez

O Hermes agora coloca um lock entre processos ao lado do arquivo de tokens — uma cerca. O lock vem do kernel (flock no Linux e no macOS, msvcrt.locking no Windows) e trava um descritor de arquivo, não a existência de um arquivo, então um processo que travou e deixou um arquivo de lock órfão para trás não consegue enganá-lo. O lock vive em um arquivo irmão com nome do tipo linear.json.refresh.lock.

Toda a sequência — ler o refresh token, fazer o POST para o authorization server, gravar o resultado de volta no disco — agora acontece dentro desse lock, o que muda o comportamento para:

  • Releia o disco assim que o lock for obtido. Se um processo par concluiu uma rotação enquanto esperávamos, e o par que ele gravou ainda está válido, este processo simplesmente adota os tokens novos que o par trouxe e pula o POST, em vez de apresentar um token que já foi gasto.
  • Se o par do processo par tiver expirado, este processo faz POST com o refresh token do par — que é o único válido no momento — em vez da cópia obsoleta que está na própria memória.
  • Ausência de expiração conta como válido. Quando o servidor não declara um tempo de vida, o Hermes não assume que o token morreu.
  • Sem lock, sem refresh. No timeout da cerca, o refresh falha fechado em vez de apostar em um replay. Só códigos de erro de contenção significam “um par detém a cerca”; qualquer outra coisa falha rápido em vez de esperar.

O que isso significa para você: rodar Desktop, dashboard e gateway ao mesmo tempo é seguro, e ter o Linear conectado em dois profiles também. As quedas deixam de ser aleatórias e inexplicáveis — um refresh ou funciona, ou falha com um erro claro.

Correção dois: refresh tokens ficam com pin no issuer que os concedeu

O próximo risco é mais silencioso. Um authorization server OAuth anuncia o próprio endereço de identidade nos seus metadados; esse valor é o issuer. O Hermes descobre esses metadados a partir da URL que o servidor MCP publica — e se o que está por trás dessa URL mudar de identidade (uma migração de serviço, alguém editando os metadados ou, pior, um domínio sequestrado), o Hermes entregaria um refresh token de vida longa a um servidor que nunca o emitiu. Um refresh token vale muito mais do que um access token; essa entrega é uma credencial de longo prazo dada a um estranho.

A correção: o Hermes grava um campo hermes_issuer junto com os tokens em cache, anotando quem originalmente os concedeu. Esse campo é só contabilidade interna do Hermes — ele é removido antes que o SDK valide o token e nunca sai na rede. Toda vez que inicializa uma conexão, o Hermes compara o issuer armazenado com o descoberto no momento:

  • Divergência → o refresh token é descartado, na memória e no disco, enquanto o access token ainda válido é mantido, porque ele continua válido para o servidor que o emitiu. Ele segue funcionando até expirar, e então uma reautorização normal roda contra o novo issuer.
  • Correspondência → nada acontece, os tokens armazenados continuam funcionando.
  • Arquivos de token legados, sem issuer registrado (qualquer instalação anterior ao campo) → o issuer descoberto atual é adotado uma vez e registrado, em vez de jogar você em um re-login forçado. É uma escolha deliberada: o Hermes tem muitas instalações existentes cujos arquivos de token são anteriores ao campo, e forçar todas elas a se reautenticar seria puro incômodo.

Vale notar: isso diverge de propósito de uma mudança irmã no codex (openai/codex), que falha de forma dura quando falta o issuer, porque o credential store dele já trazia o campo desde o primeiro dia.

Correção três: RFC 9207 e a única exceção do Figma

A RFC 9207 é uma extensão do OAuth: o authorization server acrescenta um parâmetro iss quando redireciona o navegador de volta ao client, declarando “eu sou quem digo ser”. Com isso, o client consegue confirmar que o callback realmente veio do authorization server em que confia, e não de um código de autorização injetado por outro — é uma defesa contra ataques de confusão de authorization server.

Existiam dois modos de falha opostos aqui, e o Hermes corrigiu um de cada:

Os relays descartavam o iss. Quando um servidor anuncia suporte a esse parâmetro nos seus metadados e depois o omite, o SDK por baixo rejeita o callback com OAuthFlowError: Authorization response missing iss parameter. O caminho do CLI sempre o repassava, mas os relays de callback do dashboard, do Desktop e da TUI não levavam o iss até o client MCP — então, contra um servidor realmente em conformidade com a RFC 9207, como Cloudflare ou Resend, fazer login pelas interfaces gráficas era um beco sem saída. Agora todo relay repassa o parâmetro adiante.

O Figma anuncia o que não envia. O authorization server do Figma (https://api.figma.com) afirma suportar iss e nunca o inclui, então todo código de autorização válido era descartado pelo SDK e hermes mcp login figma nunca conseguia concluir. O Hermes abre uma exceção para exatamente esse issuer: um callback que traz um code mas não traz iss recebe o valor que falta preenchido a partir do issuer descoberto nos metadados, com um aviso registrado no log. Um iss presente mas diferente continua sendo rejeitado, e todos os outros servidores mantêm a regra estrita. Não há toggle nem variável de ambiente nova.

Correção quatro: um lembrete de re-auth uma vez por dia no Desktop

Assim que um token morre, o servidor fica estacionado e deixa de tentar abrir uma aba do navegador — não há ninguém ali sentado para clicar em Allow. Isso é bom, exceto que tornava um token morto invisível: o health check disparava um toast de aviso no momento em que o estado mudava e depois ficava quieto pelo resto da sessão do app. Dispense esse toast na segunda-feira e o servidor fica parado até você abrir por acaso a página Capabilities.

O Desktop agora dá um toque uma vez por dia: na transição para um estado ruim, e de novo quando o snooze de 24 horas expira, se o servidor continuar quebrado. Ele nunca dá toques enquanto os tokens estão saudáveis. O toast traz dois botões:

  • Sign in — encaminha para o botão de autenticação que já existe na página MCP. Nada dispara um fluxo OAuth em segundo plano por conta própria.
  • Disable — chama o endpoint existente para definir enabled: false naquele servidor. A tarefa de reconcile de config do gateway o derruba em cerca de 60 segundos, e um backend hermes serve o descarta no próximo /reload-mcp.

O snooze é registrado por profile e por servidor, então um servidor quebrado não consegue te importunar uma dúzia de vezes por dia, e um servidor que você já reconheceu não fica em silêncio para sempre.

Qual PR corrigiu o quê

PR Rótulo Mudança de comportamento
#95246 type/security (2026-09-14) Refresh tokens armazenados não podem mais ser enviados a um issuer diferente: em caso de divergência, o refresh token é descartado e o access token não expirado é mantido; arquivos de token legados adotam o issuer descoberto uma vez
#111186 type/bug, P1 (2026-09-15) O refresh passa a ser cercado entre processos: um refresh token rotacionado é apresentado exatamente uma vez, o par novo vindo de outro processo é adotado com o POST ignorado, e um timeout da cerca falha fechado
#111628 type/bug, P2 (2026-09-15) O login OAuth conclui contra servidores RFC 9207 a partir do dashboard, do Desktop e da TUI (Cloudflare, Resend)
#112059 type/bug, P2 (2026-09-15) O login no Figma conclui apesar do parâmetro iss omitido; a regra estrita é relaxada apenas para esse issuer
#110078 type/feature (2026-09-13) O Desktop dá um toque por dia para servidores MCP que precisam de re-auth, com Sign in e Disable
#111481 fix (2026-09-15) Profiles servidos pelo dashboard mantêm suas próprias conexões MCP e credenciais em vez de pegar emprestada a sessão de outro profile

O que fazer quando um servidor MCP pede que você faça login de novo

Percorra estes passos na ordem; a maioria dos casos se resolve em um ou dois passos.

  1. Leia a mensagem antes de reconstruir qualquer coisa. “Refresh failed” e “the issuer changed” são classes diferentes de problema — a primeira é um token expirado ou revogado, a segunda significa que o authorization server daquele lado mudou de lugar.
  2. Encontre o servidor afetado. hermes mcp list mostra quais servidores estão parados, e hermes mcp test linear testa uma única conexão.
  3. Reautorize uma vez. hermes mcp login linear para um servidor, hermes mcp reauth --all para refazer todos os servidores OAuth. No Desktop, basta clicar em Sign in no toast.
  4. Deixe a sessão enxergar os novos tokens. O conjunto de ferramentas de uma sessão é congelado quando ela começa, então use /reload-mcp depois de reautorizar, ou abra uma sessão nova.
  5. Atualize as duas máquinas. A cerca é cooperativa — ela precisa que todos os processos participem. Atualize um host e o outro continua repetindo tokens do jeito antigo, então garanta que ambos incluam a PR #111186.
  6. Lembre que um gateway nunca abre um navegador. Se você roda um gateway em um servidor, um token morto só deixa um aviso no log; conclua o fluxo em outro lugar e ele se reconecta na próxima verificação.
  7. Na dúvida, resete. Os arquivos de token ficam em ~/.hermes/mcp-tokens/, um por nome de servidor. Apagar o arquivo certo e rodar hermes mcp login de novo é o reset mais limpo disponível.
  8. Desabilite o que você não usa mais. Ninguém precisa de um lembrete diário sobre uma integração que você abandonou no trimestre passado.

Se o Hermes mora em um host remoto sem navegador, o fluxo é diferente — encaminhamento de porta via SSH, colar a callback URL de volta e device codes estão cobertos em como concluir o OAuth em um servidor remoto. Para os flags exatos do subcomando, veja a referência do comando hermes mcp, e se você está justamente escolhendo quais servidores conectar, o catálogo oficial de MCP remoto é um ponto de partida melhor do que resultados aleatórios de busca.

A versão curta

Duas das quatro correções são o mesmo princípio visto de ângulos diferentes: uma credencial só vai para o servidor que a emitiu (vínculo com o issuer) e um refresh token rotacionado é consumido exatamente uma vez (a cerca entre processos). A terceira impõe a identidade do chamador em todo caminho que um callback pode percorrer (RFC 9207, com a única exceção do Figma), e a quarta é um patch humano — quando algo precisa da sua atenção, avise, uma vez por dia, com um botão que resolve.

Na prática, isso significa que as quedas de MCP deixam de parecer azar. Rode Desktop, dashboard e gateway ao mesmo tempo; quando um servidor realmente precisar de você, você vai saber no mesmo dia, em vez de descobrir que toda ferramenta está quebrada desde terça-feira. O hermes update pega tudo isso — as correções estão na linha 0.21.x, e tudo depois do patch de estabilidade v0.21.2 já as inclui.