MemDrive
MemDrive gives LLMs persistent, local-only memory in a chosen folder through six MCP tools.
List directories in the memory library to explore stored notes and assets.
Search full-text with BM25 ranking, Chinese segmentation, file-type/tag/date filters, exact-phrase matching, pagination, index rebuild, and read-only diagnostics.
Save text or Markdown using new/overwrite/append modes, automatically creating parent folders.
Save images with notes from the clipboard, local paths, or URLs into an
assets/folder, with automatic Markdown references.Read text, Markdown, and images; Markdown can be read with its referenced images in one budgeted response or in two steps for better image quality.
Open original images in the OS viewer only when explicitly requested.
Open a note’s containing folder in the OS file manager only when explicitly requested.
All file access is sandboxed to
MEMDRIVE_ROOT, and the server is local-only with no network listeners.
Uses a Google Drive sync folder as the library root, allowing the MCP server to read and write Markdown notes and assets locally while the Google Drive client handles cloud syncing.
Uses an iCloud sync folder as the library root, allowing the MCP server to read and write Markdown notes and assets locally while iCloud handles syncing.
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., "@MemDrive save a note that my dentist appointment is May 12 at 3pm"
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.
MemDrive
An MCP server that gives LLMs persistent memory on your own storage: a local folder (Google Drive / OneDrive / iCloud sync folder, or any plain directory). Local only, by design.
The model reads and writes Markdown + a sibling assets/ image folder
through six small tools. No cloud service, no account, no server to run —
the MCP server runs on your own machine and touches only the folder you
point it at.
Status: 0.8.2. Local filesystem backend only. Full-text search index with BM25 ranking (Chinese segmentation, filters, pagination, read-only diagnostics). 0.1.1 added one total response budget to
read_file(see Reading back and Limits) and put the clipboard rule first in thesave_filetool description. 0.1.2 fixes the preview ladder so previews use the budget instead of stopping at the first coarse shrink step (see TEST-REPORT.md §7). 0.1.3 tries higher JPEG quality at full resolution before shrinking, states the three limit layers (host / server / model) in theread_filetool description, and adds CHANGELOG.md (see TEST-REPORT.md §8). 0.2.0 addsopen_image(open an original image in the OS viewer, only when the user explicitly asks) and a setup wizard that finds a sync folder and writes the Claude Desktop config for you (see Setup and TEST-REPORT.md §9). 0.3.0 adds a Cursor adapter to the wizard: the wizard asks which host to configure first and writes Cursor's global config (~/.cursor/mcp.json) as well (see TEST-REPORT.md §12). 0.4.0 adds a VS Code adapter (Copilot Global config,~/.copilot/mcp-config.json) and a per-host response budget written by the wizard into each host's config (see TEST-REPORT.md §13). 0.5.0 raises the Cursor budget to 10,000,000 on the 2026-10-30 verified read and addsopen_containing_folder(open a note's folder in the OS file manager, only when the user explicitly asks, no highlight or selection — see TEST-REPORT.md §14). 0.6.0 adds a Claude Code adapter to the wizard (user scope,~/.claude.jsontop-levelmcpServers, budget 10,000,000) — UNVERIFIED on a real machine, budget estimated by the author's decision, not measured; see Claude Code config and TEST-REPORT.md §17.
Quick start
Requires Node.js 24+.
Easiest — setup wizard (recommended for first install). Download the release zip, extract it, and run the launcher for your system (it installs dependencies, builds, and starts the wizard, which asks which host to configure and which folder to use, and writes the Claude Desktop, Cursor, VS Code, or Claude Code config):
Windows: double-click
setup.batmacOS: double-click
setup.command(or run./setup.shin a terminal)Linux: run
./setup.shin a terminal
The wizard needs Node.js 24+ already installed; if it is missing, the launcher says so and offers to open https://nodejs.org for you. It never installs anything silently and never changes a config file without backing it up first (see Setup).
# Run without installing (after this package is published to npm):
npx -y memdrive-mcp
# Or from a checkout:
npm install
npm run build
node dist/index.jsThe library root defaults to ~/memdrive (created automatically).
Override it with MEMDRIVE_ROOT:
MEMDRIVE_ROOT=~/Documents/memory npx -y memdrive-mcpRelated MCP server: mor
Claude Desktop config
The setup wizard (see Setup) writes this for you. Only edit it by hand if the wizard could not find your config file — it then prints a ready-to-paste snippet instead.
Add this to your Claude Desktop config file (claude_desktop_config.json):
{
"mcpServers": {
"memdrive": {
"command": "npx",
"args": ["-y", "memdrive-mcp"],
"env": {
"MEMDRIVE_ROOT": "/Users/you/Library/CloudStorage/GoogleDrive-you@example.com/My Drive/memory"
}
}
}
}Point MEMDRIVE_ROOT at a folder your sync client already manages and the
official client syncs everything to the cloud in the background — MemDrive
itself does zero network auth and zero uploads.
On Windows use a Windows path (C:\\Users\\you\\OneDrive\\memory); on
macOS/Linux use a POSIX path. Restart Claude Desktop after editing the
config.
Cursor config
The setup wizard (see Setup) writes this
for you, in Cursor's global config file: %USERPROFILE%\.cursor\mcp.json
on Windows, ~/.cursor/mcp.json on macOS/Linux. If that file does not
exist yet but the .cursor directory does, the wizard creates it (no
backup is needed — there is nothing to back up); if the directory does
not exist, the wizard writes nothing and prints a ready-to-paste snippet
instead. Project-level .cursor/mcp.json files are not written: the
global config makes the server available in every project. Only the
Windows path has been verified on a real machine; the macOS/Linux
paths use the same home-based logic and are marked untested.
{
"mcpServers": {
"memdrive": {
"type": "stdio",
"command": "node",
"args": ["/absolute/path/to/memdrive-mcp/dist/index.js"],
"env": {
"MEMDRIVE_ROOT": "/absolute/path/to/your/library",
"MEMDRIVE_RESPONSE_BUDGET_BYTES": "10000000"
}
}
}
}The type: "stdio" field is required by Cursor; Claude Desktop entries
carry no type field. The wizard writes the 10,000,000-byte budget
above into Cursor's entry (see
Limits for the evidence:
verified 2026-10-30, exact ceiling unmeasured). Restart Cursor after
editing the config, and make sure the server is enabled in Cursor's
MCP settings. Cursor's MCP logs (Output panel → "MCP Logs") are the
first place to look when a tool does not appear.
VS Code config
The setup wizard (see Setup) writes this
for you, in VS Code's Copilot Global config file:
%USERPROFILE%\.copilot\mcp-config.json on Windows,
~/.copilot/mcp-config.json on macOS/Linux. If that file does not
exist yet but the .copilot directory does, the wizard creates it (no
backup is needed — there is nothing to back up); if the directory does
not exist, the wizard writes nothing and prints a ready-to-paste snippet
instead. Only the Windows path has been verified on a real machine; the
macOS/Linux paths use the same home-based logic and are marked untested.
The COPILOT_HOME environment variable is not read — only the
home-directory location above is used.
{
"mcpServers": {
"memdrive": {
"type": "stdio",
"command": "node",
"args": ["/absolute/path/to/memdrive-mcp/dist/index.js"],
"env": {
"MEMDRIVE_ROOT": "/absolute/path/to/your/library",
"MEMDRIVE_RESPONSE_BUDGET_BYTES": "10000000"
}
}
}
}The entry shape above was verified on a real Windows machine (manual
MCP: Add Server flow, Copilot Global config): top-level
mcpServers, type: "stdio", command: "node", absolute
dist/index.js args, and VS Code's own "tools": ["*"] field
alongside them. When the wizard merges into an existing entry, that
tools field is preserved, as are every other key and every other
server; a freshly created entry gets no tools field. The wizard
writes the 10,000,000-byte budget above into VS Code's entry (see
Limits for the evidence:
partly verified, exact ceiling unmeasured). Fully quit and restart
VS Code after editing the config, and make sure the server is enabled
in VS Code's MCP settings. VS Code's MCP logs are the first place to
look when a tool does not appear. Only list_directory and a bundled
image read have been verified in VS Code on a real machine so far;
the other tools still need a real-machine check (see
Roadmap).
Claude Code config
UNVERIFIED — read this first. The Claude Code adapter was added from Anthropic's official docs only (checked 2026-10-31) and has never been tested on a real machine, in any host. The 10,000,000-byte budget below is an estimate set by the author's decision (Claude Code is a paid product; a limit as stingy as Claude Desktop's would make no sense for paying users), not a measurement. If a bundled read fails or comes back truncated in Claude Code, please report the exact error — that report is how the default gets corrected.
The setup wizard (see Setup) writes
this for you, in Claude Code's user scope: the top-level
mcpServers key of the one ~/.claude.json file
(%USERPROFILE%\.claude.json on Windows,
~/.claude.json on macOS/Linux). When CLAUDE_CONFIG_DIR is
set, the file lives inside that directory instead. Local scope
(per-project entries inside the same file) and project scope
(.mcp.json in a project root) are not written: the wizard
writes the user-global config only, so the server is available
in every project. If ~/.claude.json does not exist, the wizard
writes nothing and prints a ready-to-paste snippet instead — it
never creates the file, because ~/.claude.json is Claude
Code's own main state file (it also holds project records),
not a dedicated MCP file, and creating a bare copy was never
verified.
{
"mcpServers": {
"memdrive": {
"command": "node",
"args": ["/absolute/path/to/memdrive-mcp/dist/index.js"],
"env": {
"MEMDRIVE_ROOT": "/absolute/path/to/your/library",
"MEMDRIVE_RESPONSE_BUDGET_BYTES": "10000000"
}
}
}
}The entry carries no type field: Claude Code reads an entry
without a type as a stdio server, the same shape as a Claude
Desktop entry. The budget above controls only how much this
server sends. Claude Code applies its own output limits on top
(default 25,000 tokens per tool result; long text results are
spilled to a file for the model to read back), so a large
bundled read may arrive as a file reference rather than inline
content. To raise Claude Code's own limit, set
MAX_MCP_OUTPUT_TOKENS in the shell before starting Claude
Code (for example MAX_MCP_OUTPUT_TOKENS=50000 claude). Check
the connection with claude mcp list or the /mcp panel.
Optional: raise (or lower) the read_file total response budget if your
client allows larger tool results than Claude Desktop's ~1 MB ceiling:
"env": {
"MEMDRIVE_ROOT": "...",
"MEMDRIVE_RESPONSE_BUDGET_BYTES": "5000000"
}The default is 950,000 bytes; values are clamped to 100,000–50,000,000. See Limits before raising it.
Setup wizard & launchers
node dist/index.js setup runs an interactive wizard (no new
dependencies — plain Node readline). After npm publish, the same
wizard runs as npx memdrive-mcp setup; until the package is
published, use the zip + launcher form below.
What the wizard does, in order:
Chooses a host. The wizard asks which host to configure — 1 Claude Desktop, 2 Cursor, 3 VS Code, 4 Claude Code (Enter = 1). You can skip the question with an argument:
node dist/index.js setup cursor(orsetup claude/setup claude-desktop,setup vscode— only that one word, novs-code/codealias,setup claude-code— only that spelling,codeis not an alias). The argument is an optional shortcut for people who already know which host they want; the prompt is the default, foolproof path, including fornpx memdrive-mcp setupand the launchers, which call the wizard with no argument. An unrecognised argument falls back to the prompt instead of guessing a host.Finds a library folder. It probes for existing sync folders — OneDrive, Google Drive, iCloud — plus the
~/memdrivedefault, and shows a numbered menu. Google Drive on Windows is probed in three steps: the DriveFS install folder must exist, then a PowerShell volume-label probe finds the drive letter, then common paths (G:\My Drive,H:\My Drive, …) are checked for existence — no drive letter is ever assumed. You can also type a custom absolute path (~is expanded). The chosen folder is created if it does not exist.Finds the host config by probing the known locations. Claude Desktop: macOS Application Support; Windows
%APPDATA%\Claudeplus the%LOCALAPPDATA%Store/per-user locations. Cursor: the global config file only,%USERPROFILE%\.cursor\mcp.jsonon Windows and~/.cursor/mcp.jsonon macOS/Linux, returned only when the.cursordirectory exists. VS Code: the Copilot Global config file only,%USERPROFILE%\.copilot\mcp-config.jsonon Windows and~/.copilot/mcp-config.jsonon macOS/Linux, returned only when the.copilotdirectory exists. Claude Code: the user scope only, the top-levelmcpServerskey of the one~/.claude.jsonfile (%USERPROFILE%\.claude.jsonon Windows,~/.claude.jsonon macOS/Linux, or insideCLAUDE_CONFIG_DIRwhen that variable is set), returned only when the file already exists. Exactly one file → it is used; several → you pick one; none → nothing is written and the wizard prints a ready-to-paste snippet instead.Writes the config safely. An existing file is backed up first (
<file>.bak, or<file>.bak-<timestamp>when a backup already exists, so an earlier backup is never overwritten). ThemcpServers.memdriveentry (command: node,args: [<absolute path to dist/index.js>],env: {MEMDRIVE_ROOT: <chosen folder>}, plustype: "stdio"for Cursor and VS Code, plus the host's response budget inenvfor Cursor, VS Code, and Claude Code — see Limits; Claude Desktop entries carry no budget env, and Claude Code entries carry notypefield) is merged in; every other key and every other server is preserved, and an existing entry's"tools": ["*"]field (VS Code) is carried over. For Cursor and VS Code, a missing config file is created instead (the host directory already exists), with no backup to take. A missing Claude Desktop or Claude Code file is never created. A corrupt config file aborts the write entirely — nothing is backed up, nothing is changed. Then it tells you to fully quit and restart the host you configured.
The launchers (setup.bat for Windows, setup.sh for macOS/Linux,
setup.command for double-clicking on macOS) are thin shells: they
check for Node.js 24+, run npm install + npm run build (failures
shown plainly, script stops), then run the wizard. All logic stays
in Node; the scripts contain no configuration of their own.
Windows SmartScreen note. If you downloaded the zip from the
internet, Windows may show "Windows protected your PC" when you run
setup.bat. That is SmartScreen reacting to a downloaded file, not
evidence of a problem: setup.bat is plain text — open it in
Notepad and read it before running if you like. To proceed, click
More info, then Run anyway. No code signing is used (a paid
certificate is only worth it once the project earns revenue).
Other hosts. The wizard writes Claude Desktop, Cursor, VS Code, and Claude Code config (global config for Cursor, Copilot Global config for VS Code, user scope for Claude Code). The config-writing code is isolated behind a small per-host adapter so another host can be added later, but only after that host's config surface is verified on a real machine — with one exception: Claude Code was added from Anthropic's official docs only and is unverified (see Claude Code config). Codex/Gemini are CLIs with no desktop config file, Grok has no desktop MCP config surface, and no Muse desktop app exists. New hosts are added only on user feedback after these four.
Tools
Tool | What it does |
| Lists the immediate children of a directory in the library (root by default). |
| Full-text search (BM25-ranked chunks with snippets; Chinese queries are segmented). Filters by file type, tag, file date, and modification time; exact-phrase match; pagination (limit default 20, max 200); |
| Writes text (usually Markdown). Modes: |
| Reads text, or a Markdown file together with its referenced images in one response, within one total response budget. Pass |
| Opens an image file in the OS default image viewer, at full original quality. Only when the user explicitly asks to view the original — never automatically, and never returns image bytes to the model. |
| Opens the folder containing a file (or the folder itself) in the OS file manager, without highlighting or selecting any file. Only when the user explicitly asks to open the folder — never automatically, and never returns folder contents to the model. Omit the path to open the library root. |
Searching: keyword vs exact phrase
A keyword search splits Chinese queries into single characters and ranks every note that shares them, so a note that only shares a few characters can still show up as a weak match. That is by design: it finds things even when you remember only part of a phrase.
When the results are noisy, or you want to confirm a phrase is really
in a note, ask for an exact-phrase search instead: only notes
containing the phrase verbatim are returned. Say "用精确短语搜
「第一段哨兵词」" and the model passes exact_phrase.
Reading back: packed, two-step, and single-image
One total budget governs every read_file response (text bytes +
base64 bytes of all image blocks together, default 950,000). Text is
guaranteed first; images share only what the text leaves. An image
that does not fit is downscaled in memory to fit the remaining budget,
or skipped with a note — the read never fails as a whole because of an
image, and the file on disk is never modified. Three ways to read:
Packed read (default).
read_fileon a Markdown file returns the text together with its referenced images in one response. Best for short notes with one or two small pictures. A large screenshot (for example ~800 KB on disk, ~1.1 MB as base64) is downscaled in memory to fit the budget instead of blowing up the whole response the way it did before 0.1.1.Two-step read (several images, or one large image). First call
read_filewithinclude_images: false: you get the text plus the list of referenced image paths (library-relative, e.g.notes/assets/shot-....png). Then callread_fileonce per image path. Each direct image read gets the whole budget to itself, so each picture keeps the best quality the budget allows — this is the way to get a large screenshot back at full usable quality.Single-image read.
read_fileon an image path directly returns that one image. It is subject to the same total budget: the image is returned untouched when it fits, and downscaled in memory only when the image itself exceeds the budget (or the 3.5 MB per-image ceiling). If an image cannot be fitted within the budget at all (for example a format the built-in decoder cannot shrink), the result is text explaining that, not an error.
Viewing an original image: open_image
read_file returns a preview that fits the response budget — good
enough to read, not always the original pixels. When the user
explicitly asks to view the original ("open this image", "在系统里打开这张图"),
open_image opens the file in the operating system's default image
viewer (Photos on Windows/macOS, the default viewer on Linux) at
full quality. The tool returns only a short text confirmation; no
image bytes go to the model.
The rule is written into the tool description itself: the model must
only call open_image on an explicit user request, never
automatically after a read or a save. Opening a viewer window is a
visible side effect on the user's screen, and unprompted popups are
not acceptable. Only image files inside the library root open;
non-images, directories, missing files, and sandbox escapes are
rejected, and the viewer is launched with an argument array (never
a shell string), so a crafted filename cannot inject a command.
Opening a note's folder: open_containing_folder
When the user explicitly asks to open a note's folder in the system
("open the folder", "在系统里打开这个笔记所在的文件夹"),
open_containing_folder opens that folder in the operating system's
file manager (Explorer on Windows, Finder on macOS, the default file
manager on Linux). A file opens its parent folder; a folder opens
itself; an empty path opens the whole library root. The folder opens
without highlighting or selecting any file — no explorer /select,
no Finder selection, no reveal-with-selection.
The rule is written into the tool description itself, the same way
as open_image: only on an explicit user request, never
automatically after a read or a save. The tool returns only a short
text confirmation naming the folder opened; no folder contents go to
the model (use list_directory to see what is in the folder).
Only paths inside the library root open; missing paths and sandbox
escapes are rejected, nothing is modified, and the folder is
launched with an argument-array launcher (never a shell string),
so a crafted filename cannot inject a command. On Windows the
folder launch goes through the shell (cmd /c start "" explorer.exe /n,<path>, switch and path in one comma-joined argument), so the
folder opens as a new Explorer window every time instead of a hidden
tab in an already-open Explorer window; image files still open
through the normal viewer launch, unchanged.
Saving with images
save_file accepts an images array mixing three source kinds.
Clipboard comes first, because it is the one people get wrong:
The literal string
"clipboard"— first choice for screenshots. Just tell the model "把截图存下来" / "把剪贴板里的图存下来". The server reads the clipboard directly on your own machine. You do not need to paste, upload, or send the image into the chat, and the model must never claim it cannot read the clipboard or that the library is text-only. (This rule is written at the very top of thesave_filetool description, so the model sees it before anything else.)Local file paths — e.g.
~/Pictures/shot.png(quotes stripped,~expanded).http(s) URLs — downloaded directly, zero token cost.
Images land in a sibling assets/ folder, renamed
<document-name>-<timestamp>.<ext>, and for Markdown files a standard
 reference is appended automatically. Originals are
always kept at full quality.
Interaction rule (built into the tool description): before calling
save_file, the model must show you the exact destination path and
filename and get your confirmation in the chat. There is no popup — the
confirmation happens in the conversation.
Tags
Notes can be tagged in two ways:
YAML front matter
tags:— inline (tags: [a, b]) or a block list (- a).#tagin the note body.
To filter search results by tag, pass tags to search_files; a file
must carry at least one of the listed tags to match.
Tags are free-form with no controlled vocabulary; the diagnostics report
(diagnostics_only) lists the tags already in use in the library so they
can be reused.
Clipboard edge cases
Clipboard empty or holding text instead of an image: the text is still saved; the result tells you to re-screenshot.
Headless environments (Docker, servers, NAS): clipboard capture reports itself as unsupported with a suggestion to use a file path or URL. The server never crashes on clipboard/image failures.
Limits (all in src/config.ts, nowhere else — except the response budget, which is also env-overridable)
Constant | Value | Meaning |
| 950,000 | Default total budget for one |
| 1,000,000 | Text returned by |
| 3,500,000 | Per-image preview ceiling (binary bytes). Image previews are downscaled in memory below this and below what the remaining response budget allows, whichever is smaller. The preview ladder tries a full-resolution JPEG re-encode first, trying JPEG qualities 95, 90, then 80 before shrinking, then shrinks in 0.9 steps at quality 80, returning the first attempt that fits — so the preview stays close to the largest size the budget allows instead of stopping at a coarse first shrink, and spends headroom on sharpness when full resolution already fits. The on-disk original is never modified. |
Why a total budget, and why 950,000: Claude Desktop rejects a whole
tool result over 1,048,576 bytes. The old per-part caps (1 MB text,
3.5 MB per image, no combined check) let a bundled read blow past
that wall — in the real 2026-10-28 test, an 813.5 KB screenshot
became ~1.11 MB of base64 and the entire read was rejected. 950,000
leaves ~98 KB of headroom for JSON framing and notes. Raise the
budget only if your client demonstrably accepts larger results; the
server cannot detect a client's limit (MCP initialize carries no
host/limit information, and no clientInfo identification is done),
so the default stays at the lowest known ceiling and is treated as
universal across Claude Desktop plans and unknown hosts. Three
different limits are at work — the host's result cap, this server's
budget and 3.5 MB per-image ceiling, and the model's own
pixel/token scaling — and the read_file tool description states
all three, so the model does not have to guess which one bit.
Per-host response budgets (written by the wizard into each host's
config entry as MEMDRIVE_RESPONSE_BUDGET_BYTES; the server cannot
identify the host, so the env value is the only way to pass the
budget through). Do not read more into the Cursor and VS Code rows
than the evidence states: Cursor is verified and VS Code is partly
verified, and neither exact ceiling was measured.
Host | Budget | Evidence |
Claude Desktop | 950,000 | Verified: 2026-10-28 single-variable experiment — with the budget disabled via the environment variable, the same bundled read failed twice with the exact 1,048,576 error; 950,000 leaves ~98 KB of headroom for JSON framing and notes. |
Cursor | 10,000,000 | Verified: 2026-10-30 single bundled read passed ( |
VS Code | 10,000,000 | Partly verified: 2026-10-29 four-image bundled read passed ( |
Claude Code | 10,000,000 | ESTIMATED, UNVERIFIED. Set by the author's decision 2026-10-31, never tested on a real machine: Claude Code is a paid product, so a limit as stingy as Claude Desktop's would make no sense for paying users; if the estimate is wrong, user reports of oversized-result errors are the feedback path. Claude Code also applies its own output limits on top of this server-side budget (default 25,000 tokens per tool result; long text results spilled to a file), so this value controls only how much the server sends. Raise Claude Code's own limit with |
ChatGPT, Grok, and other hosts are untested for the limit; the 950,000 default is treated as universal for unknown hosts rather than assuming a larger limit any of them has not demonstrated.
Why 3.5 MB: base64 inflates binary by 4/3, so 3.5 MB becomes ~4.67 MB of base64 text, under every vendor image cap checked in the official docs (5 MB Bedrock/Vertex, 7 MB Gemini, 10 MB Claude API, 20 MB OpenAI). Do not raise it without re-checking those docs. It still caps any single preview even when the total budget is raised above it.
Other caps: at most 20 referenced images per read_file response (the
total budget usually binds first);
search_files returns ranked chunks (at most 200, paginated) and
caps its result text at MEMDRIVE_SEARCH_MAX_BYTES (default 80 KB);
URL downloads have a 30 s timeout and a 500 MB safety
ceiling (a disk-exhaustion guard, not a product limit). The search
index is a cache stored outside the library (system cache dir) and
rebuilds automatically; on the first search after startup it may
still be building, in which case the result falls back to a direct
scan (keyword substring, files under 256 KB only) and says so.
Security
Root sandbox (hard). Every path is locked to
MEMDRIVE_ROOT.../escapes, absolute paths outside the root, and symlinks inside the root that point outside are all rejected — including symlinks in the parent chain of not-yet-created files. Violations return a clear error.Local image sources are OS paths, not library paths. A
save_fileimage entry like~/Pictures/shot.pngintentionally reads from outside the root; only the destination is sandboxed.No network listeners. stdio transport only; nothing is exposed.
Zero native dependencies. Pure-JS dependency tree (MCP SDK, zod, jimp). No
node-gyp, no C++ builds, no Sharp.Image formats. jimp (the pure-JS decoder) reads PNG/JPEG/GIF/BMP/ TIFF. WebP files pass through untouched when under the preview cap and are reported (not crashed on) when over it; SVG files are returned as text, never as image blocks; GIFs read as their first frame.
Verifying with MCP Inspector (step 1, zero model cost)
npm run build
npx -y @modelcontextprotocol/inspector --cli node dist/index.js --method tools/list
npx -y @modelcontextprotocol/inspector --cli node dist/index.js --method tools/call --tool-name list_directoryNote: the Inspector CLI spawns the server with its own environment, so
MEMDRIVE_ROOT set in your shell may not reach it — it defaults to
~/memdrive. The interactive Inspector UI (npx @modelcontextprotocol/inspector
without --cli) lets you set environment variables explicitly.
Development
npm install
npm run build # tsc, strict
npm run typecheck # tsc --noEmit
npm test # protocol tests: SDK Client over stdio (see TEST-REPORT.md)The SDK version is pinned exactly (@modelcontextprotocol/sdk 1.32.2,
the v1.x maintenance line) and package-lock.json is committed. The SDK
has a v2 line under separate package names; this project deliberately
stays on v1.x until there is a reason to migrate.
Roadmap (not in this MVP)
Windows clipboard capture verified on real hardware (2026-10-28, screenshot save via Claude Desktop); the macOS clipboard branch is still implemented from the documented OS pattern only — no macOS machine was available. The 0.1.3 quality ladder passed its real-conversation retest in Claude Desktop (2026-10-28: 359.0 KB preview of the 1.22 MB comment screenshot, original untouched).
Still to verify on a real Windows machine (see TEST-REPORT.md §14): the setup wizard's VS Code path end-to-end (host prompt choice 3 and
setup vscodeargument, Copilot Global config creation/merge with"tools": ["*"]preserved, restart VS Code, all six tools in VS Code — onlylist_directoryand a bundled image read have been verified there so far), the wizard's Cursor path end-to-end (host prompt and argument, global config creation, restart Cursor, tools + read/save/open_image/ open_containing_folder in Cursor),open_containing_folderopening a folder once in each host, and a Claude Desktop regression check (its entry is byte-identical); the macOS wizard branches (setup.command, iCloud/Google Drive candidates,open) and the PowerShell Google Drive probe are untested on real hardware. No further Cursor or VS Code size-limit probing is planned.
License
MIT — see LICENSE.
Support
If MemDrive saves you time, you can support its development. Thank you!
Ko-fi: ko-fi.com/newmannn91
USDT (TRC-20 only): send only TRC-20 USDT to the address below. Do not send other tokens or use another network — they cannot be recovered.
TU9GBiQ1Ji1Fm5YECYThjcchTofj8B6uCDWeChat Pay: scan the code below.
微信用户可扫码赞赏。
Available Tools
6 toolslist_directoryA
List the immediate children (subfolders and files) of a directory inside the MemDrive memory library. Returns one line per entry with type, size, and modification time. Directories sort first. Use this to explore what is already stored before reading or saving. Paths are relative to the library root (MEMDRIVE_ROOT). Omit path or pass "" for the root. Absolute paths are accepted only when they stay inside the root.
Inputs: path.
open_containing_folderA
Open the folder containing a file or directory from the MemDrive memory library in the operating system's file manager (Explorer on Windows, Finder on macOS, the default file manager on Linux). A file opens its parent folder; a folder opens itself; an empty path opens the whole library root. The folder is opened without highlighting or selecting any file. USE ONLY WHEN THE USER EXPLICITLY ASKS TO OPEN THE FOLDER — for example "open the folder", "show me this note in the file manager", or "在系统里打开这个笔记所在的文件夹". NEVER call open_containing_folder automatically after a read_file, a save_file, or any other tool call, and never open a folder the user has not asked to open. Opening a file manager window is a visible side effect on the user's screen; the model must not cause it unprompted. The folder contents are never returned to the model: the result is only a short text confirmation naming the folder that was opened (or a note when no file manager is available, for example on a headless machine). To see what is in the folder, use list_directory instead. Only paths inside the library root can be opened. Missing paths and paths escaping the root are rejected with a note. Nothing is modified. Paths are relative to the library root (MEMDRIVE_ROOT). Absolute paths are accepted only when they stay inside the root.
Inputs: path.
open_imageA
Open an image file from the MemDrive memory library in the operating system's default image viewer (Photos on Windows/macOS, the default viewer on Linux), at full original quality. USE ONLY WHEN THE USER EXPLICITLY ASKS TO VIEW THE ORIGINAL IMAGE — for example "open this image", "show me the original", or "在系统里打开这张图". NEVER call open_image automatically after a read_file, a save_file, or any other tool call, and never open an image the user has not asked to view. Opening a viewer window is a visible side effect on the user's screen; the model must not cause it unprompted. The image itself is never returned to the model: the result is only a short text confirmation (or a note when no viewer is available, for example on a headless machine). To see the picture in the conversation, use read_file on the image path instead. Only image files inside the library root can be opened. Non-image files, directories, missing files, and paths escaping the root are rejected with a note. The file on disk is never modified. Paths are relative to the library root (MEMDRIVE_ROOT). Absolute paths are accepted only when they stay inside the root.
Inputs: path (required).
read_fileA
Read a file from the MemDrive memory library. TOTAL RESPONSE BUDGET — THREE LIMITS ON THREE DIFFERENT LAYERS. (1) HOST: the MCP host rejects a whole tool result over about 1 MB ("Tool result is too large. Maximum size is 1MB"), a host limit outside this server and separate from the model context window. (2) SERVER: text bytes plus base64 image bytes in one response must fit a single total budget — default 950,000 bytes, overridable with MEMDRIVE_RESPONSE_BUDGET_BYTES (clamped to 100,000–50,000,000). Text is guaranteed first; images share what remains and are downscaled in memory (trying higher JPEG quality before shrinking dimensions) or skipped with a note so a read never fails over an image and the file on disk is never modified. read_file returns a preview that fits the budget; to view the original image on disk, use open_image (only when the user asks). (3) MODEL: the model converts image pixels into tokens under its own limits, unrelated to the byte limits above. TWO-STEP READING — USE THIS WHENEVER A DOCUMENT REFERENCES SEVERAL IMAGES, OR ONE LARGE IMAGE: Step 1, call read_file with include_images=false to read the text and get the list of referenced image paths. Step 2, call read_file once per referenced image, passing that image path itself as path, so the image alone gets the whole response budget and keeps the best quality the budget allows. Reading an image file directly is also limited by the same total budget: a single image is returned untouched when it fits, and downscaled in memory only when the image itself exceeds the budget. Text files: returned as text (truncated at the smaller of the 1 MB text cap and the total budget, with a note when truncated). Markdown files: with include_images=true (the default), the text is returned TOGETHER WITH every locally-referenced image that fits the remaining budget in the same response — each image becomes an image content block, so the model sees words and pictures together. Remote (http) and data: images are skipped with a note. Images over the 3.5 MB preview cap are downscaled in memory only; the file on disk is never modified. Image files: returned as a single image content block (downscaled in memory only when over the preview cap or the total budget). If an image cannot be fitted within the budget at all, the result is text explaining that, not an error — the original is untouched. SVG files are returned as text (they are XML), never as image blocks. Paths are relative to the library root (MEMDRIVE_ROOT). Absolute paths are accepted only when they stay inside the root.
Inputs: path (required), include_images.
save_fileA
CLIPBOARD FIRST — READ THIS BEFORE ANYTHING ELSE: When the user says to save a screenshot or an image from the clipboard — for example "把截图存下来", "把剪贴板里的图存下来", "save the screenshot", or any equivalent wording — pass images: ["clipboard"]. That is all it takes: the server reads the clipboard directly on the user's own machine. You do NOT need to see the clipboard or the image. NEVER ask the user to paste, upload, or send the image into the chat. NEVER say you cannot read the clipboard, that you cannot see images, or that this memory library can only store text. If the clipboard holds no image, the server reports that in the result and the text is still saved — tell the user to take the screenshot again and retry; do not invent a clipboard failure.
Interaction rule — read before calling:
Before calling this tool, show the user the EXACT destination path and
filename you intend to write (for example: "notes/2026-10-28.md") and
get their explicit confirmation in the chat. Never call save_file with
a path or filename the user has not confirmed. If the user has not
confirmed a location yet, ask them first instead of calling.
There is no popup or confirmation dialog: confirmation happens here,
in the conversation, through you.
Save text (usually Markdown) into the MemDrive memory library.
Modes: "new" (default) fails when the file already exists; "overwrite" replaces it; "append" adds to the end, creating the file when missing. Parent directories are created automatically.
Tags: two syntaxes are supported — YAML front matter tags: (inline [a, b] or block - a) and body #tag. Proactively suggest adding tags ONLY in these three cases: (1) the user mentions a category or topic while saving (e.g. saving a work plan); (2) the note clearly belongs to a tag already used in the library; (3) the user saves several notes on the same topic in a row (ask once, e.g. whether to tag all of them X). NEVER ask about tags for quick screenshot/clipboard saves, when the user only said "save it" with no topic signal, or after you already asked once in this conversation and the user ignored it or said no. When the user explicitly asks to save with tags, just save with those tags — none of the rules above apply. If the tool result includes a Tip: line about tagging, you MUST say that tip to the user.
Images: pass an "images" array with any mix of the literal string "clipboard" (capture the latest screenshot from the OS clipboard — see the clipboard rule above), local file paths, or http(s) image URLs. Images are copied/downloaded/captured into a sibling assets/ folder, renamed "-.", and for Markdown files a standard image reference is appended to the document automatically. The on-disk original is always kept at full quality; only the preview handed to a model is ever downscaled.
Clipboard behavior: when the clipboard is empty or holds text instead of an image, the text is still saved and the result explains what to do (re-screenshot). Clipboard capture never crashes the server; in headless environments it reports itself as unsupported and suggests a file path or URL instead.
Paths are relative to the library root (MEMDRIVE_ROOT). Absolute paths are accepted only when they stay inside the root. The root is a hard sandbox: "../" escapes and symlinks pointing outside are rejected.
Path cleaning: surrounding quotes are stripped, a leading ~ expands to the home directory, and separators are normalized.
Inputs: path (required), content (required), mode, images.
search_filesA
Search the memory library by keyword, ranked by relevance (full-text index + BM25; Chinese queries are segmented, so 1–2 character terms match). Returns one hit per matching file (its best-matching chunk) with the file path, section heading, relevance score, and a snippet with the matched term highlighted as term. Filename matches are included too: a substring of a filename finds the file, including non-text files such as images. Results are paginated: limit defaults to 20, pass offset for the next page. Filters: file_types (e.g. [".md"]), tags (a file must carry at least one), date_from/date_to (file date: front matter > filename > modification time; a date-only upper bound covers the whole day), modified_after/modified_before (modification time). exact_phrase requires the phrase to appear verbatim. diagnostics_only returns a read-only library report (empty files, duplicate content, broken image references, unreferenced attachments older than 24h) and never modifies anything; keyword is not needed for it. rebuild_index forces a full index rebuild first (use after a bulk import or a cache clear). On the first search after startup the index may still be building in the background; the result then says so and falls back to a direct scan (keyword, path, and file types only — tags, dates, and exact_phrase are not applied there, and the result says so). Paths are relative to the library root (MEMDRIVE_ROOT). Omit path or pass "" to search the whole library.
Inputs: path, tags, limit, offset, +9 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 6 tools
Each tool has a distinct role: search, read, write, list, and two deliberate OS-side-effect helpers. The only real overlap is read_file vs open_image, since both can surface an image, but the descriptions explicitly split 'show it in the conversation' from 'open it on the user's screen' and constrain the latter to explicit requests.
Every name is snake_case verb_noun (search_files, save_file, read_file, list_directory, open_containing_folder, open_image), with the two viewer helpers sharing a clean open_ prefix. No mixed conventions or vague verbs.
Six tools is a tight, well-scoped surface for a personal memory library, and each one (search, read, save, list, open folder, open image) fills a distinct slot. Nothing feels padded or redundant.
Read, write (new/overwrite/append), search, and browse are covered, but the surface has no delete, move, or rename operation, so the file lifecycle is open-ended and an agent cannot clean up mistaken or stale notes. These are notable gaps an agent would hit in real use.
Maintenance
Related MCP Connectors
Persistent memory for AI agents. Hosted cloud connector or local Windows install.
1Portable AI memory shared across models and harnesses - plain markdown you own.
Personal wiki and memory layer for AI assistants. Persistent, structured memory across sessions.
Markdown notes in folders, with files, that your AI assistant can read, write and organise.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceProvides persistent long-term memory for AI assistants with tag-based retrieval, wiki-style linking, and source references, storing memories as markdown files with SQLite index.2MIT
- AlicenseNot gradedqualityBmaintenanceEnables AI assistants to search, read, create, update, and remove personal markdown notes stored locally, providing persistent memory across sessions.20 npm2MIT
- AlicenseNot gradedqualityBmaintenanceA personal, offline-first memory for AI assistants that enables them to read, search, and write to a shared folder of Markdown files, so they all remember you without needing a cloud.1MIT
- AlicenseNot gradedqualityBmaintenanceTurns a folder of Markdown notes into an agent-native knowledge base, providing long-term memory with provenance, token-budgeted retrieval, and safe write-back with versioning.MIT