Skip to main content
Glama
README.md
# memory-mcp

A persistent, conflict-aware memory MCP server for AI coding assistants (Cursor, Claude Code).

Every AI coding session starts from zero. This MCP server gives the AI a queryable memory store that survives across sessions - and unlike append-only tools, it detects when a new fact contradicts an older one and automatically invalidates the stale memory instead of letting both exist.

## How it works

When you store a memory like "we switched from Postgres to SQLite":

1. The content is embedded (OpenAI `text-embedding-3-small`)
2. Vector search finds similar active memories
3. Claude Haiku checks each candidate - does this contradict the new fact?
4. Confirmed conflicts get invalidated (`valid_to = now`) - never deleted
5. The new memory is inserted as currently true
6. A supersession link is recorded so the history is traceable

Old facts are never deleted. You can query what was true at any point in time.

## Tools

| Tool | Description |
|---|---|
| `store_memory` | Save a fact about the project. Detects and invalidates conflicts automatically. |
| `retrieve_context` | Hybrid vector + keyword search over currently-true memories. |
| `query_history` | Point-in-time query - what was true at a given timestamp? |
| `list_conflicts_resolved` | Audit log of every invalidation and why. |

## Stack

- TypeScript + Node.js 18
- PostgreSQL 16 + pgvector (HNSW index, cosine similarity)
- OpenAI `text-embedding-3-small` (1536 dims)
- Anthropic Claude Haiku (conflict detection)
- MCP SDK (stdio transport)
- Zod (input validation)

## Setup

```bash
# 1. Start Postgres
docker compose up -d

# 2. Install dependencies
npm install

# 3. Copy and fill in API keys
cp .env.example .env
# edit .env - add OPENAI_API_KEY and ANTHROPIC_API_KEY

# 4. Run migrations
npm run migrate

# 5. Start the server
npm run dev
```

## Register in Cursor

Add to `.cursor/mcp.json`:

```json
{
  "mcpServers": {
    "developer-memory": {
      "command": "node",
      "args": ["/absolute/path/to/my-memory-mcp/dist/index.js"]
    }
  }
}
```

Build first: `npm run build`

## Key design decisions

**Invalidate, never delete** - setting `valid_to` instead of deleting preserves history. Point-in-time queries (`query_history`) would be impossible otherwise.

**Conflict similarity threshold: 0.45** - the spec suggested 0.75, but measured against labeled contradiction pairs on `text-embedding-3-small`, true conflicts score 0.50 to 0.82 cosine similarity. At 0.75 the candidate filter silently drops most real conflicts before the LLM sees them. 0.45 catches them all.

**RRF over score averaging** - cosine similarity and `ts_rank` live on incomparable scales. Reciprocal Rank Fusion uses only rank positions, so the two search legs fuse cleanly without normalization guesswork.

**LLM default to no-conflict** - if Claude returns an unparseable response, the code treats it as no conflict. A false negative leaves a stale memory (recoverable). A false positive deletes a true fact (worse).

## Project structure

```
src/
├── index.ts       entry point
├── config.ts      env → typed config
├── types.ts       shared interfaces
├── db.ts          MemoryStore + migrations + vector/keyword search
├── embeddings.ts  OpenAI embeddings client
├── memory.ts      conflict detection + invalidation + ingestion pipeline
├── retrieval.ts   RRF hybrid retrieval
└── server.ts      MCP server + 4 tool handlers
```