brain-mcp
Click on "Install 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., "@brain-mcpremember: always use parameterized queries for database operations"
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.
π§ Brain MCP
Active semantic memory for Claude and Cursor. Not just a knowledge store: it reinforces good practices, prevents recurring mistakes, and improves code quality/security for the AIs that use it β with controlled token cost, and without depending on the AI "remembering" to read or write.
Every solution, decision, or lesson becomes a node searchable by natural language (vector embeddings). Lessons become compact guardrails injected automatically at the start of every interaction.
Index
What Brain does Β· Measurable gains Β· Comparison with other solutions Β· Architecture Β· Quick start Β· MCP Tools Β· Classifier Β· Hybrid search Β· Dedup/contradiction Β· Active loop + auto-injection + auto-capture Β· Control Room Β· Brain UI Β· Backup Β· Live log Β· Scripts Β· Integration Β· Security Β· Troubleshooting Β· Roadmap
β¨ What Brain does
Self-sufficient active improvement loop: guardrails (rules + pitfalls) for the current project are automatically injected before every message (
UserPromptSubmithook) β doesn't depend on the AI remembering to check. At the end of a task, new knowledge is automatically captured (SessionEnd/stophooks) β doesn't depend on the AI remembering to write.Quality classifier (Ollama Cloud, with automatic model fallback): every write goes through a server-side second opinion that adjusts
kind/priority/directiveand flags how much to trust the entry.Hybrid search (vector + BM25 via SQLite FTS5, fused by Reciprocal Rank Fusion) β finds matches both by meaning and by exact keyword, with per-project diversification.
Deduplication and contradiction detection: a real UNIQUE constraint in the database (not just an application-level check) rejects exact duplicates before spending embedding/classifier calls; semantically near-identical nodes (similarity β₯ 0.92) trigger a non-blocking warning.
Ebbinghaus decay in guardrail ranking β knowledge reinforced by recurring use rises; what's no longer accessed decays (~14-day half-life).
Full traceability: who consumed what, when, and from where (claude / cursor / browser), with correct attribution even when the client forgets to report its own source.
Interactive neural visualization: a force-directed knowledge graph, with timeline, filters, and search.
Durability: automatic daily backup to GitHub (versioned JSON export) + restore with recovery from two disaster modes (SQLite lost, ChromaDB lost).
Related MCP server: MCP Memory Server
π Measurable gains
Real numbers measured from the project's own usage, not estimates:
Metric | Value |
Cost of | Hundreds of tokens vs. thousands β the return is just |
Quality classifier calls per week | ~44-50, a residual fraction (~0.3%) of Ollama Cloud's free-tier quota (measured in GPU-time) |
Automated tests | 155+, whole suite runs in ~1s |
Accumulated knowledge nodes (across 3 repos) | 45+ active (+ 12 consolidated into 4 generalized rules/pitfalls), growing organically without manual intervention |
| 3 calls in ~2 weeks of manual use β guaranteed on every message (Claude) / every new session (Cursor) via hooks |
Real bugs found in a self-audit | 6 confirmed and fixed in a single pass (a 1000x-wrong timeout, invalid model IDs, 2 race conditions, a project leak in search, a fragile disaster-recovery script) β no memory system "just works" without periodic auditing |
π Comparison with other solutions
We researched how well-known AI agent memory systems solve the same problems before deciding to build our own. Honest summary β not everything here is unique to us, and they have capabilities we don't:
Capability | Brain MCP (ours) | ||||
Automatic capture (hooks) | β
| β 12 hooks, 15+ clients | Partial (via marketplace) | β (memory managed via tool calls) | β |
Automatic guardrail injection (without the AI remembering to ask) | β every message in Claude Code, every session in Cursor β the only pattern like this we found | β | β | β | β |
Quality classifier (2nd opinion before persisting) | β with automatic model fallback | β (compression, not a quality gate) | β | β | β |
Hybrid search (vector + keyword) | β FTS5 + ChromaDB, RRF | β BM25 + vector + graph, RRF | β vector + graph | Searchable (recall memory) | β temporal knowledge graph |
Dedup with a real database guarantee (not just app-level) | β UNIQUE index | Partial (5-min window by hash) | ADD-only (never overwrites) | N/A | N/A |
Decay/ranking by usage reinforcement | β Ebbinghaus | 4 consolidation tiers (sleep curve) | Recency-based ranking | 3 tiers (core/recall/archival) | Versioned temporal graph |
Infrastructure cost | Free (local Ollama + SQLite + self-hosted ChromaDB); cloud classifier uses a residual fraction of the free tier | Free (local SQLite) | Paid SaaS | SaaS/self-host | Paid SaaS |
Hosting | 100% local | 100% local | Cloud | Cloud or self-host | Cloud |
Retroactive quality audit | β
| β | β | β | β |
Consolidation of recurring knowledge (N loose solutions β 1 generalized rule) | β
| β | β | Partial (sleep-time consolidation, doesn't generate a new rule) | β |
Cross-project knowledge (one lesson applies to any repo) | β
global tier ( | β (per session/agent) | β (per user) | N/A (per-agent memory) | β (per group/user) |
Where they beat us: agentmemory supports far more clients (15+) and has a session-replay visualizer; Letta has "sleep-time agents" (asynchronous memory refinement during idle time) that we haven't implemented; Zep has a versioned temporal knowledge graph, more sophisticated than our simple Ebbinghaus decay.
Where we win: we're the only one we found with a dedicated quality classifier (a model's second opinion before persisting, with automatic fallback) and deterministic guardrail injection on every turn β the others rely either on context compression (fragile, as discussed) or on the agent itself deciding to search memory.
π§± Architecture
packages/mcp-server/ Node + TypeScript β MCP server (stdio) + HTTP bridge (Express :3456)
packages/brain-ui/ React + Vite + Tailwind β neural graph (:5173)
ChromaDB Vectors (Docker :8000)
Ollama Local embeddings β nomic-embed-text (:11434)
SQLite Metadata + access log (packages/mcp-server/data/brain.db)Layer | Technology |
MCP Server | Node.js + TypeScript + |
Vector DB | ChromaDB (Docker) |
Embeddings | Ollama |
Metadata | SQLite ( |
HTTP Bridge | Express :3456 |
UI | React + Vite + TailwindCSS + |
π Quick start
cp .env.example .env # 1. local config (OLLAMA_API_KEY is optional β without it, the classifier stays pending_review)
pnpm install # 2. dependencies
docker compose up -d # 3. ChromaDB
ollama pull nomic-embed-text # 4. embedding model (first time only)
pnpm build:server # 5. compiles the MCP server (generates dist/)
pnpm dev # 6. brings up API + UI togetherPrerequisites: Docker Desktop, Ollama running, Node 22+ (
.nvmrc/enginesinpackage.json), and pnpm.
π οΈ MCP Tools
Tool | Purpose |
| Token-cheap. Rules + pitfalls for a project as one-line directives. Call once at the start of a task. |
| Stores a solution/decision/lesson. |
| Hybrid search (vector + BM25) with per-project diversification. |
| Lists items, with optional project/tag filters. |
| Edits an item (auto re-embeds when title/content/tags change). Also accepts |
| Removes an item by id. |
Every tool accepts source (claude / cursor / browser / manual) β used for attribution in the dashboard.
add_knowledge returns, besides id: review_status (see classifier below), classifier_applied, and optionally duplicate_of (blocked by dedup) or similar_to (contradiction/near-duplicate warning).
Knowledge classification (kind)
kind | meaning |
| a one-off fix/lesson (default) |
| a best practice to always follow |
| an anti-pattern to avoid |
| an architectural decision |
rule and pitfall with a directive (one imperative line) are what get_guidelines delivers to AIs.
Lifecycle fields
field | type | description |
|
|
|
|
| Only |
| UUID (optional) | Tracks which item replaced this one β useful for auditing decisions that changed. |
To deprecate a guardrail that's gone stale:
# via update_knowledge (MCP)
update_knowledge({ id: "...", status: "deprecated", superseded_by: "new-uuid" })π€ Quality classifier (Ollama Cloud)
Every call to add_knowledge goes through a second evaluation, done by a server-side model via Ollama Cloud, before the embedding is generated. The classifier decides:
worth_keepingβ is it worth keeping? If not,priorityis forced to5(never removed, just loses ranking relevance).suggested_kind/suggested_priorityβ may reclassify what the writing AI suggested.directiveβ automatically generates the one-line directive whenkindisrule/pitfall(useful when Cursor writes without filling that field).
The result is recorded in each node's review_status field:
review_status | meaning |
| the classifier ran and adjusted the fields |
| classifier unavailable (no |
| node reviewed manually (or created before the classifier existed) |
The write is never blocked β a classifier failure only changes review_status to pending_review, it never prevents add_knowledge.
Configuration
# .env
OLLAMA_API_KEY=... # required for the classifier to run
OLLAMA_CLOUD_MODEL=qwen3-coder:480b-cloud
OLLAMA_FALLBACK_MODELS=minimax-m2.1:cloud # optional, comma-separated
CLASSIFIER_TIMEOUT_MS=60000 # real observed latency: 2-20s
CLASSIFIER_ENABLED=true # false disables it without touching code
CLASSIFIER_LANGUAGE=en # language of generated content (title/content/directive/reasoning) β defaults to "en" if omittedThe classifier's prompts themselves are always in English (the model follows English instructions fine regardless of what language it needs to write in) β only CLASSIFIER_LANGUAGE changes the language of the generated content. This matters in practice: a knowledge base ends up mixing languages if this value changes midway, because similarity search won't merge the same lesson written in two different languages (embeddings land far apart). Pick a language and stick with it.
Automatic model fallback
If OLLAMA_CLOUD_MODEL fails (HTTP error, timeout, malformed JSON, or an invalid response shape), the classifier automatically tries each model in OLLAMA_FALLBACK_MODELS, in order, before giving up. Every attempt is logged to stderr (used fallback "..."), so you can tell from the log if the primary model is having issues.
Why we don't hardcode one model forever: Ollama's cloud models come and go (deprecation, free/paid tier changes). We tested several candidates against 3 fixed real samples (pnpm classify:test) before deciding:
Model | Accessible on free tier? | Notes |
| β | Best latency (1.5-2.7s), but with a known deprecation date |
| β | The only one to consistently get |
| β | Fast, but already flagged with factual/judgment errors in production |
| β | Inconsistent across runs, unstable latency (up to 60s) |
| β | Unstable latency (one call took 53s) |
| β 403 | Require Ollama Cloud's Pro plan |
Measured usage (pnpm classify:test + organic capture): ~44-50 calls/week, a small fraction of the free-tier quota (measured in GPU-time, not requests).
Test in isolation / audit existing nodes
pnpm classify:test # runs 3 real sample nodes, shows the model's judgment
pnpm audit:brain # dry-run β re-evaluates ALL nodes, saves a report in backups/
pnpm audit:brain -- --apply # applies the suggestions (review the report first!)audit:brain is the mechanism for retroactively reclassifying nodes written before the classifier existed, or written by Cursor without kind/directive. Reviewing the report manually before running with --apply is recommended β the model sometimes downgrades items that are already useful rule/pitfall entries to solution, which would remove them from get_guidelines.
π Hybrid search (vector + BM25)
search_knowledge fuses two relevance signals via Reciprocal Rank Fusion (k=60):
Vector search (ChromaDB + Ollama embeddings) β captures semantic similarity.
Keyword search (SQLite FTS5, BM25 ranking) β captures exact matches that embeddings alone miss (e.g.
TIP-2375, package names, acronyms).
Items that rank well in both searches come out on top. The FTS5 index is kept up to date automatically by triggers on every INSERT/UPDATE/DELETE β no need to reindex manually.
Per-project diversification: when a search has no explicit project, at most 3 results from the same project show up β this keeps a project with lots of nodes from drowning out relevant results from others.
𧬠Deduplication and contradiction detection
On write (add_knowledge):
Exact dedup β SHA-256 hash of normalized
title + content. If an identical node already exists, the write is aborted before the classifier and embedding (zero API cost), and the response carriesduplicate_ofpointing at the existing node.Contradiction/near-duplicate detection β after embedding, a 1-result vector search checks whether something with cosine similarity β₯ 0.92 already exists. If so, the node is still written normally, but the response includes
similar_toas a warning β useful for catching cases like "decision X" vs. "revised decision X" that aren't identical but deserve a look.
Neither check blocks the write beyond the exact-duplicate case β the goal is to flag, not to prevent.
π The active improvement loop (with token control)
Task start β the AI calls
get_guidelines(project)once β gets β€12 short directives (hundreds of tokens, not thousands).Solving something β writes with
kind+directive(only if it was worth it β see criteria below).Next session β the directive resurfaces via
get_guidelinesβ the lesson is reinforced, the mistake is avoided.
The instructions that trigger this loop live in ~/.claude/CLAUDE.md (global, loads for every project), in the repo's own CLAUDE.md, and in .cursor/rules/brain.mdc (Cursor, Project Rules format with alwaysApply: true).
Auto-injection of guardrails (Claude Code + Cursor) β doesn't depend on the AI remembering to read
"Call get_guidelines once per task" assumes you can tell when a new task begins. In a long, continuous conversation (days, with context compaction along the way), that boundary gets ambiguous β real data showed get_guidelines being called only 2-3 times across weeks of use, even with dozens of writes in the same period. The problem isn't memory loss (the Brain lives outside the context, immune to compaction) β it's losing the trigger for when to re-check it.
Fix: a per-client hook automatically injects the current project's guardrails, without depending on the AI remembering to call the tool β sharing the same script (inject-guidelines-hook.ts) and the same throttle logic (services/guidelinesInjection.ts: only re-injects if >20min have passed since the last time in this session, or if the project changed β avoids repeating the same block over and over):
Claude Code:
UserPromptSubmithook (.claude/settings.json) β fires before every message, injects viahookSpecificOutput.additionalContext.Cursor:
sessionStarthook (.cursor/hooks.json) β fires once per new session/conversation (Cursor doesn't expose a per-turn hook with injection capability βbeforeSubmitPromptfires on every prompt, but its output schema only hascontinue/user_message, confirmed against a real captured payload; it doesn't inject context). Output format is also different: a flat{"additional_context": "..."}, matching Cursor's own snake_case convention (vs. Claude Code's nested object).
The script auto-detects which client called it (detectHookSource, same logic as auto-capture) and builds the output in the right shape β one single file covers both.
A real gotcha that silently broke both Cursor hooks (sessionStart and stop) for a while: the commands used cd "<path>" && pnpm exec tsx <script>. Native Claude Code runs these commands via cmd.exe (where && works), but Cursor runs them via PowerShell, which doesn't accept && as a separator β every hook failed with exit code 1, and it stayed masked because manually calling the tool (get_guidelines via .cursor/rules/brain.mdc) kept working and gave the false impression that auto-injection was active. The root cause only became visible by reading Cursor's own internal log (%APPDATA%/Cursor/logs/<timestamp>/window*/output_*/cursor.hooks.workspaceId-*.log), which records STDERR for every hook execution. Fix: drop cd && entirely and use pnpm --dir "<path>" exec tsx <script>, which runs in any shell with no separator at all. Confirmed working in production (Cursor's log: exit code: 0 + Merged 1 valid response(s) for step sessionStart).
Auto-capture (Claude Code + Cursor) β doesn't depend on the AI remembering
The AI doesn't always remember to call add_knowledge at the end of a task. This is solved by native hooks that run automatically at the end of every interaction, no manual step needed β in both clients:
Claude Code:
SessionEndhook in.claude/settings.json.Cursor:
stophook in.cursor/hooks.jsonβ fires at the end of every agent response (more reliable thansessionEnd, which Cursor aborts on window close before the process can finish running).
Pipeline (shared by both clients β auto-capture-hook.ts β auto-capture-worker.ts):
The hook receives
transcript_path(the conversation's JSONL) and spawns a detached process β the session closes immediately, without waiting.The worker reads only the new lines since its last run (tracked in
data/auto-capture-state.json, persession_id), builds a compact summary (user requests, files edited, commands run, the assistant's latest responses). The parser (services/transcript.ts) tolerates both transcript formats β Claude Code nestsroleinsidemessage.role; Cursor puts it at the object's top level β and identifies mutating/reading tools by the shape ofinput(file_path/command), not by a fixed name, since each client names its tools differently (Bashvs.Shell, for example).If there's substance (something beyond pure exploration), the summary goes to the classifier (
summarizeSessionForCapture), which extracts 0β3 knowledge candidates worth noting β the common case is zero.Each candidate goes through the normal
POST /knowledgeβ i.e. it goes through the per-item classifier, hash dedup, and contradiction detection described above all over again.Everything is logged to
data/auto-capture.log(outside git) for debugging.
Each consuming repo needs its own .claude/settings.json and .cursor/hooks.json pointing (by absolute path) to this repo's auto-capture-hook.ts β the script is shared, but the hook config is per-repo in both clients.
Quality criteria β when to write
Only write if at least one of these is true:
The problem wasn't obvious (required real investigation)
It cost real time (>10 min of debugging or research)
It's a decision another AI would repeat without this context
It's a pattern that will show up again in the project
Don't write: trivial fixes, typos, obvious language/framework behavior.
Global knowledge (cross-project)
project is optional in add_knowledge β omitted (or "global") marks the lesson as valid for any project. get_guidelines and search_knowledge always included this tier (project = '') in search; what was missing was a way to write to it β project used to be required in the schema, so this tier was never populated. Use it for lessons that aren't specific to one repo: shell/Windows quirks, patterns from a generic tool (pnpm, Docker, Git), decisions that would apply to any stack.
Consolidation β from loose solutions to generalized rules (pnpm consolidate)
Knowledge accumulates horizontally by default: every add_knowledge/auto-capture writes an isolated solution, even when it's the 3rd time the same lesson shows up. Only rule/pitfall reach get_guidelines β a pile of similar solutions never becomes a guardrail on its own.
scripts/consolidate.ts closes this loop:
Embeds all active
solutionitems and clusters them by cosine similarity (union-find, threshold configurable viaCONSOLIDATE_THRESHOLD, default 0.80).For each cluster of 2+, asks the classifier (same Ollama Cloud fallback infra) to confirm whether it's really the same recurring pattern β not just the same topic β and, if so, synthesize ONE generalized
rule/pitfall(and decide whether it'sglobalor project-specific).Applies (
--apply): creates the new item and marks the original solutions asdeprecatedwithsuperseded_bypointing at the new id β reversible, nothing is deleted.
pnpm consolidate # dry-run β shows the proposed clusters, writes nothing
pnpm consolidate -- --apply # creates the rules/pitfalls, deprecates the original solutionsRun it periodically (no automatic scheduling yet) β the more solutions accumulate, the more genuine clusters show up.
get_guidelines ranking
Returned directives are ordered by:
priorityASC βpriority=1items always come first, regardless of recencyProject scope β project-specific items before global ones
Ebbinghaus strength β
(access_count + 1) Γ e^(-0.05 Γ days since last access). Models forgetting: knowledge that isn't reinforced loses strength with a ~14-day half-life; frequent, recent access beats old access, even if more numerous.
When a directive goes stale, mark it deprecated instead of deleting it β this preserves the decision history, with superseded_by pointing at the replacement.
π Control Room (admin panel)
β£ CONTROL ROOM button (or d key):
Totals: nodes, events, searches, views, active days
Who's consuming (by source: claude / cursor / browser)
Activity timeline (30 days)
Per-project stats
Most accessed
Live activity feed (what's coming and going)
Node explorer: table with text/type filters, sorting, click to open
β GUARDRAILS panel (g key): shows the active directives per project β what the brain is currently reinforcing.
Node size = real AI usage
Each node's size grows with access_count, which only increases when an AI (claude/cursor) actually consumes the node** (search or view). Browser opens are recorded (where/when), but don't inflate the node β that's just user curiosity. Growth is logarithmic and capped.
π¨ Brain UI β features
Neural graph, force-directed; colors: π΄ recent (24h) Β· π΅ active Β· π£ dormant (or color by project)
Semantic search with a ranked results panel and automatic zoom onto matches
Node detail: resizable (drag the edge), reading mode (β€’), inline editing (full CRUD: kind, directive, tags, content) + event history
Timeline (βΆ): watch the brain grow chronologically
Per-project filters (toggle on/off) and an ambient particle field
Live pulse when a new node is captured
Shortcuts:
/search Β·Escclear/close Β·dControl Room Β·gGuardrails Β·aadd Β·?help
πΎ Backup & restore
Knowledge lives in SQLite (packages/mcp-server/data/brain.db, outside git). A portable, versioned JSON export is committed to backups/brain-export.json and pushed to GitHub.
pnpm backup # exports JSON + git commit + push
pnpm restore # rebuilds SQLite + embeddings from the backupThe backup runs on API start and every 24h while it's running β this alone already covers most cases, on any OS.
A Windows Scheduled Task (
BrainMCP-DailyBackup) runs the backup every day at 7pm, even with the API turned off (scripts/brain-backup.cmd).Turn off auto-backup:
BRAIN_AUTO_BACKUP=false.
Mac/Linux β cron equivalent (not validated in practice; this project has only ever been developed on Windows β if you test it, a PR with fixes is welcome):
# crontab -e β runs the backup every day at 7pm even with the API off
0 19 * * * cd /absolute/path/to/brain-mcp/packages/mcp-server && pnpm backupExport scope (a repo shared between public and private projects)
The same Brain can run as a central server for multiple repos β if one of them is private/client work, knowledge written from there (feature names, architecture decisions, internal bug details) also goes into the export, unfiltered, by default. That's fine while this repo stays private, but it becomes a real leak the moment it goes public.
BACKUP_EXCLUDE_PROJECTS (comma-separated, in .env, never committed) removes specific projects from the export before it's written/committed:
# .env β excludes these projects from the public backups/brain-export.json
BACKUP_EXCLUDE_PROJECTS=client-project-a,client-project-bThis only affects backups going forward β it doesn't retroactively scrub anything already committed to git history.
π Live log
Running pnpm dev (or pnpm start:api), every relevant action prints a compact line to the terminal β no need to open the UI to know what's happening:
18:31:18 π guidelines cursor [my-project] β 10 directive(s)
18:32:07 π search claude [other-project] "csv import bug" β 3 result(s)
18:32:19 β add claude [other-project] "Fix CSV bug..." β auto_classified (rule, p2)
18:33:10 π view browser "TIP-2375..." β accessed 4xCovers add, search, guidelines, update, delete, view, list β both in the HTTP bridge (api.ts, used by the UI and by auto-capture) and in the stdio MCP server (index.ts, used directly by Claude/Cursor during a real coding session). Dashboard polling routes (/stats, /activity, /export, /knowledge/graph, /health) are deliberately silent β they're not "actions", they'd just spam the log.
Important: pnpm dev only brings up the HTTP bridge + the UI. The stdio MCP server that Claude/Cursor actually use during a coding session is a separate process, spawned by the client itself (.mcp.json/.cursor/mcp.json) β it doesn't show up in pnpm dev's terminal. To see those calls live, either (a) run pnpm dev:server in a separate terminal, or (b) watch the Control Room's Activity feed in the UI, which reads the same table (access_log) both processes write to.
The stdio server's code writes exclusively to stderr β never to stdout, which is reserved for the JSON-RPC protocol (writing there would corrupt communication with the client).
Method | Route | Description |
GET |
| nodes + edges for the UI |
GET |
| semantic search |
GET |
| detail + history (records a view) |
POST |
| adds |
PATCH |
| updates |
DELETE |
| removes |
GET |
| compact directives |
GET |
| dashboard aggregates |
GET |
| activity feed |
GET |
| full JSON dump |
GET |
| health check |
Source is read from ?source= or the X-Brain-Source header (the UI sends browser).
π¦ Scripts
Script | Action |
| API + UI together (concurrently) |
| server only / UI only |
| compiles server + UI |
| HTTP bridge |
| export+push / restore |
| ChromaDB |
| runs the test suite (155+ tests, ~1s) |
| watch mode for development |
| tests the classifier in isolation with 3 sample nodes |
| reclassifies existing nodes (dry-run / apply) |
| groups recurring solutions and synthesizes generalized rules/pitfalls (dry-run / apply) |
π Integration
Claude Code
.mcp.json (at the project root) registers the brain server; .claude/settings.local.json enables it via enabledMcpjsonServers. Inside this repo, .mcp.json and .claude/settings.json already use command: "node" (generic, via PATH) and relative paths/$CLAUDE_PROJECT_DIR β works out-of-the-box on any clone, nothing to edit. To use the Brain from another repo (consuming this one's server), replicate a .mcp.json there pointing at the absolute path of this repo's dist/index.js β that's genuinely necessary there, since it's a cross-repo reference.
.claude/settings.json (versioned, shared) also registers the auto-capture SessionEnd hook β nothing extra to configure, it already ships in the repo. Requires the HTTP bridge to be running (pnpm start:api) to actually write.
If you use nvm with multiple Node versions installed, the generic
command: "node"can cause the ABI-mismatch error described in Troubleshooting β in that case, swap it for an absolute path to a fixed version'snode.exe.
Cursor
~/.cursor/mcp.json (global) registers the brain server for all projects. This repo's own .cursor/mcp.json does the same at the project level, with a relative path (packages/mcp-server/dist/index.js) β this only resolves correctly when Cursor invokes it with cwd at this repo's root, which is the default for project-scoped configs. Each external working repo (one that only consumes the server, isn't this repo) needs its own .cursor/rules/brain.mdc with the loop instructions (copy this repo's and adjust project) and its own .mcp.json/.cursor/mcp.json pointing at this repo's dist/index.js absolute path β the root .cursorrules format is legacy/deprecated since Cursor 0.43.
Claude subagents in Cursor
Cursor 2.4+ reads .claude/agents/ natively, but doesn't resolve Claude Code's model aliases (sonnet/haiku/opus) β it silently falls back to the parent agent's model. This repo's converter generates native versions in .cursor/agents/ (which take precedence over .claude/agents/ when names collide), mapping sonnet β claude-sonnet-5, haiku β claude-4.5-haiku, opus β claude-opus-4.8, deriving readonly: true when tools don't include Write/Edit/Bash, and stripping fields Cursor ignores (tools, memory):
pnpm sync:cursor-agents -- "C:/path/to/repo" [other-repo...].claude/agents/*.md remains the source of truth β edit there and rerun the script.
β οΈ Both clients (Claude and Cursor) run the compiled
dist/β after any change tomcp-server, runpnpm build:server, otherwise they keep silently running the old code.
π‘οΈ Security
ChromaDB and the HTTP bridge listen only on
127.0.0.1β not exposed to the network.Every MCP tool input is validated with Zod (max lengths, restricted enums, non-empty tags).
Your own backup repo/mirror should be private if it holds anything sensitive.
CLAUDE.mdand.cursor/rules/brain.mdcexplicitly instruct never to write credentials, tokens, PII, or connection strings into the Brain.
π§ Troubleshooting
"Brain MCP is down (native module version mismatch)"
better-sqlite3 is a native module compiled for a specific Node version (ABI/NODE_MODULE_VERSION). This breaks when:
Multiple Node versions installed via nvm and the active version changes between when the module was compiled and when the MCP client (Claude/Cursor) loads it.
Zombie MCP processes from old sessions keep running in the background with a different Node version, holding a lock on the
.nodefile and blocking the rebuild.
This project's requirement: Node β₯ 22 (see .nvmrc and engines in package.json). If you hit this, pin both .mcp.json (Claude) and .cursor/mcp.json (Cursor) to a fixed version's node.exe instead of the generic "node" β resolving via PATH would inherit whatever nvm use is active at spawn time, which is exactly what caused this.
Fix:
# 1. Kill zombie MCP processes (Windows) β look for "dist/index.js" in the CommandLine
Get-CimInstance Win32_Process -Filter "Name = 'node.exe'" | Where-Object { $_.CommandLine -like '*dist*index.js*' } | Select ProcessId
Stop-Process -Id <PID...> -Force
# 2. Rebuild the native module for the active Node version
pnpm rebuild
# 3. Rebuild dist and restart the MCP clients (Claude/Cursor)
pnpm build:serverIf pnpm rebuild fails with EBUSY/EPERM, that's a sign a zombie process is still holding the file β repeat step 1.
πΊοΈ In progress / next steps
Implemented and stable: full MCP tools, guardrail loop with Ebbinghaus ranking and deprecation, server-side quality classifier with automatic fallback, hybrid search (BM25 + vector) with per-project diversification, hash-based dedup with a real database constraint + contradiction detection, auto-capture via SessionEnd/stop hooks (Claude Code + Cursor), auto-injection of guardrails via UserPromptSubmit/sessionStart (Claude Code + Cursor), cross-project global knowledge tier, and automatic consolidation of recurring solutions into generalized rules/pitfalls (pnpm consolidate), hardened security, a test suite (155+), Control Room, node explorer, backup/restore with disaster recovery, and the full visualization layer.
Deliberately deferred ideas (low cost/benefit today β open an issue when a real need shows up):
Multi-machine sync beyond git (current limitation: don't use on two machines at once without a manual
git pullfirst). We evaluated moving to a hosted DB (Mongo Atlas free tier) β dropped for now: no serious self-hosted competitor (Letta/agentmemory) uses Mongo, and the migration would mean rewriting BM25 (FTS5 is SQLite-specific) without solving the real problem (public-backup scope).Neighbor highlighting on hover / a graph minimap.
Impact metrics β a cheap proxy: count how often a consulted
pitfallreappears as a tag on newsolutionnodes.Parallelizing restore above ~100 nodes (sequential today; ChromaDB writes need to stay sequential, but
getEmbeddingcalls could be batched).
Known limitations
Never validated on Mac/Linux. Everything here has been developed and tested on Windows.
pnpm --dirshould in theory be portable to POSIX shells, and a cron equivalent is documented above for the backup schedule, but neither has been confirmed in practice. If you try it, a PR with fixes (or just a "this worked" confirmation) is very welcome.This repo's git history predates the public release and may still contain internal references from the private repo it was extracted from. If you're forking this to build your own private version, treat the initial commit as the clean baseline.
When a concrete need shows up, just open an issue β the system is modular enough to grow without rework.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/GuiLopes29/brain-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server