Server data from the Official MCP Registry
Cross-session memory for Claude over MCP — prioritised briefs, supersession, cross-project recall.
Cross-session memory for Claude over MCP — prioritised briefs, supersession, cross-project recall.
Valid MCP server (1 strong, 3 medium validity signals). No known CVEs in dependencies. Package registry verified. Imported from the Official MCP Registry.
4 files analyzed · 1 issue found
Security scores are indicators to help you make informed decisions, not guarantees. Always review permissions before connecting any MCP server.
This plugin requests these system permissions. Most are normal for its category.
Set these up before or after installing:
Environment variable: HANDOFF_VAULT
Environment variable: HANDOFF_PROJECT
Add this to your MCP configuration file:
{
"mcpServers": {
"io-github-kirill-sviridov-handoff-mcp": {
"env": {
"HANDOFF_VAULT": "your-handoff-vault-here",
"HANDOFF_PROJECT": "your-handoff-project-here"
},
"args": [
"handoff-mcp"
],
"command": "uvx"
}
}
}From the project's GitHub README.
Your agent's memory between sessions. Persistent, cross-project hand-off for Claude over the Model Context Protocol — it remembers the goal, the decisions, the dead-ends, and what's next, so the next session never starts from zero.
Claude forgets everything between sessions. handoff-mcp gives it a memory that
survives the context window: it tracks the progress of your work — goals,
decisions and their rationale, dead-ends, open questions, the next step — and at
the boundary of a session produces a prioritised, token-budgeted brief so the
next session resumes already knowing where you left off.
It is not a context dump. Two things make it different from similarity-based memory stores (mem0 / OpenMemory style):
Status: v0.2, early. The core (vault, brief, supersession, keyword search, cross-project recall) is tested and stable; the semantic, consolidation, importer, and multi-device sync layers are optional and newer. It leans on the agent calling the tools at the right moments — see Limitations for the honest edges.
It is also cross-project: one vault, namespaced per project. The brief is
project-scoped (where did I leave off here), but search_memory recalls across
all projects — so when you say "in one of my projects we did X", Claude can
find and pull that decision out of another project.
python examples/two_sessions_demo.py — session 1 works and stops; session 2 is
a fresh process that resumes from the brief alone.
Session 1 logs its progress (and changes its mind once):
log_event("goal", "Ship the finance agent: income/expense tracking…")
d = log_event("decision", "Store transactions in a flat JSON file.")
log_event("decision", "Use SQLite instead of JSON — need queries.", supersedes=[d]) # retracts ↑
note_entity("Architecture", "SQLite-backed; LLM summary calls run in a worker.") # durable
log_event("deadend", "Provider streaming API times out on long months; needs chunking.")
log_event("question", "Recurring transactions: templates or materialised rows?")
log_event("next_step","Write the SQLite schema, then the ingest function.")
checkpoint("Chose SQLite; finance schema is next.")
Session 2 calls get_brief() and gets back only the current state — the
retracted JSON decision is gone:
# Hand-off brief — agent-hub
## Goal
- Ship the finance agent: income/expense tracking with scheduled summaries.
## Next step
- Write the SQLite schema for transactions and categories, then the ingest function.
## Decisions
- Use SQLite for transactions instead of JSON — need queries for summaries. See [[Architecture]].
## Dead ends (tried & failed)
- Tried the provider's streaming API for the summary job — times out on long months. Don't retry without chunking.
## Open questions
- Should recurring transactions be modelled as templates or materialised rows?
## Related knowledge
- [[Architecture]] — SQLite-backed; LLM summary calls run in a worker.
The retracted "flat JSON file" decision never appears. The graph-linked
[[Architecture]]note is pulled in automatically. The brief is ~170 tokens.
And cross-project recall — search_memory("timeout chunking", scope="all") pulls
a decision out of a different project:
- [hermes] In the Hermes project we solved long-job timeouts by chunking requests.
- [agent-hub] Tried the provider's streaming API for the summary job — times out on long months…
Two offline, deterministic benchmarks — no LLM, no network — so they regenerate
identically anywhere and are pinned by tests/test_benchmark.py. Full numbers,
methodology, and how to reproduce: benchmarks/RESULTS.md.
1. Supersession in isolation (benchmarks/supersession_benchmark.py). A
project's decisions evolve across 8 statements over 4 topics; each has one decision
a later one retracts (JSON→SQLite, cookies→JWT, …). Retrieved by decision (so
every stale fact is reachable), the flat log vs the active view:
| mode | stale leaked | current kept |
|---|---|---|
| supersession OFF (flat log) | 4 / 4 | 4 / 4 |
| supersession ON (active view) | 0 / 4 | 4 / 4 |
Supersession removes exactly the retracted decisions while keeping every current one — and scoring current kept too means an empty answer can't pass as a win. A similarity store with no notion of one fact retiring another behaves like the OFF row.
2. Brief vs naive dump (benchmarks/brief_reconstruction.py). What a resuming
session actually reads — the budgeted, supersession-aware brief vs pasting back the
whole log. As history grows the dump balloons and keeps carrying every retraction;
the brief stays bounded (a soft cap) and contradiction-free while retaining all
key items (e.g. at 228 events: 3063→217 tokens, 14×, 3 contradictions → 0).
These measure the mechanism honestly rather than staging a head-to-head against another store — a fair cross-system run needs both under identical retrieval plus an LLM endpoint we can't reproduce in CI (see ADR-0008).
"Tokens" here and elsewhere in this README are estimated as
len(text) / 4(model-agnostic), not counted with a real tokenizer.
The markdown vault is the source of truth — human-readable, openable in Obsidian, your data on your disk. The SQLite + FTS5 index is derived from the vault and can be rebuilt at any time; it powers ranking, the token budget, and cross-project full-text search.
See docs/architecture.md and the
ADRs for the design rationale.
| Tool | When Claude calls it |
|---|---|
get_brief(project?, token_budget?) | At session start — load where the last session left off. |
log_event(type, content, importance?, supersedes?, supersedes_query?, project?) | As work happens — record goals, decisions, dead-ends, files, questions, next steps. supersedes retires a prior event by id; supersedes_query retires the best-matching active event of the same type when you don't have its id (ADR-0007). |
search_memory(query, scope=current|all, limit?) | When the user references past or other-project work. limit caps the number of results (default 10). Each hit includes the event id, feedable straight into log_event's supersedes. |
note_entity(name, content, project?) | To record durable project knowledge (architecture, conventions, components). |
checkpoint(summary?) | At session end — finalise the session and emit the brief. |
consolidate(project?, older_than_days?) | To compress old sessions into durable notes (opt-in, needs an LLM). |
sync(remote_url?) | To sync memory across devices — pull, commit, and push the vault's private git remote (opt-in). First call with a repo URL configures it; then a bare call syncs. See Multi-device sync. |
Also exposed: an MCP resource session://brief and a prompt resume for
auto-loading the brief at the top of a session.
log_event types: goal, decision, deadend, file, question, next_step.
No subscription. No required API keys. The core is free and fully local — your memory is plain files on your disk, and handoff-mcp never phones home (no hosted service, no telemetry).
| Capability | Needs a model / key? |
|---|---|
| Memory, brief, supersession, keyword search, cross-project, importers | No — local, offline, free |
| Semantic recall (optional) | No by default (hashing or local sentence-transformers); bring your own OpenAI-compatible key only if you pick the openai backend |
| Consolidation (optional, occasional) | A model — your own key (a few cents, run rarely; it's not a hot path) or a local model |
So most of the value costs nothing and needs no key. The optional layers either run locally or use your own provider — you're never locked into ours.
Keyword search (FTS5/bm25) is the default and needs nothing extra. You can enable a semantic layer that fuses keyword and embedding similarity with Reciprocal Rank Fusion:
HANDOFF_SEMANTIC=1 handoff-mcp # turn the layer on (default: hashing backend)
The backend you pick decides whether this actually understands paraphrases.
The default hashing backend is a lexical baseline — it hashes tokens, so it
adds fuzzy lexical matching (and demonstrates the hybrid pipeline) but does not
recall on meaning when the words differ. For genuine paraphrase-tolerant recall,
choose local or openai, which use learned embeddings.
Pluggable embedding backends, selected with HANDOFF_EMBEDDER — one server,
no forks:
| Backend | Install | Paraphrase? | Notes |
|---|---|---|---|
hashing (default) | — | No — lexical | Deterministic, offline, zero-dependency toy baseline (feature-hashing). Good for demos/tests; not real semantics. |
local (recommended) | pip install -e ".[semantic-local]" | Yes | Offline sentence-transformers; no key, pulls in torch. Default model Qwen/Qwen3-Embedding-0.6B (multilingual incl. Russian, 1024-dim, Apache-2.0). |
openai | pip install -e ".[semantic-openai]" | Yes | Any OpenAI-compatible endpoint (OpenAI, Together, a self-hosted proxy, …). Set HANDOFF_EMBED_BASE_URL / OPENAI_BASE_URL and HANDOFF_EMBED_API_KEY / OPENAI_API_KEY. |
# Example: semantic recall via any OpenAI-compatible endpoint
pip install -e ".[semantic-openai,semantic]"
export HANDOFF_SEMANTIC=1 HANDOFF_EMBEDDER=openai
export HANDOFF_EMBED_BASE_URL=https://your-openai-compatible-endpoint/v1 HANDOFF_EMBED_API_KEY=sk-…
export HANDOFF_EMBED_MODEL=text-embedding-3-small
Design (see ADR-0004):
Embedder protocol — the hashing default is
deterministic and dependency-free; openai / local plug in for real semantic
quality without changing anything else..[semantic]) — vectors
persist as BLOBs and search works with an exact cosine scan; if sqlite-vec is
installed and loadable, a vec0 table provides fast KNN with the same top-ranked results.search_memory.rerank=False to get raw relevance order.To use a lighter/faster local model instead, set HANDOFF_EMBED_MODEL (e.g.
Alibaba-NLP/gte-multilingual-base, 305M/768-dim — also set
HANDOFF_EMBED_TRUST_REMOTE_CODE=1 as that model requires it).
Over a long-lived project the episodic log grows without bound — a volume problem, not just an indexing one. Consolidation ("sleep") folds it down:
HANDOFF_LLM_MODEL=gpt-4o-mini handoff-mcp # enables the consolidate tool
consolidate(project?, older_than_days?) distils old finished sessions' active
decisions into the durable entity notes (Architecture, Decisions, Dead-ends, …),
then archives the originals to <project>/archive/ and drops them from the
active index. So the vault shrinks but the lasting knowledge — which the brief
already surfaces — is kept.
HANDOFF_LLM_MODEL is set (OpenAI-compatible; reuses the embedder's endpoint
settings). The brief, search, and supersession stay deterministic.See ADR-0006.
Bootstrap a project's memory from data you already have, so it's useful from minute one instead of empty:
handoff-import git ./my-repo --project my-project # commit history → memory
handoff-import claude session.jsonl --project my-project # a Claude Code transcript
HANDOFF_VAULT).uv venv && uv pip install -e ".[dev]"
# Run the two-session demo: session 1 works, session 2 reads the brief.
python examples/two_sessions_demo.py
handoff-mcp speaks standard MCP over stdio, so it works with any MCP client —
Claude, Cursor, Codex, Kilo Code, Windsurf, Cline, VS Code, Zed, … The config is
essentially the same everywhere; only the file location (and, for Codex, the
format) differs.
Canonical config — the mcpServers JSON block used by Claude, Cursor, Kilo
Code, Windsurf, Cline, and most others:
{
"mcpServers": {
"handoff": {
"command": "handoff-mcp",
"env": {
"HANDOFF_VAULT": "/path/to/your/vault",
"HANDOFF_PROJECT": "my-project"
}
}
}
}
| Client | Where it goes |
|---|---|
| Claude Code | claude mcp add handoff -- handoff-mcp, or a .mcp.json in the project |
| Claude Desktop | claude_desktop_config.json |
| Cursor | .cursor/mcp.json (project) or ~/.cursor/mcp.json (global) |
| Kilo Code | Settings → MCP → Add Server → Local (stdio), or .kilocode/mcp.json |
| Windsurf | ~/.codeium/windsurf/mcp_config.json |
| Cline / Roo Code | the extension's MCP panel → cline_mcp_settings.json |
| VS Code (Copilot) | .vscode/mcp.json — note the different schema below |
Codex CLI uses TOML in ~/.codex/config.toml (or run codex mcp add):
[mcp_servers.handoff]
command = "handoff-mcp"
[mcp_servers.handoff.env]
HANDOFF_VAULT = "/path/to/your/vault"
HANDOFF_PROJECT = "my-project"
VS Code uses "servers" and an explicit type:
{ "servers": { "handoff": { "type": "stdio", "command": "handoff-mcp",
"env": { "HANDOFF_VAULT": "/path/to/your/vault", "HANDOFF_PROJECT": "my-project" } } } }
Notes:
handoff-mcp must be on PATH — install it as a tool
(uv tool install git+https://github.com/kirill-sviridov/handoff-mcp; PyPI release coming)
or, from a checkout, use "command": "python", "args": ["-m", "handoff_mcp.server"].HANDOFF_PROJECT per agent/repo; keep HANDOFF_VAULT pointed at the same
shared vault across all of them (see below).One central vault, with a folder per project inside it:
~/.handoff-mcp/vault/ # HANDOFF_VAULT (default; override per machine)
├── .index.db # derived SQLite index — rebuildable, gitignore it
├── my-project/
│ ├── sessions/<id>.md # episodic notes
│ └── entities/<Name>.md # durable knowledge
└── another-project/…
search_memory) only
works because every project lives in a single store. So point every agent's
HANDOFF_VAULT at the same directory and just vary HANDOFF_PROJECT.handoff-sync --setup
configures the remote and gitignores the derived index for you.HANDOFF_VAULT inside that
repo (e.g. ./.handoff) — but then search sees only that project. The shared
vault is recommended.Your vault is just a private git repo, so memory can follow you across machines. Sync is strictly opt-in — with no git configured, memory works fully on one device.
| Tier | Setup | You get |
|---|---|---|
| Local-only (default) | nothing | full memory, one device, no account |
| Manual | handoff-sync --setup <private-repo-url> once | handoff-sync (or the sync tool) pulls, commits, pushes on demand |
| Automatic | above + HANDOFF_AUTO_SYNC=1 | pull at session start, push at checkpoint |
Entity notes merge cleanly across machines via git's built-in union driver
(*/entities/*.md merge=union in the vault's .gitattributes, written by setup).
In a shell-less client (e.g. Cursor), just ask the agent to sync — the sync
tool needs no terminal. Setup needs working git auth (gh auth login or an SSH
key); if a push fails, the command tells you exactly what to fix.
Knowing when to use it. The server never pushes anything to the model; the model decides when to call the tools. Three layers make that reliable, from most portable to most capable:
Tool descriptions (built in) — every tool says when to call it. Works in any MCP client.
Server instructions (built in) — a short ritual the server sends on
connect (load the brief at start, log as you work, checkpoint at the end).
Portable across Claude Desktop, Cursor, and other MCP clients.
Claude Code skills (skills/) — encode the workflow and,
crucially, trigger on colloquial cues:
session-handoff — when you say "го в следующую
сессию" / "that's it for today", the agent knows to checkpoint on its own.
It also bootstraps the project's instruction file on first use (CLAUDE.md,
or AGENTS.md / .cursor/rules/handoff.mdc / .windsurfrules per client).session-planning — optional companion: breaks a
big task into session-sized chunks and persists the plan in memory.Install by copying into your skills dir:
cp -r skills/handoff skills/planning ~/.claude/skills/ # user-wide
# or .claude/skills/ inside a specific project
(Skills are a Claude Code / claude.ai feature; other agents rely on layers 1–2.)
For non-Claude clients, drop the memory block into the project's instruction
file with the handoff-init CLI (idempotent):
handoff-init # CLAUDE.md
handoff-init --client codex # AGENTS.md · --client cursor / windsurf
For Claude Code specifically, also add this to your CLAUDE.md so the brief loads
automatically even without the skill:
At the start of a session call
get_brief. Record decisions, dead-ends and the next step withlog_eventas you work — one atomic item per call (1-2 sentences with the why), not a whole-session summary; reference durable notes as[[Entity]].checkpointbefore you stop. When I mention past work or another project, callsearch_memory.
Honest edges of v0.2, so you know what you're adopting:
supersedes (by id)
or supersedes_query (by best match). That is deliberate (it's what keeps the
brief deterministic and auditable), but it means the quality of the memory
depends on the agent actually logging retractions. It won't silently
de-duplicate contradictions the way an LLM-extraction store attempts to.log_event / get_brief / checkpoint at the
right moments. The tool descriptions and the skill nudge this, but a client that
never calls the tools gets an empty vault. Cross-session memory is only as good
as what got logged.hashing) is lexical, not paraphrase-aware.
Real paraphrase recall needs local or openai (see
Semantic recall). The deterministic brief itself
never uses embeddings._sync_index) — it picks up its own writes
live, but another process's new events only on the next launch (a sync — manual
or the HANDOFF_AUTO_SYNC pull-on-brief — re-indexes mid-session when it pulls
new events). For concurrent threads inside one process, access to the shared
index is serialised by a lock.uv pip install -e ".[dev]"
ruff check . && mypy && pytest
python examples/two_sessions_demo.py # the hand-off in action
python examples/demo_presentation.py # paced/narrated version (for recording a GIF)
python examples/stdio_smoke.py # run it as a real stdio MCP server
python benchmarks/brief_reconstruction.py # brief vs naive full-dump
python benchmarks/supersession_benchmark.py # supersession on vs off, in isolation
MIT — see LICENSE.
Be the first to review this server!
by Modelcontextprotocol · Developer Tools
Web content fetching and conversion for efficient LLM usage
by Modelcontextprotocol · Developer Tools
Read, search, and manipulate Git repositories programmatically
by Toleno · Developer Tools
Toleno Network MCP Server — Manage your Toleno mining account with Claude AI using natural language.