project-memory-mcp
v0.1.7
Published
Project-scoped centralized memory as an MCP stdio server (JSON file backend).
Maintainers
Readme
Project Memory MCP Server
Shared, project-scoped memory for Claude Code, Codex, and Gemini CLI — so all three agents remember the same context.
You (any CLI) ──► MCP Server ──► .ai/memory.json (per project)This repository ships a Node.js MCP server that persists project facts/decisions into <project>/.ai/memory.json. Any supported CLI can read/write the same file, so switching between Claude, Codex, and Gemini feels stateful: load context with memory_get_bundle, perform work, and save what changed with memory_save.
Why Project Memory MCP?
- Shared context – one
.ai/memory.jsonper repo keeps Claude, Codex, and Gemini in sync. - Batteries included tools – search, bundle generation, pinning, proposals, auto-compaction.
- Silent capture – optional hooks auto-save versions, deps, and commits at the end of a session.
- CLI + programmatic – run the stdio server via
project-memory-mcpor import the build artifacts in Node projects.
Installation Options
Pick the workflow that fits your team:
1. Global CLI (recommended for everyday use)
npm install -g project-memory-mcp
project-memory-mcp setupThis installs the CLI once, adds a globally available project-memory-mcp command, and stores the server inside your global npm cache.
2. On-demand via npx (no global install)
npx project-memory-mcp setupnpx downloads the package on first run and reuses the cached copy afterwards. Great for trying the tool or wiring up a single machine.
3. Local clone (development / hacking)
git clone https://github.com/nicobailon/project-memory-mcp-js.git
cd project-memory-mcp-js
npm install
npm run buildThen point your CLI at node /absolute/path/dist/server.js. This is also how you contribute and run the hook tests. See Local development for details.
Quick Start (any install path)
- Run the setup wizard inside the repo you want to enable (or pass
--project /path):project-memory-mcp setup # global install # or npx project-memory-mcp setup # on-demand - Choose how the CLI should spawn the server (
npx, global binary,node dist/server.js, or a custom command). - Pick which CLIs to configure (Claude Code, Gemini CLI, Codex CLI).
- Ask your CLI: “Call
memory_statusand show the output.” You should see the correctprojectRootand.ai/memory.json.
Prefer the manual route or need automation flags? Jump to Local development and docs/LOCAL_SETUP.md.
Run it as a project dependency
If you want every teammate to install the server via your project’s package.json, add it as a dev dependency:
npm install --save-dev project-memory-mcpNow you can expose scripts:
{
"scripts": {
"memory:serve": "project-memory-mcp serve",
"memory:setup": "project-memory-mcp setup --yes --runner node"
}
}From there your CLI entries can point at npx project-memory-mcp or node ./node_modules/project-memory-mcp/dist/server.js. The published package bundles the dist/ tree, so no build step is needed on consumer machines.
Local development
Working out of this repo?
npm installnpm run buildnpm run test:hooks(optional smoke test for hook flows)- Wire your CLIs using
node dist/server.jsorproject-memory-mcp(after linking vianpm link)
See docs/LOCAL_SETUP.md for the long-form guide, env overrides, troubleshooting, and hook wiring defaults.
Need to ship a new npm release from your workstation? Follow docs/LOCAL_NPM_DEPLOY.md.
CLI Setup & Tool Map
Already installed globally, via npx, or from source? Run the wizard from the repo you want to enable:
# Install globally (recommended)
npm install -g project-memory-mcp
# Or use npx (no install needed)
npx project-memory-mcp
# Or clone and run locally
git clone https://github.com/nicobailon/project-memory-mcp-js.git
cd project-memory-mcp-js && npm install && npm run build && npm start| What you want | Tool to call |
|---|---|
| Check setup is correct | memory_status |
| Load context before a task | memory_get_bundle |
| Save something to memory | memory_save |
| Search past memory | memory_search |
| Push observations for suggestions | memory_observe |
| Review pending suggestions | memory_suggest |
| Accept/reject a suggestion | memory_suggestion_feedback |
That's the core loop: get bundle → do work → save what matters. The suggestion engine can also detect patterns (dependency installs, version checks, error→fix cycles) mid-session and proactively nudge you to save.
Quick setup (per project)
Install the package (pick one)
npm install -g project-memory-mcp # global install # or use npx — no install neededRun the guided setup (recommended)
# from the repo you want to wire up (or pass --project) npx project-memory-mcp setup # or, after a global install: project-memory-mcp setup- Prompts for a server ID (e.g.,
project-memory-npx). IDs must match[a-z0-9-]{3,32}; provide--server-id <name>when scripting or running with--yes. - Prompts for the project directory (defaults to your current working folder).
- Lets you choose how to launch the MCP server (
npx, global binary,node /path/to/dist/server.js, or a custom command). - Lets you pick which CLIs to configure (Claude Code, Gemini CLI, Codex CLI).
- Automatically updates
~/.claude.json(with a.bakbackup) and runs the necessarygemini mcp/codex mcpcommands so they point at the right project. - Flags:
--project /path,--server-id my-server-name,--cli claude,gemini,--runner global(alias:--runner-profile),--yes,--command, and--argslet you script it or skip prompts. Runproject-memory-mcp setup --helpfor the full list. - Requires the corresponding CLIs to already be installed and on your
PATH.
Claude setup in sandboxes: set
PROJECT_MEMORY_MCP_CLAUDE_CONFIG_PATH=/custom/path/claude.json(orCLAUDE_CONFIG_PATH) before running the wizard if~/.claude.jsonisn’t writeable. The wizard reads/writes that custom file and still produces a.bakalongside it.- Claude Code – edit
~/.claude.jsonand add:
Restart Claude inside"mcpServers": { "project-memory": { "command": "npx", "args": ["project-memory-mcp"], "cwd": "/path/to/my-app" } }/path/to/my-app. - Gemini CLI
Editcd /path/to/my-app gemini mcp add project-memory npx project-memory-mcp --trust gemini mcp list # should show CONNECTED /mcp # (in-session) inspect tools/resources.gemini/settings.jsonif you need customcwdor env vars. - Codex CLI
Always startcd /path/to/my-app codex mcp add project-memory npx project-memory-mcp codex mcp listcodexfrom/path/to/my-appso the server detects the right root.
- Prompts for a server ID (e.g.,
Verify routing
- In that project, ask your CLI to
Call memory_status and show the output.You should seeprojectRoot=/path/to/my-appandmemoryFilePath=/path/to/my-app/.ai/memory.json. If not, fix the MCP config (cwd, env vars, etc.) before continuing. - Need a reminder of commands? Run
memory_helpanytime for the cheat sheet.
The wizard remembers your last choices inside
<project>/.ai/memory-mcp.json, so future runs can pre-fill the same server ID and runner. Remove that file if you want to start from scratch.Once you've run
setuponce, you can re-apply the saved config to any CLI without prompts:project-memory-mcp switch # all CLIs project-memory-mcp switch --cli claude # Claude only project-memory-mcp switch --project ~/code/apiPrefer the long-form guide (env overrides, troubleshooting, auto hooks)? See
docs/LOCAL_SETUP.md.- In that project, ask your CLI to
Daily Workflow
1. memory_get_bundle → load relevant context for your task
2. ... do your work ...
3. memory_save → store what changed / what you learnedAny model (Claude, Codex, Gemini) can then pick up where another left off.
Call MCP tool `memory_save` with:
- title: "Project runtime versions"
- type: "fact"
- content: "PHP 7.4.33, Laravel 5.6.40"
- tags: ["php","laravel","environment"]
- source: "claude"Call `memory_get_bundle` with prompt "I am fixing login API bugs" and maxItems 12.Suggestion Engine (mid-session)
The server includes a live suggestion engine that detects patterns and nudges you to save useful context:
- Push observations — call
memory_observewith{"type":"bash_command","content":"npm install axios"}after running commands. - Engine matches rules — version checks, dependency changes, deploys, error→fix cycles, config file edits.
- Scoring —
base_weight × recency_boost × feedback_multiplier. Only suggestions scoring >= 3 are surfaced. - Review — call
memory_suggestto see pending suggestions, thenmemory_suggestion_feedbackto accept or reject. - Feedback loop — accepts increase the scoring weight for that category; rejects decrease it. Weights persist in
.ai/suggestion-feedback.json.
High-confidence suggestions (score >= 5) can optionally auto-save when autoSaveEnabled is turned on in config.
Automatic compaction & archives
- The server automatically compacts when more than 400 active items exist (see
CONFIG.autoCompact). Oldest entries are moved to.ai/memory-archive.json, and a summary note is added so you still know what was archived. - To trigger compaction manually (or tune thresholds per project), call
memory_compactand provide overrides such as{ "maxItems": 250 }. - Archived content stays available for future manual review—
memory_get_bundleonly surfaces the most relevant active notes while summaries keep the historical trail discoverable. - Want zero-click context? Configure the optional
dist/hooks/auto-memory.jsscript (... startonUserPromptSubmit,... stoponStop) to auto-inject bundles and auto-save transcripts.
Reference
Read
| Tool | Purpose |
|---|---|
| memory_help | Quick-start usage tips + sample prompts |
| memory_status | Show resolved project root, memory file path, counts, revision |
| memory_search | Keyword/tag search over saved items |
| memory_get_bundle | Compact ranked memory bundle for current task |
| memory_list_proposals | List proposals by status |
Write (direct)
| Tool | Purpose |
|---|---|
| memory_save | Save memory item immediately (no approval step) |
| memory_pin | Pin or unpin an existing item |
Write (gated)
| Tool | Purpose |
|---|---|
| memory_propose | Create proposals (pending approval) |
| memory_approve_proposal | Approve/reject proposal, optional edits |
Modify / Delete
| Tool | Purpose |
|---|---|
| memory_update | Update fields of an existing item by ID |
| memory_delete | Permanently remove an item by ID |
Suggestion Engine
| Tool | Purpose |
|---|---|
| memory_observe | Push an observation (bash command, file edit, error, etc.) for pattern detection |
| memory_suggest | Pull pending suggestions from the engine |
| memory_suggestion_feedback | Accept or reject a suggestion (accepted suggestions are saved to memory) |
Maintenance
| Tool | Purpose |
|---|---|
| memory_compact | Archive older items into .ai/memory-archive.json and add a summary note, keeping the active store lean |
All tools accept an optional projectRoot input for multi-project routing.
Tool invocation cheatsheet
| Tool | Minimal CLI prompt | Notes |
|---|---|---|
| memory_status | Call memory_status and show the output. | Confirms projectRoot + .ai/memory.json before you start. |
| memory_help | Call memory_help. | Returns this cheat sheet + best-practice prompts. |
| memory_get_bundle | Call memory_get_bundle with {"prompt":"Fixing login bugs"} | Adjust maxItems, types, or projectRoot per task. |
| memory_save | Call memory_save with {"title":"New API",...} | Provide content; optionally tags, pinned, source. |
| memory_search | Call memory_search with {"query":"redis"} | Add includeContent, tags, or types filters. |
| memory_propose | Call memory_propose with {"items":[...],"reason":"code review"} | Use when you want an approval step before saving. |
| memory_approve_proposal | Call memory_approve_proposal with {"proposalId":"prop_...","action":"approve"} | Include edits to tweak proposal content before approval. |
| memory_pin | Call memory_pin with {"itemId":"mem_...","pinned":true} | Pinning keeps key notes surfaced in bundles. |
| memory_update | Call memory_update with {"itemId":"mem_...","title":"new title"} | Update title, content, type, tags, or pinned status. |
| memory_delete | Call memory_delete with {"itemId":"mem_..."} | Permanently removes the item from the store. |
| memory_compact | Call memory_compact with {"maxItems":250} | Keeps the active store lean; omit payload to use defaults. |
| memory_observe | Call memory_observe with {"type":"bash_command","content":"npm install axios"} | Pushes an observation; returns any triggered suggestions. |
| memory_suggest | Call memory_suggest | Lists pending suggestions waiting for review. |
| memory_suggestion_feedback | Call memory_suggestion_feedback with {"suggestionId":"sug_...","action":"accept"} | Accepts (saves to memory) or rejects (adjusts scoring weights). |
Usage tips:
- Claude Code / Codex CLI: type the phrase exactly (e.g. “Call memory_status…”). They’ll run the tool and return the output inline.
- Gemini CLI: either type the sentence or run
memory_compact {"maxItems":250}directly in the terminal./mcpshows the live list of tools + descriptions.
memory.json structure:
| Field | Description |
|---|---|
| version | Format version (currently 1) |
| project | Metadata: id, root, timestamps |
| items | Approved/saved memory entries |
| proposals | Gated workflow entries (pending, approved, rejected) |
| revision | Incremented on every write |
projectRoot is resolved in this priority:
- Tool input
projectRoot(if provided) MEMORY_PROJECT_ROOTenv var- Nearest ancestor with
.git - Current working directory
Storage path:
MEMORY_FILE_PATHenv var if set (relative paths resolve from project root)- Otherwise
<projectRoot>/.ai/memory.json
- Update an item:
Call memory_update with {"itemId":"mem_...","title":"corrected title","content":"new content"}. You can update any combination oftitle,content,type,tags, andpinned. - Delete one item:
Call memory_delete with {"itemId":"mem_..."}. Permanently removes it from the store. - Delete all:
rm -f <projectRoot>/.ai/memory.json
| Variable | Purpose |
|---|---|
| MEMORY_PROJECT_ROOT | Force a specific project root |
| MEMORY_FILE_PATH | Override the memory file path (relative resolves from project root) |
| PROJECT_MEMORY_MCP_CLAUDE_CONFIG_PATH | Override where the setup wizard reads/writes Claude’s config (defaults to ~/.claude.json) |
| CLAUDE_CONFIG_PATH | Same as above, kept for compatibility; only used if the project-specific variable is unset |
MEMORY_PROJECT_ROOT=/path/to/project npm run start
# Point setup at a sandbox-friendly Claude config file
PROJECT_MEMORY_MCP_CLAUDE_CONFIG_PATH=.tmp/claude-test.json \
npx project-memory-mcp setup --claudeserver.ts # entrypoint (source)
dist/server.js # entrypoint (runtime)
src/main.ts # MCP bootstrap and transport connection
src/tools.ts # tool registration and handlers
src/suggestions.ts # suggestion engine (rules, scoring, feedback)
src/prompts.ts # MCP prompt (slash command) registration
src/storage.ts # lock, load/write, atomic persistence
src/runtime.ts # project root/path resolution
src/domain.ts # scoring/tokenization/validation helpers
src/config.ts # constants + suggestion config
src/maintenance.ts # auto-compaction and archiving
src/logger.ts # stderr logger
hooks/extractors.ts # heuristic extractors (versions, deps, errors)
.ai/memory.json # persisted project memory (per project)
.ai/suggestion-feedback.json # suggestion scoring weights (per project)Add .ai/ to .gitignore if you don't want memory committed to the repo.
Troubleshooting
| Problem | Fix |
|---|---|
| memory.json not created | Call memory_status — project root is probably wrong. Fix cwd (Claude) or run CLI from correct folder. |
| Server not available in CLI | Check CLI MCP config. Confirm node -v works and server file exists. |
| Data not saved | You must call a write tool (memory_save or approve a proposal). Chat alone does not persist. |
Testing checklist
- Configure CLI MCP server for target project
- Restart CLI session
memory_statusreturns expected pathmemory_savereturns successmemory_searchfinds the saved item- Verify another CLI (Claude/Gemini/Codex) can see the same item
The hooks/ directory contains hooks that automatically capture memory items from your session — no manual memory_save needed for routine facts.
How it works
The auto-save hook runs silently in the background after each session:
Trigger: When your CLI session ends:
- Claude Code: on
Stopevent (Ctrl+C or session end) - Gemini CLI: on
SessionEndevent - Codex CLI: on notify events (when configured)
- Claude Code: on
Processing:
dist/hooks/auto-save.jsreads the session transcript (JSONL or JSON format)- Heuristic extractors analyze tool calls and results
- Extracts structured facts like versions, dependencies, commits, error fixes
Saving:
- Deduplicates against existing memory using title hashing and similarity checks
- Saves new items to
.ai/memory.jsonwithsource: "auto-hook"andtags: ["auto-hook"] - Updates cursor in
.ai/.auto-save-cursor.jsonto track progress
Next session: Only processes new transcript lines since last cursor position
What gets captured automatically
| Category | Example Command | Extracted Item | Type |
|---|---|---|---|
| Version checks | node -v | "node version: v20.11.0" | fact |
| | python --version | "python version: 3.11.5" | fact |
| | npm -v, pip -v, go version | version facts | fact |
| Dependencies | npm install express | "Added dependency: express" | fact |
| | pip install requests | "Added dependency: requests" | fact |
| | cargo add tokio | "Added dependency: tokio" | fact |
| Git commits | git commit -m "fix auth bug" | "Commit: fix auth bug" | note |
| Error fixes | Command fails → retried command succeeds | "Resolved: [error summary]" | fact |
| File changes | Write/Edit tool calls | "Files modified this session (5)" | note |
All auto-saved items get these tags:
auto-hook- identifies auto-captured items- Category-specific tags:
version,environment,dependency,commit,error-resolution,file-changes
Example: What you'll see
During session:
$ node -v
v20.11.0
$ npm install express
added 57 packages
$ git commit -m "Add express server"
[main abc1234] Add express serverAfter session ends (automatic, silent):
Your .ai/memory.json will contain:
{
"items": [
{
"id": "mem_a1b2c3d4",
"type": "fact",
"title": "node version: v20.11.0",
"content": "Detected via `node -v`",
"tags": ["version", "environment", "auto-hook"],
"source": "auto-hook",
"createdAt": "2026-02-12T13:39:17.364Z"
},
{
"id": "mem_e5f6g7h8",
"type": "fact",
"title": "Added dependency: express",
"content": "Installed via `npm install express`",
"tags": ["dependency", "auto-hook"],
"source": "auto-hook",
"createdAt": "2026-02-12T13:39:18.123Z"
},
{
"id": "mem_i9j0k1l2",
"type": "note",
"title": "Commit: Add express server",
"content": "Full command: git commit -m \"Add express server\"",
"tags": ["commit", "auto-hook"],
"source": "auto-hook",
"createdAt": "2026-02-12T13:39:19.456Z"
}
]
}Setup per CLI
Claude Code (already configured)
This repo includes .claude/settings.json:
{
"hooks": {
"Stop": [{
"hooks": [{
"type": "command",
"command": "node \"$CLAUDE_PROJECT_DIR/dist/hooks/auto-save.js\"",
"async": true,
"timeout": 15
}]
}]
}
}Status: ✅ Works automatically in this project
To disable: Remove or rename .claude/settings.json
Environment: Hook receives CLAUDE_PROJECT_DIR env var pointing to project root
Gemini CLI (already configured)
This repo includes .gemini/settings.json:
{
"hooks": {
"SessionEnd": [{
"matcher": "*",
"hooks": [{
"name": "auto-save",
"type": "command",
"command": "node \"$GEMINI_PROJECT_DIR/dist/hooks/auto-save.js\""
}]
}]
}
}Status: ✅ Works automatically in this project
To disable: Remove or rename .gemini/settings.json
Environment: Hook receives GEMINI_PROJECT_DIR env var pointing to project root
Important: Gemini hooks must output valid JSON to stdout. The hook returns {} for compatibility.
Codex CLI (needs manual setup)
Setup required: Edit your global Codex config at ~/.codex/config.toml:
# Enable history persistence (required for hooks to access transcript)
[history]
persistence = "save-all" # or just true
# Add the notify hook (adjust path to your installation)
[notify]
command = ["node", "/absolute/path/to/project-memory-mcp-js/dist/hooks/codex-notify.js"]Replace /absolute/path/to/project-memory-mcp-js with your actual installation path.
Optional: Pass explicit history file path:
[notify]
command = ["node", "/path/to/dist/hooks/codex-notify.js", "--history", "/path/to/history.jsonl"]To disable: Remove the [notify] section from config.toml
Environment: Hook receives CODEX_PROJECT_DIR env var pointing to project root
How it works:
- Codex calls
codex-notify.jswith its notification payload - The notify hook parses Codex's payload format and forwards it to
auto-save.js - Same extraction and saving process as Claude/Gemini
Deduplication strategy
The hook prevents duplicate items using multiple strategies:
- Title hash tracking: SHA-256 hash of each title stored in cursor file
- Jaccard similarity: Compares word overlap between new and existing titles (threshold: 80%)
- Cross-session dedup: Hashes persist across sessions to prevent re-capturing the same facts
- Cursor position: Only processes new transcript lines since last run
Cursor tracking
State tracked in .ai/.auto-save-cursor.json:
{
"sessionId": "current-session-id",
"lastLineIndex": 42,
"itemHashes": ["hash1", "hash2", "..."],
"updatedAt": "2026-02-12T13:39:17.658Z"
}sessionId- Current session ID (resets cursor position on session change)lastLineIndex- Last processed transcript line (0-indexed)itemHashes- Recent title hashes (keeps last 200 for dedup)updatedAt- Last update timestamp
Session change behavior: When sessionId changes, lastLineIndex resets to -1 but itemHashes are preserved for cross-session deduplication.
Minimum threshold
To reduce noise, the hook only processes transcripts with at least 2 assistant messages in the new lines since last cursor position.
Single-turn exchanges are skipped.
Testing the hook
Automated test (all three CLIs):
npm run test:hooksThis creates a test transcript and verifies all three hooks work correctly.
Manual test (single hook):
echo '{"session_id":"test","transcript_path":"/tmp/test.jsonl","cwd":"'$(pwd)'"}' | node dist/hooks/auto-save.jsVerify saved items:
# Check memory file
cat .ai/memory.json | jq '.items[] | select(.source == "auto-hook")'
# Check cursor
cat .ai/.auto-save-cursor.jsonTroubleshooting
| Issue | Solution |
|---|---|
| Hook not running | Check CLI settings file exists (.claude/settings.json, .gemini/settings.json) or Codex config.toml |
| No items saved | Check transcript has at least 2 assistant messages. Try running node -v and ending session. |
| Items not appearing | Verify .ai/memory.json exists and check for errors in hook stderr |
| Duplicates appearing | Check cursor file .ai/.auto-save-cursor.json is being updated |
| Codex hook not working | Ensure history.persistence is enabled and history file exists |
Debug mode: Run hook manually with test payload to see errors:
node dist/hooks/auto-save.js < test-payload.json