Skip to main content
Glama
ady133t

Cloudflare Memory MCP

by ady133t
README.md
# Cloudflare Memory MCP

A per-project memory MCP server for opencode, hosted entirely on Cloudflare's **free tier**:

- **Notes** — plain keyed memories with tags and types
- **Vector** — semantic recall via [Vectorize](https://developers.cloudflare.com/vectorize/) (embeddings from Workers AI `@cf/baai/bge-m3`, 1024-dim, multilingual)
- **Graph** — entities + relations in [D1](https://developers.cloudflare.com/d1/) (SQLite) with recursive traversal
- **Web dashboard** — open `/` in a browser to see every project with memories. Each project card opens a detail view with an interactive force-directed graph of its entities and relations (wheel = zoom, drag = pan, double-click or buttons = reset, hover = highlight neighbors) and a **Delete project** button that removes the project, its notes, graph, FTS rows, caches, and vector embeddings

```
opencode ── MCP (streamable HTTP + Bearer auth) ──> Worker
                                                     ├─ Vectorize   memory-vectors (namespace per project)
                                                     ├─ D1          projects / notes / nodes / edges / FTS5
                                                     ├─ Workers AI  bge-m3 embeddings
                                                     └─ KV          recent-memories cache
```

## Screenshots

### Project List

The dashboard lists every project known to the account, including memory, entity,
relation, and last-active counts.

<p align="center">
  <img src="docs/screenshots/project-list.png" alt="Cloudflare Memory MCP project list" width="100%" />
</p>

### Knowledge Graph

Open a project to inspect its memories and graph. The graph supports curved,
directional edges, hover highlighting, wheel zoom, drag panning, and reset controls.

<p align="center">
  <img src="docs/screenshots/knowledge-graph.png" alt="Cloudflare Memory MCP knowledge graph" width="100%" />
</p>

## How folder isolation works

The server runs its MCP sessions inside a **Durable Object** (free tier, SQLite
backend), which pins every message for a session to one isolate. Project identity
is resolved in this order:

1. **`project_root` tool argument** (recommended) — you pass your working folder
   to any tool, e.g. `remember({ project_root: "D:/Projects/foo", content: ... })`.
   The server hashes a normalized form of the path into a stable project id and
   **remembers it for the rest of the session**, so later calls can omit it. The
   `identify_project` tool exists for explicitly locking it in.
2. **`project_id` tool argument** — an explicit id (also cached for the session).
3. **`x-project-root` / `x-project-id` headers** — for clients that configure them.
4. **MCP roots** — automatic folder detection when the client supports it.

Because the identity is just a hash of the normalized path, the same folder opened
from a different absolute path still resolves to the same project. Every read and
write is scoped to that id — Vectorize queries use a namespace-per-project and
every D1 query filters on `project_id`, so two folders can never see each other's
memory.

### Using it from opencode

opencode knows its working directory (your `bash` cwd). The agent should pass it
once per session. This repo ships a ready-to-copy template at **`AGENTS.md`** —
copy it into any project you want memory in, set `PROJECT_ROOT` to that project's
directory, and the agent will handle identity automatically:

```
When using the memory MCP tools, pass project_root (your bash cwd) on the first
memory call of the session.
```

## Setup

Prereqs: Node 18+, a Cloudflare account, and `npx wrangler login` done once.

```sh
npm install
npm run setup        # creates D1 db, KV namespace, Vectorize index; patches wrangler.toml
npm run db:migrate:remote
npx wrangler secret put MEMORY_AUTH_TOKEN   # optional but strongly recommended
npm run deploy
```

Your endpoints:
- MCP: `https://cloudflare-memory-mcp.<your-subdomain>.workers.dev/mcp`
- Dashboard: `https://cloudflare-memory-mcp.<your-subdomain>.workers.dev/`

Local development:

```sh
npm run dev              # wrangler dev --local  (AI + Vectorize are remote-only)
npm run db:migrate:local
```

## Wiring into opencode

One entry — the same config works in every project. No per-project values needed:

```jsonc
// ~/.config/opencode/opencode.jsonc
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "memory": {
      "type": "remote",
      "url": "https://cloudflare-memory-mcp.<your-subdomain>.workers.dev/mcp",
      "oauth": false,
      "headers": {
        "Authorization": "Bearer {env:MEMORY_AUTH_TOKEN}"
      }
    }
  }
}
```

Project identity is then driven by the agent itself: it passes its working folder
as `project_root` on the first memory call of each session (see the AGENTS.md tip
above), and the server scopes everything to that folder from then on.

The dashboard (`/`) prompts for the same `MEMORY_AUTH_TOKEN` once and stores it in
localStorage. If you leave `MEMORY_AUTH_TOKEN` unset, everything is public — only
do that for personal/throwaway experiments.

## MCP tools

| Tool | Purpose |
|---|---|
| `identify_project` | Lock the project identity for the session from a `project_root` / `project_id` (returns the resolved id) |
| `remember` | Save a note (embeds it for semantic recall); optionally upserts `entities` + `relations` into the graph |
| `query_memory` | Semantic/vector search — recall by meaning. Note: Vectorize mutations are async, so a note is queryable semantically a few seconds after `remember`; use `search_memory` for instant recall |
| `search_memory` | Exact keyword search (D1 FTS5) |
| `get_recent_memories` | Latest notes for the project |
| `get_project_context` | Bundle stats + recent memories + top entities + recent relations (call explicitly) |
| `add_entity` / `add_relation` | Build the knowledge graph deliberately |
| `query_graph` | Neighbors of an entity (depth 1–3) or most-connected entities |
| `forget` | Delete a note + its embedding |

All tools accept an optional `project_root` (folder path or `file://` URI) and
`project_id`; once either is supplied in a session it is remembered.

## Free-tier budget

| Resource | Free allowance | Notes |
|---|---|---|
| Durable Object | 100k requests/day, ~28h/day active compute | Hosts MCP sessions; ~1 request per MCP message |
| Vectorize | **5M stored dims** account-wide | ~4,800 embedded notes @1024-dim; prune with `forget`. Queries ~29k/mo |
| Workers AI | 10k neurons/day | bge-m3 embedding of a 1k-token note ≈ 1 neuron → thousands/day |
| D1 | 5 GB / 500 MB per DB | notes + graph + FTS are effectively unlimited for personal use |
| KV / Worker | 1 GB / 100k req/day | caches + serving; a few requests per opencode session |

Design levers if you ever approach the vector ceiling: embed only durable memories
(`remember` with `type: "decision" | "fact"`) and let `search_memory` (FTS5, uncapped)
hold the long tail; or recreate the index with `bge-small` (384-dim, English-only)
for ~3× capacity.

## Project layout

```
src/index.ts      fetch router: dashboard, /api/*, /mcp -> Durable Object
src/session-do.ts MCP session host (Durable Object): sessions, SSE, roots, identity
src/tools.ts      the 10 tool definitions (zod schemas + implementations)
src/store.ts      D1 / Vectorize / KV data layer
src/identity.ts   project-id derivation from folder roots
src/embed.ts      Workers AI bge-m3 embeddings
src/api.ts        dashboard API (projects, detail, graph, delete)
src/web.ts        dashboard page (token-gated)
migrations/       D1 schema
scripts/setup.mjs  creates Cloudflare resources and patches wrangler.toml
AGENTS.md          template to copy into projects that use this memory server
```