bernard
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., "@bernardsearch codebase decisions for authentication approach"
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.
bernard
Repo-committed, project-scoped AI memory for coding agents β zero API keys, zero embeddings, zero external services.
πΉπ· TΓΌrkΓ§e README
bernard is your codebase's team memory. It stores the decisions you made, the architecture notes, the bugs you fixed, and the preferences you settled on β as plain text (JSONL) inside the repo. Then you, or an AI agent, can pull that context back in seconds before starting new work. The knowledge lives where the code lives β in git β so it's committed, versioned, reviewed, and shared with the whole team.
It connects to AI coding agents (Claude Code, Cursor, and anything that speaks MCP) as an MCP server, so the agent searches past decisions and records new ones on its own.
// .bernard/records.jsonl β committed, one JSON object per line
{"id":"ber_a1b2c3","type":"decision","title":"Switched to Postgres","content":"MySQL β Postgres for JSONB and better indexing.","tags":["db"],"created":"2026-06-27T10:00:00Z","invalid":false,"superseded_by":null,"source":"manual"}Why bernard?
A memory.md file rots: it's unstructured, unsearchable, and nobody trims it. Cloud "agent memory" products want an API key, ship your code context to a third party, and vanish when the subscription lapses.
bernard takes the opposite stance:
It lives in your repo. Clone the repo, get the memory. No service to run, nothing to provision.
Zero API keys, zero embeddings, zero network calls. Everything is local files + a pure algorithm.
It's structured and queryable. Typed records, tags, synonyms, recency weighting, and supersession β not a flat dump.
It's MCP-native. Your agent uses it automatically; you rarely touch the CLI.
It's private by construction. Data never leaves the repo, so it's GDPR/KVKK-friendly by default.
Related MCP server: Claude Habitat
Smart without an API key
How is "no API key" still smart? Intelligence comes from two places, neither of which needs the network:
The agent itself. An agent like Claude Code is already capable. bernard's job isn't to understand β it's to put the right past records in front of the agent. bernard retrieves, the agent reasons.
BM25 retrieval. A classic, robust, dependency-free full-text ranking algorithm, plus synonym expansion (e.g.
loginβauth), recency weighting (newer decisions rank higher), and record invalidation (superseded decisions drop out). All local, all instant.
The bridge between the two is bernard. No keys, no subscription, no privacy leak.
Quick start (with Claude Code)
# In your project, set up the memory store once:
npx -y bernard-mcp initAdd bernard as an MCP server in your project's .mcp.json:
{
"mcpServers": {
"bernard": {
"command": "npx",
"args": ["-y", "bernard-mcp", "mcp"]
}
}
}That's it. Your agent can now call bernard_search before starting a task and bernard_add to record decisions. Commit .bernard/ and your teammates inherit the same memory.
Installation
Run on demand with npx (no install):
npx -y bernard-mcp <command>Or install globally to get a bernard binary:
npm i -g bernard-mcp
bernard --helpOr add it as a dev dependency:
npm i -D bernard-mcpRequires Node.js β₯ 20.
CLI usage
Every command has an English name and a Turkish alias (e.g. add / ekle). Output language follows your locale β see Internationalization.
Initialize
bernard initCreates (never overwrites existing files):
.bernard/records.jsonlβ the records (JSONL, committed).bernard/categories.jsonβ types + synonyms (committed).bernard/config.jsonβ project settings (committed)adds
.bernard/.cache/to.gitignoreadds
.bernard/records.jsonl merge=unionto.gitattributes
Add a record
bernard add --type decision --title "Switched to Postgres" \
--content "MySQL β Postgres for JSONB and better indexing." \
--tags db,architectureRun bernard add with no flags in a terminal and it asks interactively.
Search
bernard search authentication flow
bernard search "token refresh" --type bug
bernard search migration --tag dbResults print with score, type, title, date, tags, and a content snippet.
List
bernard list
bernard list --type decision
bernard list --tag authInvalidate (supersede)
bernard invalidate ber_abc123
bernard invalidate ber_abc123 --by ber_def456Marks a record invalid without deleting it (invalid: true). --by links the record that replaces it, preserving the decision history. Search drops invalid records by default.
To record a new decision that directly supersedes an old one:
bernard add --type decision --title "Switched to Param" \
--content "Better fees than the previous provider." --tags payment \
--supersedes ber_abc123Distill
bernard distill
bernard distill --applySurfaces possible conflicts (multiple records with the same type + tags), supersession chains, and suggestions. For each conflict it proposes keep the newest, invalidate the rest and prints ready-to-run bernard invalidate β¦ --by β¦ commands. --apply (TTY) applies them interactively. It never deletes anything β it only marks and suggests.
Stats
bernard statsA summary dashboard: total records, distribution by type, top 5 tags, invalid count, oldest/newest dates, and source (manual/agent) breakdown.
Tags (synonym management)
bernard tags
bernard tags --add "auth=login,session"
bernard tags --remove "auth"Lists/edits the types and synonym groups in categories.json. Synonyms power query expansion at search time, so the team's vocabulary becomes the team's recall.
Suggest (draft from commits)
bernard suggest --n 3
bernard suggest --addDrafts records from recent commits: type guessed from the title (featβdecision, fixβbug, refactorβarchitecture, docs/choreβnote) and tags from changed directories. This is only a starting point β the real understanding is the agent's. --add (TTY) saves drafts after you confirm.
Install hook (post-commit reminder)
bernard install-hook
bernard install-hook --removeInstalls a marked, idempotent .git/hooks/post-commit reminder to run bernard suggest after each commit. It never overwrites a non-bernard hook (it prints a snippet to add by hand). --remove removes only bernard's hook.
MCP integration (the main path)
bernard's real power is connecting to an AI agent. As an MCP server it exposes six tools that an agent uses automatically β searching prior decisions before it starts, recording new ones, and invalidating stale ones.
Add to your project's .mcp.json:
{
"mcpServers": {
"bernard": {
"command": "npx",
"args": ["-y", "bernard-mcp", "mcp"]
}
}
}If you installed globally, use the binary directly:
{
"mcpServers": {
"bernard": {
"command": "bernard",
"args": ["mcp"]
}
}
}The server exposes:
Tool | What it does |
| Search past decisions, architecture notes, fixed bugs, and preferences. The key tool β the agent calls it before starting work. |
| Add a new record ( |
| List records, optionally filtered by type/tag. |
| Invalidate a record ( |
| Update fields of an existing record; only provided fields change. |
| Fetch recent commits + diffs so the agent can extract records worth keeping. |
Works with any MCP client (Claude Code, Cursor, and others) β the config block is the same, just placed wherever that client reads MCP servers.
Data model
Records live in .bernard/records.jsonl, one JSON object per line:
{
"id": "ber_a1b2c3d4",
"type": "decision",
"title": "Switched to Postgres",
"content": "MySQL β Postgres for JSONB and better indexing.",
"tags": ["db", "architecture"],
"created": "2026-06-27T10:00:00.000Z",
"invalid": false,
"superseded_by": null,
"source": "manual"
}Record types:
type | Meaning |
| A decision that was made |
| An architecture note / structural fact |
| A fixed bug and its fix |
| A style / approach preference |
| A free-form note |
JSONL is line-oriented: appends are cheap, git diffs stay clean (one record per line), it's machine-friendly, and still human-readable via git log -p.
Invalidation lifecycle
bernard never deletes knowledge; it invalidates stale decisions and preserves history. A new decision is added (optionally with --supersedes) linking the old one; the agent can do the same via bernard_add + bernard_invalidate. Over time, bernard distill surfaces conflicts and gives you ready commands to keep the newest and invalidate the rest. Invalid records stay in the file but drop out of search; the superseded_by chain answers "why did this change?".
Team sharing (merge=union)
init adds a .gitattributes line:
.bernard/records.jsonl merge=unionBecause each record is an independent line in an append-mostly file, git unions both sides when two branches add different records β so teammates writing to memory at the same time don't hit needless merge conflicts.
Architecture: what's committed, what's local
your-project/
βββ .git/
βββ .gitignore # .bernard/.cache/ is added here
βββ .gitattributes # .bernard/records.jsonl merge=union
βββ .mcp.json # agent integration (committed)
βββ .bernard/
βββ records.jsonl # COMMITTED β shared memory
βββ categories.json # COMMITTED β types + synonyms
βββ config.json # COMMITTED β project settings
βββ .cache/ # LOCAL β gitignored
βββ index.json # BM25 index (derived, auto-rebuilt)Committed: records.jsonl, categories.json, config.json. Memory is code β it should be shared, reviewed, and versioned with git history.
Local: .bernard/.cache/. The BM25 index.json is fully derivable from records.jsonl (rebuilt automatically when the records change, or when the locale changes). Committing derived data would only create meaningless conflicts.
Internationalization
bernard ships bilingual: English (default) and Turkish. All user-facing output is localized; command names stay English with Turkish aliases, and stable identifiers (record types, field names) stay canonical.
Locale precedence (highest first):
--locale <en|tr>flag (e.g.bernard --locale tr stats)BERNARD_LOCALEenvironment variablelocalein.bernard/config.json(set atinit, committed β team-wide)en(default)
Retrieval is locale-aware too: Turkish gets a conservative stemmer (so tokenlarΔ± matches token); English uses lowercase tokenization (BM25 + synonyms carry the recall).
Configuration
.bernard/config.json:
{
"project": "my-app",
"locale": "en",
"retrieval": {
"max_results": 8,
"recency_weight": 0.3,
"half_life_days": 30,
"type_weights": {
"decision": 1.2,
"architecture": 1.2,
"bug": 1.0,
"preference": 1.0,
"note": 0.9
}
}
}recency_weightβ how much newer records are boosted.half_life_daysβ how fast the recency boost decays.type_weightsβ per-type ranking multipliers.
Roadmap
Optional local embeddings (Ollama) β keep BM25 as the keyless default; add a fully local, keyless semantic layer for those who want it.
Automatic extraction from commits/diffs β propose decision/architecture records straight from git history.
Team conflict resolution β a shared flow for the conflicts
distillfinds: flag, discuss, invalidate.distillautomation β periodic distillation and warnings via pre-commit hook or CI.
Contributing
Contributions are welcome β see CONTRIBUTING.md. The project is intentionally dependency-light (only the MCP SDK + zod for the server) and ships a zero-dependency smoke test:
npm install
npm testPlease also read the Code of Conduct and the Security Policy.
License
MIT Β© Yener YiΔit Γelik
Not generated β crafted.
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
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/yeneryigitcelik-debug/bernard'
If you have feedback or need assistance with the MCP directory API, please join our Discord server