Skip to main content
O MCP Channels é o sistema de push notifications do ChatCLI. Servidores MCP enviam mensagens proativas (alertas, eventos de CI, deploys, webhooks externos) e o ChatCLI as armazena em um ring durável, injeta nas próximas turns de chat / agent / coder, e — opcionalmente — aciona o agent automaticamente via um rules engine com três modos de operação.
Channels funcionam em todos os três transports MCP suportados pelo ChatCLI:
  • sse (HTTP+SSE clássico) — listener persistente no GET /sse.
  • http (Streamable HTTP da spec 2025-03-26) — listener opt-in via GET no endpoint configurado.
  • stdio — qualquer mensagem JSON-RPC sem id que o processo filho emita em stdout é tratada como notification.
A entrega é passiva por padrão (banner discreto no próximo prompt) e reativa por opt-in explícito (regras confirm ou auto em ~/.chatcli/mcp/triggers.json).

Arquitetura em uma figura

Cada caixa é um arquivo do código real (cli/mcp/channels.go, cli/mcp/triggers/triggers.go, cli/channel_triggers.go, cli/agent_system_prompt.go). A separação é proposital: o ChannelManager é process-local e independente do CLI; o engine de triggers é um pacote separado sem dependência reversa; o CLI pluga via OnMessage.

Como uma mensagem chega ao agent

1

Server emite notification

Servidor MCP envia uma mensagem JSON-RPC sem id (notification, no jargão do spec). Pode ser via SSE stream, GET de Streamable HTTP, ou linha de stdout em stdio.
2

Transport extrai e roteia

O transport correspondente parseia, identifica como notification (id ausente) e chama ChannelManager.ProcessSSENotification. O método da notification vira o channel: notifications/ci-pipeline → channel ci-pipeline; channel/message com params.channel → channel params.channel.
3

Filtro de subscription

Se a config do servidor tem channels: [...] e o channel não está na lista, a mensagem é descartada (log debug). Lista vazia significa “aceita tudo”.
4

Persist + ring + unread

Mensagem ganha um seq monotônico, vai pro ring em memória (FIFO, default 200), é gravada append-only em ~/.chatcli/mcp/channels.jsonl, e o contador de unread incrementa.
5

Fan-out para subscribers

Cada OnMessage registrado recebe uma cópia. O CLI registra dois: o engine de triggers (que avalia rules) e o renderer de banner (que prepara a UI).
6

System prompt — turno seguinte

No próximo turn de chat/agent/coder, as últimas 5 mensagens do ring são injetadas como um bloco no system prompt (uncached, pra não invalidar cache do prefixo).
7

Trigger reativo (opcional)

Se alguma rule casou, o engine emite uma Action que cai em uma das três filas: notify (banner discreto), confirm (pergunta yes/no), auto (executa o agent no próximo tick do prompt).

Persistência

Garantias

  • Durável entre sessões: mensagens recebidas enquanto o ChatCLI estava fechado ficam visíveis no próximo boot (até o limite de load).
  • Append-only: nunca reescrevemos o arquivo. Crash do processo no meio de uma write deixa, no pior caso, uma linha truncada — o loader skip por linha, então uma linha corrompida nunca contamina as outras.
  • Rotação automática: ao chegar em 10 MiB, o arquivo é renomeado para .1 e um novo é aberto. Só mantemos um histórico — channels são telemetria, não log de auditoria forense.
  • Best-effort: se a escrita falhar (disco cheio, permissão), o ChatCLI loga um warning uma vez, marca persistence como desabilitada só nessa sessão e continua servindo o ring em memória.

Boot — replay

Ao iniciar, o ChatCLI lê as últimas 200 linhas combinadas de channels.jsonl.1 + channels.jsonl em ordem cronológica. Mensagens recarregadas:
  • entram no ring;
  • não contam como unread (já foram vistas);
  • preservam seu seq original (o contador interno é avançado pro máximo observado).

Como configurar

Persistência é ligada por padrão quando HOME é resolvível. Em ambientes containerizados sem HOME (raro), o ChannelManager cai para in-memory-only e loga um info no startup. Não há flag de configuração para “desligar persistence” — o caminho é deletar manualmente o arquivo se necessário (rm ~/.chatcli/mcp/channels.jsonl*).

Filtro por servidor — channels

O campo opcional channels na config do servidor MCP é um allow-list que decide quais channels desse servidor são aceitos pelo ring.
Regras:
  • Vazio/omitido → aceita todos os channels que o servidor emitir.
  • "*" explícito → equivalente a vazio.
  • Lista → só os channels listados literalmente entram no ring; outros são descartados (log debug).
  • Whitespace nos entries é trimmado, então " alerts " casa com "alerts".
O filtro acontece no momento da chegada da mensagem (antes do ring e da persistência), então um servidor barulhento que emite 20 channels mas você só quer 2 não polui nada.
Diferente das tools (enabledTools/disabledTools), aqui não há suporte a glob no channels da ServerConfig — é match literal. Globs (alerts/*, *) só existem nas rules de trigger, que rodam depois do filtro.

Auto-injection no system prompt

Em todo turn (chat, agent, coder), as 5 mensagens mais recentes do ring são adicionadas ao system prompt como um bloco extra:

Garantias importantes

  • Sem cache hint: esse bloco é volátil e propositalmente fica fora do prefixo cacheado da Anthropic. Inserir um bloco que muda toda turn dentro do cache trasharia o KV cache inteiro — pior do que não cachear nada.
  • Mesmo conteúdo em chat / agent / coder: o builder de system prompt em todos os três modos foi unificado pra incluir o bloco.
  • Ring vazio → bloco omitido: zero overhead quando você nunca recebeu nada.

Por que apenas 5?

Calibrado pra equilíbrio: o suficiente pra dar contexto recente (CI rodando + alert mais quente) sem ocupar tokens demais quando o usuário tem muitos servers conversando. Se você precisar de mais histórico em uma turn específica, use /channel inject que injeta as últimas 10.

Reactive Triggers (rules engine)

Esta é a parte opt-in. Quando você quer que o ChatCLI reaja a eventos (ex: “se o CI falhou, peça pra investigar”; “se houver alerta crítico, rode o agent”), defina rules em ~/.chatcli/mcp/triggers.json.

Schema da rule

Tabela de campos:

Match — exemplos práticos

Pega channel exatamente alerts/critical, qualquer servidor.
Pega alerts/critical, alerts/warning, alerts/info — qualquer sub-channel de alerts/. Não pega errors/critical (prefixo diferente).
Tripla condição: servidor prom-alerts, channel alerts/*, e content casando o regex. Todas precisam ser satisfeitas (AND).

Modos

mode: notify (default — zero surpresa)

Quando a rule casa:
  1. Imediatamente: um toast aparece no stderr com o nome da rule e um preview do content.
  2. Na próxima vez que você for digitar (próximo prompt tick), aparece um banner acima do prompt com o item na inbox.
  3. Nada roda. Você decide se quer agir.

mode: confirm (user-in-the-loop)

Quando a rule casa:
  1. Toast no stderr + prompt no banner: “run /channel confirm <id> yes ou /channel confirm <id> no”.
  2. Nada roda até o usuário responder.
  3. /channel confirm <id> yes (ou só /channel confirm <id>, default é yes) dispara o agent no template da rule.
  4. /channel confirm <id> no descarta a action.
Confirm actions têm expiração de 30 min — se você não responder, somem da fila pra não acumular indefinidamente.

mode: auto (autônomo — opt-in forte)

Requer tools whitelist obrigatória (validação rejeita rules auto sem tools). Quando casa:
  1. Toast no stderr informando que o trigger entrou na fila.
  2. No próximo prompt tick (depois de drainar park resumes e antes de processar input do usuário), o agent é acionado automaticamente no template da rule.
  3. A execução é renderizada dentro de uma AUTO-AGENT envelope box — visualmente distinta de uma resposta a turno normal.
  4. Esc aborta como em qualquer execução de agent.

Guard-rails do auto

  • tools whitelist obrigatória — validação no startup rejeita rule auto sem tools.
  • rateLimit e dedupWindow recomendados — sem eles, um servidor barulhento pode disparar dezenas de turns por minuto.
  • Esc sempre aborta, não importa se o agent foi triggado por user ou por rule.
  • /channel pause desliga tudo globalmente — usar antes de uma operação sensível para evitar interferência.

Configuração de rules

Schema root:
Exemplos prontos pra colar:

CI watcher (notify only — safe default)

Prod alerts → confirm

Canary deploys → auto (com whitelist apertada)

Validação

Falhas de schema rejeitam o arquivo inteiro (atomic apply — ou tudo entra, ou nada muda). Erros comuns: Reload em runtime: /channel rules reload.

Comandos /channel

Todos os subcomandos suportam autocomplete (Tab após /channel ), e a completude é multi-nível: clear oferece --all, rules oferece reload, confirm completa os IDs pendentes ao vivo e depois a decisão yes/no, e run completa os seqs de mensagens recentes. A command palette (bare /channel) compartilha a mesma fonte.

Exemplos

Listando

Filtrando

Rodando manualmente uma mensagem

Inspecionando rules


@channels — o agente lê o inbox

Tudo acima é dirigido pelo usuário. No agent/coder mode o modelo ganha a própria janela read-mostly para o mesmo inbox através do builtin @channels, para que eventos empurrados guiem uma tarefa em andamento sem esperar o usuário colar.
Ações confirm pendentes nunca são expostas ao modelo. Elas gateiam side effects reais (agent rodando num template de rule) e permanecem decisão humana via /channel confirm <id>. O agente pode ler e reconhecer o inbox; não pode aprovar ações em nome do usuário. list/unread são read-only; ack só reseta contadores de atenção.
Unread inteligente. Notificações de protocolo que o cliente já tratou sozinho — notavelmente os refreshes de tool-list dinâmica — são registradas no ring para auditoria mas não incham o contador de unread. Um inbox que se reabastece de conversa máquina-a-máquina nunca poderia ser drenado; só eventos que realmente precisam de atenção contam.

Auto-injection vs /channel inject — quando usar cada um

A auto-injection é silenciosa e contínua — basta o ring ter conteúdo. /channel inject é uma ação explícita que adiciona um system message ao histórico (permanece até a próxima compaction). /channel run é a opção “investigar agora” sem regra cadastrada.

Reconexão e fault tolerance

Sessões server-side em Streamable HTTP: quando o servidor emite Mcp-Session-Id no initialize, o transport ecoa o header em todas as requests (incluindo o GET do listener). Reconexão preserva a sessão automaticamente.

Casos de uso

CI/CD

rule notify em ci-pipeline. Você vê falhas no banner; /channel run <seq> lança o agent pra investigar com tools.

Prod alerts (Prometheus/Datadog)

rule confirm em alerts/critical com prompt template. Cada alerta vira uma pergunta “investigate?” que vc responde sob demanda.

Canary deploys

rule auto com whitelist apertada (kubectl_*, http_request). Toda canary inicia automaticamente as smoke checks.

Webhooks externos (GitHub/Jira/Slack)

Servidor MCP custom traduz webhooks em notifications. ChatCLI puxa do ring nos próximos turns ou aciona regras dedicadas.

Limites e trade-offs

Estes números não são configuráveis hoje. Eles podem ser parametrizados em uma release futura — abra uma issue se você bater em algum.

Troubleshooting

Comportamento esperado se você só tem rules notify ou nenhuma rule. notify é passivo por desenho. Para ação:
  • Adicione uma rule confirm ou auto em ~/.chatcli/mcp/triggers.json e rode /channel rules reload.
  • Ou rode /channel run <seq> manualmente em cima da mensagem.
Pode ser listener desligado. O listener de Streamable HTTP é opt-in da spec — checa:
  1. /mcp logs <nome> — se aparecer server returned 405 on GET <url> ou 404 ou 501, o servidor não implementa push e o listener parou limpamente.
  2. Caso contrário, verifica se o servidor está emitindo notifications no formato JSON-RPC sem id. Conteúdo data: não-JSON também é capturado (cai no channel raw), mas não dispara rules específicas de outros channels.
Em ordem:
  1. /channel rules — confirma que a rule existe e está ativa.
  2. /channel pause foi rodado? /channel resume reativa.
  3. Match: o server/channel/contentRegex realmente bate? Lembre que channel: "alerts" não pega "alerts/critical" — use "alerts/*".
  4. rateLimit ou dedupWindow — uma fire recente pode estar suprimindo.
Sim, com a sessão fechada: rm ~/.chatcli/mcp/channels.jsonl*. Na próxima boot, o ring começa vazio.Em runtime, a rotação corta no limite de 10 MiB automaticamente — você pode chegar até ~20 MiB no total (ativo + .1) antes do próximo corte.
/channel rules reload. Se a recarga falhar (schema inválido), o erro aparece na resposta e o ChatCLI mantém as rules anteriores ativas — sem fica num estado “sem rules”.Se o erro for de regex, copie-cole o regex pra https://regex101.com flavor “Golang” para isolar.
Sintoma: o agent investiga, faz algo que dispara outra notification, que dispara o trigger de novo. Fix:
  1. Bump rateLimit significativamente (ex: "15m").
  2. Refine o contentRegex pra só pegar o caso patológico.
  3. /channel pause enquanto você investiga a config offline.
Use /channel inject — coloca as últimas 10 como system message no histórico. Persiste no histórico até a próxima compaction.Para visualizar sem injetar no LLM: /channel list mostra até 20.
Verifique a tools whitelist da rule. Em auto, as tools listadas podem ser invocadas sem aprovação extra; tool fora da whitelist cai no fluxo normal de aprovação do agent — o que pode ser sua intenção ou não. Refine a whitelist no triggers.json.

Próximos passos

MCP Integration

Configure servidores MCP — base pra usar channels.

MCP Config

Referência completa do mcp_servers.json (campo channels incluído).

Sistema de Hooks

Hooks de lifecycle do ChatCLI — complementam channels em casos de eventos internos.

Command Reference

Lista completa de slash commands, incluindo /channel.