chatmux

by echoedinvoker

Not rated
GitHub

Description

Local-first MCP server that puts your own LINE and Telegram chats behind one data layer — a daemon on your machine logs in with your own account, stores messages to JSONL + SQLite/FTS5, and exposes them to MCP clients like Claude Code. Not an npx one-liner: you clone the repo…

About

Local-first MCP server that puts your own LINE and Telegram chats behind one data layer — a daemon on your machine logs in with your own account, stores messages to JSONL + SQLite/FTS5, and exposes them to MCP clients like Claude Code. Not an npx one-liner: you clone the repo and run the daemon yourself.

Details

Author
echoedinvoker
Categories
Communication, Other, AI

Setup

Install chatmux in your MCP client (Claude Desktop, Cursor, Windsurf, and others).

Repository: https://github.com/echoedinvoker/chatmux

Follow the installation instructions in the repository README, then restart your MCP client.

Local-first personal chat data layer daemon. Connects IM platforms (v0.1: LINE) via child-process adapters, stores messages to JSONL + SQLite/FTS5, exposes MCP tools for AI clients.

chatmux is the core. Platforms plug in below it, consumers sit above it, and both sides of that boundary live in their own repos:

Adapters speak theadapter protocol; consumers speakMCP. Either side can be replaced without touching the other.

git clone https://github.com/echoedinvoker/chatmux.git cd chatmux bun install

2. Decide whether to connect an account yet

With noadapters.json,bun run startlaunches theLINE adapter, which means step 3 puts your LINE account on the line — readAccount Risk Warningbefore you run it. If you would rather look around first, start with no adapter at all:

mkdir -p ~/.local/share/chatmux cat > ~/.local/share/chatmux/adapters.json <<'JSON' { "adapters": [], "mcp": { "port": 7717 } } JSON bun run start

The daemon comes up with storage and the full MCP interface — you caninitialize, list tools, and read resources. There is simply no chat data behind them until an adapter is connected. SetCHATMUX_DATA_DIRto keep this trial run out of your real data directory:

CHATMUX_DATA_DIR=/tmp/chatmux-trial bun run start

Each entry inadapterstakesplatform, acommandstring, and anargsarray(plus optionalcwdandenv):

{ "platform": "telegram", "command": "python", "args": ["-m", "chatmux_adapter_telegram"] }

For Telegram, follow the setup inchatmux-adapter-telegram— it has its own credentials and login flow, and does not involve LINE.

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

Register the daemon's MCP endpoint with Claude Code:

claude mcp add --transport http chatmux http://127.0.0.1:7717/mcp claude mcp list # chatmux: ... - ✔ Connected

The daemon listens on two transports at once: aTCP port on127.0.0.1(default7717) for standard MCP clients like Claude Code, and aunix socketfor same-host sidecar consumers likechat.nvim. Use the TCP url for Claude Code — the MCP spec only defines stdio and streamable HTTP transports, so no MCP client accepts a unix socket path.

Port is configurable viaCHATMUX_MCP_PORT, ormcp.portinadapters.json; set it to0to disable the TCP listener. Seedocs/mcp-interface.md.

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

- Core daemon(Bun): central process managing storage, safety, and MCP server
- LINE adapter(Node+tsx): child process connecting to LINE via IOSIPAD slot
- Storage: JSONL append-only truth source + SQLite/FTS5 queryable view
- MCP server: Streamable HTTP over TCP (standard MCP clients; loopback by default, settable for containers) + unix socket (same-host sidecars), 8 tools + 4 resources

Core exposes primitives, not policy. Anything that decideswhat matters— which chats are worth surfacing, where a notification goes, when to stay quiet — belongs in a consumer, on the far side of the MCP boundary.

examples/notifier/is a working reference: it tails the event log with a persisted cursor and hands each message to a hook you fill in. Itsmcp-client.tsuses rawfetchrather than the TypeScript SDK, so it doubles as a wire-protocol reference for consumers in any language.

cp config/chatmux.service ~/.config/systemd/user/ systemctl --user daemon-reload systemctl --user enable --now chatmux

EditWorkingDirectoryto point at your clone before copying it.

The unit shipsRestart=always, noton-failure. A chat backend is supposed to be there all day, and there are three ways it can stop being there — it crashes, something sends it a signal, or it exits cleanly — of whichon-failureonly recovers from the first.systemctl --user stopstill stops it: a stop you asked for is not a failure, under either setting.

⚠️If you are on an older unit withRestart=on-failure,kill -TERMwill not bring it back — and that is not a missing restart policy.systemd counts SIGTERM, SIGHUP, SIGINT and SIGPIPE as an intended stop, soon-failureleaves the service sitting ininactiveafter any of them. Onlykill -9(SIGKILL) counts as a failure there.

With theRestart=alwaysthis unit now ships,TERM comes back too— measured 2026-08-02:kill -TERM $MainPIDmovedNRestarts1 → 2 and produced a newMainPIDwithin the 10sRestartSec. That makes TERM the useful test:kill -9restarts undereithersetting, so it cannot tell you which one is in effect. If you want to confirmalwaysis live, send TERM and watchsystemctl --user show chatmux -p MainPID,NRestartschange.

StartLimitIntervalSec=300/StartLimitBurst=5cap a crash loop: five starts inside five minutes and systemd stops trying, leaving the unitfailedfor you to look at rather than restarting into the same wall forever. Clear it withsystemctl --user reset-failed chatmux.

A systemd user service is the intended way to run chatmux. If you want it in a container instead,deploy/container/is a reference that builds and answers — not an official image, and it runszero adapters, because adapters hold logged-in sessions and a container you rebuild is the wrong home for those.

The one thing you cannot skip isCHATMUX_MCP_HOST. The daemon binds127.0.0.1by default, which inside a container is the container's own loopback — a published port then maps to a socket nobody is listening on, and every connection is refused while the logs look perfectly healthy. Readdeploy/container/README.mdbefore assuming your port mapping is broken.

bun run dev # Start with --watch (auto-reload) bun test # Run all tests bun run start # Start daemon

Seedocs/for detailed architecture and protocol documentation.

Known and accepted, with what would make each worth revisiting.

- The chat list caps at 1000, silently.chat://chatsis hard-coded to that limit. Consumers can detect an overflow by comparing thetotalfield against what arrived, so it will not bite you without saying so. Worth raising once a vault approaches ~500 chats, or the first time that completeness check fires.
- The JSONL log holds duplicate history.Backfill re-ingested some messages many times over, leaving the event log several times larger than the messages in it. This has stopped: recent growth is almost entirely new distinct messages, and the worst-case duplicate count has been frozen across repeated measurements. It is not a correctness problem — ingestion is idempotent and the SQLite projection is unaffected — so the fix, if ever needed, is a one-off compaction rather than a code change. Worth doing if the log passes ~500 MB, if the duplicate count starts climbing again, or if cold start slows noticeably.
- Retractions in Telegram one-to-one chats are missed.Group retractions land; direct ones do not, because the adapter cannot recover the chat id for those events from its entity cache, and core will not match a message on id alone — that ambiguity is exactly what the storage key was widened to remove. So a message you retracted on your phone can stay visible here. Worth fixing once the adapter can resolve the chat id itself, or as soon as retraction accuracy matters to a consumer.
- Reactions are not stored at all.The platforms send them; no layer reads them. Nothing in core, the schema, or the MCP surface represents a reaction, so a consumer cannot show what a phone shows. Worth building when reactions carry meaning you would otherwise miss — it is new storage, not a display tweak.
- read_receiptis declared but never emitted.The LINE adapter advertises the capability and core is ready to ingest it; nothing constructs the event. Whether read state should reach a UI at all is an open product question, not a pending bug — but the declaration is wrong today, so do not branch onsupported_eventsfor this one. Worth fixing as soon as any consumer does branch on it, or once that product question gets an answer.

This project uses@evex/linejs, an unofficial LINE client library. Using unofficial APIs may violate LINE's Terms of Service. Your LINE account may be restricted, suspended, or permanently banned.Use at your own risk.

The IOSIPAD device slot is used to avoid interfering with your phone's LINE app, but LINE may change their multi-device policy at any time.

This software is provided "as is", without warranty of any kind. The author is not responsible for any consequences of using this software, including but not limited to account restrictions, data loss, or violations of third-party terms of service.

This is a personal tool for personal use. Do not use it for spam, harassment, unauthorized access to others' messages, or any illegal activity.

chatmux storesdecrypted message content in plaintexton your local machine:

- ~/.local/share/chatmux/events.jsonl— all events (append-only)
- ~/.local/share/chatmux/chatmux.db— SQLite database with messages, contacts, chats
- ~/.local/share/chatmux/adapters/line/auth.json— LINE auth token
- ~/.local/share/chatmux/adapters/line/storage.json— E2EE key storage

These files are protected by filesystem permissions (owner-only).Do not share these files.The auth token grants full access to your LINE account. The E2EE keys can decrypt your messages.

v0.1 does not encrypt the database. SQLCipher encryption is planned for v0.2.

A high-performance trading system for Claude Desktop, providing real-time market data via Tiingo and optional Telegram alerts.

Secure audio transcription meets AI. Connect Alice recordings to Claude, ChatGPT, Gemini, and more.

A bridge server connecting Claude Desktop with the chakoshi moderation API for content safety.

Sends notifications from Claude Code with customizable sounds and cross-platform support.

Connect Restream MCP to Claude or ChatGPT to create, schedule, manage, and analyze live streams across 30+ platforms.

Let Claude book a real restaurant table for you - an AI voice agent places the actual phone call, even to restaurants that only take reservations by phone.

WhatsApp channel plugin for Claude Code. Connect WhatsApp as a native channel to your Claude Code session — send/receive messages, voice transcription, access control, and remote tool approval. No API keys needed, uses Baileys for WhatsApp Web connectivity.

Access GPT-5, Claude, Gemini and other models through a single MCP connection. Save development time and money on subscriptions.

An MCP server to interact with OpenAI's ChatGPT API for conversational AI and text generation.

An AI-powered email intelligence platform that integrates with Gmail and OpenAI. It can be run as a CLI tool or deployed on AWS Lambda for enhanced capabilities.

No reviews yet — be the first

Sign in to leave a review

Use Google, GitHub, or an email account so ratings stay tied to real people.

Email sign in

No reviews posted yet.