claude-continuity-mcp
Bridges Claude Design (which lacks local filesystem access) by using Google Drive to relay orders and assets between Design and other Claude clients.
claude-continuity-mcp — Persistent, Shared Memory for Claude
Claude remembers a conversation. This MCP remembers a project.
An MCP server that gives Claude something it doesn't have today: memory that persists across sessions and is shared across all its clients (Code, Desktop, Cowork, and — bridged, see below — Design). It remembers which files you already read and returns only the diffs, resumes tasks where you left off, and coordinates orders between clients. 100% local and deterministic at its core, no API keys — the one exception is the Design bridge, which needs Google Drive since Design has no local access.
What this actually does (in plain English)
Claude forgets everything the moment a conversation ends, and it can't share anything between its different apps — what you told Claude Code doesn't exist for Claude Desktop. This project fixes that by keeping a small memory file on your own computer, shared by every Claude app you use.
Once it's installed, in practice:
You stop re-explaining your project every time. Claude remembers which files it already read. Nothing changed? It skips straight to the point. Something changed? It only reads the change, not the whole file again.
You can pick up work exactly where you left it — even in a different app. Save a quick note in one Claude app, resume it from any other, decisions and pending tasks intact.
You can leave a task for another app to finish. Tell Cowork to leave a job for Claude Code; it picks it up, does it, and reports back — even though the two can't talk to each other directly.
You can teach it a permanent rule, once. "Never use Redux in this project" — say it once, and it shows up automatically from then on, in any app, without you repeating it.
You can ask "where in my whole project is X handled?" — not just in one file, across the entire codebase — and get the exact files and line numbers, not a guess.
It never summarizes your files with another AI to save space — you always get the real, exact content. And everything stays on your machine: no accounts, no API keys, nothing sent anywhere.
Who this is for: anyone who uses more than one Claude app (say, Claude Code and Claude Desktop, or Cowork) on the same project over multiple sessions. If you only ever have one quick chat at a time, this won't change much for you — and that's fine, it's not built for that.
graph TD
A[Claude Code] --> M
B[Claude Desktop] --> M
C[Claude Cowork] --> M
D[Claude Design] --> M
M[("claude-continuity-mcp<br/>━━━━━━━━━━━━━<br/>smart_read · checkpoint · inbox<br/>project_search · rules · SQLite ledger")] --> P[(Your project<br/>on disk)]
style M fill:#1a1a2e,stroke:#00c8e8,stroke-width:2px,color:#fff
style P fill:#0d0d1a,stroke:#666,stroke-width:1px,color:#cccFour different clients, one shared state: whatever one reads, decides, or leaves pending, any of the others inherits.
Related MCP server: acheron-mcp-server
Why it exists
Claude is amnesiac across sessions and blind across clients: what you read in Claude Code doesn't exist for Claude Desktop, and every new session re-reads the same files as if it were the first time. This MCP is the same local process shared by all your clients, with state persisted to disk — it's the memory Claude doesn't have. The token savings it produces are real, but they're a consequence, not the pitch: what sets the project apart is continuity.
What it is — and what it is NOT
It is:
Continuity: what was read/decided in one session stays available in the next, and in any other client.
A layer so that 20,000-token files don't enter Claude's context whole when you only need 400.
Deterministic where it matters:
smart_readreturns exact fragments of the file (local BM25/embeddings ranking) and exact diffs — never lossy summaries generated by a weaker model.
It is NOT:
A "magic token saver" for your daily chat. If your session is normal conversation, this won't help you — and no tool will.
A code compressor. Compressing code with cheap LLMs degrades Claude's answers and ends up costing more in retries. Never done here.
Note: bulk processing on free models (formerly
router_bulk_process) was split into its own repo,individra-bulk-offload, because it's a different product: it depended on external services that diluted this 100%-local core.
The 6 tools
router_smart_read — Surgical reading with memory (local, $0, no APIs)
In short: reads files smartly, remembers what it already saw, and only shows you what changed.
smart_read(file_path="docs/manual.md", query="where are webhooks configured?")
→ the 2-4 exact relevant fragments, with line numbersSmall file (≤ ~6KB): returned whole, cleaned up.
Large file +
query: hybrid chunking + ranking (fastembed if installed, pure-Python BM25 if not) → only the relevant fragments. Typical savings: 70-90% of the file, zero loss of fidelity.Large file without
query: structural map (outline with line numbers) to decide what to ask for.HTML: cleaned locally (scripts, tags, boilerplate stripped) before processing.
Cross-session memory (diff reads): every read gets recorded (hash + snapshot in a local SQLite file). In any future session, on any client:
Unchanged file →
{"status":"unchanged","outline":[...]}— ~50-100 tokens instead of thousands. In tests: 90 tokens vs. 10,195 for the file.Modified file → only the unified diff against the snapshot (99%+ measured savings).
force_full=trueskips memory when you need the full content.
router_checkpoint — Context handoff between sessions and clients
In short: save your progress, resume it later — from any Claude app.
checkpoint(action="save", name="refactor-auth",
summary="Login migrated to JWT, refresh token still pending",
decisions=["use RS256"], open_items=["expiration tests"],
files=["src/auth.py"])When closing a long task (or when context is filling up), Claude saves the state as human-readable JSON in checkpoints/ — editable by you, shareable with your team. A new session — on Desktop, Code, or Cowork, doesn't matter — calls action="resume" and recovers everything in ~300 tokens, including which files changed on disk since the checkpoint (hash comparison, no re-reading). action="list" shows the available checkpoints.
Cold start (no checkpoints saved): resume never returns empty-handed. If nobody saved a checkpoint, it reconstructs a deterministic digest from the ledger and inbox — the last files touched (and whether they changed on disk since), plus recently completed orders. The payload declares "mode": "reconstructed_activity" explicitly: it's an activity trace, not an intentional checkpoint, and contains no architectural decisions.
router_inbox — Cross-client orders
In short: leave a task for another Claude app to pick up, run, and report back on.
# In Cowork:
inbox(action="send", to="code", message="migrate the tests to pytest",
checkpoint="refactor-auth")
# In Claude Code, on startup:
inbox(action="check", to="code")
→ the order + the linked checkpoint summary
inbox(action="complete", order_id=1, result="34/34 tests green")
# Back in Cowork:
inbox(action="history") → you see the resultClaude chats can't command each other in real time — but they share this disk. The inbox is the async mailbox: you leave an order from one client (linked to a checkpoint so the receiver has the full context of what was being worked on), the other picks it up on startup, executes it, and reports the result. Tell Cowork "leave this task for Claude Code" and that's it — the server's instructions make each client check its inbox when starting work.
Pack clients: cowork, code, and desktop load this MCP directly — for any of them to receive orders, they just need it loaded and to check their inbox with action="check". design (Claude Design) is different and needs a caveat: it's a cloud-hosted product with no access to your local filesystem, so it cannot load this MCP or call action="check" itself — despite design being a valid destination/origin value. Orders addressed to design have to be bridged by a client that does have the MCP (see below). The destination field is free text either way, so you can also invent your own roles for anything else you connect.
Code ↔ design handoff (assets): orders and results can carry assets — a list of file paths or URLs (brief, wireframe, .fig/.png export, specs). That way the back-and-forth between development and design travels with the material, not just the text:
# Claude Code asks Claude Design for a mockup, with the material:
inbox(action="send", to="design", from_client="code",
message="landing hero, dark + cyan accent",
checkpoint="landing-v2",
assets=["/proj/brief.md", "https://.../wireframe.png"])
# Claude Design can't check this inbox itself. A client that HAS the MCP
# (Code or Cowork) relays the order into a shared Google Drive doc that
# Design's own Drive connector can read (see "Bridging Claude Design" below):
inbox(action="check", to="design") → run by Code/Cowork, not by Design
# Claude Design reads the brief from Drive, builds the mockup, and drops
# the result as a Drive doc. The bridging client picks that up and closes
# the loop:
inbox(action="complete", order_id=7, result="mockup ready, 2 variants",
assets=["https://figma.com/.../hero", "/exports/hero-v1.png"])
# Claude Code sees the result and the returned export:
inbox(action="history") → result + result_assetsThe inbox is bidirectional between cowork, code, and desktop natively. With design, it's bidirectional in effect but bridged — never direct.
Bridging Claude Design (Google Drive as mailbox): since Design can't reach your disk, a client that already has the MCP (Cowork or Code) writes the order as a Google Drive doc named INBOX-DESIGN-order-<N> (brief + linked checkpoint). Design — which does have a Google Drive connector — reads it from there, builds, and leaves the result as INBOX-DESIGN-order-<N>-RESULT with the published artifact URL. The bridging client reads that doc and closes the loop with router_inbox complete. It's semi-manual (someone has to trigger each hop) and it's the one part of this project that isn't 100% local — Drive is the intermediary precisely because Design has no other channel in. This convention is saved as a permanent project rule (router_rules), so any client resuming this project sees it without being told again.
Traceability (completed_by): to records who an order was for; completed_by (passed to action="complete" as by) records who actually executed it — and they can differ. If an order addressed to design gets executed by a client bridging on its behalf instead of the real Claude Design product, completed_by makes that visible instead of silently blending into "done". history() flags it explicitly with executed_by_different_client: true when the two don't match.
router_status — Honest session metrics
In short: check how much this is actually saving you — real numbers, no marketing.
The headline metric is tokens_kept_out_of_context: tokens from the original sources that didn't enter Claude's context window. It also reports reads, memory hits (unchanged/diff), ledger state, and pending inbox orders. deep=true adds local diagnostics (fastembed/python).
router_project_search — Search the whole project (incremental BM25)
In short: "where in my whole project is X?" — answered with the exact file and line, not a guess.
project_search(project_dir="/proj", query="where are webhook signatures validated?")
→ the most relevant files, each with its exact fragments and line numberssmart_read answers "where is X in this file?". The question you usually have is "where is X in the project?" — and that's where Claude falls back to Grep.
Incremental index: the first call indexes; later calls only re-index files whose hash changed (unchanged files cost ~0). On this repo: 23 files in 0.41s cold, 0.08s warm.
Persistent and shared: the index lives in the ledger, so it survives across sessions and clients. Grep starts from zero every time; this doesn't.
Better than literal grep for concepts: ranks by relevance and splits
snake_case/camelCase, so "webhook retry" findshandleWebhookRetry().Deliberately bounded: skips
node_modules,.git,dist, binaries and files >400KB. Deterministic, 100% local — exact fragments, never summaries.
router_rules — Permanent project rules, with provenance
In short: teach it a rule once ("never use Redux"), and it remembers, everywhere, forever.
rules(action="add", project_dir="/proj", text="never use Redux", from_client="code")
rules(action="promote", project_dir="/proj", checkpoint="arch-decisions") # a checkpoint decision → permanent ruleDistinct from the task state a checkpoint captures, a rule is a permanent project decision ("never use Redux", "tests go in tests/"). Each one carries provenance: who decided it (client/human), when, and which checkpoint it came from. Rules live in .claude-continuity-rules.json at the project root — git-friendly, hand-editable, travels with the repo, and survives any change to Anthropic's APIs.
Deterministic: the rule is literal text written by the human or promoted from a checkpoint's
decisions— never a lossy LLM summary.Zero-friction distribution: you don't call the tool to read rules — they're injected as
project_rulesintosmart_readandresumepayloads for files in that project (piggybacking on calls that already happen).Doesn't fight
CLAUDE.md, feeds it:sync_to_claudemd=truewrites the rules into a delimited section (<!-- INDIVIDRA RULES START/END -->) of the project'sCLAUDE.md, leaving your own content untouched — so Claude Code's native memory gets them too.
Real savings (honest numbers)
Scenario | Savings | Fidelity |
Re-reading a known file, unchanged | 98-99% | 100% (outline + memory) |
Re-reading a known file that changed | 90-99% | 100% (exact diff) |
Resuming a task in a new session/client (resume) | ~300 tokens vs. re-exploring everything | 100% |
Searching for something specific in a 20k-token file | 80-95% | 100% (exact fragments) |
Ingesting dirty HTML/web content | 40-60% | 100% (only noise removed) |
Normal conversational chat | ~0% | — |
The fixed overhead is ~500 tokens of tool definitions per session. If you're not processing large files or batches, that's your net cost: be honest with yourself about your use case.
Installation
git clone https://github.com/gabicacabelos/claude-continuity-mcp.git && cd claude-continuity-mcp
pip install -r requirements.txt
# Optional (local semantic ranking for smart_read): pip install fastembedNo API key or .env file needed: all six tools are 100% local.
Registering it across all your Claude sessions
Claude Desktop / Cowork — claude_desktop_config.json:
{
"mcpServers": {
"claude-continuity": {
"command": "python",
"args": ["C:/path/to/claude-continuity-mcp/server.py"]
}
}
}Claude Code (user scope = available in every project):
claude mcp add claude-continuity --scope user -- python /path/to/server.pyUser-invocable prompts (slash commands)
Continuity can't depend only on the model choosing to call these tools. The server exposes three MCP prompts that clients surface as slash commands, so you trigger the flow deterministically:
Prompt | What it drives |
| Restore the latest checkpoint (or the cold-start digest) + check pending inbox orders + brief you on where work stands |
| Save a checkpoint of this session and leave a linked order in another client's inbox (args: |
| Check this client's inbox and execute pending orders end-to-end, marking each complete with evidence |
(Exact invocation varies by client — e.g. in Claude Code they appear under /mcp__<server-name>__resume.)
Automatic activation
The server declares instructions that get injected into every session's system prompt, telling Claude when to use each tool without you having to ask. To reinforce it, add a line to your global CLAUDE.md:
To read files >15KB or search for something specific in them, use router_smart_read with a query.
When closing long tasks, save a router_checkpoint; when resuming, summarize it.Architecture
smart_read ──▶ sanitizer (HTML/text, local) ──▶ ledger (have I seen this?)
│ ├─ unchanged → outline (~50 tok)
│ └─ changed → unified diff
└──▶ ranker (with query)
├─ fastembed (bge-small, local ONNX) if available
└─ pure-Python BM25 (fallback, 0 deps)
checkpoint ──▶ checkpoints/*.json (human-readable/editable) + hash verification on resume
inbox ──────▶ shared SQLite queue (orders between Cowork/Code/Desktop/Design + handoff assets + results)
rules ──────▶ .claude-continuity-rules.json at project root (git-friendly, provenance) → injected into smart_read/resume, optional CLAUDE.md sync
project_search ▶ incremental BM25 index in the ledger (re-indexes only changed hashes) → files ranked + exact fragments
hook (opt-in) ▶ PostToolUse → raw_reads staging table (WAL) → drained into the ledger by the MCPEverything local: the ledger and the inbox are SQLite files on disk; checkpoints are readable/editable JSON. No network, no API keys, no external dependencies for the core.
An MCP process doesn't auto-update itself
Each client launches server.py as its own process and keeps it alive for the duration of the session. Editing the code on disk — or running git pull/git push — does not affect a process that's already running: Python doesn't reload modules on its own. If you changed something and the new behavior isn't showing up, that's not a bug: restart the MCP (reconnect via /mcp or restart the client).
So you don't have to infer that by hand, router_status includes code_staleness: it compares the modification time of server.py/router/*.py against when the process started, and returns {"stale": true, "changed_file": "...", "hint": "..."} if the disk changed after boot. Additionally, when there's staleness, every tool injects a code_stale field into its response (with the changed file and a hint) — you find out on the very first call you make, without needing to remember to check router_status. In normal operation the field doesn't exist: it only appears if the code actually changed on disk after boot.
Passive capture (optional, Claude Code only)
The memory only fills up when Claude chooses router_smart_read. If it uses its native Read instead, the ledger never finds out. An opt-in PostToolUse hook closes that gap: value accumulates even when Claude never picks this MCP's tools.
Add to your .claude/settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Read",
"hooks": [{ "type": "command", "command": "python /path/to/claude-continuity-mcp/hooks/capture_read.py" }]
}
]
}
}The hook is deliberately dumb: it does one INSERT into a staging table (raw_reads) and exits — no hashing, no file reading. The MCP later promotes those rows into the real ledger on its own time (drain_raw_reads), so the hook never pays that cost. The ledger runs in WAL mode so the hook (a separate process) and the MCP can write concurrently.
Honest costs and caveats:
~250ms per native
Read, almost entirely Python interpreter startup — not the DB write. If you read a lot of files, that adds up. Don't enable it if that bothers you; everything else works without it.If the DB is busy, the hook discards the capture silently (100ms SQLite timeout). Losing a capture is free; freezing your terminal is not.
Claude Code only. Desktop/Cowork/Design have no hooks — there, memory still depends on the tools being called.
It's coupling to an Anthropic-controlled API that can change. That's why it's opt-in and strictly additive: if the hook stops firing, nothing breaks.
Hot configuration (router_config.json)
Runtime tunables can be adjusted without restarting the MCP. Copy router_config.example.json to router_config.json (next to server.py, gitignored) and edit what you need — the next call to any tool picks it up (the file is only re-parsed when its modification time changes, near-zero overhead):
Key | Default | What it controls |
|
| Strict mode: if non-empty, only paths inside these roots can be read or indexed. Empty = no root restriction (the sensitive-file deny-list still applies) |
|
| Threshold below which a file is returned whole |
|
| Fragments to return when the call doesn't pass |
|
| If the diff weighs more than this fraction of the file, it's not worth it and normal content is returned |
|
| Query→fragments cache and embedding vector cache |
Values are type-checked and clamped to sane ranges; anything invalid falls back to its default instead of breaking the tool. If router_config.json doesn't exist, the defaults apply (zero-config). Logic changes (new code in server.py/router/) still require a restart — that's unavoidable; hot config only removes restarts for tunable adjustments.
Security model
This server reads files from your disk and persists what it reads. Worth knowing before you install it:
File access is restricted on two levels.
Deny-list, always on (no configuration needed). Credentials and key material are never read and never stored:
~/.ssh,~/.aws,~/.gnupg,~/.kube,.env(and.env.*),*.pem,*.key,*.p12,*.kdbx,.netrc,.pgpass, shell history, and similar. Paths are fully resolved (symlinks and..) before validation, so traversal likeproj/../../.ssh/id_rsais rejected too.Allow-list, opt-in (strict mode). Set
allowed_rootsinrouter_config.jsonand anything outside those roots is refused — for reads, for project indexing, and for passive capture. Empty by default so a fresh install doesn't break; recommended if you work on machines holding third-party data.
What gets persisted, and where. Every file read through smart_read (and every file captured by the optional hook) is stored in cache/ledger.db — including a full plaintext snapshot of files up to 400 KB, kept for 30 days. That's what makes diff reads possible. The DB is local and gitignored, but it is not encrypted: treat cache/ as sensitive as the projects you point this at. Delete it any time; it rebuilds itself.
Content-level secret redaction. The path deny-list above blocks known credential files (.env, *.pem...) — it doesn't help when a real secret is hardcoded inside an ordinary source file. Before content is returned or persisted (smart_read, passive capture, project search), it's scanned for recognizable credential shapes — AWS access keys, GitHub/OpenAI/Anthropic/Slack tokens, JWTs, private-key blocks, and quoted password=/api_key=-style assignments — and matches are masked in place (AKIA█REDACTED█), never stored or shown in full. This is shape-based pattern matching, not a secret scanner: it won't catch a credential that doesn't look like one of these formats, and unquoted values are deliberately not touched (too easy to confuse with a variable reference like token = getToken()). Determinism holds — same secret value always redacts to the same mask, so diffs still detect real changes without exposing either value.
Inbox orders are untrusted input. An order's message is free text that another Claude client reads and acts on, and from is self-declared — not verified. Anything with write access to cache/inbox.db can queue an order. The tool payload, the server instructions, and the /inbox prompt all mark orders as untrusted and tell the model to show you the literal text and get confirmation before anything hard to reverse (push, delete, publish, install, running commands). This is a mitigation, not a guarantee — a language model can be talked out of a guardrail. Don't run an inbox you don't control, and read orders before approving destructive work.
The Google Drive bridge sends project data off your machine. The Design handoff (see above) writes briefs into a Google Doc so Claude Design can read them. That content leaves local disk and lands in your Drive account under whatever sharing permissions it has. The "100% local" property does not hold for that flow — don't put secrets in a brief.
Not covered. This is a local, single-user tool: no authentication, no sandbox, and no protection against another process on the same machine that already has your filesystem permissions. It assumes you trust the projects you point it at and the code your Claude clients run.
Known limitations
BM25 is lexical: a query with no words in common with the text degrades to the first chunks of the file. Installing
fastembedimproves semantic queries (requires onnxruntime compatible with your Python version).BM25 is lexical, project-wide too:
router_project_searchranks by shared vocabulary, so a query with no words in common with the code degrades. It complements Grep and semantic search, it doesn't replace them.code_stalenessdetects that the code changed — it doesn't restart itself. Auto-restart isn't safe here: multiple clients may share the same process, and killing it mid-inbox-order would cut the tool out from under another session with no guarantee it comes back on its own.
License
MIT — see LICENSE.
This server cannot be installed
Maintenance
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/gabicacabelos/claude-continuity-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server