chatmux
Servidor MCP local-first que coloca tus propios chats de LINE y Telegram detrás de una capa de datos única — un daemon en tu máquina inicia sesión con tu propia cuenta, almacena mensajes en JSONL + SQLite/FTS5, y los expone a clientes MCP como Claude Code. No es un comando npx de una línea: clonas el repositorio y ejecutas el daemon tú mismo.
Documentación
chatmux
Daemon de capa de datos de chat personal, local-first. Conecta plataformas de mensajería (v0.1: LINE) mediante adaptadores de procesos hijo, almacena mensajes en JSONL + SQLite/FTS5, y expone herramientas MCP para clientes de IA.
Los tres repos
chatmux es el núcleo. Las plataformas se conectan por debajo, los consumidores se sitúan por encima, y ambos lados de esa frontera viven en sus propios repos:
| Repo | Rol |
|---|---|
| chatmux (este) | Daemon central: almacenamiento, barandilla de seguridad, servidor MCP, adaptador LINE |
| chatmux-adapter-telegram | Segundo adaptador de plataforma (Telegram, sesión de usuario MTProto) |
| chat.nvim | Consumidor de referencia: lee y responde chats dentro de Neovim |
Los adaptadores hablan el protocolo de adaptador; los consumidores hablan MCP. Cualquiera de los dos lados puede reemplazarse sin tocar el otro.
Inicio rápido
1. Instalar
git clone https://github.com/echoedinvoker/chatmux.git
cd chatmux
bun install
2. Decide si conectar una cuenta todavía
Sin adapters.json, bun run start lanza el adaptador LINE, lo que significa que el paso 3 pone tu cuenta LINE en juego — lee Advertencia de riesgo de cuenta antes de ejecutarlo. Si prefieres explorar primero, empieza sin ningún adaptador:
mkdir -p ~/.local/share/chatmux
cat > ~/.local/share/chatmux/adapters.json <<'JSON'
{
"adapters": [],
"mcp": { "port": 7717 }
}
JSON
bun run start
El daemon arranca con almacenamiento y la interfaz MCP completa — puedes initialize, listar herramientas y leer recursos. Simplemente no hay datos de chat detrás hasta que se conecte un adaptador. Establece CHATMUX_DATA_DIR para mantener esta prueba fuera de tu directorio de datos real:
CHATMUX_DATA_DIR=/tmp/chatmux-trial bun run start
Cada entrada en adapters toma platform, una cadena command y un array args (además de cwd y env opcionales):
{ "platform": "telegram", "command": "python", "args": ["-m", "chatmux_adapter_telegram"] }
Para Telegram, sigue la configuración en chatmux-adapter-telegram — tiene sus propias credenciales y flujo de inicio de sesión, y no involucra LINE.
3. Primer inicio de sesión (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. Conectar Claude Code
Registra el endpoint MCP del daemon con Claude Code:
claude mcp add --transport http chatmux http://127.0.0.1:7717/mcp
claude mcp list # chatmux: ... - ✔ Connected
El daemon escucha en dos transportes a la vez: un puerto TCP en 127.0.0.1 (por defecto 7717) para clientes MCP estándar como Claude Code, y un socket unix para consumidores sidecar del mismo host como chat.nvim. Usa la URL TCP para Claude Code — la especificación MCP solo define transportes stdio y HTTP streamable, así que ningún cliente MCP acepta una ruta de socket unix.
El puerto es configurable mediante CHATMUX_MCP_PORT, o mcp.port en adapters.json; establécelo en 0 para deshabilitar el listener TCP. Ver docs/mcp-interface.md.
Arquitectura
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): proceso central que gestiona almacenamiento, seguridad y servidor MCP
- Adaptador LINE (Node+tsx): proceso hijo que se conecta a LINE mediante el slot IOSIPAD
- Almacenamiento: fuente de verdad JSONL append-only + vista consultable SQLite/FTS5
- Servidor MCP: HTTP streamable sobre TCP (clientes MCP estándar; loopback por defecto, configurable para contenedores) + socket unix (sidecars del mismo host), 8 herramientas + 4 recursos
Herramientas MCP
| Herramienta | Descripción |
|---|---|
list_chats | Lista chats con vista previa del último mensaje, búsqueda, paginación |
read_messages | Lee mensajes de un chat, paginados por marca de tiempo |
read_events | Sigue el registro de eventos desde un cursor opaco — reanudable, sobrevive al reordenamiento de backfill, y re-entrega un mensaje cuando se edita o retracta |
search_messages | Búsqueda de texto completo (CJK soportado mediante FTS5 trigram + fallback LIKE) |
send_message | Envía mensaje a través de SafetyRail (limitado por tasa, con seguimiento de errores) |
get_media | Ruta de archivo local para la imagen o sticker de un mensaje; descarga y cachea en la primera llamada |
probe_latest | Diagnóstico, solo lectura: pide al adaptador los N mensajes más recientes de un chat sin almacenarlos |
get_status | Estado del sistema: conexión del adaptador + estadísticas de almacenamiento |
Recursos MCP
| URI | Descripción |
|---|---|
chat://chats | Lista completa de chats |
chat://chats/{id}/messages | Mensajes recientes de un chat |
chat://chats/{id}/info | Detalles del chat con miembros |
chat://status | Estado del sistema |
Escribir un consumidor
El núcleo expone primitivas, no políticas. Cualquier cosa que decida qué importa — qué chats merecen mostrarse, a dónde va una notificación, cuándo permanecer en silencio — pertenece a un consumidor, al otro lado de la frontera MCP.
examples/notifier/ es una referencia funcional: sigue el registro de eventos con un cursor persistido y entrega cada mensaje a un hook que tú completas. Su mcp-client.ts usa fetch crudo en lugar del SDK de TypeScript, por lo que también sirve como referencia de protocolo de cable para consumidores en cualquier lenguaje.
Servicio systemd
cp config/chatmux.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now chatmux
Edita WorkingDirectory para apuntar a tu clon antes de copiarlo.
Cómo se recupera
La unidad incluye Restart=always, no on-failure. Se supone que un backend de chat está ahí todo el día, y hay tres formas en que puede dejar de estar — se bloquea, alguien le envía una señal, o sale limpiamente — de las cuales on-failure solo se recupera de la primera. systemctl --user stop aún lo detiene: una detención que pediste no es un fallo, bajo ninguna de las dos configuraciones.
⚠️ Si estás en una unidad antigua con
Restart=on-failure,kill -TERMno lo traerá de vuelta — y eso no es una política de reinicio faltante. systemd cuenta SIGTERM, SIGHUP, SIGINT y SIGPIPE como una detención intencionada, así queon-failuredeja el servicio eninactivedespués de cualquiera de ellas. Solokill -9(SIGKILL) cuenta como fallo allí.Con el
Restart=alwaysque esta unidad ahora incluye, TERM también vuelve — medido 2026-08-02:kill -TERM $MainPIDmovióNRestarts1 → 2 y produjo un nuevoMainPIDdentro delRestartSecde 10s. Eso hace de TERM la prueba útil:kill -9se reinicia bajo cualquiera de las dos configuraciones, así que no puede decirte cuál está en efecto. Si quieres confirmar quealwaysestá activo, envía TERM y observasystemctl --user show chatmux -p MainPID,NRestartscambiar.
StartLimitIntervalSec=300 / StartLimitBurst=5 limitan un bucle de bloqueos: cinco arranques en cinco minutos y systemd deja de intentarlo, dejando la unidad en failed para que la examines en lugar de reiniciar contra la misma pared para siempre. Límpialo con systemctl --user reset-failed chatmux.
Contenedores
Un servicio de usuario systemd es la forma prevista de ejecutar chatmux. Si lo quieres en un contenedor en su lugar, deploy/container/ es una referencia que construye y responde — no es una imagen oficial, y ejecuta cero adaptadores, porque los adaptadores mantienen sesiones iniciadas y un contenedor que reconstruyes es el hogar equivocado para esas.
Lo único que no puedes omitir es CHATMUX_MCP_HOST. El daemon se vincula a 127.0.0.1 por defecto, que dentro de un contenedor es el loopback del propio contenedor — un puerto publicado entonces mapea a un socket que nadie está escuchando, y cada conexión es rechazada mientras los registros parecen perfectamente saludables. Lee deploy/container/README.md antes de asumir que tu mapeo de puertos está roto.
Desarrollo
bun run dev # Start with --watch (auto-reload)
bun test # Run all tests
bun run start # Start daemon
Ver docs/ para documentación detallada de arquitectura y protocolo.
Limitaciones
Conocidas y aceptadas, con lo que haría que valga la pena revisar cada una.
- La lista de chats se limita a 1000, silenciosamente.
chat://chatsestá codificado a ese límite. Los consumidores pueden detectar un desbordamiento comparando el campototalcontra lo que llegó, así que no te morderá sin avisarte. Vale la pena subirlo cuando una bóveda se acerque a ~500 chats, o la primera vez que esa verificación de completitud se active. - El registro JSONL contiene historial duplicado. El backfill re-ingirió algunos mensajes muchas veces, dejando el registro de eventos varias veces más grande que los mensajes que contiene. Esto se ha detenido: el crecimiento reciente es casi en su totalidad mensajes nuevos distintos, y el peor caso de duplicados se ha congelado en mediciones repetidas. No es un problema de corrección — la ingesta es idempotente y la proyección SQLite no se ve afectada — así que la solución, si alguna vez se necesita, es una compactación única en lugar de un cambio de código. Vale la pena hacerlo si el registro supera ~500 MB, si el conteo de duplicados comienza a subir de nuevo, o si el arranque en frío se ralentiza notablemente.
- Las retractaciones en chats uno-a-uno de Telegram se pierden. Las retractaciones de grupo llegan; las directas no, porque el adaptador no puede recuperar el id de chat para esos eventos desde su caché de entidades, y el núcleo no hará coincidir un mensaje solo por id — esa ambigüedad es exactamente lo que la clave de almacenamiento se amplió para eliminar. Así que un mensaje que retractaste en tu teléfono puede permanecer visible aquí. Vale la pena arreglarlo una vez que el adaptador pueda resolver el id de chat por sí mismo, o tan pronto como la precisión de retractación importe a un consumidor.
- Las reacciones no se almacenan en absoluto. Las plataformas las envían; ninguna capa las lee. Nada en el núcleo, el esquema o la superficie MCP representa una reacción, así que un consumidor no puede mostrar lo que un teléfono muestra. Vale la pena construirlo cuando las reacciones tengan significado que de otro modo te perderías — es almacenamiento nuevo, no un ajuste de visualización.
read_receiptestá declarado pero nunca se emite. El adaptador LINE anuncia la capacidad y el núcleo está listo para ingerirla; nada construye el evento. Si el estado de lectura debería llegar a una UI es una pregunta de producto abierta, no un bug pendiente — pero la declaración está mal hoy, así que no te ramifiques ensupported_eventspara esto. Vale la pena arreglarlo tan pronto como algún consumidor se ramifique en ello, o una vez que esa pregunta de producto tenga respuesta.
⚠️ Advertencia de riesgo de cuenta
Este proyecto usa @evex/linejs, una biblioteca de cliente LINE no oficial. Usar APIs no oficiales puede violar los Términos de Servicio de LINE. Tu cuenta LINE puede ser restringida, suspendida o baneada permanentemente. Úsalo bajo tu propio riesgo.
El slot de dispositivo IOSIPAD se usa para evitar interferir con la app LINE de tu teléfono, pero LINE puede cambiar su política de multi-dispositivo en cualquier momento.
⚠️ Aviso legal
Este software se proporciona "tal cual", sin garantía de ningún tipo. El autor no es responsable de ninguna consecuencia del uso de este software, incluyendo pero no limitado a restricciones de cuenta, pérdida de datos o violaciones de términos de servicio de terceros.
Esta es una herramienta personal para uso personal. No la uses para spam, acoso, acceso no autorizado a mensajes de otros, o cualquier actividad ilegal.
🔒 Divulgación de privacidad
chatmux almacena contenido de mensajes descifrado en texto plano en tu máquina local:
~/.local/share/chatmux/events.jsonl— todos los eventos (append-only)~/.local/share/chatmux/chatmux.db— base de datos SQLite con mensajes, contactos, chats~/.local/share/chatmux/adapters/line/auth.json— token de autenticación LINE~/.local/share/chatmux/adapters/line/storage.json— almacenamiento de claves E2EE
Estos archivos están protegidos por permisos del sistema de archivos (solo propietario). No compartas estos archivos. El token de autenticación otorga acceso completo a tu cuenta LINE. Las claves E2EE pueden descifrar tus mensajes.
v0.1 no cifra la base de datos. El cifrado SQLCipher está planificado para v0.2.
Licencia
MIT