Skip to main content
Glama

my-memory

A self-hosted memory for AI agents. Your notes are a folder of Markdown files in git; my-memory serves them over MCP so any agent (Claude Code, Codex, claude.ai, your own bots) can search, read and write them. Every write is a git commit attributed to the client that made it.

Leer en español

Keep this file and README.es.md in sync: when you edit one, update the other.

  • Git is the source of truth. Search runs on a SQLite index (catalog, FTS5 full text by section, wikilinks) that can be rebuilt from git at any time.

  • One commit per write, authored by the client: Claude Code (laptop), Alex (Claude)…. The commit message is the reason the agent gave, plus Client: / Tool: / Session: trailers.

  • Safe for several agents at once. One writer queue, optimistic concurrency (hash from read), and pushes from elsewhere (your PC, Forgejo's web editor) integrated by rebase.

  • Your conventions, not ours. The notes repo declares its folders, required frontmatter and filename rules in memory.toml; its AGENTS.md is sent to every client as MCP instructions, with a short index.

  • Two front doors. A private listener for static per-client tokens, file uploads and the git webhook. A public listener that only accepts OAuth 2.1 tokens (dynamic client registration and PKCE, sign-in delegated to Forgejo) for remote clients like claude.ai.

  • Attachments. Binaries go to git LFS with a companion note holding their text (PDFs via pdftotext; scanned PDFs and images via local OCR with tesseract, when installed), so they are searchable too.

  • Typed links. works_at:: [[acme]] says what a link means, and links can filter by it.

  • Optional search by meaning. Point [embeddings] at any OpenAI-compatible embeddings endpoint (Ollama or llama.cpp on the same CPU, TEI, OpenAI…) and search fuses full text with vector similarity, so "car" finds the note that says "vehicle". No model runs inside the server and no GPU is needed; without it, search is full text only.

How it works inside (index, write flow, concurrency, auth): docs/design.md.

Once the server is connected to an agent (see Connecting clients), send it this, and nothing else:

https://raw.githubusercontent.com/PabloPC05/my-memory/main/docs/agent-setup.md

The agent reads the setup guide and installs your memory on its own host: it finds the memory among its tools, proposes importing what it already knows about you, installs standing instructions and, where the host allows it (Claude Code, your own agents), blocks it from saving anywhere else. It asks for one approval before changing anything. Inspired by Notion's agent setup page.

On Claude Code it can also install, if you want, a hook that brings relevant notes to the agent without it having to think of searching (as Supermemory does): before each message, inject-memory.mjs searches your memory for it and adds the best few matches to the turn, framed as information to check, not instructions, within a small budget. It needs MY_MEMORY_URL and a read-only MY_MEMORY_TOKEN, and adds nothing on any error or timeout.

Related MCP server: Cortex

Tools

Tool

What it does

list

Short index, or every note in a folder with description, aliases and last change

search

Full-text search by section (accents ignored, prefixes match) with snippets; hybrid with search by meaning when [embeddings] is set

read

A note or one section, with its hash; revision reads an old version

links

Outgoing [[links]] (broken ones included) and backlinks with context, optionally only one relation

history

Commits that touched a note: who, when, why

recent

Latest changes to the whole memory: per commit when, who (author and client), tool, reason and the notes it touched; filters since (2026-05-01 or 7d), folder, client

lint

Maintenance worklist, worst first: bad frontmatter and superseded notes outside the archive, broken links, look-alike duplicates, old inferences, missing pages, unlinked mentions, orphans, stale and oversized notes. Counts per kind; filter by kinds, folder, limit

clients

Who is connected: every token and OAuth app with last seen (time and tool), writes in the last 7 and 30 days, and the memory-setup version it left in the notes. Never returns secrets

create

New note; unique names across folders; refuses look-alike duplicates unless confirm_new

edit

Exact text replacements, all or nothing

write

Rewrite a whole note; needs the hash you read

move

Rename or move; rewrites every [[link]] to it in the same commit

delete

Remove a note (it stays in history); refused while other notes link to it

revert

Undo a commit from recent/history with a new commit, or only what it did to one note; keeps later changes that don't overlap, refuses with REVERT_CONFLICT otherwise

attach

Store a file in LFS with a searchable companion note

Clients with a read-only token only see the read tools: list, search, read, links, history, recent, lint and clients.

lint is the cheap half of keeping the memory tidy (Karpathy's "LLM wiki": ingest, answer, lint). It is computed from the index and git dates in milliseconds, without a model, so the agent gets candidates instead of reading every note; judging them (is this a contradiction, does that inference still hold, merge or link?) is the agent's half. See docs/design.md.

The server also offers the agent setup guide to every connected client, offline (it ships with the package): as the MCP prompt setup ("Set up this memory on your host") and as the resource memory://agent-setup, both with the Markdown of docs/agent-setup.md. The server instructions mention them, so an agent that finds itself on an unconfigured host can follow them without being given the URL.

Notes format

---
name: Universidad
description: Carrera, asignaturas de este curso y cómo las lleva
aliases: [uni, carrera]
---
# Universidad

Text, with links like [[otra-nota]], [[otra-nota#Sección|texto]] or ![[documento.pdf]].

File names (without .md) must be unique across the repo, so [[name]] always resolves; this is what Obsidian does with "shortest path" links.

A link can also say what it means, with Dataview-style inline fields: works_at:: [[acme]] on its own line, [lives_with:: [[sam]]] inside a sentence, or works_at: "[[acme]]" in the frontmatter. links returns the relation and can filter by it, so "who works at Acme" is one call.

memory.toml at the root of the notes repo (every key is optional):

[folders]
allowed    = ["me", "people", "topics", "projects", "archive"]
archive    = "archive"      # excluded from list/search unless asked
index_full = ["me"]         # listed note by note in the short index
root_files = ["AGENTS.md", "README.md"]

[notes]
required_fields  = ["name", "description"]
filename_pattern = "^[a-z0-9]+(-[a-z0-9]+)*\\.md$"
superseded_field = "superseded_by"
max_description  = 200

[instructions]
file = "AGENTS.md"

[attachments]
page_heading = "Page {n}"
ocr_notice   = "Text recognised with OCR ({languages}); it may contain errors."

[relations]
allowed = ["works_at", "lives_with"]  # warn on other keys; without it, keys in use are suggested

[lint]
exclude            = []            # folders left out of every check (root files and the archive always are)
orphan_exclude     = ["me"]        # never reported as orphans; defaults to folders.index_full
stale_days         = 180           # unchanged for longer → stale
inference_days     = 90            # "(deducido, 2026-01-31)" older than this → old_inference
inferred_markers   = ["deducido"]  # provenance words that mark an inference
max_bytes          = 16384         # bigger → oversized (attachment notes are exempt)
missing_page_min   = 2             # a missing target linked from this many notes → missing_page
mention_min_length = 4             # shortest name or alias looked for as an unlinked mention

Running it

Requirements: Node.js 24, git, git-lfs, and optionally poppler-utils (pdftotext for PDF text, pdftoppm for OCR of scans) and tesseract with the language data you want (for OCR of scanned PDFs and images). Missing tools are reported at startup (ocr.tools in the log) and only make that extraction unavailable.

npm ci && npm run build
my-memory token add laptop --name "Claude Code (laptop)" --scope write --file tokens.toml
my-memory serve --config config.toml
my-memory clients --config config.toml     # who is connected: last seen, writes, setup version (--json for scripts)
my-memory lint --config config.toml        # maintenance worklist (--json, --kind stale,orphan, --folder, --limit)

Last-seen times are kept in activity.sqlite in the data dir (written at most once a minute per client; it is not in the notes repo) and the write counts come from the Client: trailers of the git history.

config.toml:

data_dir = "/var/lib/my-memory"
public_url = "https://memory.example.com"      # only with the public listener
tokens_file = "tokens.toml"
webhook_secret_file = "webhook-secret"          # paths, or credential:NAME for systemd credentials

[remote]                                        # omit to keep the notes repo local
url = "http://127.0.0.1:3000/me/notes.git"
username = "my-memory"
token_file = "forgejo-token"

[listen.private]
address = "127.0.0.1"
port = 8782

[listen.public]                                 # behind your TLS reverse proxy
address = "127.0.0.1"
port = 8781

[oauth]                                         # required by the public listener
forgejo_url = "https://git.example.com"         # as your browser reaches it
forgejo_internal_url = "http://127.0.0.1:3000"  # as the server reaches it
client_id_file = "oauth-client-id"              # an OAuth2 app registered in Forgejo,
client_secret_file = "oauth-client-secret"      # redirect URI <public_url>/oauth/callback
allowed_users = ["me"]

[ocr]                                           # every key is optional
enabled = true
languages = "spa+eng"                           # tesseract language codes; default "eng"
max_pages = 50                                  # pages of a scanned PDF that are OCR'd
page_timeout_seconds = 120                      # per page (render + recognition) or image
# dpi = 300; tesseract = "tesseract"; pdftoppm = "pdftoppm"

[embeddings]                                    # optional: search by meaning; omit for full text only
url = "http://127.0.0.1:11434/v1/embeddings"    # any OpenAI-compatible endpoint (Ollama, llama.cpp, TEI, OpenAI…)
model = "nomic-embed-text"
# api_key_file = "embeddings-key"               # sent as a Bearer token, if the endpoint needs one
# dimensions = 512                              # for models that can shorten their output
# batch_size = 32
# timeout_seconds = 60
query_prefix = "search_query: "                 # some models want their inputs marked (nomic, e5)
document_prefix = "search_document: "

Uploads for attach: curl -T file.pdf -H "Authorization: Bearer $TOKEN" http://host:8782/uploads/file.pdf returns an upload_id. There is no size limit.

NixOS

{
  inputs.my-memory.url = "github:<owner>/my-memory";
  # in your configuration:
  imports = [ inputs.my-memory.nixosModules.default ];
  services.my-memory = {
    enable = true;
    remote = { url = "http://127.0.0.1:3000/me/notes.git"; tokenFile = "/run/secrets/forgejo-token"; };
    listen.private = { address = "127.0.0.1"; port = 8782; };
    tokensFile = "/var/lib/secrets/my-memory/tokens.toml";   # root:my-memory 0640
    # ocr = { enable = true; languages = [ "spa" "eng" ]; maxPages = 50; pageTimeoutSeconds = 120; dpi = 300; };
  };
}

After my-memory token add … --file <tokensFile>, run systemctl reload my-memory. The package brings pdftotext, pdftoppm and tesseract; the module installs the language data listed in ocr.languages (default Spanish and English), and builds that tesseract only when ocr.enable is on.

For search by meaning, add embeddings = { url = "http://127.0.0.1:11434/v1/embeddings"; model = "nomic-embed-text"; queryPrefix = "search_query: "; documentPrefix = "search_document: "; }; (with apiKeyFile if the endpoint needs a key), for example next to services.ollama.

Search by meaning

With [embeddings], every section of every note gets a vector from the endpoint, stored in index.sqlite. search then takes mode: hybrid (default) fuses the full-text ranking and the vector ranking with Reciprocal Rank Fusion, text is the plain full-text search, semantic uses vectors only. The answer has the same shape as always.

  • Writes never wait for it. Sections are embedded in the background after each commit or sync; until then they are found by text. Only sections whose text changed are embedded again, and changing model, dimensions or document_prefix embeds everything again.

  • If the endpoint is down, writes work as usual, search falls back to full text with a warning in the log, and pending sections are retried with backoff.

  • Status in the log: embeddings.status at startup (model, vectors, sections pending), embeddings.done after each run, embeddings.failed while the endpoint fails.

  • my-memory reindex also embeds the sections that are missing; --reembed embeds them all again. With a credential: key it cannot read outside systemd, it skips embedding and the service does it when it starts.

  • On a small CPU-only server, Ollama with nomic-embed-text (multilingual: bge-m3 or multilingual-e5-small, the e5 ones with prefixes query: and passage: ) embeds a few thousand sections in minutes, once.

Connecting clients

# Claude Code
claude mcp add --transport http memory http://host:8782/mcp --header "Authorization: Bearer $MY_MEMORY_TOKEN"
# Codex (~/.codex/config.toml)
[mcp_servers.memory]
url = "http://host:8782/mcp"
bearer_token_env_var = "MY_MEMORY_TOKEN"

claude.ai: Settings → Connectors → Add custom connector → https://memory.example.com/mcp.

Then give the agent the setup link (Give your agent one link).

One credential per agent

my-memory attributes every write (the git commit author and its Client: trailer), every log line and the clients view to the credential the request used. So each agent must connect with its own credential. If two agents share one, nobody can tell who wrote what, clients cannot tell them apart, and a leaked key cannot be revoked without cutting off the other agent too.

  • Static tokens: create one per agent and never share it: my-memory token add <id> --name "<Agent name>" --scope write|read --file tokens.toml. OAuth clients (for example claude.ai connecting directly) get their own identity automatically.

  • Behind a gateway or proxy that joins several MCP servers into one endpoint, the gateway is the client my-memory sees. Give the gateway one key per agent and map each key to its own my-memory token (same public URL, a different key per agent); otherwise every write shows the gateway's name. This pattern has been done with Caddy plus agentgateway, but any gateway that can pick the upstream credential from the incoming key works.

  • Check it: run my-memory clients or call the clients tool. Each agent must appear on its own row, with its last activity and its writes.

my-memory migrate <clone> shows what would change: relative Markdown links become [[wikilinks]], name collisions are renamed, and frontmatter that is not valid YAML gets quoted. Add --apply to commit it. Only those spans change.

Development

npm ci
npm test          # vitest: unit, integration (real git repos) and HTTP/OAuth end to end
npm run typecheck
nix flake check   # builds the package and runs the NixOS VM test

License

Apache-2.0.

Maintenance

ActivityMaintained
ResponsivenessNo recent community issues

Related MCP Connectors

Related MCP Servers

  • F
    license
    C
    quality
    D
    maintenance
    Git-backed MCP server for creating and maintaining an Obsidian-style markdown knowledge base with full CRUD, search, and git sync.
    7
    -
  • A
    license
    Not graded
    quality
    B
    maintenance
    A dynamic, governed memory layer for Markdown notes that serves knowledge to AI clients and humans through a secure MCP server, with scoped access, git-audited changes, and optional LLM-powered semantic search.
    Apache 2.0
  • A
    license
    A
    quality
    C
    maintenance
    MCP server for managing a local, domain-agnostic knowledge base using Markdown notes with frontmatter. Enables AI agents to capture, read, search, link, and maintain notes with atomic writes and privacy controls.
    13
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    An MCP server that gives AI agents read and write access to a folder of Markdown notes, supporting full-text search, reading, listing, and writing notes. It is deliberately small, with no dependencies beyond the MCP SDK, and includes proper path traversal protection.
    MIT