chatmux
Servidor MCP local-first que coloca seus próprios chats do LINE e Telegram em uma única camada de dados — um daemon na sua máquina faz login com sua própria conta, armazena mensagens em JSONL + SQLite/FTS5 e as expõe para clientes MCP como Claude Code. Não é um comando npx de uma linha: você clona o repositório e executa o daemon por conta própria.
Documentação
chatmux
Daemon de camada de dados de chat pessoal, local-first. Conecta plataformas de IM (v0.1: LINE) via adaptadores de processo filho, armazena mensagens em JSONL + SQLite/FTS5, expõe ferramentas MCP para clientes de IA.
Os três repositórios
chatmux é o núcleo. Plataformas se conectam abaixo dele, consumidores ficam acima, e ambos os lados dessa fronteira vivem em seus próprios repositórios:
| Repositório | Função |
|---|---|
| chatmux (este) | Daemon central: armazenamento, trilho de segurança, servidor MCP, adaptador LINE |
| chatmux-adapter-telegram | Segundo adaptador de plataforma (Telegram, sessão de usuário MTProto) |
| chat.nvim | Consumidor de referência: leia e responda chats dentro do Neovim |
Adaptadores falam o protocolo de adaptador; consumidores falam MCP. Qualquer lado pode ser substituído sem tocar no outro.
Início rápido
1. Instalação
git clone https://github.com/echoedinvoker/chatmux.git
cd chatmux
bun install
2. Decida se quer conectar uma conta já
Sem adapters.json, bun run start inicia o adaptador LINE, o que significa que o passo 3 coloca sua conta LINE em risco — leia o Aviso de Risco de Conta antes de executar. Se preferir explorar primeiro, comece sem nenhum adaptador:
mkdir -p ~/.local/share/chatmux
cat > ~/.local/share/chatmux/adapters.json <<'JSON'
{
"adapters": [],
"mcp": { "port": 7717 }
}
JSON
bun run start
O daemon sobe com armazenamento e a interface MCP completa — você pode initialize, listar ferramentas e ler recursos. Simplesmente não há dados de chat por trás deles até que um adaptador seja conectado. Defina CHATMUX_DATA_DIR para manter este teste fora do seu diretório de dados real:
CHATMUX_DATA_DIR=/tmp/chatmux-trial bun run start
Cada entrada em adapters recebe platform, uma string command e um array args (mais opcional cwd e env):
{ "platform": "telegram", "command": "python", "args": ["-m", "chatmux_adapter_telegram"] }
Para Telegram, siga a configuração em chatmux-adapter-telegram — ele tem suas próprias credenciais e fluxo de login, e não envolve LINE.
3. Primeiro login (código QR)
bun run start
# A QR code will appear in the terminal
# Open LINE on your phone → open the QR scanner → scan
# iOS: Home → the scan icon
# Android: Home → Add friends → QR code
# After successful login, authToken is saved for future auto-login
4. Conecte o Claude Code
Registre o endpoint MCP do daemon com o Claude Code:
claude mcp add --transport http chatmux http://127.0.0.1:7717/mcp
claude mcp list # chatmux: ... - ✔ Connected
O daemon escuta em dois transportes ao mesmo tempo: uma porta TCP em 127.0.0.1 (padrão 7717) para
clientes MCP padrão como Claude Code, e um socket unix para consumidores sidecar no mesmo host, como
chat.nvim. Use a URL TCP para o Claude Code — a especificação MCP só define transportes stdio e HTTP com streaming, então nenhum cliente MCP aceita um socket unix.
A porta é configurável via CHATMUX_MCP_PORT, ou mcp.port em adapters.json; defina-a como 0 para
desabilitar o listener TCP. Veja docs/mcp-interface.md.
Arquitetura
LINE adapter ←── stdio JSON-RPC ──→ core daemon ←── MCP Streamable HTTP ──→ Claude Code
(Node+tsx) (child process) (Bun) (127.0.0.1 TCP / unix) (MCP client)
├─ SafetyRail
├─ Storage (JSONL → SQLite/FTS5)
├─ Adapter Runner
└─ MCP Server
- Daemon central (Bun): processo principal que gerencia armazenamento, segurança e servidor MCP
- Adaptador LINE (Node+tsx): processo filho que conecta ao LINE via slot IOSIPAD
- Armazenamento: fonte de verdade JSONL append-only + visão consultável SQLite/FTS5
- Servidor MCP: HTTP com streaming sobre TCP (clientes MCP padrão; loopback por padrão, configurável para containers) + socket unix (sidecars no mesmo host), 8 ferramentas + 4 recursos
Ferramentas MCP
| Ferramenta | Descrição |
|---|---|
list_chats | Lista chats com pré-visualização da última mensagem, busca, paginação |
read_messages | Lê mensagens de um chat, paginadas por timestamp |
read_events | Segue o log de eventos a partir de um cursor opaco — retomável, sobrevive a reordenação de backfill e reentrega uma mensagem quando ela é editada ou retraída |
search_messages | Busca de texto completo (CJK suportado via FTS5 trigram + fallback LIKE) |
send_message | Envia mensagem através do SafetyRail (limitado por taxa, com rastreamento de erros) |
get_media | Caminho de arquivo local para imagem ou sticker de uma mensagem; baixa e armazena em cache na primeira chamada |
probe_latest | Diagnóstico, somente leitura: pede ao adaptador as N mensagens mais recentes de um chat sem registrá-las |
get_status | Status do sistema: conexão do adaptador + estatísticas de armazenamento |
Recursos MCP
| URI | Descrição |
|---|---|
chat://chats | Lista completa de chats |
chat://chats/{id}/messages | Mensagens recentes de um chat |
chat://chats/{id}/info | Detalhes do chat com membros |
chat://status | Status do sistema |
Escrevendo um consumidor
O núcleo expõe primitivas, não políticas. Qualquer coisa que decide o que importa — quais chats valem ser mostrados, para onde uma notificação vai, quando ficar em silêncio — pertence a um consumidor, do outro lado da fronteira MCP.
examples/notifier/ é uma referência funcional: ele segue o log de eventos
com um cursor persistido e entrega cada mensagem a um hook que você preenche. Seu
mcp-client.ts usa fetch puro em vez do SDK TypeScript, então ele também serve como
referência de protocolo de rede para consumidores em qualquer linguagem.
Serviço systemd
cp config/chatmux.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now chatmux
Edite WorkingDirectory para apontar para seu clone antes de copiá-lo.
Como ele volta
A unidade vem com Restart=always, não on-failure. Um backend de chat deveria estar lá
o dia todo, e há três maneiras de ele parar de estar — ele crasha, algo envia
um sinal, ou ele sai limpo — das quais on-failure só se recupera da primeira.
systemctl --user stop ainda o para: uma parada que você pediu não é uma falha, sob nenhuma das
configurações.
⚠️ Se você está em uma unidade antiga com
Restart=on-failure,kill -TERMnão irá trazê-lo de volta — e isso não é falta de política de reinício. systemd conta SIGTERM, SIGHUP, SIGINT e SIGPIPE como uma parada intencional, entãoon-failuredeixa o serviço eminactiveapós qualquer um deles. Apenaskill -9(SIGKILL) conta como falha ali.Com o
Restart=alwaysque esta unidade agora traz, TERM volta também — medido em 2026-08-02:kill -TERM $MainPIDmoveuNRestarts1 → 2 e produziu um novoMainPIDdentro doRestartSecde 10s. Isso torna o TERM o teste útil:kill -9reinicia sob qualquer configuração, então não pode dizer qual está em vigor. Se você quiser confirmar quealwaysestá ativo, envie TERM e observesystemctl --user show chatmux -p MainPID,NRestartsmudar.
StartLimitIntervalSec=300 / StartLimitBurst=5 limitam um loop de crash: cinco inícios dentro de cinco
minutos e o systemd para de tentar, deixando a unidade em failed para você examinar em vez de
reiniciar na mesma parede para sempre. Limpe com systemctl --user reset-failed chatmux.
Containers
Um serviço de usuário systemd é a maneira pretendida de executar chatmux. Se você quiser em um
container em vez disso, deploy/container/ é uma referência que constrói
e responde — não é uma imagem oficial, e executa zero adaptadores, porque adaptadores
mantêm sessões logadas e um container que você reconstrói é o lugar errado para elas.
A única coisa que você não pode pular é CHATMUX_MCP_HOST. O daemon vincula 127.0.0.1 por
padrão, que dentro de um container é o loopback do próprio container — uma porta publicada
então mapeia para um socket que ninguém está escutando, e cada conexão é recusada enquanto os
logs parecem perfeitamente saudáveis. Leia deploy/container/README.md antes de assumir que seu
mapeamento de porta está quebrado.
Desenvolvimento
bun run dev # Start with --watch (auto-reload)
bun test # Run all tests
bun run start # Start daemon
Veja docs/ para documentação detalhada de arquitetura e protocolo.
Limitações
Conhecidas e aceitas, com o que tornaria cada uma digna de revisão.
- A lista de chats limita a 1000, silenciosamente.
chat://chatsestá codificado em esse limite. Consumidores podem detectar um overflow comparando o campototalcom o que chegou, então não vai te surpreender sem avisar. Vale aumentar quando um vault se aproxima de ~500 chats, ou na primeira vez que essa verificação de completude disparar. - O log JSONL contém histórico duplicado. O backfill re-ingeriu algumas mensagens muitas vezes, deixando o log de eventos várias vezes maior do que as mensagens nele. Isso parou: o crescimento recente é quase inteiramente novas mensagens distintas, e a pior contagem de duplicatas foi congelada em medições repetidas. Não é um problema de correção — a ingestão é idempotente e a projeção SQLite não é afetada — então a correção, se algum dia precisar, é uma compactação pontual em vez de uma mudança de código. Vale fazer se o log passar de ~500 MB, se a contagem de duplicatas começar a subir de novo, ou se o início a frio ficar visivelmente mais lento.
- Retrações em chats um-a-um do Telegram são perdidas. Retrações em grupo chegam; diretas não, porque o adaptador não pode recuperar o id do chat para esses eventos do seu cache de entidades, e o núcleo não vai combinar uma mensagem só pelo id — essa ambiguidade é exatamente o que a chave de armazenamento foi ampliada para remover. Então uma mensagem que você retraiu no seu celular pode ficar visível aqui. Vale corrigir quando o adaptador puder resolver o id do chat sozinho, ou assim que a precisão de retração importar para um consumidor.
- Reações não são armazenadas de forma alguma. As plataformas as enviam; nenhuma camada as lê. Nada no núcleo, no esquema ou na superfície MCP representa uma reação, então um consumidor não pode mostrar o que um celular mostra. Vale construir quando reações carregarem significado que você perderia — é armazenamento novo, não um ajuste de exibição.
read_receipté declarado mas nunca emitido. O adaptador LINE anuncia a capacidade e o núcleo está pronto para ingeri-lo; nada constrói o evento. Se o estado de leitura deveria chegar a uma UI é uma questão de produto aberta, não um bug pendente — mas a declaração está errada hoje, então não faça branch emsupported_eventspara este. Vale corrigir assim que qualquer consumidor fizer branch nisso, ou quando essa questão de produto for respondida.
⚠️ Aviso de Risco de Conta
Este projeto usa @evex/linejs, uma biblioteca de cliente LINE não oficial. Usar APIs não oficiais pode violar os Termos de Serviço da LINE. Sua conta LINE pode ser restringida, suspensa ou banida permanentemente. Use por sua conta e risco.
O slot de dispositivo IOSIPAD é usado para evitar interferir com o app LINE do seu telefone, mas a LINE pode mudar sua política de múltiplos dispositivos a qualquer momento.
⚠️ Aviso Legal
Este software é fornecido "como está", sem garantia de qualquer tipo. O autor não é responsável por quaisquer consequências do uso deste software, incluindo, mas não se limitando a restrições de conta, perda de dados ou violações de termos de serviço de terceiros.
Esta é uma ferramenta pessoal para uso pessoal. Não a use para spam, assédio, acesso não autorizado a mensagens de outros ou qualquer atividade ilegal.
🔒 Divulgação de Privacidade
chatmux armazena conteúdo de mensagens descriptografado em texto puro na sua máquina local:
~/.local/share/chatmux/events.jsonl— todos os eventos (append-only)~/.local/share/chatmux/chatmux.db— banco de dados SQLite com mensagens, contatos, chats~/.local/share/chatmux/adapters/line/auth.json— token de autenticação LINE~/.local/share/chatmux/adapters/line/storage.json— armazenamento de chaves E2EE
Esses arquivos são protegidos por permissões de sistema de arquivos (somente proprietário). Não compartilhe esses arquivos. O token de autenticação concede acesso total à sua conta LINE. As chaves E2EE podem descriptografar suas mensagens.
v0.1 não criptografa o banco de dados. A criptografia SQLCipher está planejada para v0.2.
Licença
MIT