memory-service
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., "@memory-servicesearch my memory for the decision on Postgres search"
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.
memory-service
Self-hosted, versioned project memory for Claude Cowork (or any MCP client). One place for Claude to read and write structured notes across projects — searchable, versioned, editable by a human, and independent of any single chat session.
Why this exists
Cowork's built-in memory is scoped to a session and doesn't reliably survive the split between local-desktop sessions and cloud-run scheduled tasks — a fact learned the hard way, not a theoretical concern. If a cloud task researches something today, a local session tomorrow has no way to know. There's no version history, no way to search across everything Claude has learned, and no way for a human to look at what got stored without asking Claude to recite it.
memory-service is the fix: an explicit, external store. Every Cowork session — local, cloud, scheduled, interactive — talks to the same MCP server. Writes land in Postgres and a git repository in the same operation, so every change is both instantly queryable and permanently versioned. A human can browse, search, and edit the same data through a plain web UI, no prompt required.
Prior art
The individual ingredients here aren't new. WUPHF
pairs a git-backed markdown wiki with a SQLite/BM25 index for search — the closest existing
project to "git holds the real history, a database holds a fast index into it," just without
MCP, pgvector, or a shared write path. Mem0's
self-hosted stack is the closest on the other axis — Postgres+pgvector, an MCP server, a web
dashboard — but versions nothing via git; history is a database table.
DiffMem goes further than either: git as the
only store, no database index at all, live git log/grep shell-outs at query time. And the
"memory bank" convention used by several AI coding tools (Cline, Roo Code) — markdown files an
agent is prompted to maintain inside the project repo — solves a related but different problem
with no dedicated service, index, or MCP contract at all.
What doesn't seem to exist elsewhere is the specific combination: MCP tools and a plain human-editable web UI sharing one write path (so they can't drift apart), backed by a real git-commit-per-write history and a properly indexed hybrid (full-text + vector) Postgres search — rather than either git alone, a database alone, or a UI that's read-only.
Related MCP server: mem0-lite
Architecture
One FastAPI process serves three things from the same codebase, sharing one database connection pool and one write path:
┌─────────────────────────────────────────────────────┐
│ FastAPI process │
│ │
│ web UI (Jinja2+htmx) MCP server (/mcp) │
│ │ │ │
│ └──────────┬───────────────┘ │
│ ▼ │
│ app/services/*.py │
│ (the ONLY code that touches DB or git) │
│ │ │
│ ┌───────────┴───────────┐ │
│ ▼ ▼ │
│ Postgres git repo │
│ (query + search) (version history) │
└─────────────────────────────────────────────────────┘Single write path. Every mutation — whether it comes from an MCP tool call or a web form
submission — goes through app/services/*.py and nowhere else. Neither the MCP layer
(app/mcp/tools.py) nor the web routes (app/web/routes.py) touch the database or the git
repo directly; they're both thin wrappers around the same service functions. That means the two
surfaces can never drift apart in behavior, and there's exactly one place to look for how a
write actually happens (app/services/entries.py's upsert_entry/update_entry).
Hybrid search, not just one or the other. Every entry gets both a Postgres tsvector
(full-text, GIN-indexed) and a pgvector embedding (intfloat/multilingual-e5-small,
HNSW-indexed), computed locally on CPU — no embeddings API call, no data leaving the box. A
search merges both rankings with Reciprocal Rank Fusion, so an exact keyword match and a
semantically related note that doesn't share any words both surface. See
app/services/search.py.
Git is the real history; Postgres is the fast index into it. Every write does: DB
transaction → render+commit a markdown file (one .md per entry, YAML frontmatter) → insert an
entry_versions row recording the resulting commit hash → commit the transaction. If the git
commit fails, the DB transaction rolls back — the two are never allowed to disagree about what
the latest version is. entry_versions exists purely so "show me the last 5 changes" is an
indexed SQL query instead of a git log shell-out; the commit hash it stores is how you get
back to the actual git object if you need the full diff. See app/services/git_store.py.
Everything nests, nothing is hardcoded. Projects contain subtopics, subtopics can nest
arbitrarily deep (kunde-mueller/vorgang-2026-08/...), and subtopic paths auto-create on first
write — an agent doesn't need a separate "create subtopic" call before it can file a note under
one. Projects don't auto-create (a project carries a sensitivity_level that has real
access-control implications later, so creating one is a deliberate action — either a human in
the web UI, or the one memory_create_project MCP tool).
MCP tools
Tool | Purpose |
| Full-text + semantic search, optionally scoped to a project/subtopic and/or filtered by tags (OR across multiple tags; can browse by tag alone with an empty query). Every result includes its exact |
| Current entries for a project or subtopic (call this before answering). Every result includes its exact |
| Create or update an entry, identified by (subtopic, title) -- pass the |
| Entries flagged as needing follow-up |
| Version history for one entry |
| Permanently delete one entry (DB row + git file, one commit) |
| Batch dedup check for daily sync tasks (has this mail/message already been logged?) |
| Create a new project — the only structural MCP tool; rename/delete are web-UI-only, human-confirmed actions |
| Record a direct, typed link between two entries ( |
| Remove a specific link between two entries (idempotent) |
| Every entry directly linked to this one, in either direction |
| Scan for likely-duplicate entries via embedding similarity (optionally scoped to one project); reports candidate pairs by default, only links (never merges) with |
Relations are direct links only — no transitive graph traversal, no automatic entity
extraction. They live in Postgres only, not mirrored into git frontmatter like tags/sources
are, since a link to another entry would go stale if that entry is later renamed or deleted;
the relation row's own created_at/created_by is audit trail enough. See
app/services/relations.py.
Quickstart
docker compose up --buildPostgres: localhost:5433 (user/db
memory)
# migrate, then seed 5 example projects with nested subtopics and sample entries
docker compose exec app alembic upgrade head
docker compose exec app python -m scripts.seed_dummy_dataRun tests (spins up its own memory_test database on the same Postgres):
docker compose up -d db
DATABASE_URL=postgresql+asyncpg://memory:memory_dev_password@localhost:5433/memory_test \
python -m pytestRepo layout
Path | What's there |
| SQLAlchemy models: |
| All business logic — |
| The 8 MCP tools, each a thin wrapper over |
| Jinja2 + htmx server-rendered UI — no SPA build step, no CDN dependencies (EasyMDE and htmx are vendored) |
| Schema migrations |
| pytest suite — service-layer, MCP-layer (via |
| Real engineering gotchas hit and fixed while building this — async SQLAlchemy footguns, an MCP-client redirect bug, a Traefik routing collision. Worth a read if you're extending this. |
Status
Built and running in production for one real deployment (Postgres + git-backed history + web
UI + MCP server, behind Authelia OIDC via Traefik). Not yet hardened for multi-tenant use —
row-level security by project_id is designed but not yet implemented (currently
application-layer filtering only); see tasks/lessons.md and the design doc referenced in
tasks/todo.md for what's done versus planned.
This server cannot be deployed
Maintenance
Related MCP Connectors
Shared, governed long-term memory for AI agents across tools and sessions via MCP and REST.
- memnodeOAuthdev.memnode
Persistent, inspectable memory for AI agents with lineage, correction, and a hosted MCP endpoint.
Private-by-default, local-first memory/context/task orchestrator for MCP apps and agents.
Hosted MCP memory: save sessions/decisions once, search from Claude, Cursor, ChatGPT. EU-hosted FTS.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceA database-backed MCP server that acts as a project memory bank, enabling AI assistants to store, retrieve, and search structured context like decisions, tasks, and architecture using SQLite and vector embeddings.Apache 2.0
- AlicenseAqualityBmaintenanceProvides self-hosted memory management for coding agents via MCP, with local vector storage and tools for adding, searching, updating, and deleting memories.81MIT
- AlicenseAqualityBmaintenanceMCP server for local-first project memory, enabling AI agents to log, search, and retrieve structured project history, summaries, and per-file dossiers with client-specific configuration and diagnostics.151MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to maintain and query project memory independently of the underlying model, with versioned, auditable storage and multi-stage retrieval through a single MCP gateway.MIT