Gobus MCP: um agente que investiga o gov.br — do Cloud Run ao forecast¶
Em pouco menos de duas semanas — de 19 de junho a 1 de julho de 2026 — a plataforma DGB ganhou uma nova porta: o Gobus MCP, um servidor que abre todo o acervo enriquecido de notícias do governo a agentes de IA como o Claude. Não é mais um endpoint humano; é uma interface para uma máquina que raciocina. Com ele, um agente passou a poder perguntar "o que o MEC comunicou esta semana?", "essa política está em qual fase?", "há algum pico anômalo de cobertura?" e "quais temas devem crescer nos próximos dias?" — e responder consultando dados reais, não a própria memória. Este post conta o nascimento do Gobus: as quatro queries GraphQL que o alimentam, o provisionamento no Cloud Run, o site de documentação, as duas fases de capacidades analíticas, e — o fio mais honesto da história — a saga do transporte, um bug de sessão que só apareceu quando subagentes começaram a usar o servidor de verdade.
O que é MCP, e por que o gov.br precisa dele¶
O Model Context Protocol (MCP) é um protocolo aberto que deixa um agente de IA usar ferramentas e consultar dados externos de forma padronizada e segura — em vez de cada integração ser costurada à mão, o agente "descobre" o que pode fazer e chama funções bem definidas. Na prática: o Claude conectado ao Gobus enxerga um catálogo de tools (ações), resources (dados) e prompts (fluxos guiados), e escolhe o que usar para responder à pergunta do usuário.
O desenho do Gobus tem uma regra de ouro: o MCP consulta exclusivamente o GraphQL. Nada de acesso direto a Postgres, Typesense ou Neo4j. O servidor MCP é uma camada fina de tradução — a inteligência de dados continua vivendo atrás do graphql-api, o caminho único consolidado no post anterior.
Claude / agente de IA
│ MCP (tools · resources · prompts)
▼
┌──────────────────┐
│ Gobus MCP │ Cloud Run · southamerica-east1
│ (FastMCP) │ SA dedicada: run.invoker (mínimo)
└────────┬─────────┘
│ GraphQL (ÚNICO caminho de leitura)
▼
┌──────────────────┐
│ graphql-api │
└────────┬─────────┘
│
┌─────┼───────────┬────────────┐
▼ ▼ ▼ ▼
Postgres Typesense Neo4j embeddings
As queries que alimentam o Gobus¶
Antes do servidor existir, o GraphQL precisou aprender a falar a língua da análise. Foram adicionadas 4 novas queries (graphql-api#20), cada uma sustentando um tipo de investigação que o agente faria:
agencyAnalytics— métricas de publicação por agência e período (viaDATE_TRUNC GROUP BYno Postgres): volume, sentimento e legibilidade, na granularidade DAY / WEEK / MONTH.trendingThemes— temas em crescimento, comparando duas janelas do Typesense (recente vs. baseline), comgrowth_score = window_daily / baseline_daily.entityCoverage— série temporal de menções de uma entidade canônica por agência (news_entities JOIN news).entitySearch— resolução fuzzy de nomes de entidade: match exato ementity_aliasem UNION compg_trgmsobre oentity_registry, ordenado por confiança.
Essas quatro queries são o pré-requisito de tudo o que veio depois — o Gobus só é tão capaz quanto o schema que consulta. A entrega chegou com 13 testes passando localmente cobrindo os novos resolvers de analytics e de entidades.
Do Terraform ao ar: o Cloud Run e os quatro fixes de infra¶
O servidor foi provisionado inteiramente por Terraform (infra#206): um serviço Cloud Run destaquesgovbr-gobus-mcp em southamerica-east1, com uma service account dedicada de permissão mínima — apenas roles/run.invoker na graphql-api, nada além. Um Artifact Registry próprio (com imagem placeholder, para o CI/CD do repo gobus-mcp substituir), a URL da graphql-api injetada por referência (sem hardcode), acesso público via HTTPS (a autenticação fica a cargo do cliente MCP) e sem min_instances — escala a zero quando ninguém está investigando.
O primeiro obstáculo apareceu no CI, não na aplicação. O deploy falhava com Permission 'iam.serviceAccounts.getAccessToken' denied: o repositório gobus-mcp simplesmente não estava registrado no Workload Identity Federation como caller autorizado. A correção seguiu o mesmo padrão de todos os outros repos e liberou o primeiro deploy real (infra#207).
Depois vieram três fixes que só a operação revelou — o tipo de detalhe que nenhum tutorial antecipa:
Cada terraform apply desfazia o deploy
A imagem do serviço voltava sozinha para o placeholder hello:latest a cada apply, apagando o que o CI/CD havia publicado — a revisão em produção rodava o placeholder apesar de existir imagem real no Artifact Registry. A causa: o Terraform gerencia a configuração do serviço, mas o CI/CD gerencia a imagem. A solução é o padrão canônico para esse conflito — lifecycle { ignore_changes = [template[0].containers[0].image] } (infra#209). O mesmo PR removeu a env var MCP_TRANSPORT=sse, já ignorada pelo server.py desde o fix de uma race condition — o transporte passou a ser determinado pelo PORT que o Cloud Run injeta.
Os outros dois fixes de infra são inseparáveis da história do transporte — e é para ela que vamos agora.
A saga do transporte: um 404 que só os subagentes viam¶
Este é o fio mais instrutivo da entrega. O Gobus nasceu com transporte SSE (Server-Sent Events), e SSE é stateful: cada cliente abre uma sessão cujo ID vive na memória de uma instância. Isso tem uma consequência imediata em Cloud Run — se houver mais de uma instância, o POST /messages/ pode cair numa instância diferente da que abriu o GET /sse, e o protocolo estoura com -32602. A defesa foi limitar o serviço a max_instance_count = 1 (infra#210): uma instância só, sessão sempre em casa.
Funcionou — até os subagentes entrarem em cena.
HTTP 404 — Could not find session
Subagentes que faziam múltiplas chamadas MCP sequenciais falhavam na segunda com 404 Could not find session (gobus-mcp#4). O padrão era cruel: a primeira chamada estabelecia a sessão e funcionava; entre uma chamada e outra o stream SSE de um subagente efêmero fechava; a segunda chamada não achava mais a sessão. Na sessão principal do Claude Code tudo ia bem, porque o cliente mantém o stream aberto de forma persistente. Além disso, todo deploy invalidava todas as sessões ativas de uma vez.
A primeira reação foi um paliativo de infra: o timeout default do Cloud Run é de 300s (5 min), e ele encerrava a stream SSE justamente durante sessões de subagente com chamadas espaçadas. O timeout foi elevado para 3600s, o máximo suportado pela plataforma (infra#211). Ajudou, mas não curou a doença — o problema não era o tempo, era o estado em memória.
O plano de cura óbvio era migrar para streamable-http, o transporte stateless do FastMCP 3.x, onde cada request é independente. Mas a primeira tentativa (commit 8588d02) bateu num muro de compatibilidade: o FastMCP 3.x fala a spec MCP 2025-03-26 (baseada em POST), enquanto o cliente do Claude Code daquele momento falava a spec 2024-11-05 (GET + SSE). Incompatibilidade total — o servidor novo não conversava com o cliente existente.
A saída foi engenhosa: servir os dois protocolos ao mesmo tempo (gobus-mcp#5). Com o salto de FastMCP 2.9 → 3.x e a flag stateless_http=True, o /mcp passou a processar cada POST de forma independente — sem session ID, sem expiração. E o /sse legado foi mantido para compatibilidade, implementado manualmente com um SseServerTransport registrado como custom_route (o FastMCP 3.x não cria o /sse sozinho):
| Endpoint | Spec | Comportamento |
|---|---|---|
/mcp |
2025-03-26 | Stateless HTTP: cada POST independente, sem sessão |
/sse |
2024-11-05 | SSE stream + POST /messages (backward-compat) |
O .mcp.json passou a apontar para /mcp, com o /sse como rede de segurança caso o cliente ainda não suportasse a spec nova. A entrega chegou com 48 testes unitários verdes.
E o desfecho — o mais honesto de todos. Depois de toda essa engenharia de transporte HTTP, a resposta prática para o Claude Code acabou sendo rodar o MCP em modo stdio local, porque os endpoints HTTP ainda tinham bugs no CLI. A issue gobus-mcp#4 foi fechada como wontfix, com o stdio local documentado como transporte recomendado (gobus-mcp#6). O trabalho no dual transport não foi perdido — deixou o servidor pronto para o dia em que o HTTP stateless for a via padrão, e liberou conceitualmente o max_instance_count=1. Mas a lição ficou: a arquitetura mais elegante nem sempre é a que o cliente do dia consegue usar.
Para conectar no Claude Code hoje
O caminho recomendado é stdio local — sem sessão que expira, sem stream que cai entre chamadas de subagente. Os endpoints HTTP (/mcp e /sse) continuam disponíveis para clientes remotos.
Fase 1 e Fase 2: de leitor a analista¶
Com o transporte domado, o Gobus deixou de ser só um leitor de notícias e virou um analista. Duas fases, ambas construídas com TDD obrigatório (testes antes da implementação) e ancoradas nos achados do EXPERIMENTO_V3.
Fase 1 (gobus-mcp#6) trouxe capacidades de legibilidade e de políticas públicas:
gobus_get_readability_recommendations(agency_key, days, limit)— diagnóstico de legibilidade por agência, com o gap até a meta Flesch e recomendações de estilo.gobus_get_policy_lifecycle(policy_name, date_from)— o ciclo de vida comunicacional de uma política pública: fases, âncoras narrativas e em que fase ela está agora.- Um MCP App novo,
ui://readability-dashboard— um dashboard HTML/SVG autocontido, sem CDN externo.
Fase 2 (gobus-mcp#7) fechou o arco do título — do Cloud Run ao forecast — com 3 tools e 2 resources:
gobus_detect_anomalies(sensitivity)— detecta picos sustentados (temas que crescem em ambas as janelas 3d/21d e 7d/28d) e cobertura concentrada (entidades com altovolumeRatioe poucas agências cobrindo), com limiares por sensibilidade high / medium / low.gobus_forecast_trends(horizon_days, limit)— um score composto ponderado sobre 3 janelas (3d / 7d / 21d), com momentum (acelerando / desacelerando / estável) e confiança (nº de janelas que concordam). Traz até uma nota sobre o viés de borda de fim de semana na janela de 3 dias.gobus_score_article(unique_id)— uma nota editorial de 0 a 10, combinando legibilidade (50%) + concisão (30%) + densidade de entidades (20%), comparada ao benchmark de 90 dias da própria agência.gobus://readability-report— JSON de legibilidade por agência, ordenado pelo gap até a meta (Flesch 50).gobus://health/pipelines— health-check dos pipelines frágeis (trendingScore, sentimento, legibilidade):OK | DEGRADED | DEAD.
A Fase 2 rendeu 15 testes novos, chegando a 90 no total (red→green). E deixou uma armadilha documentada: o blueprint pedia trendingThemes { label baselineCount }, mas a introspecção do schema real mostrou themeLabel e baselineDailyAvg. As queries foram escritas com os nomes reais — porque um blueprint desatualizado quebra em runtime, não em tempo de build.
O agente precisa aprender a usar as ferramentas¶
Ter tools não basta. Uma investigação sobre o estado da arte de agentes que consomem MCP (jun/2026) apontou um problema surpreendente: o MCP entrega o quê, mas não o quando e o como (gobus-mcp#2). Um agente "limpo", sem system prompt, conectado ao Gobus tem acesso a todas as tools — mas não sabe encadeá-las na ordem certa.
O dado que mais chama atenção veio de um paper (arxiv 2602.14878): 97,1% das descrições de tools MCP têm problemas, e 56% não descrevem sequer o propósito com clareza. Como a string de description é o principal sinal que o modelo usa para escolher a tool e os argumentos, descrições vagas levam direto a chamadas erradas. A issue estabeleceu quatro alavancas de melhoria:
- Auditar as descrições das tools contra 6 componentes — propósito, parâmetros, retorno, efeitos colaterais, exemplos e restrições — com atenção especial a pré-condições (ex.:
resolve_entityé pré-requisito de qualquer operação de entidade). - Uma skill host-side (
.claude/skills/gobus.md) que ensina o agente quando acionar cada fluxo — o mapa UC → tool principal → tools auxiliares — sem depender de o usuário conhecer os nomes dos prompts. - Controlar o tamanho do contexto em respostas grandes — um
max_nodes(default 20) emget_entity_networke umsummary_onlyemget_entity_profile. - Instruir paralelismo explícito nos prompts — chamadas independentes (como
search_news+get_agency_analytics) podem rodar em paralelo, e o prompt deve dizer isso.
É a diferença entre publicar uma API e projetar uma experiência de agente.
A casa da documentação¶
Um servidor para IA precisa de documentação para humanos. O repo gobus-mcp ganhou um site MkDocs Material co-localizado, no mesmo padrão da graphql-api — 20 páginas cobrindo as 7 tools, 3 resources e 4 prompts, além de casos de uso, arquitetura e deploy, com build limpo sob mkdocs build --strict (gobus-mcp#1). E a documentação central ganhou um módulo dedicado, com diagrama Mermaid do fluxo Claude ↔ Gobus MCP ↔ graphql-api, tabela de capacidades e snippets de conexão (HTTP em produção e stdio local) (docs#53).
As contagens crescem por fase
O site (7 tools · 3 resources · 4 prompts) retrata o Gobus no seu lançamento. As Fases 1 e 2 acrescentaram 5 tools e 2 resources a essa superfície — a documentação profunda acompanha em destaquesgovbr.github.io/gobus-mcp.
Antes e depois¶
| Antes | Depois | |
|---|---|---|
| Acesso de IA ao acervo gov.br | inexistente | servidor MCP (tools · resources · prompts) |
| Caminho de dados do MCP | — | só GraphQL (sem Postgres/Typesense/Neo4j direto) |
| Transporte | SSE stateful, sessão em memória | /mcp stateless + /sse legado; stdio local para o CLI |
| Subagentes com N chamadas | 404 na 2ª chamada | funciona (stdio / stateless) |
| Timeout Cloud Run | 300s (matava a stream) | 3600s (máximo) |
| Deploy vs. Terraform | apply resetava a imagem |
ignore_changes na imagem |
| Capacidades | leitura de notícias | + legibilidade, políticas, anomalias, forecast, score |
Números¶
| Métrica | Valor |
|---|---|
| Novas queries GraphQL | 4 (graphql-api#20) |
| Tools MCP | 7 iniciais · +2 na Fase 1 · +3 na Fase 2 |
| Resources · Prompts | 3 (+2 na Fase 2) · 4 |
| Testes | 13 (graphql-api) · 48 (dual transport) · 90 no total (gobus-mcp) |
| Specs MCP suportadas | 2025-03-26 (/mcp) + 2024-11-05 (/sse) |
| Timeout Cloud Run | 300s → 3600s |
| Instâncias Cloud Run | max_instance_count = 1 (SSE stateful) |
| Páginas de documentação | 20 (gobus-mcp#1) |
| Qualidade de descrições de tools (paper) | 97,1% com problemas · 56% sem propósito claro |
| Região · Runtime | southamerica-east1 · Cloud Run · FastMCP 3.x |
Lições¶
- A regra "só GraphQL" pagou dividendos. Manter o MCP sem acesso direto aos bancos fez do servidor uma camada fina e substituível — toda a lógica de dados continua num único ponto auditável, e o Gobus herda de graça tudo o que o
graphql-apijá sabe. - O bug de sessão só existia com o usuário real. SSE stateful funcionava na sessão principal e desmoronava em subagentes efêmeros. Nenhum teste unitário pegaria isso — foi preciso um agente de verdade, fazendo chamadas espaçadas, para o
404 Could not find sessionaparecer. - A arquitetura elegante nem sempre é a viável hoje. O dual transport foi a solução tecnicamente correta, mas o que destravou o Claude Code foi o pragmático
stdiolocal. Vale construir para o futuro e desbloquear o presente. - Terraform gerencia config; CI/CD gerencia imagem. Sem
ignore_changesna imagem, cadaapplydesfazia silenciosamente o deploy. É o padrão canônico quando duas automações tocam o mesmo recurso. - Um blueprint desatualizado quebra em runtime. Confiar no
themeLabel/baselineDailyAvgreais em vez dolabel/baselineCountdo documento evitou uma falha que só apareceria com o agente já em produção — a introspecção do schema é a fonte da verdade. - Dar tools ao agente é metade do trabalho. As descrições, a skill host-side e os hints de paralelismo são o que transforma um catálogo de funções numa experiência de agente que de fato investiga o gov.br.
O Gobus saiu do zero a um analista com forecast em menos de duas semanas. O próximo passo do arco é colocar essas capacidades a serviço de perguntas reais — entidades em alta, o ciclo de vida das políticas públicas e o fechamento do trabalho iniciado aqui.
Série A evolução da plataforma DGB (jun–jul 2026) · post 3 de 4. Post anterior: O grafo do gov.br · Próximo post: Entidades em alta, políticas públicas e o fechamento do ciclo.