Skip to main content
O arquivo mcp_servers.json define quais servidores MCP o ChatCLI deve conectar. Ele é carregado automaticamente no início da sessão.

Localização

No modo client (TTY), basta criar o arquivo em ~/.chatcli/mcp_servers.json — o ChatCLI detecta automaticamente e inicializa os servidores sem precisar de variáveis de ambiente.

Auto-Enable (regras de detecção)

O ChatCLI inicializa o subsistema MCP automaticamente — manager + watcher de hot-reload — quando qualquer uma das condições abaixo é verdadeira:
A terceira regra é o que mantém o hot-reload funcional desde o primeiro uso. Se você abre o ChatCLI antes de ter um mcp_servers.json (cenário comum em primeira instalação), o watcher do ~/.chatcli/ já está rodando — basta criar o arquivo e os servidores sobem na hora, sem chatcli restart.

Hot-Reload e Tolerância a Erros

O watcher do mcp_servers.json reconcilia o estado vivo com o que está em disco usando fsnotify. Eventos de Create, Write, Rename e Remove são debounced em 400 ms para evitar reload mid-write quando editores reescrevem via rename.
Salvar um JSON inválido não derruba o ChatCLI: o manager continua registrado e o watcher segue ativo. Você corrige o arquivo no editor, salva, e o próximo evento dispara Reload normalmente. Útil quando você está editando ao vivo durante uma sessão.

Formato


Campos

Core (obrigatórios)

Tier 1 — efeito direto em runtime

Tier 2 — capacidades adicionais

Tier 3 — Catch-all (Extensions)

Qualquer chave JSON que não seja um dos campos acima é preservada verbatim quando o chatcli regrava o arquivo. Útil pra colar configs de outros clientes (AWS EKS MCP, Cline, Cursor) sem perder anotações vendor-specific. Importante:
  • As chaves extra são ignoradas pelo runtime do chatcli — só sobrevivem ao round-trip de save/load.
  • Não podem shadow um campo tipado: um JSON manual com Extensions["command"] nunca substitui o command real.
  • Quando promovermos uma chave (e.g. oauth2) pra campo tipado em uma release futura, o conteúdo já existente em Extensions migra automaticamente.

Variáveis de Ambiente (env)

O campo env aceita um objeto chave-valor que é mesclado com o ambiente do processo pai (os.Environ()). Duas regras importantes:

1. Herança do PATH e caches

O processo MCP herda todo o ambiente do ChatCLI antes de aplicar os valores de env. Sem essa herança, launchers como npx, uvx, docker e pipx não encontrariam seus binários nem seus caches por usuário.
Aqui o servidor filho recebe PATH + HOME + FS_LOG_LEVEL=debug — não apenas FS_LOG_LEVEL isolado. Se a chave do env colidir com o ambiente herdado, o valor do env ganha (semântica do Unix: última atribuição no cmd.Env prevalece).

2. Expansão ${VAR} / $VAR

Valores em env passam por os.Expand contra o ambiente pai antes de ir pro processo filho. Isso permite manter secrets fora do JSON — em variáveis de shell, ou em arquivos .env carregados via source/direnv/mise/asdf.
Se o shell tem GH_TOKEN=ghp_xxx exportado, o servidor MCP recebe GITHUB_TOKEN=ghp_xxx. Se a variável referenciada não existe, o resultado é string vazia — mesmo comportamento do shell. Faça lint do seu shell antes de assumir que o token chegou.
Use sempre ${VAR} em vez de literais ("GITHUB_TOKEN": "ghp_xxx"). Tokens em plaintext no JSON acabam em git history, em logs, em backups e em screenshots. A expansão ${VAR} é a forma idiomática — paridade com Claude Desktop e OpenCode.
A expansão acontece uma vez no spawn do processo. Se você mudar GH_TOKEN no shell durante uma sessão, o servidor MCP que já estava rodando continua com o valor antigo. Use /mcp restart <nome> para forçar a re-resolução.

Padrões recomendados


Auto-aprovação e Trust

ChatCLI’s tool-execution pipeline tem um audit-log que registra toda invocação MCP coberta por autoApprove, alwaysAllow ou trust. O helper Manager.ShouldAutoApprove(toolName) consultado pelo pipeline já está pronto pro futuro gate de aprovação interativa no coder mode.

autoApprove / alwaysAllow

Listas de tool names que dispensam aprovação. Aceitam "*" (qualquer tool do server). Os nomes podem ser usados com ou sem o prefixo mcp_"read_file" e "mcp_read_file" casam.
alwaysAllow é alias popularizado pelo Cline — chatcli funde as duas listas no mesmo set runtime, então copy-paste de configs do Cline funciona sem renomear.

trust: true

Auto-aprova toda tool do server, sem precisar listar uma por uma:
trust: true é destinado a servidores que o operador separadamente verificou (código auditado, container imutável, etc.). ChatCLI emite um warning no startup toda vez que conecta com trust: true pra que a escolha fique visível em logs — uma config trust commitada por engano não passa silenciosa.

Audit log

Toda chamada auto-aprovada gera um entry info-level:
Grep no log do chatcli (CHATCLI_LOG_LEVEL=info ou maior) para auditar tudo que passou pelo bypass.

Filtragem de tools

Quando um servidor expõe dezenas de tools mas o workflow só usa um subconjunto, esconder o resto economiza tokens em todo turno do LLM.

disabledTools (blocklist)

Aceita "*" (esconde tudo — útil pra desabilitar um server sem remover o entry).

enabledTools (allowlist — precede blocklist)

Quando não-vazio, apenas as tools listadas são expostas. disabledTools no mesmo server é ignorado. /mcp status mostra a contagem de tools escondidas (blocklist) ou conta total quando a allowlist está ativa.

Timeouts

Padrões cobrem o caso comum (npx -y cold start). Override quando o servidor demora mais que isso por desenho.

timeout (segundos)

Cap por chamada RPC. Padrão 60s. Aplica-se tanto a tools/call quanto a tools/list.

initTimeout (segundos)

Cap do handshake MCP initialize — e, em SSE, do endpoint-event wait. Padrão 10s. Bump pra servidores que carregam credenciais, fazem auth-handshake ou montam o ambiente no startup:
SSE: o http.Client.Timeout é setado para max(timeout, initTimeout) pra que um timeout curto não mate o GET de SSE (que fica aberto pelo tempo da conexão).

Working directory (cwd) — stdio

  • Aceita ${VAR} / $VAR expansion (mesmo lookup do env).
  • Aceita leading ~/ expandido contra HOME.
  • Validado no spawn: caminho inexistente ou non-directory faz a conexão falhar com erro acionável em vez de silenciosamente herdar o CWD do chatcli.
  • Vazio = herda o CWD do chatcli (comportamento legado).
Ignorado em SSE.

Headers HTTP e autenticação — sse / http

Os dois transports HTTP (sse e http) compartilham o mesmo mecanismo de headers customizados e autenticação tipada.

headers

Headers extra anexados a toda request HTTP do transport:
  • sse: GET do stream + POST de mensagens JSON-RPC.
  • http: cada POST JSON-RPC ao endpoint Streamable HTTP.
Values passam por ${VAR} expansion (mesmo lookup do env).

Autenticação HTTP (auth)

Bloco tipado com três modos:

bearer

Resulta em Authorization: Bearer <token>.

basic

Resulta em Authorization: Basic base64(user:pass).

header (custom)

Resulta em X-API-Key: <token>. Quando header é omitido, o default é X-API-Key.
Salvaguardas: empty type é no-op mesmo com token/username populados (evita meio-config vazando credencial). Token cuja env var não existe suprime o header inteiro em vez de mandar Authorization: Bearer vazio (que passaria por um auth check ingênuo no server).
Authorization/headers customizados são re-aplicados a cada request, então rotacionar a env var em runtime já produz a próxima chamada com o token novo — sem /mcp restart.

OAuth 2.1 (interativo, sem config)

Os três modos de auth acima são para tokens já emitidos. Quando um servidor remoto exige um desafio 401 + fluxo OAuth (ex.: o servidor MCP da AWS), você não configura auth de jeito nenhum — o ChatCLI descobre o authorization server, registra um cliente dinamicamente, roda o fluxo PKCE no navegador e guarda/renova o token no cofre de auth criptografado automaticamente. Autorize com /mcp login <nome> ou deixe o agente chamar @mcp-login. Veja Autorização OAuth 2.1. Env var opcional: CHATCLI_MCP_OAUTH_PORT (porta do callback loopback, padrão 8765).

Metadados (description, tags, category)

Campos cosméticos rendered em /mcp status pra facilitar identificar quem é quem quando há muitos servers:
Cada linha de output é opt-in: configs sem nenhum desses campos renderizam exatamente como antes da v1.115.

Channel subscriptions (channels)

Quando um servidor MCP emite push notifications (JSON-RPC messages sem id), o ChatCLI as encaminha pro MCP Channels ring — uma estrutura durável que alimenta o system prompt, o banner de inbox e o trigger engine. O campo channels na config do servidor é um allow-list de channel names: só os channels literalmente listados são aceitos para esse servidor. Útil quando um servidor é tagarela em múltiplas categorias (ex: emite tanto ci-pipeline quanto deploys/staging quanto metrics/raw) mas seu workflow só se importa com uma fatia.
Match é literal (não há glob aqui). "channels": ["alerts"] não pega "alerts/critical". Use o nome exato do channel que o servidor emite. Os globs (alerts/*) existem só nas rules de trigger (triggers.json), que rodam depois desse filtro.
O filtro acontece na entrada — antes do ring em memória e antes da persistência. Channels rejeitados não consomem o budget do ring nem aparecem em /channel list. Funciona com os três transports: sse (via stream), http (via listener GET) e stdio (via notifications no stdout). Veja MCP Channels pra detalhes do que acontece depois do filtro.

Trigger rules (~/.chatcli/mcp/triggers.json)

Arquivo separado do mcp_servers.json, opt-in, que descreve como o ChatCLI deve reagir a channel events. Sem esse arquivo, channels funcionam só como inbox passivo. Com ele, você define rules que disparam banners, perguntas yes/no ou execuções automáticas do agent.

Localização

Carregado automaticamente no startup quando o MCP está habilitado. Reload manual: /channel rules reload.

Schema

Campos

Modos — resumo

Validação

Falhas no parse rejeitam o arquivo inteiro (atomic apply — ou todas as rules entram, ou nada muda). Em reload com erro, o ChatCLI mantém as rules anteriores ativas e mostra o erro na saída. Erros típicos:

Exemplos prontos

CI watcher passivo

Prod alerts com confirmação

Canary deploys autônomos

Persistência do ring

Separado das rules, o ChatCLI mantém um arquivo JSONL durável em ~/.chatcli/mcp/channels.jsonl com todas as mensagens recebidas. Rotaciona aos 10 MiB pra channels.jsonl.1 (um backup). Replay automático no boot (até 200 últimas mensagens). Detalhes completos: MCP Channels — Persistência.

Exemplos por Transporte

Servidores locais que se comunicam via stdin/stdout com JSON-RPC 2.0:
O ChatCLI gerencia o ciclo de vida do processo: inicia no startup, mata no shutdown. Content-Length framing (LSP-style) é usado na comunicação.

Testando com curl

Útil pra isolar problemas de proxy/CA/handshake antes de envolver o ChatCLI. O wire é o mesmo que o transport http envia:
Flags importantes:
  • -i mostra response headers (pro Mcp-Session-Id).
  • -N desativa buffering — se o servidor responder SSE, você vê os eventos chegando em tempo real em vez de esperar fechar.
Se o curl funciona mas o ChatCLI dá timeout de 10s, é quase sempre proxy corporativo não exportado (Go respeita HTTPS_PROXY/HTTP_PROXY/NO_PROXY no shell — curl lê ~/.curlrc e o Go não) ou CA interna ausente do trust store que o Go enxerga (SSL_CERT_FILE=/path/corp-ca.pem resolve).

Exemplos Completos

Múltiplos Servidores

Nunca commite tokens ou senhas no arquivo. Use a sintaxe ${VAR} (documentada acima em Variáveis de Ambiente) para que o secret venha do shell — nunca do JSON.

Servidor Desabilitado

Defina "enabled": false para manter a configuração sem conectar:

Protocolo

O ChatCLI fala MCP Protocol v2024-11-05 (anunciado no initialize) sobre três transports:
  • stdio — JSON-RPC 2.0 via stdin/stdout do processo filho (newline-delimited).
  • sse — modelo HTTP+SSE da spec original (GET /sse + POST /messages).
  • http — Streamable HTTP da revisão 2025-03-26 (POST único ao endpoint configurado).
Métodos suportados:

Nomeação de Tools

O prefixo mcp_ é adicionado automaticamente para evitar colisões com ferramentas nativas (plugins, @coder, @webfetch, etc.).

Servidores MCP Populares

Consulte modelcontextprotocol.io para a lista completa de servidores MCP.

Troubleshooting

Verifique:
  1. O comando existe e está no PATH (which npx)
  2. O arquivo de config está em ~/.chatcli/mcp_servers.json
  3. enabled está true
  4. Use /mcp status para ver erros de conexão
  5. Use /mcp logs <nome> para ver as últimas linhas de stderr do servidor (npm 404, panic, missing executable)
  6. Verifique logs com CHATCLI_LOG_LEVEL=debug
A causa quase sempre é uma das duas:
  1. Variável não exportada no shell${GH_TOKEN} na config expande para vazio se GH_TOKEN não está em os.Environ(). Confirme com echo $GH_TOKEN antes de iniciar o ChatCLI.
  2. Mudou o token mid-sessão — a expansão acontece no spawn. Após exportar a variável nova, rode /mcp restart <nome> para forçar o re-spawn com o ambiente atualizado.
Para inspecionar o que o servidor recebeu, abra /mcp logs <nome> — servidores MCP costumam logar “missing token” / 401 em stderr.
A partir das fixes de hot-reload, o watcher é iniciado quando o diretório pai existe — mesmo sem o arquivo. Se você nunca criou ~/.chatcli/, não há watcher.Solução: crie o diretório uma vez (mkdir -p ~/.chatcli) e reinicie o ChatCLI. Daí em diante, criar/editar mcp_servers.json dispara reload automaticamente.Para forçar um reload manual a qualquer momento: /mcp reload.
Não. Quando o LoadConfig falha (0 bytes, JSON inválido), o ChatCLI loga um warning mas mantém o manager + watcher rodando. Você corrige o JSON no editor, salva, e o próximo evento dispara Reload normalmente — sem reiniciar a sessão.
  1. O servidor pode não suportar tools/list
  2. Use /mcp tools para listar tools descobertas
  3. Reinicie com /mcp restart
O timeout padrão é 60s por chamada RPC (cobre cold-start de npx -y). Se um servidor específico demora mais:
  1. Suba o timeout (segundos) só pra esse server na config — veja Timeouts.
  2. Se a falha é no handshake (não na chamada), bumpe initTimeout separadamente.
  3. Para servers de longa duração (proxies LLM, agentes lentos), prefira SSE — o stream fica aberto enquanto o server processa.
Verifique:
  1. autoApprove ou alwaysAllow no config tem o nome dela (ou "*") — veja Auto-aprovação.
  2. trust: true no server faz bypass total. Toda chamada auto-aprovada gera log info MCP tool auto-approved by config tool=<nome>. Grep no log do chatcli pra auditar.
  3. Para remover o bypass, edite o JSON (hot-reload reaplica) ou rode /mcp reload.
  1. Confira que a env var referenciada por auth.token/headers está exportada no shell. ${UNSET_VAR} expande para string vazia e ChatCLI suprime o header inteiro em vez de mandar Authorization: Bearer (vazio) — então o server reporta 401 como se nem auth tivesse vindo.
  2. Rotacionou o token no shell? Headers são re-aplicados a cada request, não precisa de /mcp restart. Mas confirme que o shell que iniciou o ChatCLI tem a nova variável (export, source, etc.).
  3. Para type: "header" com nome custom, confirme que o server espera exatamente o nome configurado (ChatCLI envia literalmente o que está em auth.header).
Erro retornado por servidores Streamable HTTP que checam o primeiro media type do Accept como preferência do cliente (FastMCP, alguns gateways MCP). ChatCLI envia Accept: text/event-stream, application/json exatamente nessa ordem desde a primeira release com transport: "http" — então esse erro só aparece se você sobrescrever Accept via headers na config. Remova a entrada Accept dos headers e o transport assume o padrão.
Quase sempre é uma de três causas (em ordem de probabilidade):
  1. Proxy corporativo não exportado. Curl lê ~/.curlrc (com proxy=...), Go não. Exporte HTTPS_PROXY, HTTP_PROXY e NO_PROXY no shell que roda o ChatCLI — nosso http.Client usa http.DefaultTransport que respeita essas três variáveis nativamente.
  2. Barra final faltando na url. Servidores FastMCP / Starlette / FastAPI 307-redirecionam /mcp para /mcp/. O Go segue o 307 com POST, mas proxies no caminho frequentemente quebram o redirect (body ou headers descartados). Configure url com a barra exatamente como o servidor responde 200 OK no curl.
  3. CA corporativa fora do trust store que o Go enxerga. Em macOS especialmente, CAs adicionadas via mobile-config às vezes não são pegadas. Exporte SSL_CERT_FILE=/etc/ssl/certs/corp-ca.pem apontando pra mesma bundle que o curl usa.
Pra distinguir entre essas causas, bumpe initTimeout para 60 ou 120 na config — se passa de “hang em 10s” para “responde em 30s”, é proxy lento; se continua hangando, é (1) ou (2).
Provavelmente foi escondida por config:
  1. disabledTools lista o nome dela — remova ou inverta para enabledTools.
  2. enabledTools está populado mas a tool não está na allowlist — adicione ou esvazie enabledTools para voltar ao default “tudo visível”.
  3. /mcp status mostra a contagem de tools escondidas (blocklist).
Verifique:
  1. O nome no overrides está exatamente como o nome do plugin, incluindo o @ (ex: "@webfetch", não "webfetch")
  2. O servidor MCP está connected (/mcp status)
  3. O campo enabled do server está true
  4. Se o server caiu e reconectou, o override volta automaticamente no próximo turno de conversa
O campo overrides apenas esconde o built-in — não valida se o MCP server realmente oferece uma ferramenta equivalente. Se você colocar "overrides": ["@webfetch"] mas o MCP server não tem web_fetch, o LLM simplesmente não terá essa capacidade. Remova o override ou adicione a ferramenta ao MCP server.
Remova o built-in da lista overrides do servidor MCP. Ambas as ferramentas (MCP e built-in) ficarão visíveis simultaneamente e o LLM escolherá baseado no nome e descrição de cada uma.