Skip to main content
Glama
ejsinfuego

Local Brain MCP

by ejsinfuego
README.md
Local Brain MCP
===============

Local Brain MCP is a small MCP server that you **run locally** on your machine. It lets AI assistants store and retrieve personal memories on your own disk, and optionally use **semantic (embedding) search** for RAG‑style workflows.

You run the server on your computer (e.g. `python server.py`); Cursor and other MCP clients connect to it at `http://localhost:3000/mcp`. Optionally, you can expose it via something like Cloudflare Tunnel to use the same memory from other devices.

Features
--------

- **Local, transparent storage**
  - SQLite database file on disk (`memory.db` by default).
  - DB location is configured via **environment variables** (see “Database layout”). The `dbUrl` *tool argument* is intentionally ignored so all clients share the same configured DB.
  - Simple schema: `memories` table with `title`, `content`, `tags`, `source`, timestamps, and IDs.

- **MCP tools**
  - `save_memory` — insert a memory row (optionally generating an embedding).
  - `update_memory` — patch `title` / `content` / `tags` / `source` on an existing row; optional embedding refresh when `content` changes.
  - `delete_memory` — remove a row by id (embeddings removed with it).
  - `fetch_memories` — RAG-style retrieval: by default uses semantic vector search over embeddings; falls back to keyword search if embeddings are unavailable.
  - `backfill_all_embeddings` — generate embeddings for older memories that don’t have one yet (after you configure an embedding provider).

- **Embeddings / vector search**
  - The server does not run an embedding model itself. Embeddings are **handled by OpenAI** (or optionally OpenRouter) via their APIs.
  - Optional embeddings are stored in a separate `memory_embeddings` table.
  - **OpenAI** is the default: set `OPENAI_API_KEY` and the server will use OpenAI’s embeddings API. Optionally, set `EMBEDDING_PROVIDER=openrouter` and `OPENROUTER_API_KEY` to use OpenRouter instead.

Quick start
-----------

1) **Install dependencies**

Create and activate a virtual environment, then install Python deps.

```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```

1) **Configure embeddings (optional but recommended)**

Embeddings are generated by **OpenAI’s API** (the server has no built‑in embedding model). Set `OPENAI_API_KEY` and the server will call OpenAI to create and store embeddings for semantic search.

- **Option A: OpenAI**

```bash
export EMBEDDING_PROVIDER=openai        # default, can be omitted
export OPENAI_API_KEY=sk-...
export EMBEDDING_MODEL=text-embedding-3-small  # optional override
```

- **Option B: OpenRouter**

```bash
export EMBEDDING_PROVIDER=openrouter
export OPENROUTER_API_KEY=sk-or-...
# Any OpenRouter embedding model (example shown):
export EMBEDDING_MODEL=openai/text-embedding-3-small

# Optional but recommended for OpenRouter analytics:
export OPENROUTER_SITE_URL="https://your-site-or-localhost"
export OPENROUTER_APP_NAME="Local Brain MCP"
```

1) **Run the MCP server**

From the project root:

```bash
source .venv/bin/activate
python server.py
```

You should see FastMCP start up and log something like:

- `Local Brain MCP` listening on `http://0.0.0.0:3000`
- MCP endpoint: `http://localhost:3000/mcp`

You can then point Cursor (or another MCP client) at `http://localhost:3000/mcp` as a remote MCP server.

Run with Docker Compose
-----------------------

If you prefer running this in Docker, use the included `docker-compose.yml`.

1) **Set env vars (optional, for embeddings)**

Create a `.env` file in the project root (or export env vars in your shell):

```bash
# .env example
EMBEDDING_PROVIDER=openai
OPENAI_API_KEY=sk-...
EMBEDDING_MODEL=text-embedding-3-small
```

2) **Build and start**

```bash
docker compose up -d --build
# or, on systems with the legacy CLI:
docker-compose up -d --build
```

3) **Use the MCP endpoint**

The server is available at:

- `http://localhost:3000/mcp`

4) **Stop the service**

```bash
docker compose down
# or:
docker-compose down
```

Notes:

- SQLite data is persisted in the Docker volume `memory_data`.
- Inside the container, the DB path is set to `MEMORY_DB_URL=/data/memory.db`.

Cursor integration note (local env auto-load)
---------------------------------------------

When you run `server.py` locally, it will also try to load environment overrides from `~/.cursor/mcp.json` (specifically the `mcpServers.local-brain-mcp.env` block), if present. This helps you keep your server configuration in one place.

Database layout
---------------

By default, the SQLite file is `memory.db` in the project root. This can be overridden with:

- Env vars: `dbUrl`, `DB_URL`, or `MEMORY_DB_URL` (first non‑empty wins).

Notes:

- The `dbUrl` **tool parameter is intentionally ignored** for path resolution.
- If the chosen DB path is relative, it is resolved relative to the server’s current working directory.

Tables:

- `memories`
  - `id` (INTEGER, primary key)
  - `created_at` (TEXT, ISO datetime, default `datetime('now')`)
  - `title` (TEXT, nullable)
  - `content` (TEXT, required)
  - `tags` (TEXT, JSON‑encoded list of strings)
  - `source` (TEXT, optional source identifier)

MCP tools
---------

**`save_memory`**

**Purpose**: Insert a new memory row (and optionally its embedding).

Parameters:

- `content` (str, required): main text content of the memory.
- `title` (str, optional): short title.
- `tags` (List[str], optional): arbitrary tags, stored as JSON.
- `source` (str, optional): where the memory came from (e.g. `"cursor"`, `"cli"`, `"web"`).
- `dbUrl` (str, optional): accepted for API compatibility, but ignored for database path resolution.
- `generate_embedding` (bool, optional, default `True`):
  - If `True`, and an embedding API is configured (e.g. `OPENAI_API_KEY`), the server calls OpenAI (or OpenRouter) to generate an embedding and stores it.

Returns:

- A dict with `id`, `title`, `content`, `tags`, `source`.

**`update_memory`**

**Purpose**: Update fields on an existing memory (omit fields you do not want to change).

Parameters:

- `memory_id` (int, required): id of the row to update.
- `title`, `content`, `tags`, `source` (optional): new values; at least one must be provided.
- `generate_embedding` (bool, default `True`): when `content` changes, if `True` recompute the embedding; if `False`, drop the stored embedding so vector search cannot use stale vectors.

Returns: dict with `id`, `title`, `content`, `tags`, `source`.

**`delete_memory`**

**Purpose**: Delete a memory by id.

Parameters:

- `memory_id` (int, required).

Returns: `{ "id": <int>, "deleted": <bool> }` (`deleted` is `False` if no row existed).

**`fetch_memories`**

**Purpose**: Retrieve memories relevant to a query, or the most recent rows when the query is empty.

Parameters:

- `query` (str, optional): text query; if omitted or blank (or whitespace-only), returns the latest memories (recent-first)—a “list recent” behavior, not search.
- `limit` (int, optional, default `5`, max `50`): max number of results.
- `dbUrl` (str, optional): accepted for API compatibility, but ignored for database path resolution.
- `use_vector_search` (bool, optional, default `True`): RAG-style retrieval.
  - `True` (default): embed the query and return memories ranked by semantic similarity; falls back to keyword search if embeddings are unavailable.
  - `False`: keyword-only — SQL `LIKE` on `content` and `title`, ordered by recency.
- `fields` (list of str, optional): if set, each result includes only these keys. Allowed: `id`, `created_at`, `title`, `content`, `tags`, `source`. Omit for all fields. Useful to shrink MCP tool payloads (e.g. exclude `content` when listing or probing).
- `tags_any` (list of str, optional): if set, return only rows containing at least one of the provided tags (case-insensitive).
- `source_prefix` (str, optional): if set, return only rows where `source` starts with this prefix.

Returns:

- A list of dicts; by default each includes `id`, `created_at`, `title`, `content`, `tags`, `source`. With `fields`, only the requested keys are present.

Using this as a RAG memory
--------------------------

Typical pattern for an AI assistant:

1. **On new long‑term information**  
   Call `save_memory` with:
   - `content`: the text snippet you want to remember.
   - `title` / `tags`: short descriptors.
   - `generate_embedding=True` (default) so it’s indexed semantically.

2. **Before answering a user query**  
   Call `fetch_memories` with the user’s question (or a brief summary) as `query`. By default it uses RAG (semantic retrieval); use the top results as context when generating the answer.

3. **Fallback behavior**  
   If embeddings are not configured or not yet stored, vector search cleanly degrades to keyword search, so the tools remain usable.

4. **Hygiene**  
   Use `update_memory` to correct a row without duplicating it; use `delete_memory` to drop obsolete entries.

Embedding backfill (recommended after enabling embeddings)
---------------------------------------------------------

If you set an embedding provider/API key after you already have memories saved, run `backfill_all_embeddings` once to generate embeddings for older rows so semantic search works across your full history.

Development notes
-----------------

- The server is implemented in `server.py` using **FastMCP**.
- All schema creation is performed on connection; deleting `memory.db` will recreate an empty database on next start.
- Embeddings are handled by OpenAI (or OpenRouter); configuration is via environment variables only.