Thinks
FreeMaintainedMCP server that writes, replies and rephrases in your own voice, learned from a Telegram export and your git history
About
MCP server that writes, replies and rephrases in your own voice, learned from a Telegram export and your git history
README
An MCP server that writes, replies and rephrases in your own voice, learned from a Telegram export and your git history.
The server generates nothing itself and calls no API. It hands the calling model a brief: a measured style profile, real messages of yours picked for the request, numeric constraints and an output contract. The model writes — Claude Code, Claude Desktop, whatever you use.
The server speaks Russian: tool descriptions, briefs, the profile and the CLI are all in Russian, and the style probes — clerical phrases, stop words, redaction placeholders — assume a Russian-language archive. English is handled in search and in the code registers, where both languages occur.
How it works
Two phases with a SQLite file between them.
Build (manual, rare): export → filter → redact → group messages into turns → measure → index.
Serve (the MCP server): tool call → BM25 search over the archive → brief.
The unit of the corpus is a turn, not a message: a run of messages sent within 90 seconds of each other. That is what real chat looks like — a sizeable share of messages arrive in bursts, a thought split across several short replies instead of one paragraph. Indexing single messages would teach the opposite.
Install
Node 24 or newer. The corpus and the search are built on node:sqlite with
FTS5, which is stable from that version on.
Through mise
mise use -g npm:@aimuzov/thinks-mcp
From source
pnpm i && pnpm build && npm pack && npm i -g ./aimuzov-thinks-mcp-*.tgz
Note that npm i -g installs the binary into whichever Node version is active
at that moment. If mise switches versions, thinks-mcp disappears from PATH —
so in an MCP host config prefer launching through mise exec over relying on
the bare command name.
Building the chat corpus
Export your archive from Telegram: Settings → Advanced → Export Telegram data, JSON format, uncheck every media type (only the text is used). Then:
thinks-mcp build ~/Downloads/Telegram\ Desktop/DataExport/result.json
Order of magnitude: a few hundred thousand messages take about 15 seconds. The
export is parsed into memory whole, and the peak is roughly six times the file
size — about 2.5 GB for a 400 MB export. If Node runs out of heap, raise it:
NODE_OPTIONS=--max-old-space-size=8192 thinks-mcp build ...
Where things live and whether they are built:
thinks-mcp where
thinks-mcp profile
Building the code corpus
A second, independent corpus: comments from your repositories. It exists for the job the chat corpus is wrong for — writing comments in code.
THINKS_CODE_EMAILS="me@personal,me@work" thinks-mcp code ~/Projects/*/ ~/work/repo
Authorship is resolved with git blame: a block enters the corpus only if more
than half of its lines were written from one of those addresses. Other people's
comments, section rules, commented-out code and tool directives
(eslint-disable, shellcheck source=) are dropped.
It yields two registers: code for inline notes, jsdoc for docblocks. They
are measured separately because they are different genres — an inline note is
usually one line, a docblock opens with a summary and continues.
A rebuild reuses the previous git blame results for files whose contents have
not changed, keyed by blob hash — the difference between half a minute and a few
seconds on a dozen repositories.
Both corpora share one database without interfering: build rebuilds only the
chat registers, code only the code ones.
The corpus lives in ~/.config/thinks-mcp/style.db — next to your config, not
next to the code. Otherwise a package upgrade would take it along with the old
version.
Connecting
Copy .mcp.json.example to .mcp.json:
{
"mcpServers": {
"thinks": {
"command": "/opt/homebrew/bin/mise",
"args": ["exec", "npm:@aimuzov/thinks-mcp", "--", "thinks-mcp"]
}
}
}
Installed from source with npm i -g, launch through the Node version the
binary landed in:
{
"mcpServers": {
"thinks": {
"command": "/opt/homebrew/bin/mise",
"args": ["x", "node@24", "--", "thinks-mcp"]
}
}
}
The server starts even with no corpus built and says what to do — so a machine that has not imported an archive yet gets a working server, not a failed one.
Tools
| Tool | What it does |
|---|---|
write_as_me |
brief for writing something from scratch |
reply_as_me |
brief for answering an incoming message |
rephrase_as_me |
brief for rewriting existing text |
check_as_me |
deterministic 0–100 score and a list of deviations |
find_my_messages |
search the archive: "how do I usually turn things down" |
Every tool takes a register: dm for private chat, group for group chats,
longform for extended writing, code for inline comments, jsdoc for
docblocks. The profile, the constraints and the search are split by register
because the styles genuinely differ, and check_as_me checks different things
for code: line width, markers, filler instead of fact, types in braces.
For comments in code use only code and jsdoc. The chat registers are
measured on conversation — short replies, colloquial forms, emoji — and in code
they produce somebody else's voice.
write_as_me and find_my_messages also take lang (ru/en), meaningful
for code, where both are used.
The rest of the parameters:
examples(4–40, default 18) on the three brief tools — how many archive examples go into the brief;length(short/normal/long) onwrite_as_me, relative to what is usual for the register;hintonreply_as_me— what the answer should say;limit,yearFromandmatchIncomingonfind_my_messages; the last one searches the interlocutors' messages instead of yours.
check_as_me takes an optional code argument: the lines the comment sits
above. With it the check also catches comments that restate the code. Its
limitation is real — it compares words, so a Russian comment over English
identifiers cannot be judged and the check stays quiet.
Resources: style://profile and style://profile/{register}, the profile as
markdown. Prompts: as-me, reply-as-me and comment-as-me.
The loop those prompts set up: get a brief → write → check_as_me → rewrite
against the findings until the score is high.
How much a reply example is worth
reply_as_me builds its examples from pairs, and pairs come in two kinds.
A message carrying reply_to_message_id is a fact: the author picked what they
were answering. Everything else is inferred from message order — "whatever was
said last". That guess fails where chat is most ordinary: someone writes
"Sorry", the answer is about something else entirely, and the pair teaches a
model to reply off-topic.
So quoted pairs rank above inferred ones, and an inferred pair that took the author more than half an hour to send ranks lower still. Every example says which kind it is.
Some registers have almost no pairs — a Telegram export of a supergroup carries hardly any of the other participants' messages. There the brief says so outright, rather than passing topical matches off as answers.
Recency
Habits drift over a decade of chat history — punctuation, message length, rhythm. A profile averaged over the whole archive describes neither the person you are now nor the one you were ten years ago. So:
- the profile and the constraints are measured over the last few years
(
THINKS_RECENT_YEARS), with the all-time numbers shown for reference; - search results are weighted by year, so a recent example wins all else being equal. The weight is set so that a decade of age costs about a third of the typical BM25 spread in a result set: recency matters, but an irrelevant new message does not outrank a relevant old one.
Typography
Guillemets, em and en dashes, curly quotes: the typography of a printed book,
which a model reaches for by default. Whether the owner of the archive reaches
for it is decided by measurement — the share of messages carrying each mark,
counted separately for chat and for each code genre. Below 2% the mark enters
the profile as foreign, check_as_me penalises it, and the brief asks for a
plain hyphen and straight quotes instead. The threshold is its own rather than
the shared anti-pattern one: a dash in 1% of messages already gives a text away,
while a clerical word at that share does not.
The double hyphen is measured and never penalised. It is not a foreign mark but an ASCII stand-in for a dash, and it differs by register: absent from chat, normal in comments. Where its share reaches 1%, the brief asks for it outright.
Comments written with an assistant are a problem of their own. git blame calls
them yours while their marks are its: in this author's corpus the share of
guillemets in docblocks jumped from zero to 6% in 2026. Set the year you stopped
writing comments by hand in THINKS_CODE_HANDWRITTEN_UNTIL; everything from
that year on stays indexed and searchable but does not count towards the marks.
If a genre keeps fewer than 200 lines after the cut, its marks go unmeasured —
a confident zero over twenty lines is worse than no number.
Privacy
This is a private message archive, so:
- the export and the built index are never committed — the data directory sits outside the repository entirely;
- phone numbers, emails and card numbers are removed using Telegram's own entity markup — an export guarantees the entities cover the message text exactly — plus fallback regexes for anything left untagged;
- chats, senders and repositories are stored under pseudonyms; real names never reach the database;
- surnames are scrubbed from message bodies, first names are not: a bare first name identifies nobody, and without them every example would read like a redacted document;
- proper nouns are excluded from the style profile separately.
Exclude whole chats: THINKS_CHAT_STOPLIST="Chat one,Chat two".
Commands
thinks-mcp build <dump.json> # build the chat corpus
thinks-mcp code <repos...> # build the comment corpus
thinks-mcp profile jsdoc # print the profile for a register
thinks-mcp where # where the index lives
thinks-mcp holdout --answers # blind quality check
thinks-mcp serve # same as no arguments
thinks-mcp --help
In the repository itself:
mise run check # types, formatting, tests
pnpm test
pnpm build
holdout is the blind quality check: at build time 20 real incoming/answer
pairs are held out and kept out of the index. You look at the incoming messages
first, answer them through reply_as_me, then compare with what was actually
said.
Environment
| Variable | Default | Purpose |
|---|---|---|
THINKS_DATA_DIR |
$XDG_CONFIG_HOME/thinks-mcp or ~/.config/thinks-mcp |
directory holding the index |
THINKS_DUMP |
<data-dir>/dump.json |
export path, if not passed as an argument |
THINKS_DB |
<data-dir>/style.db |
index file path |
THINKS_OWNER_ID |
auto-detected | set if detection picks the wrong person |
THINKS_CHAT_STOPLIST |
empty | comma-separated chats to skip |
THINKS_CODE_EMAILS |
git config --global user.email |
comma-separated git author emails |
THINKS_RECENT_YEARS |
3 |
window that counts as "how I write now" |
THINKS_CODE_HANDWRITTEN_UNTIL |
none | year comments stopped being handwritten, for the typography count |
THINKS_BURST_WINDOW |
90 |
burst window in seconds |
THINKS_LONGFORM_MIN |
300 |
longform threshold in characters |
THINKS_HOLDOUT |
20 |
pairs held out for the blind check |
Dependencies
@modelcontextprotocol/sdk and zod, and that is all. Full-text search runs on
FTS5 from Node's built-in node:sqlite; the Russian and English stemmers are
written here (src/search/stem.ts) because FTS5 tokenises both scripts but
knows no morphology.
License
MIT
Installing Thinks
This server has no published package — it is built from source. Open the repository and follow its README.
▸ github.com/aimuzov/thinks-mcpFAQ
Is Thinks MCP free?
Yes, Thinks MCP is free — one-click install via Unyly at no cost.
Does Thinks need an API key?
Yes, it requires environment variables: THINKS_CODE_EMAILS. Unyly injects them into the config during install.
Is Thinks hosted or self-hosted?
Self-hosted: the server runs locally on your machine via the install command above.
How do I install Thinks in Claude Desktop, Claude Code or Cursor?
Open Thinks on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.
Related MCPs
Gmail
Read, send and search emails from Claude
by GoogleSlack
Send, search and summarize Slack messages
by SlackRunbear
No-code MCP client for team chat platforms, such as Slack, Microsoft Teams, and Discord.
Discord Server
A community discord server dedicated to MCP by [Frank Fiegel](https://github.com/punkpeye)
Klavis AI
Open Source MCP Infra. Hosted MCP servers and MCP clients on Slack and Discord.
Work90210/APIFold
Turn any REST API into a hosted MCP server. 18 free public servers (GitHub, Stripe, Slack, OpenAI, Notion, and more) — no setup required, bring your own API key
by Work90210arikusi/deepseek-mcp-server
MCP server for DeepSeek AI with chat, reasoning, multi-turn sessions, function calling, thinking mode, and cost tracking.
by arikusihashgraph-online/hashnet-mcp-js
MCP server for the Registry Broker. Discover, register, and chat with AI agents on the Hashgraph network.
by hashgraph-onlineprofullstack/mcp-server
A comprehensive MCP server aggregating 20+ tools including SEO optimization, document conversion, domain lookup, email validation, QR generation, weather data,
by profullstackWayStation-ai/mcp
Seamlessly and securely connect Claude Desktop and other MCP hosts to your favorite apps (Notion, Slack, Monday, Airtable, etc.). Takes less than 90 secs.
by waystation-aiCompare Thinks with
Not sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All communication MCPs
