Skip to main content
Glama

What is Engram?

Engram is a personal knowledge base that stores your notes, conversations, documents, and web pages as vector embeddings — then lets any MCP-compatible AI assistant (Claude, Cursor, Windsurf, etc.) search and recall them by meaning, not just keywords. Everything lives in a PostgreSQL database you control, runs locally or in the cloud, and costs near-zero to self-host.

Related MCP server: mcp-recall

Features

  • Hybrid Search — Combines vector similarity (pgvector HNSW) with PostgreSQL full-text BM25, fused via Reciprocal Rank Fusion

  • MCP Server — Any MCP-compatible client can store, search, and manage memories over HTTP

  • REST API — Full CRUD + search, with OpenAPI docs at /api/docs/

  • Multi-format Ingestion — Ingest PDFs, DOCX, TXT, Markdown, URLs, and Obsidian vaults

  • Auto-Enrichment — Optional LLM-powered tagging, entity extraction, and memory decay

  • React Dashboard — Browse, search, and visualize your memory graph

  • Privacy-First — Runs 100% locally with Ollama; no data leaves your machine

  • Pluggable Embeddings — Ollama (default, free), OpenRouter, or bring your own provider

Architecture

┌──────────────────────────────┐
│  AI Clients                  │
│  (Claude, Cursor, Windsurf)  │
└─────────────┬────────────────┘
              │ MCP / REST
              ▼
┌──────────────────────────────────────┐
│  MCP Server (FastMCP :8080)          │
│  REST API   (Django DRF :8000)       │
│  Dashboard  (React + Vite :5173)     │
└─────────────┬────────────────────────┘
              │
   ┌──────────┴──────────┐
   │  Application Layer   │
   │  Embedder ─→ Ollama  │
   │  Auto-Tagger         │
   │  Entity Extractor    │
   │  Memory Decay        │
   └──────────┬──────────┘
              │
   ┌──────────▼──────────┐
   │  PostgreSQL 16       │
   │  + pgvector (768d)   │
   │  + Full-text search  │
   │  + HNSW index        │
   └──────────────────────┘

Quick Start

Prerequisites: Python 3.12+, PostgreSQL 16 with pgvector, Ollama (or Docker)

The supported install path is source + Docker:

# 1. Clone the repository
git clone https://github.com/jblacketter/engram.git && cd engram

# 2. Start database + Ollama via Docker
docker compose up -d

# 3. Pull the embedding model into the Ollama container
docker compose exec ollama ollama pull nomic-embed-text

# (Alternative: if Ollama already runs natively on :11434, start only the
#  database and pull on the host instead:
#    docker compose up -d db
#    ollama pull nomic-embed-text)

# 4. Install Python dependencies
pip install -e ".[dev]"

# 5. Copy environment config (set DJANGO_SECRET_KEY)
cp .env.example .env

# 6. Run migrations and start the server
python manage.py migrate
python manage.py runserver

Note on PyPI: the engram-semantic wheel ships the Python packages only — it does not include manage.py, the React dashboard, or a console entry point, so it cannot run the full stack on its own. Use it when you need engram's modules as a library; install from source for everything else.

The API is now live at http://localhost:8000/api/ and docs at http://localhost:8000/api/docs/.

Start the MCP server (separate terminal):

python -m mcp_server

Start the frontend (separate terminal):

cd frontend && npm install && npm run dev

Dashboard at http://localhost:5173.

Connecting AI Clients

Claude Desktop

Add to ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "engram": {
      "url": "http://localhost:8080/mcp"
    }
  }
}

Claude Code

claude mcp add --transport http engram http://localhost:8080/mcp

Cursor / Windsurf

Add to your MCP settings:

{
  "mcpServers": {
    "engram": {
      "url": "http://localhost:8080/mcp"
    }
  }
}

See docs/connecting-clients.md for authenticated setups and advanced config.

API Reference

REST Endpoints

Method

Endpoint

Description

GET

/api/health/

Health check

GET

/api/memories/

List memories (paginated)

POST

/api/memories/

Create a memory

GET

/api/memories/<id>/

Get memory by UUID

PATCH

/api/memories/<id>/

Update memory

DELETE

/api/memories/<id>/

Delete memory

POST

/api/search/

Hybrid semantic + keyword search

GET

/api/stats/

Memory statistics

GET

/api/tags/

List all tags

POST

/api/ingest/file/

Ingest a file (PDF, DOCX, TXT, etc.)

POST

/api/ingest/url/

Scrape and ingest a URL

POST

/api/ingest/batch/

Batch ingest files and URLs

Auth: Authorization: Bearer <REST_API_KEY> (disabled when env var is empty).

MCP Tools

Tool

Description

store_memory

Store a new memory with optional tags and importance

get_memory

Retrieve a memory by UUID

update_memory

Update content, tags, or importance

delete_memory

Delete a memory

search_brain

Hybrid search with configurable semantic/keyword weight, filterable by tags and source

find_related

Find semantically similar memories, filterable by tags and source

list_recent_memories

List recent memories, optionally filtered by source and/or tags

store_from_url

Fetch and ingest a URL

ingest_file

Ingest a base64-encoded file

get_stats

System statistics

Scoping memories across domains

A single Engram instance can host memories from multiple domains (e.g. QA-tool output and personal notes) without cross-contamination, using tag-based soft scoping. The convention is documented here so any client can follow it.

The convention

Every write should set two things:

  1. A scoping tag in the tags field — domain:<name>, optionally refined with project:<slug>. Examples:

    • tags=["domain:qa", "project:my-app"]

    • tags=["domain:personal"]

  2. The source field — already part of the schema — to identify the writer (e.g. "aegis", "qaagent", "mcp", "manual"). source is a distinct top-level field, not a tag; the two are complementary.

Reading with scope

The MCP tools and REST search honor these on read. Examples:

# MCP
await search_brain("flaky test", tags=["domain:qa"])
await find_related(memory_id, tags=["domain:qa"])
await list_recent_memories(tags=["domain:qa"])

# REST
POST /api/search/  {"query": "flaky test", "tags": ["domain:qa"]}

Scoped read surfaces (filterable)

The following read surfaces accept tags and/or source filters, so callers that pass them can stay inside one domain:

Surface

tags

source

MCP search_brain

yes

yes

MCP find_related

yes

yes

MCP list_recent_memories

yes

yes

REST POST /api/search/

yes

yes

Per-agent keys (enforced scoping)

Each tool/agent can hold its own key bound to the domains it may touch:

python manage.py agent_keys create claude-code --default-domain engram --allow tagteam
python manage.py agent_keys list
python manage.py agent_keys revoke claude-code

The plaintext key (egk_…) is shown exactly once; only its SHA-256 hash is stored. Use it as a bearer token on REST (Authorization: Bearer egk_…) and MCP. For agent principals the scoping is enforced server-side, identically on both surfaces:

  • Writes with no domain: tag get the key's default domain injected; a domain: tag outside the key's allowed list is rejected (403 / tool error) — never silently rewritten.

  • Scoped reads default to the key's default domain; other allowed domains must be named explicitly; disallowed domains are rejected.

  • Formerly unscoped surfaces are closed for agents: GET /api/memories/, /api/memories/<id>/, /stats/, /tags/, MCP get_memory/get_stats/list_domains are filtered to the key's allowed domains; out-of-scope ids return 404 (no existence oracle); memories with no domain: tag are invisible to agent keys.

Note: the MCP server checks for agent keys at startup — restart it after creating the first key.

Owner surface (intentionally unscoped)

The global REST_API_KEY/MCP_API_KEY and the dashboard session remain the single-user owner surface with full visibility across all domains — including untagged memories. With no agent keys configured, behavior is exactly the pre-scoping single-user system.

Limits of soft scoping

  • The project model is still tag-based: no workspace/tenant column exists (a decision checkpoint tracks whether to harden it — see docs/decision_log.md). Hard multi-tenant isolation would mean separate instances or a schema column.

  • Owner-surface writes are still discipline-based: the owner can create untagged memories (visible only to the owner surface).

Project Structure

engram/
├── api/                # REST API (DRF views, serializers, auth)
├── core/               # Data models, services (memory CRUD, search)
├── embeddings/         # Embedding providers (Ollama, OpenRouter)
├── intelligence/       # Auto-tagger, entity extraction, decay, reports
├── ingestion/          # File, URL, batch, and Obsidian importers
├── mcp_server/         # FastMCP server with tool definitions
├── engram/             # Django project settings and URL config
├── frontend/           # React + TypeScript + Tailwind SPA
├── docker/             # Entrypoint scripts, pgvector init SQL
├── nginx/              # Reverse proxy config (production)
├── docs/               # Extended documentation
├── tests/              # Test suite
├── Dockerfile          # Django production image
├── Dockerfile.mcp      # MCP server image
├── docker-compose.yml  # Dev: PostgreSQL + Ollama
└── pyproject.toml      # Python package config

Configuration

Key environment variables (see .env.example for the full list):

Variable

Default

Description

DJANGO_SECRET_KEY

—

Django secret key

DJANGO_SETTINGS_MODULE

engram.settings.development

Settings module

POSTGRES_DB

engram

Database name

POSTGRES_HOST

localhost

Database host

OLLAMA_BASE_URL

http://localhost:11434

Ollama API URL

OLLAMA_EMBED_MODEL

nomic-embed-text

Embedding model

OLLAMA_CHAT_MODEL

llama3.2:3b

Chat model for enrichment

OPENROUTER_API_KEY

—

Fallback embedding provider

MCP_API_KEY

—

MCP auth token (empty = open)

REST_API_KEY

—

REST auth token (empty = open)

AUTO_ENRICH_ON_CREATE

false

Auto-tag and extract entities

Production Deployment

docker compose -f docker-compose.prod.yml up -d

This starts PostgreSQL, Ollama (with GPU support), Django (gunicorn), the MCP server, and nginx with TLS. See docs/setup-docker.md for full production setup and docs/setup-supabase.md for cloud-hosted PostgreSQL.

Tech Stack

Backend: Django 5.1 &#8226; Django REST Framework &#8226; FastMCP 2.0 &#8226; PostgreSQL 16 + pgvector &#8226; Ollama Frontend: React 18 &#8226; TypeScript &#8226; Vite &#8226; Tailwind CSS &#8226; D3 &#8226; Recharts Infra: Docker &#8226; nginx &#8226; gunicorn &#8226; uvicorn

Documentation

Guide

Description

Connecting Clients

Claude Desktop, Claude Code, Cursor setup

Embedding Providers

Ollama, OpenRouter, OpenAI, Cohere comparison

Docker Setup

Local and production Docker guide

Supabase Setup

Cloud PostgreSQL with Supabase

Schema

Database schema deep dive

Workflows

Daily capture and search patterns

Extending

Adding providers, tools, and importers

Windows LAN Deploy

Deploy on a Windows server for home LAN access

Troubleshooting

Common issues and fixes

Roadmap

Feature roadmap

License

MIT

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    A local MCP server for AI assistants to store and retrieve personal memories on disk, with optional semantic search using embeddings.
    -
  • A
    license
    Not graded
    quality
    C
    maintenance
    A self-hosted MCP memory server that gives AI assistants persistent, semantic memory by storing facts as vector embeddings locally, supporting semantic search and swappable embedding models.
    75 npm
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    A self-hosted MCP memory server with hybrid semantic and keyword search, providing persistent memory for AI coding assistants like Claude Code, Cursor, and Windsurf.
    15 npm
    4
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    A local-first memory MCP server that enables storing, searching, and managing personal memories with hybrid keyword and semantic recall, all on-device.
    20 npm
    MIT