Servidores MCP respaldados por apps: por que o Hermes agora verifica se o app está instalado primeiro


Você instala o Blender. Ativa o add-on MCP oficial dentro dele. Depois segue um tutorial, deixa o Hermes instalar um plugin do Blender, e a instalação diz “concluído” — e, de fato, get_objects_summary aparece na lista de ferramentas. Então você pergunta: “Que objetos estão na minha cena agora?” A resposta volta: “ferramenta não conectada.”

Você abre o mcp.json. O endereço está certo. Você reinicia o Blender. Nada. O problema real é que a porta nunca esteve no mcp.json, para começar — o Blender escolhe uma na inicialização e a escreve em um arquivo de runtime próprio. O endereço que você escreveu à mão na config estava obsoleto desde o primeiro dia.

Três mudanças chegaram recentemente para fechar exatamente essa classe de “a config parece certa, só não conecta”: plugins podem declarar de qual aplicativo precisam (#119049), o runtime confia apenas no arquivo de endpoint que o app escreve (#119050), e o catálogo de plugins ganhou seu primeiro caso real, o Blender Lab (#119365).

Por que “colocar o endereço na config” nunca ia funcionar

Apps locais no estilo ferramenta falam com o Hermes por uma ponte de loopback. Para não brigar por portas, um app de desktop normalmente aloca uma dinamicamente na inicialização e depois escreve seu endereço http atual, um token temporário e o pid do processo em um arquivo de runtime — no Blender, essa é a ponte na porta 9876. Daí decorrem duas consequências:

  • Uma URL fixada no código fica obsoleta em algum momento, e fica obsoleta em silêncio. A config está sintaticamente perfeita; só a conexão está morta.
  • Uma ferramenta que não conecta tinha exatamente uma mensagem: “não conectada”. Você não conseguia distinguir se o app estava fechado, se a porta mudou ou se a versão era antiga demais.

Então o desenho se inverteu: não adivinhe — pergunte ao aplicativo.

Um plugin pode declarar de qual aplicativo precisa, no plugin.json

Plugins portáteis (o tipo que traz um plugin.json mais arquivos, sem código nativo) ganharam um bloco de declaração em extensions."com.nousresearch.hermes".servers. O nome do server precisa casar com uma entrada no mcp.json do plugin:

"extensions": {
  "com.nousresearch.hermes": {
    "servers": {
      "blender": {
        "app": {
          "darwin": {
            "presence": "bundle",
            "location": "/Applications/Blender.app",
            "version": { "kind": "plist" }
          },
          "win32": {
            "presence": "executable",
            "location": "%ProgramFiles%/Blender Foundation/Blender/blender.exe",
            "version": { "kind": "uninstall_registry", "display_name_prefix": "Blender" }
          },
          "linux": {
            "presence": "executable",
            "location": "/usr/bin/blender",
            "version": { "kind": "none" }
          }
        },
        "requires": { "app": true, "min_version": "5.1" },
        "liveness": {
          "kind": "server_json",
          "path": "%LOCALAPPDATA%/Blender/server.json",
          "fields": { "url": "http", "token": "token", "pid": "pid" }
        }
      }
    }
  }
}

O parser é rigoroso com cada linha disso, o que vale saber antes de escrever uma:

  • app é indexado por sistema operacional e aceita apenas darwin, win32 e linux, com pelo menos um bloco obrigatório. location precisa ser absoluto ou começar com ~, %VAR% ou $VAR; qualquer coisa contendo .. ou um esquema de URL é rejeitada de imediato.
  • presence tem dois valores: bundle (um app bundle do macOS) e executable.
  • Uma fonte de versão (version.kind) é uma entre plist (só darwin), uninstall_registry (só win32, e exige um display_name_prefix), pe_resource (win32) ou none. Se você definir requires.min_version, todo bloco de SO precisa de fato conseguir ler uma versão — um bloco none falha na validação.
  • liveness.kind é server_json ou none. Escolher server_json exige path; as chaves opcionais são endpoint_path (padrão /mcp) e as sobreposições de nome de campo pid_key, url_key, token_key (padrões pid, http, token).

Note a divisão entre os dois blocos: app é dado (“onde o aplicativo fica”), requires é política (“se eu exijo que ele esteja lá”). Essa separação é deliberada.

A barreira na instalação: não instale o que não pode funcionar

Antes, um aplicativo ausente era puramente um problema de runtime — o plugin ia para o seu diretório, suas ferramentas se registravam, e você só descobria o vazio quando chamava uma. Agora o instalador verifica cada server declarado antes da troca atômica e recusa em qualquer um destes casos:

Recusa Gatilho
missing_app O aplicativo declarado não está nesta máquina
version_too_old O app está lá, mas é mais antigo que requires.min_version
unsupported_os Este host não é coberto por nenhum bloco de SO
Incompatibilidade de plataforma A lista platforms de uma entrada do catálogo não corresponde à família de SO do host (windows / macos são aceitos como aliases de win32 / darwin)

Em instalações via catálogo, a verificação acontece antes do clone ou da escrita do destino do plugin, então você nunca acaba com um diretório meio instalado. A validação também reporta o estado de disponibilidade numa linha informativa:

hermes plugins validate --install-deps

No runtime, só o arquivo que o app escreve é autoritativo

Esta é a parte mais firme da mudança. Toda tentativa de conexão a um server declarado primeiro relê aquele arquivo de runtime e usa o endpoint que está dentro dele:

  • O arquivo de runtime é autoritativo. Um arquivo ausente, um endpoint malformado ou um pid morto levantam LiveEndpointUnavailable — e o Hermes não recorre à URL configurada estaticamente. Há um teste segurando essa linha: test_missing_runtime_file_never_falls_back.
  • O token é registrado antes de ser usado. Um token temporário encontrado no arquivo de runtime é entregue ao redator antes de o Hermes montar um cabeçalho Authorization a partir dele, então ele não vaza quando você cola logs ou lê um transcript depois.
  • Os requisitos de aplicativo também controlam a admissão de ferramentas. Um server com requisitos de app não tem suas ferramentas oferecidas no conjunto disponível enquanto o host não consegue satisfazê-los.

Quando uma ferramenta sumiu, diga por quê

A pior versão dessa experiência não é “a ferramenta está faltando” — é “a ferramenta está faltando e ninguém vai dizer por quê”. Ferramentas declaradas mas indisponíveis agora retornam estado estruturado: o estado atual, os detalhes do aplicativo, uma ação concreta do usuário e uma política de retry. O catálogo de ferramentas adiadas e o resumo de busca vazia também mantêm uma linha estável para cada server declarado oculto.

Cinco estados são distinguíveis:

Estado Significado Mais ou menos o que fazer
missing_app Não instalado Instale o app, depois instale/ative o plugin
version_too_old Instalado, mas antigo demais Atualize o app
app_not_running Instalado, mas não está rodando Abra-o e deixe a ponte subir
endpoint_unavailable Arquivo de runtime ausente ou endpoint inválido Reinicie a ponte de dentro do app
no_interactive_session Precisa de uma sessão gráfica de login Rode em uma sessão de desktop, não em um serviço headless

Note também que a política de retry só pode ser after_user_action (faça o acima e tente de novo) ou never_here (este host nunca vai satisfazer isso, por exemplo, o SO não é suportado). “Tente de novo” e “pare de tentar” são respostas diferentes, e a ferramenta agora te dá a certa.

O caso real que já está no catálogo: Blender Lab

O catálogo oficial de plugins agora carrega uma entrada blender, que embrulha este repositório público: hermes-plugin-blender.

# instala o sha fixado da entrada do catálogo, não a ponta de um branch
hermes plugins install NousResearch/hermes-plugin-blender
hermes plugins enable blender

O que ele faz é simples: sobe o server MCP oficial do Blender Lab via stdio (código upstream fixado no commit 2cea8d56, v1.0.3), e esse server se conecta à ponte do add-on do Blender em 127.0.0.1:9876. Uma skill de workflow original vem junto.

Três coisas a saber antes de contar com ele:

  1. Os pré-requisitos são por sua conta. Blender 5.1+, o add-on MCP oficial (instalado, ativado, com acesso online permitido e a ponte iniciada), além de Git e uv na máquina de backend. Instalar o pacote não instala o Blender, não muda suas preferências, não instala o add-on nem abre uma cena.
  2. Aqueça o ambiente uv antes da primeira conexão. A primeira build upstream leva minutos; isso foi medido no Windows, e um timeout normal de conexão MCP vai interrompê-la:
uv tool run --from "git+https://projects.blender.org/lab/blender_mcp.git@2cea8d566dde07fbac28a61d698909d69724e853#subdirectory=mcp" blender-mcp --help
  1. Este wrapper ainda não usa a barreira de aplicativo. O README dele declara abertamente que a detecção automática de aplicativo, versão e liveness não é implementada nem verificada pelo wrapper, e que os pré-requisitos são manuais. Em outras palavras: o contrato app / requires / liveness está vivo no core, e este plugin específico ainda não o adotou — a sequência usual de “capacidade primeiro, plugins depois”.

Mais uma coisa que merece ser dita às claras: o próprio Blender Lab avisa que suas ferramentas executam Python arbitrário dentro do Blender, sem proteção contra exclusão de arquivos ou exfiltração de dados. Mantenha a ponte só em loopback, use em workflows confiáveis e prefira uma cena descartável. Isto não é um sandbox.

Assimetrias de plataforma e os cantos que ainda estão abertos

  • As verificações de host não são simétricas entre plataformas. O Windows exige uma sessão de processo diferente de zero (a sessão ativa do console ou uma window station de processo). O Linux exige um ID de sessão de processo legível e o diretório de runtime do usuário atual. O macOS retorna true — a biblioteca padrão não tem como perguntar sobre a sessão gráfica atual sem abrir um subprocesso.
  • Builds do aplicativo com autenticação desabilitada talvez nunca escrevam o arquivo de runtime. O PR lista isso como uma questão aberta pendente de verificação em hardware; tal build ficaria permanentemente em endpoint_unavailable.
  • A fronteira de ativação não mudou. Quando um plugin carrega no meio da execução, seus handlers do lado do gateway (comandos de barra, callbacks de botão, transforms de entrada) se conectam imediatamente — mas ferramentas e seções de system prompt ainda esperam a próxima sessão (o invariante do prompt cache), e servers MCP portáteis precisam de um mcp.reload explícito.

Fechando

Se o seu plugin conversa com software de desktop, o problema nunca foi a config estar escrita errado — foi que um humano não deveria estar escrevendo aquele endereço. Agora um plugin declara sua dependência de aplicativo, o instalador recusa antes de tocar no seu profile, o runtime confia apenas no endpoint que o próprio app publica, e uma ferramenta morta finalmente diz qual elo da corrente quebrou.

Para o quadro mais amplo de plugins, veja o modelo de confiança do catálogo de plugins e o catálogo oficial de MCP remoto; se a sua preocupação é a fronteira de segurança de ferramentas locais como esta, Segurança do catálogo MCP é a leitura mais próxima. Para a variante vendored no repositório, o guia de skills locais do projeto cobre .hermes/skills/ e sua barreira de confiança.