folio
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@foliosearch my memories for how I prefer commit messages formatted"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
English | 中文
folio
A personal memory hub that unifies memories across AI coding harnesses (Claude Code, Codex, Cursor, Kimi Code, ZCode).
Every harness keeps its own memory/rules store, in incompatible formats that never talk to each other. folio one-way imports those scattered memories into a local file-based memory bank (~/.folio) with a unified format and unified search, then serves the bank back over an MCP server so any MCP-capable harness can read all of it.
Three design principles
One-way import: only reads from each harness's native store and syncs incrementally into the memory bank; it never writes back to any harness's native directory. The bank is the single source of truth — a memory stays in the bank even after its source file is deleted.
Unified format: every memory is Markdown + frontmatter, in one of four types —
user(user preferences),feedback(feedback and lessons learned),project(project state and decisions),reference(reference material).Serve back over MCP: the bank is exposed to harnesses via
folio serve(a stdio MCP server); harnesses without MCP support are not integrated.
Related MCP server: memory-mcp
Screenshots
Architecture
┌──────────────────── Harness native stores (read-only) ────────────────────┐
│ ~/.claude ~/.codex ~/.cursor ~/.kimi-code ~/.zcode <project>/.* │
└───────────────────────────────────┬───────────────────────────────────────┘
│ ① import (adapters, incremental sync / watch)
▼
┌───────────────────────┐
│ organize │ LLM or local dedup rules:
│ dedup / merge / retype │ dry-run preview,
│ / retag / flag │ --apply to execute
│ conflicts │
└───────────┬───────────┘
▼
┌────────────────────────────────────────┐
│ local memory bank ~/.folio │
│ (single source of truth) │
│ memory/<type>/*.md + MEMORY.md index │
│ state/ sync state cache/ search index │
└─────────┬───────────────────┬──────────┘
│ ③ serve │ browse/manage
▼ ▼
folio serve folio CLI
(stdio MCP server) list/search/show/stats/
5 tools + 1 resource organize/conflicts/doctorRequirements
Node.js ≥ 18 (also for development builds)
pnpm 9 (the repo pins
pnpm@9.15.4;corepack enableprepares it automatically)
Quick start
# Install from npm (self-contained bundle, zero runtime deps)
npm i -g folio-memory # gives you the `folio` command
# Or build from source (monorepo: core / cli / mcp-server)
pnpm install && pnpm -r build
npm i -g packages/cli # or: cd packages/cli && pnpm link --global
# Four steps
folio init # ① initialize ~/.folio (directory layout + default config.toml)
folio sync # ② incrementally import memories from detected harnesses
folio organize # ③ organize: dry-run preview (falls back to local dedup rules without an API key)
folio install --all # ④ register the folio MCP server into each harness's configinstall targets only detected harnesses by default; --all targets all 5. The original config is backed up before every write (<file>.bak-<timestamp>), and folio uninstall removes the registration.
Without a global install you can also run node packages/cli/dist/cli.js <command> directly.
Command reference
Global options: --home <dir> (takes precedence over FOLIO_HOME, default ~/.folio), --json (machine-readable output), -V/--version. Exit code 2 for usage errors, 1 for runtime errors.
Command | Purpose | Common options |
| Initialize the home directory (idempotent) | — |
| Collect memories from harnesses and sync incrementally |
|
| Watch memory source directories; auto-sync after debounce |
|
| List memories |
|
| Full-text search (Chinese supported) |
|
| Show a memory's full frontmatter and body (accepts a unique id prefix) | — |
| Organize: merge duplicates, retype/retag, flag conflicts (dry-run by default) |
|
| List conflicting memory pairs flagged with | — |
| Start the MCP server over stdio (stdout is the protocol channel) | — |
| Register the MCP server into harness configs (backup before write) |
|
| Remove the registration from harness configs (backup before write) |
|
| Health check: home/config/adapters/memory bank/search index |
|
| Grouped stats by type/scope/source harness | — |
Config file ~/.folio/config.toml
Default config generated by init; environment variables take precedence over the file.
[llm]
enabled = true # master switch for LLM-assisted features (classify, organize)
# apiKey = "sk-..." # prefer the FOLIO_LLM_API_KEY environment variable
baseURL = "https://api.openai.com/v1" # OpenAI-compatible endpoint
model = "gpt-4o-mini"
classifyOnSync = false # whether sync auto-classifies new memories via the LLM
[sync]
autoOrganize = false # whether to auto-organize after sync completes
# Per-adapter switches; treated as enabled when omitted
# [adapters.claude-code]
# enabled = falseEnvironment variables:
Variable | Purpose |
| Memory bank home directory (default |
| Override the corresponding |
| Override each harness's home directory (defaults |
The LLM is used for exactly two things: classifying new memories in sync --classify, and generating organize plans in organize. Everything works without an API key; organize automatically degrades to local dedup rules.
Adapter support matrix
Harness | What gets imported | Where the MCP server is registered | Notes |
Claude Code |
| top-level | skips the |
Codex |
| top-level |
|
Cursor |
| top-level | project rules are |
Kimi Code |
| top-level | skips |
ZCode |
| nested | skips |
All adapters are read-only against harness directories; session records (sessions/history, etc.) are never touched. sync automatically includes the current working directory (and any --project-dir) in the project-level rules scan.
Memory file format
Each memory is a Markdown file under ~/.folio/memory/<type>/:
---
id: m_abc123def456
type: feedback # user | feedback | project | reference
scope: project:/path/to/proj # global or project:<project identifier>
title: Build before testing
tags: [testing, build]
source:
harness: claude-code # source harness ("mcp" when written via MCP)
path: /original/source.md # original file path (optional)
importedAt: 2026-09-29T12:00:00.000Z
hash: <body sha256> # basis for incremental sync and dedup
created: 2026-09-29T12:00:00.000Z
updated: 2026-09-29T12:00:00.000Z
supersedes: [m_xxx] # optional: ids this memory supersedes
conflictsWith: [m_yyy] # optional: ids this memory conflicts with
archived: false # optional: archived into archive/ after a merge
---
Body (Markdown).MEMORY.md (the bank index) is rebuilt automatically by folio — do not edit it by hand.
MCP server
folio serve starts over stdio and provides 5 tools + 1 resource:
Tools:
memory_search,memory_list,memory_read,memory_write,memory_updateResource:
memory://index(current content of the bank index MEMORY.md)
The default launch command registered by folio install is folio serve; customize it with --command "node /abs/path/cli.js".
Privacy
All data stays on the local filesystem; there is no telemetry of any kind.
The only network egress is the LLM API you configure yourself (requests are made only by
sync --classify/organize/doctor --check-llm).
Docker end-to-end tests
The repo ships a full-pipeline verification image (it never installs any harness on your host):
docker build -f docker/Dockerfile -t folio-e2e . # build stage runs pnpm install / build / full unit tests
docker run --rm folio-e2e # in-container: init → sync → search → install → MCP smokeThe image globally installs real harness CLIs (@anthropic-ai/claude-code, @openai/codex, @moonshot-ai/kimi-code — installability check only, no login, no runs); Cursor and ZCode have no headless install path, so docker/e2e.sh fakes their home directories per the documented layout. Any failed assertion exits non-zero.
Note: @moonshot-ai/kimi-code requires Node ≥ 22.5 at runtime (it uses createZstdDecompress from node:zlib), so on the node:20 base only the install check passes and --version fails; the build report marks it installed-but-run-failed as expected, without affecting the rest of the verification.
Real-harness lab (optional)
docker/Dockerfile.harness-lab + docker/harness-lab.sh spin up a lab with real models: the container installs Claude Code / Codex / Kimi Code, configures a third-party API key per each vendor's docs (all three can share one Kimi Code subscription key — api.kimi.com/coding exposes both an Anthropic-compatible and an OpenAI/Responses-compatible endpoint), runs one real session per harness that induces a long-term memory write, then verifies folio imports it and serves it over MCP:
docker build -f docker/Dockerfile.harness-lab -t folio-harness-lab .
echo "KIMI_API_KEY=sk-..." > /tmp/folio-lab.env # key never enters the image or the repo
docker run --rm --env-file /tmp/folio-lab.env folio-harness-labThird-party key configuration per harness (verified in the lab):
Harness | Configuration |
Claude Code |
|
Codex |
|
Kimi Code |
|
Cursor and ZCode are GUI desktops with no headless mode; they are out of lab scope (their adapters are covered by fixtures + e2e).
Field notes: ① Claude Code's headless mode (claude -p) has a known flaky MCP tool-registration race — the server shows Connected and its resource is visible, but the tool list occasionally misses the session; retrying or using a fresh project directory recovers. ② Current Kimi Code has no long-term memory files (the official data-locations doc lists no memories/ directory); its adapter is future-proofing for when the feature lands. ③ Whether a model calls MCP tools is inherently stochastic — the lab's hard assertion is "at least one harness completes a real write".
Desktop app (in development)
packages/desktop (@folio/desktop) is the Electron desktop app for folio. The renderer is a 1:1 port of the gui-mock/ design prototype (Vite + React 18 + Tailwind 3 + shadcn); all data is produced for real by @folio/core over IPC.
Features: memory-bank file-tree browsing / search (⌘K) / reading & editing / create / archive / reveal in Finder, MEMORY.md index page, sources page (adapter detection status + MCP register/unregister), organize page (confirm each LLM plan item before applying), activity page (sync log), settings page (LLM endpoint/model/key, adapter toggles), sidebar manual sync + watch auto-sync toggle.
pnpm --filter @folio/desktop dev # development mode (electron-vite dev, HMR)
pnpm --filter @folio/desktop build # output to out/{main,preload,renderer}
pnpm --filter @folio/desktop start # preview the built output
pnpm --filter @folio/desktop test # services unit tests (vitest, no display needed)During development, point at a temporary bank with FOLIO_HOME=/tmp/xxx pnpm --filter @folio/desktop dev to avoid touching the real ~/.folio. Without an Electron environment, out/renderer is a plain static site (with mock-data fallback) — host it on any static server to preview the UI.
Note for Node 18 hosts: electron 44's install.js (the postinstall that downloads the binary) goes through @electron/get v5, which requires Node ≥ 22, so pnpm install under Node 18 skips the binary download. To actually run Electron, patch it in manually (one-time):
curl -sL https://github.com/electron/electron/releases/download/v44.4.5/electron-v44.4.5-darwin-arm64.zip -o /tmp/e.zip
unzip -q -o /tmp/e.zip -d packages/desktop/node_modules/electron/dist
echo "Electron.app" > packages/desktop/node_modules/electron/path.txtSecurity baseline (enforced item by item — read packages/desktop/src/main/ before changing any of this):
webPreferencesexplicitly setscontextIsolation: true/sandbox: true/nodeIntegration: false/webSecurity: true; the preload is bundled as a single-file CJS (a hard sandbox requirement).Strict CSP injected per mode (
transformIndexHtmlinelectron.vite.config.ts): proddefault-src 'self',connect-src 'none'; dev only additionally relaxes what HMR needs —connect-src 'self' ws: http://localhost:*andscript-src 'unsafe-inline'.setPermissionRequestHandler/setPermissionCheckHandlerdeny everything by default.will-navigateis always prevented;setWindowOpenHandleralways denies.Every
ipcMain.handlefirst validatesevent.senderFrame.origin(dev only accepts the vite dev-server origin, prod onlyfile://).All IPC payloads pass zod schemas at the main-process entry (
src/shared/ipc.ts, shared by all three sides).contextBridgeexposes only narrow functions + plain data, never the rawipcRenderer; subscription APIs return an unsubscribe function.Zero telemetry: no
crashReporter.start(), no analytics of any kind.The LLM API key only ever lands in
state/secrets.jsonencrypted viasafeStorage;settings:getreturns onlyhasApiKey+ a mask — the plaintext never entersconfig.tomland is never sent to the renderer.No remote module; the window reference is nulled on close, and nothing is sent after
webContentsis destroyed.
Roadmap
Second batch of adapters: Gemini CLI, Qwen Code, Trae, CodeBuddy, OpenCode
Legacy tool imports: Roo, Continue, iFlow
Local REST API (bound to 127.0.0.1 + token auth)
Go backend (single-binary distribution)
Electron desktop app: first version has landed (see "Desktop app (in development)"); packaging/distribution and auto-update are next
Repository structure
packages/
core/ # adapters, memory store, sync, search, organize, LLM (frozen public API in src/index.ts)
cli/ # folio CLI (commander)
mcp-server/ # stdio MCP server (5 tools + 1 resource)
desktop/ # Electron desktop app (in development; renderer ported from gui-mock/)
docker/ # Dockerfile + e2e.sh (containerized end-to-end verification)
assets/ # logo and icons (README header, desktop window icon)
gui-mock/ # design prototype (Vite + React static mock, not product code)This server cannot be deployed
Maintenance
Related MCP Connectors
One memory, every AI. A shared, user-owned markdown memory your AI clients read and write over MCP.
Shared memory and actions for Claude, Kiro, OpenAI, Cursor, and other MCP-compatible AI clients.
Persistent AI memory shared across Claude, ChatGPT, coding agents, and compatible MCP clients.
Hosted MCP memory for coding agents: persistent across sessions, editable markdown, team sharing.
Related MCP Servers
- AlicenseAqualityDmaintenanceAn MCP server that reads all your Claude Code project memory files and exposes them as tools. Lets any Claude instance — in any project, or via Claude.ai — query your full project history and preferences.510 npm1MIT
- AlicenseNot gradedqualityDmaintenanceA local stdio MCP server that exposes a shared brain (read/search/write) over ~/.claude/memory, allowing memories written in any agent to be readable and searchable across all three (Claude Code, Cursor, Codex). It provides tools like memory_search, memory_read, memory_write, etc., with no external services.23 npmMIT
- AlicenseBqualityBmaintenanceLocal-first memory server for AI coding agents that stores work sessions, tasks, and durable memories in Markdown files, exposed through MCP tools for session management and memory retrieval.1012 npm1MIT
- AlicenseNot gradedqualityBmaintenanceProvides a file-first personal memory layer for AI agents, enabling them to store and retrieve memories as markdown files with an SQLite index. The MCP server offers read-only search by default, with optional write tools for manual memory addition and conflict resolution.1 npmMIT