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:

RepoRol
chatmux (este)Daemon central: almacenamiento, barandilla de seguridad, servidor MCP, adaptador LINE
chatmux-adapter-telegramSegundo adaptador de plataforma (Telegram, sesión de usuario MTProto)
chat.nvimConsumidor 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

HerramientaDescripción
list_chatsLista chats con vista previa del último mensaje, búsqueda, paginación
read_messagesLee mensajes de un chat, paginados por marca de tiempo
read_eventsSigue 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_messagesBúsqueda de texto completo (CJK soportado mediante FTS5 trigram + fallback LIKE)
send_messageEnvía mensaje a través de SafetyRail (limitado por tasa, con seguimiento de errores)
get_mediaRuta de archivo local para la imagen o sticker de un mensaje; descarga y cachea en la primera llamada
probe_latestDiagnóstico, solo lectura: pide al adaptador los N mensajes más recientes de un chat sin almacenarlos
get_statusEstado del sistema: conexión del adaptador + estadísticas de almacenamiento

Recursos MCP

URIDescripción
chat://chatsLista completa de chats
chat://chats/{id}/messagesMensajes recientes de un chat
chat://chats/{id}/infoDetalles del chat con miembros
chat://statusEstado 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 -TERM no 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í que on-failure deja el servicio en inactive después de cualquiera de ellas. Solo kill -9 (SIGKILL) cuenta como fallo allí.

Con el Restart=always que esta unidad ahora incluye, TERM también vuelve — medido 2026-08-02: kill -TERM $MainPID movió NRestarts 1 → 2 y produjo un nuevo MainPID dentro del RestartSec de 10s. Eso hace de TERM la prueba útil: kill -9 se reinicia bajo cualquiera de las dos configuraciones, así que no puede decirte cuál está en efecto. Si quieres confirmar que always está activo, envía TERM y observa systemctl --user show chatmux -p MainPID,NRestarts cambiar.

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://chats está codificado a ese límite. Los consumidores pueden detectar un desbordamiento comparando el campo total contra 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_receipt está 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 en supported_events para 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