mcp_servers.json define quais servidores MCP o ChatCLI deve conectar. Ele é carregado automaticamente no início da sessão.
Localização
~/.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: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 domcp_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.
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 ocommandreal. - Quando promovermos uma chave (e.g.
oauth2) pra campo tipado em uma release futura, o conteúdo já existente emExtensionsmigra 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 deenv. Sem essa herança, launchers como npx, uvx, docker e pipx não encontrariam seus binários nem seus caches por usuário.
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.
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.
${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.Padrões recomendados
Auto-aprovação e Trust
ChatCLI’s tool-execution pipeline tem um audit-log que registra toda invocação MCP coberta porautoApprove, 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:
Audit log
Toda chamada auto-aprovada gera um entry info-level: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)
"*" (esconde tudo — útil pra desabilitar um server sem remover o entry).
enabledTools (allowlist — precede blocklist)
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:
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}/$VARexpansion (mesmo lookup doenv). - Aceita leading
~/expandido contraHOME. - 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).
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.
${VAR} expansion (mesmo lookup do env).
Autenticação HTTP (auth)
Bloco tipado com três modos:
bearer
Authorization: Bearer <token>.
basic
Authorization: Basic base64(user:pass).
header (custom)
X-API-Key: <token>. Quando header é omitido, o default é X-API-Key.
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).OAuth 2.1 (interativo, sem config)
Os três modos deauth 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:
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.
"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./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
/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). Emreload 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
- stdio (Local)
- SSE (HTTP+SSE)
- http (Streamable HTTP)
Testando com curl
Útil pra isolar problemas de proxy/CA/handshake antes de envolver o ChatCLI. O wire é o mesmo que o transporthttp envia:
-imostra response headers (proMcp-Session-Id).-Ndesativa buffering — se o servidor responder SSE, você vê os eventos chegando em tempo real em vez de esperar fechar.
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
Servidor Desabilitado
Defina"enabled": false para manter a configuração sem conectar:
Protocolo
O ChatCLI fala MCP Protocol v2024-11-05 (anunciado noinitialize) 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).
Nomeação de Tools
mcp_ é adicionado automaticamente para evitar colisões com ferramentas nativas (plugins, @coder, @webfetch, etc.).Servidores MCP Populares
Troubleshooting
Servidor não conecta
Servidor não conecta
- O comando existe e está no PATH (
which npx) - O arquivo de config está em
~/.chatcli/mcp_servers.json enabledestátrue- Use
/mcp statuspara ver erros de conexão - Use
/mcp logs <nome>para ver as últimas linhas de stderr do servidor (npm 404, panic, missing executable) - Verifique logs com
CHATCLI_LOG_LEVEL=debug
O servidor MCP não enxerga o token / connection string
O servidor MCP não enxerga o token / connection string
- Variável não exportada no shell —
${GH_TOKEN}na config expande para vazio seGH_TOKENnão está emos.Environ(). Confirme comecho $GH_TOKENantes de iniciar o ChatCLI. - 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.
/mcp logs <nome> — servidores MCP costumam logar “missing token” / 401 em stderr.Hot-reload não dispara quando crio o mcp_servers.json
Hot-reload não dispara quando crio o mcp_servers.json
~/.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.Editei o JSON e ele tem erro de sintaxe — perdi tudo?
Editei o JSON e ele tem erro de sintaxe — perdi tudo?
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.Tools não aparecem
Tools não aparecem
- O servidor pode não suportar
tools/list - Use
/mcp toolspara listar tools descobertas - Reinicie com
/mcp restart
Timeout na execução de tool
Timeout na execução de tool
npx -y). Se um servidor específico demora mais:- Suba o
timeout(segundos) só pra esse server na config — veja Timeouts. - Se a falha é no handshake (não na chamada), bumpe
initTimeoutseparadamente. - Para servers de longa duração (proxies LLM, agentes lentos), prefira SSE — o stream fica aberto enquanto o server processa.
Tool MCP foi auto-aprovada sem eu confirmar
Tool MCP foi auto-aprovada sem eu confirmar
autoApproveoualwaysAllowno config tem o nome dela (ou"*") — veja Auto-aprovação.trust: trueno server faz bypass total. Toda chamada auto-aprovada gera log infoMCP tool auto-approved by config tool=<nome>. Grep no log do chatcli pra auditar.- Para remover o bypass, edite o JSON (hot-reload reaplica) ou rode
/mcp reload.
Servidor SSE retornou 401/403
Servidor SSE retornou 401/403
- Confira que a env var referenciada por
auth.token/headersestá exportada no shell.${UNSET_VAR}expande para string vazia e ChatCLI suprime o header inteiro em vez de mandarAuthorization: Bearer(vazio) — então o server reporta 401 como se nem auth tivesse vindo. - 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.). - Para
type: "header"com nome custom, confirme que o server espera exatamente o nome configurado (ChatCLI envia literalmente o que está emauth.header).
HTTP transport: -32600 / 'Not Acceptable: Client must Accept text/event-stream'
HTTP transport: -32600 / 'Not Acceptable: Client must Accept text/event-stream'
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.HTTP transport: timeout de ~10s no initialize, mas curl funciona
HTTP transport: timeout de ~10s no initialize, mas curl funciona
- Proxy corporativo não exportado. Curl lê
~/.curlrc(comproxy=...), Go não. ExporteHTTPS_PROXY,HTTP_PROXYeNO_PROXYno shell que roda o ChatCLI — nossohttp.Clientusahttp.DefaultTransportque respeita essas três variáveis nativamente. - Barra final faltando na
url. Servidores FastMCP / Starlette / FastAPI 307-redirecionam/mcppara/mcp/. O Go segue o 307 com POST, mas proxies no caminho frequentemente quebram o redirect (body ou headers descartados). Configureurlcom a barra exatamente como o servidor responde 200 OK no curl. - 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.pemapontando pra mesma bundle que o curl usa.
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).Tool MCP não aparece no /mcp tools
Tool MCP não aparece no /mcp tools
disabledToolslista o nome dela — remova ou inverta paraenabledTools.enabledToolsestá populado mas a tool não está na allowlist — adicione ou esvazieenabledToolspara voltar ao default “tudo visível”./mcp statusmostra a contagem de tools escondidas (blocklist).
Override não está funcionando — o LLM ainda vê o built-in
Override não está funcionando — o LLM ainda vê o built-in
- O nome no
overridesestá exatamente como o nome do plugin, incluindo o@(ex:"@webfetch", não"webfetch") - O servidor MCP está connected (
/mcp status) - O campo
enableddo server estátrue - Se o server caiu e reconectou, o override volta automaticamente no próximo turno de conversa
Built-in sumiu mas o MCP server não tem a ferramenta equivalente
Built-in sumiu mas o MCP server não tem a ferramenta equivalente
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.Quero forçar o uso do built-in mesmo com MCP ativo
Quero forçar o uso do built-in mesmo com MCP ativo
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.