write-like-me-mcp
write-like-me-mcp
An MCP (Model Context Protocol) server that learns your personal writing style from a local document corpus and gives Claude self-updating style context — so the prose it produces on your behalf actually sounds like you.
100% local. Nothing leaves your machine. The server reads your documents on-device, derives statistical style metrics, and serves them to Claude over stdio. There is no network call and no LLM code path in v0.1 — the actual rewriting is done by the Claude you're already talking to, guided by the style context this server provides.
How It Works
You point it at your writing — a directory (or set of files) of things you've written: essays, emails, posts, docs, notes.
It learns your voice locally — sentence rhythm, lexical diversity, contraction habits, punctuation tics, signature phrases, formality markers — and writes a
profile.jsonplus a searchable excerpt index (examples.db).It watches for changes — edit or add to your corpus and the profile and index rebuild automatically (debounced).
Claude uses it as style context — before writing for you, Claude loads your profile and can pull real excerpts of your writing for grounding, then matches your voice. You never upload anything.
Privacy & Local-Only Posture
This is the whole point of the project, so it is worth being explicit:
Guarantee | How it's enforced |
No network access | The server has no HTTP client and no LLM call. A regression test runs the full tool flow with all sockets patched to raise. |
No verbatim corpus stored |
|
Excerpts stay local |
|
Private data stays out of the repo | The default data dir is |
Clean build artifacts |
|
Secret scanning |
|
Install
Requirement: Python ≥ 3.10. You must tell the server where your writing lives — via a config file, the
WRITE_LIKE_ME_CONFIGenv var, or~/.write-like-me.json.
Option A: Install from GitHub
pip install git+https://github.com/gwicho38/write-like-me-mcp.gitOption B: Run without installing (uvx)
uvx write-like-me-mcp /path/to/your/write-like-me.jsonOption C: Clone and install locally
git clone https://github.com/gwicho38/write-like-me-mcp.git
cd write-like-me-mcp
pip install -e .Configuration
The server is driven by a small JSON config that names your writing sources and which profile is active.
write-like-me.json example
{
"active_profile": "default",
"data_dir": "~/.write-like-me",
"profiles": {
"default": {
"sources": ["~/writing", "~/notes"],
"exclude": ["drafts/", "_Index_of_*"],
"file_extensions": [".md", ".markdown", ".txt", ".docx", ".pdf", ".html", ".htm"]
},
"work": {
"sources": ["~/work/memos"]
}
},
"llm": { "enabled": false }
}Field | Required | Default | Description |
| yes | — | Which entry in |
| yes | — | Map of profile name → settings. The schema is multi-profile-ready; v0.1 builds only the active one. |
| yes | — | Directories and/or files to learn from ( |
| no |
| Path components or globs to skip. Matches a whole component ( |
| no | see table below | Which extensions to analyze. |
| no |
| Where |
| no |
| Reserved for a future server-side rewrite path; always |
Config discovery priority
The config file is resolved at startup with this priority (first match wins):
Priority | Method | Example |
1 (highest) | CLI argument |
|
2 | Environment variable |
|
3 | Home-dir config file |
|
If no config is found, the server exits with a clear error explaining how to create one — it never silently defaults to a directory.
Setup
Claude Code (CLI)
claude mcp add write-like-me -- write-like-me-mcp /path/to/your/write-like-me.jsonThat's it. The server starts automatically with every claude session.
To change config, remove and re-add:
claude mcp remove write-like-me
claude mcp add write-like-me -- write-like-me-mcp /new/path/to/write-like-me.jsonClaude Desktop
Add this to your Claude Desktop config file:
OS | Config path |
macOS |
|
Windows |
|
Linux |
|
{
"mcpServers": {
"write-like-me": {
"command": "write-like-me-mcp",
"args": ["/path/to/your/write-like-me.json"]
}
}
}Alternative: Environment Variable
Instead of passing the config path as an argument:
export WRITE_LIKE_ME_CONFIG="/path/to/your/write-like-me.json"Supported File Types
Format | Extensions | Notes |
Plain text |
| UTF-8 with encoding fallback |
Markdown |
| UTF-8 with encoding fallback; leading YAML/TOML frontmatter and fenced code blocks are stripped so metadata and code do not count as prose |
| Extracted with PyMuPDF | |
Word |
| Headings + tables preserved |
HTML |
| Boilerplate ( |
Rich Text |
| Not supported in v0.1 — the plain reader would leak RTF control codes into the corpus and corrupt the metrics; proper handling is deferred. |
Multilingual Corpora
Several style metrics only mean something against the grammar of the language a document is written in. Passive voice is found by looking for auxiliary verbs; hedging and formality come from closed word lists; an apostrophe marks a stylistic contraction in English but a mandatory elision in French. Applying one language's tables to another does not lose signal quietly — it reports confident, wrong numbers.
So the analyzer detects each document's language and reports metrics per language:
Detection is stopword-based: no dependency, no model file, no network, and fully deterministic. Supported: English, French, Spanish, Portuguese, Italian.
Abstention over guessing. A document with too few function words — a list of names and dates, a table of numbers, a code dump — or one that does not favour a language clearly over the runner-up is left out of the breakdown rather than bucketed on thin evidence.
profile.languagesmaps each detected language to its owndoc_count,total_words,passive_voice_rate,contraction_rate, andformality_markers.Top-level metrics describe the dominant language, recorded as
metadata.dominant_language. A single-language corpus is therefore reported exactly as before.apply_stylematches the draft's language. A French draft is compared against your French metrics when your corpus has them; the response'smatched_languagesays which set was used, ornullwhen the draft's language is not represented and the dominant metrics were used instead.
Tokenization is Unicode-aware, so accented words (réunion, niño) count as
one whole token rather than being truncated at the first accent.
Available Tools
Once configured, Claude gains access to these five tools:
Tool | Description |
| Return the full learned style profile (sentence rhythm, vocabulary, punctuation, signature phrases, and a ready-to-use style guide). Call this before writing prose on the user's behalf. |
| FTS5/BM25 search for representative real excerpts of the user's writing, for few-shot grounding. Returns basenames only. |
| Compare a draft to the user's profile and return a concrete rewrite brief — draft-vs-author metric gaps plus the style guide. The calling LLM does the rewrite. |
| (Re)scan the corpus and rebuild |
| Report profile/index health, readiness, document count, and whether the (reserved) LLM path is enabled. |
Example Usage
Once set up, ask Claude things like:
"Draft a reply to this email so it sounds like me."
"Rewrite this paragraph in my voice — check my style profile first."
"Write a short post about X the way I'd write it; pull a couple of my real examples for reference."
Claude will load your style profile, optionally retrieve real excerpts, and match your voice — all from context this server computes locally.
Architecture
Claude (Desktop / Code / API)
│
▼ stdio transport (stdout = protocol; stderr = logs)
write-like-me MCP Server
├── FastMCP (5 tool definitions)
├── Style Analyzer (sentence/lexical/punctuation/phrase metrics)
├── StyleProfile (derived-stats model → profile.json)
├── Excerpt Index (SQLite FTS5 + BM25, paragraph excerpts → examples.db)
├── Watchdog Watcher (debounced corpus-change rebuilds)
└── Text Extractors (TXT/MD, PDF, DOCX, HTML)
│
▼
Your local corpus Your local data dir
~/writing, ~/notes, ... → ~/.write-like-me/<profile>/
├── profile.json (derived stats only)
└── examples.db (local excerpt index)Nothing in this diagram crosses the machine boundary: there is no network edge.
Version Boundary: v0.1 → v0.2
v0.1 is intentionally local-only and LLM-free:
apply_stylereturns a rewrite brief (metric gaps + style guide); the Claude you're already talking to performs the actual rewrite. A server-side LLMapply_stylethat returns finished rewritten prose is deferred to a future version.The config carries a reserved
llmblock (enabled: false) so this can be added later without a schema change. In v0.1 it is always disabled, and a test guarantees no socket is ever opened.
Development
git clone https://github.com/gwicho38/write-like-me-mcp.git
cd write-like-me-mcp
uv sync --extra dev
# Run the local quality gate (lint, type-check, tests)
uv run ruff check .
uv run mypy src
uv run pytest
# Run the server directly against a test config
uv run write-like-me-mcp /path/to/test/write-like-me.json
# Inspect interactively with the MCP Inspector
npx @modelcontextprotocol/inspector write-like-me-mcp /path/to/test/write-like-me.jsonTesting notes
Tests are hermetic: builds and data dirs go to
tmp_path, andHOMEis monkeypatched so the real home is never touched.The OSS-safety suite (
tests/test_oss_safety.py) and the no-verbatim-corpus suite (tests/test_no_verbatim_corpus.py) encode the privacy guarantees in the table above — they are the project's reason for existing, so keep them green.
License
MIT — see LICENSE.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/gwicho38/write-like-me-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server