mcp-rag-memory 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +35 -0
- package/README.md +475 -0
- package/dist/db/database.d.ts +22 -0
- package/dist/db/database.d.ts.map +1 -0
- package/dist/db/database.js +144 -0
- package/dist/db/database.js.map +1 -0
- package/dist/env.js +48 -0
- package/dist/index.js +12 -0
- package/dist/mcp/rag-server.d.ts +3 -0
- package/dist/mcp/rag-server.d.ts.map +1 -0
- package/dist/mcp/rag-server.js +275 -0
- package/dist/mcp/rag-server.js.map +1 -0
- package/dist/memory/memory.d.ts +69 -0
- package/dist/memory/memory.d.ts.map +1 -0
- package/dist/memory/memory.js +221 -0
- package/dist/memory/memory.js.map +1 -0
- package/dist/rag/chunker.d.ts +16 -0
- package/dist/rag/chunker.d.ts.map +1 -0
- package/dist/rag/chunker.js +109 -0
- package/dist/rag/chunker.js.map +1 -0
- package/dist/rag/embedder.d.ts +19 -0
- package/dist/rag/embedder.d.ts.map +1 -0
- package/dist/rag/embedder.js +96 -0
- package/dist/rag/embedder.js.map +1 -0
- package/dist/rag/embeddings.d.ts +14 -0
- package/dist/rag/embeddings.d.ts.map +1 -0
- package/dist/rag/embeddings.js +71 -0
- package/dist/rag/embeddings.js.map +1 -0
- package/dist/rag/pipeline.d.ts +49 -0
- package/dist/rag/pipeline.d.ts.map +1 -0
- package/dist/rag/pipeline.js +181 -0
- package/dist/rag/pipeline.js.map +1 -0
- package/dist/rag/vector-search.d.ts +39 -0
- package/dist/rag/vector-search.d.ts.map +1 -0
- package/dist/rag/vector-search.js +164 -0
- package/dist/rag/vector-search.js.map +1 -0
- package/dist/session/transcript.d.ts +75 -0
- package/dist/session/transcript.d.ts.map +1 -0
- package/dist/session/transcript.js +268 -0
- package/dist/session/transcript.js.map +1 -0
- package/package.json +56 -0
package/.env.example
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Copy to `.env` and adjust. Picked up automatically by `npx mcp-rag-memory`,
|
|
2
|
+
# `npm run dev`, and `npm run start` (see src/env.ts). Host environment
|
|
3
|
+
# variables always override these values.
|
|
4
|
+
|
|
5
|
+
# Where the SQLite store lives (default: <project>/.rag-data)
|
|
6
|
+
RAG_DB_DIR=.rag-data
|
|
7
|
+
|
|
8
|
+
# Embedding backend: "local" (zero-dep, offline) | "transformers" (ONNX, higher quality)
|
|
9
|
+
EMBEDDING_PROVIDER=local
|
|
10
|
+
# EMBEDDING_MODEL=Xenova/all-MiniLM-L6-v2
|
|
11
|
+
|
|
12
|
+
# Search mode: "hybrid" | "vector" | "keyword"
|
|
13
|
+
SEARCH_MODE=hybrid
|
|
14
|
+
|
|
15
|
+
# Recency decay half-life for memory recall scores (days)
|
|
16
|
+
RAG_MEMORY_HALF_LIFE_DAYS=14
|
|
17
|
+
|
|
18
|
+
# Ingestion guards
|
|
19
|
+
RAG_MAX_FILE_MB=10
|
|
20
|
+
# RAG_ALLOWED_DIRS=C:\repo\a;C:\repo\b
|
|
21
|
+
|
|
22
|
+
# Session auto-sync / condensing (opencode plugin reads these from its own env)
|
|
23
|
+
RAG_AUTOSYNC=1
|
|
24
|
+
RAG_SESSION_CONDENSE=1
|
|
25
|
+
RAG_SESSION_CONDENSE_MIN_CHARS=120
|
|
26
|
+
RAG_SESSION_CONDENSE_RATIO=0.35
|
|
27
|
+
|
|
28
|
+
# Optional pruning during consolidation
|
|
29
|
+
RAG_PRUNE=0
|
|
30
|
+
RAG_PRUNE_IMPORTANCE=0.2
|
|
31
|
+
RAG_PRUNE_AGE_DAYS=90
|
|
32
|
+
|
|
33
|
+
# opencode transcript sources (override autodetect)
|
|
34
|
+
# OPENCODE_DB=C:\Users\you\.local\share\opencode\opencode.db
|
|
35
|
+
# OPENCODE_DB_LOCAL=C:\Users\you\.local\share\opencode\opencode-local.db
|
package/README.md
ADDED
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
# RAG + Context Management MCP Server
|
|
2
|
+
|
|
3
|
+
<img src="assets/logo.jpg" alt="mcp-rag-memory" />
|
|
4
|
+
|
|
5
|
+
Persistent long-term memory & knowledge retrieval for AI assistants.
|
|
6
|
+
Exposes RAG (vector storage + semantic search) and context management
|
|
7
|
+
(memories) as MCP tools, so opencode can store and recall
|
|
8
|
+
context **across sessions**.
|
|
9
|
+
|
|
10
|
+
> 👤 Identity: **The Coder** — "I'm The Coder, Selamat datang dan Semoga perjalanan mu menyenangkan"
|
|
11
|
+
>
|
|
12
|
+
> 📖 Project documentation site: https://adyoi.github.io/mcp-rag-memory/ (see [`docs/`](./docs/))
|
|
13
|
+
|
|
14
|
+
## What it does
|
|
15
|
+
|
|
16
|
+
- **RAG** — ingest documents (text / files / whole directories), chunk + embed locally, store in SQLite, and semantically retrieve relevant context.
|
|
17
|
+
- **Hybrid search** — FTS5 BM25 keyword hits fused with vector similarity (reciprocal-rank fusion). Tunable `SEARCH_MODE=hybrid|vector|keyword`.
|
|
18
|
+
- **Context Management** — `remember` facts/decisions/preferences, `recall` them later (score-decayed by staleness), rate by importance, consolidate duplicates, filter by type/tag.
|
|
19
|
+
- **Zero external APIs by default** — local hashing-embedder (1024-dim), built-in `node:sqlite`. Works fully offline. Optional `EMBEDDING_PROVIDER=transformers` for a higher-quality ONNX model.
|
|
20
|
+
- **Safe ingestion** — content-hash deduplication, file size guard (`RAG_MAX_FILE_MB`) and an opt-in path allowlist (`RAG_ALLOWED_DIRS`).
|
|
21
|
+
- **MCP server** — runs on stdio, 19 tools.
|
|
22
|
+
|
|
23
|
+
## Quick start
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install
|
|
27
|
+
npm test # 103 checks: unit + MCP round-trip via SDK client
|
|
28
|
+
npm run lint # oxlint
|
|
29
|
+
npm run build # compile to dist/
|
|
30
|
+
npm run cli -- stats # CLI playground
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Run from npm (npx)
|
|
34
|
+
|
|
35
|
+
Install or run directly from the npm package — no `tsx`, no source checkout:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx mcp-rag-memory
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Point your MCP client at `npx mcp-rag-memory` (the published `bin`), or
|
|
42
|
+
at the local dev entry: `node --import tsx src/mcp/rag-server.ts`.
|
|
43
|
+
|
|
44
|
+
## Works with any MCP client
|
|
45
|
+
|
|
46
|
+
This is a **standard MCP server** (stdio transport, official
|
|
47
|
+
`@modelcontextprotocol/sdk`). It speaks the MCP spec, so **any** agent
|
|
48
|
+
that supports MCP can use it — not just opencode. Memory & knowledge
|
|
49
|
+
become **shared across all your tools**: remember once, recall everywhere.
|
|
50
|
+
|
|
51
|
+
### opencode
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"mcp": {
|
|
56
|
+
"rag-memory": {
|
|
57
|
+
"type": "local",
|
|
58
|
+
"command": ["node", "--import", "tsx", "D:/Project/mcp-server/src/mcp/rag-server.ts"],
|
|
59
|
+
"cwd": "D:/Project/mcp-server",
|
|
60
|
+
"enabled": true,
|
|
61
|
+
"environment": { "RAG_DB_DIR": "D:/Project/mcp-server/.rag-data" }
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Claude Desktop
|
|
68
|
+
|
|
69
|
+
`claude_desktop_config.json` (in `%APPDATA%\Claude`):
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"mcpServers": {
|
|
74
|
+
"rag-memory": {
|
|
75
|
+
"command": "node",
|
|
76
|
+
"args": ["--import", "tsx", "D:/Project/mcp-server/src/mcp/rag-server.ts"],
|
|
77
|
+
"env": { "RAG_DB_DIR": "D:/Project/mcp-server/.rag-data" }
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Cursor / Windsurf
|
|
84
|
+
|
|
85
|
+
Settings → MCP → add server, same `command`/`args` pattern as Claude
|
|
86
|
+
Desktop. No `cwd` needed — use absolute paths for the entry file and DB.
|
|
87
|
+
|
|
88
|
+
### VS Code (Copilot)
|
|
89
|
+
|
|
90
|
+
`mcp.json` in `.vscode/`:
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"servers": {
|
|
95
|
+
"rag-memory": {
|
|
96
|
+
"type": "stdio",
|
|
97
|
+
"command": "node",
|
|
98
|
+
"args": ["--import", "tsx", "D:/Project/mcp-server/src/mcp/rag-server.ts"],
|
|
99
|
+
"env": { "RAG_DB_DIR": "D:/Project/mcp-server/.rag-data" }
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
> Tip: once published (or installed), just point every client at
|
|
106
|
+
> `npx mcp-rag-memory` — no `tsx`, no absolute paths, no local checkout.
|
|
107
|
+
> Locally you can still use `node --import tsx <abs-path>/src/mcp/rag-server.ts`.
|
|
108
|
+
|
|
109
|
+
## opencode configuration
|
|
110
|
+
|
|
111
|
+
### Global config (`~/.config/opencode/opencode.json`)
|
|
112
|
+
|
|
113
|
+
The MCP server is registered here so it works in **every project**, not just this folder:
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"$schema": "https://opencode.ai/config.json",
|
|
118
|
+
"mcp": {
|
|
119
|
+
"rag-memory": {
|
|
120
|
+
"type": "local",
|
|
121
|
+
"command": ["node", "--import", "tsx", "src/mcp/rag-server.ts"],
|
|
122
|
+
"cwd": "D:/Project/mcp-server",
|
|
123
|
+
"enabled": true,
|
|
124
|
+
"environment": {
|
|
125
|
+
"RAG_DB_DIR": "D:/Project/mcp-server/.rag-data"
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
> `cwd` and `RAG_DB_DIR` are absolute paths so the server resolves `tsx`
|
|
133
|
+
> from this project's `node_modules` and stores data in the same DB
|
|
134
|
+
> regardless of which project opencode is opened from.
|
|
135
|
+
|
|
136
|
+
### Project config (`opencode.json` in this repo)
|
|
137
|
+
|
|
138
|
+
Keeps project-specific settings (model, LSP, permissions, instructions) — **no `mcp` block here**:
|
|
139
|
+
|
|
140
|
+
```json
|
|
141
|
+
{
|
|
142
|
+
"$schema": "https://opencode.ai/config.json",
|
|
143
|
+
"model": "anthropic/claude-sonnet-4-6",
|
|
144
|
+
"lsp": true,
|
|
145
|
+
"instructions": [".opencode/instructions.md"],
|
|
146
|
+
"permission": {
|
|
147
|
+
"edit": "allow",
|
|
148
|
+
"bash": { "git *": "allow", "*": "ask" }
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
`.opencode/instructions.md` tells every new session to call
|
|
154
|
+
`memory_context(topic="The Coder")` first, so the assistant always knows
|
|
155
|
+
who you are without asking. The same instructions file is also wired into
|
|
156
|
+
the global config to apply to every project.
|
|
157
|
+
|
|
158
|
+
### Notifications & sound (`~/.config/opencode/tui.json`)
|
|
159
|
+
|
|
160
|
+
```json
|
|
161
|
+
{
|
|
162
|
+
"$schema": "https://opencode.ai/tui.json",
|
|
163
|
+
"attention": {
|
|
164
|
+
"enabled": true,
|
|
165
|
+
"sound": true,
|
|
166
|
+
"notifications": true,
|
|
167
|
+
"volume": 0.4
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Restart opencode after any config change.
|
|
173
|
+
|
|
174
|
+
## MCP tools (19)
|
|
175
|
+
|
|
176
|
+
| Group | Tool | Purpose |
|
|
177
|
+
|-------|------|---------|
|
|
178
|
+
| System | `system_stats` | DB dir, embedding backend, doc/memory counts |
|
|
179
|
+
| RAG ingest | `rag_ingest_text` | Store text as a knowledge document (dedup-aware) |
|
|
180
|
+
| | `rag_ingest_file` | Store a file (`content_type` auto-detected) |
|
|
181
|
+
| | `rag_ingest_dir` | Recursively ingest source files |
|
|
182
|
+
| RAG query | `rag_search` | Hybrid (BM25 + vector) search, ranked chunks + scores |
|
|
183
|
+
| | `rag_retrieve` | Ready-to-inject context block with token count |
|
|
184
|
+
| RAG docs | `rag_list_documents`, `rag_document_stats` | Inventory |
|
|
185
|
+
| | `rag_delete_document` | Remove a document + chunks + FTS rows |
|
|
186
|
+
| Session sync | `rag_sync_session` | Ingest an opencode session's user inputs (or the latest in a workspace) |
|
|
187
|
+
| Memory | `memory_remember` | Save long-term memory (type/importance/tags) |
|
|
188
|
+
| | `memory_recall` | Semantic memory search (decay + recall count) |
|
|
189
|
+
| | `memory_context` | Compact context block from memories for prompts |
|
|
190
|
+
| | `memory_list`, `memory_get`, `memory_update`, `memory_forget` | CRUD |
|
|
191
|
+
| | `memory_consolidate` | Dedupe near-identical + promote hot memories |
|
|
192
|
+
| | `memory_stats` | Counts, tokens, avg importance, by type |
|
|
193
|
+
|
|
194
|
+
## Run in other AI assistants
|
|
195
|
+
|
|
196
|
+
The server is a standard MCP stdio server — it works with any MCP client
|
|
197
|
+
(Claude Desktop, Cursor, Continue, VS Code, ...), not just opencode.
|
|
198
|
+
|
|
199
|
+
**Via `npx` (after publishing).**
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
npx mcp-rag-memory
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**Claude Desktop** — `claude_desktop_config.json`:
|
|
206
|
+
|
|
207
|
+
```json
|
|
208
|
+
{
|
|
209
|
+
"mcpServers": {
|
|
210
|
+
"rag-memory": {
|
|
211
|
+
"command": "npx",
|
|
212
|
+
"args": ["-y", "mcp-rag-memory"],
|
|
213
|
+
"env": { "RAG_DB_DIR": "C:\\Users\\you\\rag-data" }
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**Cursor** (`.cursor/mcp.json`) — `continue` style JSON, same shape:
|
|
220
|
+
|
|
221
|
+
```json
|
|
222
|
+
{
|
|
223
|
+
"mcpServers": {
|
|
224
|
+
"rag-memory": {
|
|
225
|
+
"command": "node",
|
|
226
|
+
"args": ["D:/Project/mcp-server/dist/index.js"],
|
|
227
|
+
"env": { "RAG_DB_DIR": "D:/Project/mcp-server/.rag-data" }
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**Continue** — `~/.continue/config.json`:
|
|
234
|
+
|
|
235
|
+
```json
|
|
236
|
+
{
|
|
237
|
+
"mcpServers": {
|
|
238
|
+
"rag-memory": {
|
|
239
|
+
"type": "stdio",
|
|
240
|
+
"command": "node",
|
|
241
|
+
"args": ["D:/Project/mcp-server/dist/index.js"]
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Configuration.** Every `RAG_*` variable can come from three places, with the
|
|
248
|
+
first winning: the MCP client's `env` block → your shell environment → a
|
|
249
|
+
`.env` file in the working directory. Copy [`.env.example`](./.env.example) to
|
|
250
|
+
`.env` and edit, or point at any file via `RAG_ENV_FILE`. The store defaults to
|
|
251
|
+
`.rag-data/` in the working directory.
|
|
252
|
+
|
|
253
|
+
## Custom slash commands
|
|
254
|
+
|
|
255
|
+
The 19 MCP tools are called by the AI automatically — but you can also
|
|
256
|
+
trigger them directly with custom commands. Sources (same name, project
|
|
257
|
+
wins):
|
|
258
|
+
|
|
259
|
+
- Per-project: `.opencode/commands/`
|
|
260
|
+
- Global: `~/.config/opencode/commands/`
|
|
261
|
+
|
|
262
|
+
| Command | Backing tool | Use |
|
|
263
|
+
|---------|--------------|-----|
|
|
264
|
+
| `/remember <content> [--type][--importance]` | `memory_remember` | Save a memory |
|
|
265
|
+
| `/recall <topic>` | `memory_recall` | Search memories semantically |
|
|
266
|
+
| `/context <topic>` | `memory_context` | Context block for prompts |
|
|
267
|
+
| `/consolidate` | `memory_consolidate` | Dedupe + promote hot memories |
|
|
268
|
+
| `/search <query>` | `rag_search` | Semantic search documents |
|
|
269
|
+
| `/retrieve <query>` | `rag_retrieve` | Raw context block + tokens |
|
|
270
|
+
| `/ingest <text>` | `rag_ingest_text` | Store knowledge text |
|
|
271
|
+
| `/docs` | `rag_list_documents` | List all documents |
|
|
272
|
+
| `/stats` | `system_stats` + doc/memory stats | Full statistics |
|
|
273
|
+
|
|
274
|
+
## CLI
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
npm run cli -- remember "deploys every Friday" --type task --importance 0.7
|
|
278
|
+
npm run cli -- recall "deployment schedule"
|
|
279
|
+
npm run cli -- ingest-dir ./src/rag
|
|
280
|
+
npm run cli -- search "vector similarity"
|
|
281
|
+
npm run cli -- mem-context "database"
|
|
282
|
+
npm run cli -- sessions # list recent opencode sessions
|
|
283
|
+
npm run cli -- sync-session ses_123 # ingest one session's inputs
|
|
284
|
+
npm run cli -- sync-latest # ingest the most recent session
|
|
285
|
+
npm run clean-db -- --force # wipe ALL documents + memories (destructive)
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Semua akses juga tersedia sebagai custom commands (`/remember`, `/recall`,
|
|
289
|
+
`/search`, ...) dan sebagai MCP tools.
|
|
290
|
+
|
|
291
|
+
## Auto-save session inputs
|
|
292
|
+
|
|
293
|
+
Every user input you type in opencode becomes searchable in the RAG store —
|
|
294
|
+
new, untouched, friendly to your existing long-term memories. Three layers:
|
|
295
|
+
|
|
296
|
+
1. **Plugin (real-time, debounced).** `.opencode/plugin/session-logger.ts`
|
|
297
|
+
watches message events and, at most once a minute, runs
|
|
298
|
+
`sync-latest` so the session you are typing in lands in the store.
|
|
299
|
+
Disable with `RAG_AUTOSYNC=0`.
|
|
300
|
+
2. **CLI on demand.**
|
|
301
|
+
```bash
|
|
302
|
+
npm run sync-latest -- --dir "D:/Project/my-app" # latest session in a workspace
|
|
303
|
+
npm run sync-session -- ses_abc123 # a specific session
|
|
304
|
+
npm run sync-logs # .session-logs/*.jsonl (custom logs)
|
|
305
|
+
```
|
|
306
|
+
3. **MCP tool.** `rag_sync_session` exposes the same logic over MCP:
|
|
307
|
+
`{ "session_id": "..." }` or `{ "directory": "..." }` for the latest.
|
|
308
|
+
|
|
309
|
+
How it works: sessions are read from opencode's own DB
|
|
310
|
+
(`~/.local/share/opencode/opencode.db` + `opencode-local.db`, override with
|
|
311
|
+
`OPENCODE_DB` / `OPENCODE_DB_LOCAL`), each user message becomes one small
|
|
312
|
+
document (title `<session>-<ts>`, metadata `source: "opencode-db"`). Long
|
|
313
|
+
inputs are **condensed to key points** before storage (extractive, no LLM) so
|
|
314
|
+
the store stays lean; short inputs are saved whole. Raw transcripts stay in
|
|
315
|
+
opencode's DB; long-term **memories** are never mixed in. Re-runs are
|
|
316
|
+
idempotent thanks to SHA-256 content-hash dedup (`metadata.condensed` marks
|
|
317
|
+
reduced docs), so plugging this into any scheduler is safe.
|
|
318
|
+
|
|
319
|
+
The plugin needs an opencode restart to take effect.
|
|
320
|
+
|
|
321
|
+
## Architecture
|
|
322
|
+
|
|
323
|
+
```
|
|
324
|
+
src/
|
|
325
|
+
├── db/database.ts node:sqlite (documents, chunks, memories, chunks_fts, meta)
|
|
326
|
+
├── rag/
|
|
327
|
+
│ ├── embedder.ts local hashing embedder (1024-d, FNV-1a, n-grams)
|
|
328
|
+
│ ├── embeddings.ts provider layer: local | transformers (optional dep)
|
|
329
|
+
│ ├── chunker.ts paragraph/code-aware chunking with overlap
|
|
330
|
+
│ ├── vector-search.ts vector cache + FTS5 BM25 + RRF hybrid scoring
|
|
331
|
+
│ └── pipeline.ts async ingest/search/retrieve, dedup, guards, doc mgmt
|
|
332
|
+
├── session/transcript.ts read opencode sessions (global+local DB), ingest inputs
|
|
333
|
+
├── memory/memory.ts remember / recall / consolidate + decay + prune
|
|
334
|
+
├── mcp/rag-server.ts MCP server (19 tools, stdio, npm bin)
|
|
335
|
+
├── cli.ts CLI playground (incl. sync-session / sync-latest / sync-logs)
|
|
336
|
+
└── test/test-all.ts full test suite (unit + MCP round-trip)
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Data lives in `.rag-data/rag.sqlite` (git-ignored).
|
|
340
|
+
|
|
341
|
+
## Configuration (env vars)
|
|
342
|
+
|
|
343
|
+
| Variable | Default | Purpose |
|
|
344
|
+
|----------|---------|---------|
|
|
345
|
+
| `RAG_DB_DIR` | `.rag-data` | Where the SQLite store lives |
|
|
346
|
+
| `SEARCH_MODE` | `hybrid` | `hybrid` \| `vector` \| `keyword` |
|
|
347
|
+
| `EMBEDDING_PROVIDER` | `local` | `local` (zero-dep) \| `transformers` (needs optional `@huggingface/transformers`) |
|
|
348
|
+
| `EMBEDDING_MODEL` | `Xenova/all-MiniLM-L6-v2` | Transformers model name |
|
|
349
|
+
| `RAG_MAX_FILE_MB` | `10` | Reject files larger than this |
|
|
350
|
+
| `RAG_ALLOWED_DIRS` | *(unset = anywhere)* | Semicolon/pipe/comma-separated allowed ingest roots |
|
|
351
|
+
| `RAG_MEMORY_HALF_LIFE_DAYS` | `14` | Recall score decay half-life |
|
|
352
|
+
| `RAG_PRUNE` | `0` | Set `1` to allow `consolidate` to delete non-essential memories |
|
|
353
|
+
| `RAG_PRUNE_IMPORTANCE` | `0.2` | Delete memories below this importance |
|
|
354
|
+
| `RAG_PRUNE_AGE_DAYS` | `90` | ...and older than this (never-recalled only) |
|
|
355
|
+
| `RAG_AUTOSYNC` | `1` | Set `0` to disable the session-logger plugin's auto-sync |
|
|
356
|
+
| `OPENCODE_DB` / `OPENCODE_DB_LOCAL` | `~/.local/share/opencode/*.db` | Where session transcripts are read from |
|
|
357
|
+
| `RAG_SESSION_CONDENSE` | `1` | Set `0` to store session inputs verbatim |
|
|
358
|
+
| `RAG_SESSION_CONDENSE_MIN_CHARS` | `120` | Inputs at/below this length are saved whole |
|
|
359
|
+
| `RAG_SESSION_CONDENSE_RATIO` | `0.35` | Fraction of long-input length to keep as key points |
|
|
360
|
+
|
|
361
|
+
## Testing
|
|
362
|
+
|
|
363
|
+
`npm test` spins up the real MCP server over stdio using the SDK client
|
|
364
|
+
and exercises every tool end-to-end against a scratch DB (`.test-data`).
|
|
365
|
+
`npm run lint` checks the codebase with oxlint (run in CI too).
|
|
366
|
+
|
|
367
|
+
## GitHub Pages site
|
|
368
|
+
|
|
369
|
+
The [`docs/`](./docs/) directory is a self-contained static site of this
|
|
370
|
+
project (single `index.html`, no build step). To publish it on GitHub
|
|
371
|
+
Pages:
|
|
372
|
+
|
|
373
|
+
1. Push this repo to GitHub.
|
|
374
|
+
2. Repo **Settings → Pages → Build and deployment → Source: Deploy from a
|
|
375
|
+
branch**.
|
|
376
|
+
3. Choose branch `main` and folder `/docs`.
|
|
377
|
+
4. Your site is live at `https://adyoi.github.io/mcp-rag-memory/`.
|
|
378
|
+
|
|
379
|
+
## Troubleshooting
|
|
380
|
+
|
|
381
|
+
### `ConfigInvalidError: missing key "command"` for typescript / javascript / json
|
|
382
|
+
|
|
383
|
+
The `lsp` block in `opencode.json` has the wrong shape.
|
|
384
|
+
Each language key must have a `command` array:
|
|
385
|
+
|
|
386
|
+
```json
|
|
387
|
+
"lsp": {
|
|
388
|
+
"typescript": {
|
|
389
|
+
"command": ["typescript-language-server", "--stdio"]
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Fields like `language_id` or `extensions` alone are not allowed — the
|
|
395
|
+
schema enforces `additionalProperties: false` and requires `command`.
|
|
396
|
+
|
|
397
|
+
**Fix:** either write the `command` array, or (if you just want built-in
|
|
398
|
+
LSP) replace the whole block with `"lsp": true`.
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
402
|
+
### MCP tools not showing up in opencode
|
|
403
|
+
|
|
404
|
+
1. **Wrong config filename** — opencode reads only `opencode.json`,
|
|
405
|
+
`opencode.jsonc`, or `.opencode/opencode.json`. A file named
|
|
406
|
+
`opencode.jsonx` is **silently ignored**; no error, no tools.
|
|
407
|
+
2. **File not in the right place** — global MCP config lives at
|
|
408
|
+
`~/.config/opencode/opencode.json`, not in the project folder.
|
|
409
|
+
3. **Missing `cwd` for global use** — when `command` uses a relative
|
|
410
|
+
path (`src/mcp/rag-server.ts`), opencode resolves it against the
|
|
411
|
+
*workspace* directory, not the project. Add `"cwd"` to point at the
|
|
412
|
+
project that contains `node_modules/tsx`:
|
|
413
|
+
|
|
414
|
+
```json
|
|
415
|
+
"rag-memory": {
|
|
416
|
+
"type": "local",
|
|
417
|
+
"command": ["node", "--import", "tsx", "src/mcp/rag-server.ts"],
|
|
418
|
+
"cwd": "D:/Project/mcp-server"
|
|
419
|
+
}
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
4. **Missing env var** — `RAG_DB_DIR` must be set (absolute path for
|
|
423
|
+
global config) or the DB defaults to `.rag-data` relative to cwd.
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
### Sound / notification not playing when response finishes
|
|
428
|
+
|
|
429
|
+
The `attention` feature is **off by default**. Create
|
|
430
|
+
`~/.config/opencode/tui.json`:
|
|
431
|
+
|
|
432
|
+
```json
|
|
433
|
+
{
|
|
434
|
+
"$schema": "https://opencode.ai/tui.json",
|
|
435
|
+
"attention": {
|
|
436
|
+
"enabled": true,
|
|
437
|
+
"sound": true,
|
|
438
|
+
"notifications": true,
|
|
439
|
+
"volume": 0.4
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
**Known issue** ([#40445](https://github.com/anomalyco/opencode/issues/40445)):
|
|
445
|
+
sound silently fails when opencode runs under the **Node runtime** instead
|
|
446
|
+
of Bun — the audio library depends on Bun FFI. Symptoms: no sound despite
|
|
447
|
+
correct config. Not a configuration error.
|
|
448
|
+
|
|
449
|
+
---
|
|
450
|
+
|
|
451
|
+
### Server slow to start on first load (~3 s)
|
|
452
|
+
|
|
453
|
+
`tsx` compiles TypeScript on first invocation. This is normal and only
|
|
454
|
+
happens on cold start; subsequent tool calls within the same session are
|
|
455
|
+
instant.
|
|
456
|
+
|
|
457
|
+
---
|
|
458
|
+
|
|
459
|
+
### Opencode feels sluggish / UI lag after adding plugins
|
|
460
|
+
|
|
461
|
+
Plugins that run synchronous I/O or write to stderr on every tool call
|
|
462
|
+
can slow down the TUI. This project no longer ships any plugins — keep
|
|
463
|
+
`opencode.json` plugin-free for clean operation.
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
### `ajv-cli` fails with `strict mode: unknown keyword: allowComments`
|
|
468
|
+
|
|
469
|
+
The opencode schema uses custom JSON-Schema extensions (`allowComments`,
|
|
470
|
+
`allowTrailingCommas`). `ajv-cli` rejects these by default. Validate
|
|
471
|
+
config manually instead:
|
|
472
|
+
|
|
473
|
+
```bash
|
|
474
|
+
node -e "JSON.parse(require('fs').readFileSync('opencode.json','utf8')); console.log('OK')"
|
|
475
|
+
```
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { DatabaseSync } from "node:sqlite";
|
|
2
|
+
export declare const STORAGE_DIR: string;
|
|
3
|
+
export declare const DB_PATH: string;
|
|
4
|
+
export declare function getDB(): DatabaseSync;
|
|
5
|
+
/** SHA-256 hex of raw content — used for document dedup. */
|
|
6
|
+
export declare function contentHash(content: string): string;
|
|
7
|
+
export declare function newId(): string;
|
|
8
|
+
/** Serialize a Float64Array embedding to a SQLite BLOB. */
|
|
9
|
+
export declare function packVector(v: Float64Array): Uint8Array;
|
|
10
|
+
/** Deserialize a BLOB back into a Float64Array. */
|
|
11
|
+
export declare function unpackVector(blob: Uint8Array | null): Float64Array | null;
|
|
12
|
+
export declare function nowMs(): number;
|
|
13
|
+
export declare function closeDB(): void;
|
|
14
|
+
export declare function getEmbedDim(): number;
|
|
15
|
+
export declare function setEmbedMeta(dim: number, model: string): void;
|
|
16
|
+
/**
|
|
17
|
+
* Lock the store to one embedding dimension. Call after producing a vector.
|
|
18
|
+
* Prevents silently mixing vectors of different models/providers.
|
|
19
|
+
*/
|
|
20
|
+
export declare function ensureDim(vec: Float64Array, model: string): void;
|
|
21
|
+
export declare function getEmbedModel(): string;
|
|
22
|
+
//# sourceMappingURL=database.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAK3C,eAAO,MAAM,WAAW,QAEoB,CAAC;AAE7C,eAAO,MAAM,OAAO,QAAuC,CAAC;AAU5D,wBAAgB,KAAK,IAAI,YAAY,CAUpC;AAqED,4DAA4D;AAC5D,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAgB,KAAK,IAAI,MAAM,CAE9B;AAED,2DAA2D;AAC3D,wBAAgB,UAAU,CAAC,CAAC,EAAE,YAAY,GAAG,UAAU,CAEtD;AAED,mDAAmD;AACnD,wBAAgB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,GAAG,YAAY,GAAG,IAAI,CAGzE;AAED,wBAAgB,KAAK,IAAI,MAAM,CAE9B;AAED,wBAAgB,OAAO,SAGtB;AAMD,wBAAgB,WAAW,IAAI,MAAM,CAKpC;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAQtD;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAahE;AAED,wBAAgB,aAAa,IAAI,MAAM,CAKtC"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { DatabaseSync } from "node:sqlite";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import * as fs from "fs";
|
|
4
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
5
|
+
export const STORAGE_DIR = process.env.RAG_DB_DIR
|
|
6
|
+
? path.resolve(process.env.RAG_DB_DIR)
|
|
7
|
+
: path.resolve(process.cwd(), ".rag-data");
|
|
8
|
+
export const DB_PATH = path.join(STORAGE_DIR, "rag.sqlite");
|
|
9
|
+
let _db = null;
|
|
10
|
+
function ensureDir() {
|
|
11
|
+
if (!fs.existsSync(STORAGE_DIR)) {
|
|
12
|
+
fs.mkdirSync(STORAGE_DIR, { recursive: true });
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function getDB() {
|
|
16
|
+
if (_db)
|
|
17
|
+
return _db;
|
|
18
|
+
ensureDir();
|
|
19
|
+
const db = new DatabaseSync(DB_PATH);
|
|
20
|
+
db.exec("PRAGMA journal_mode = WAL;");
|
|
21
|
+
db.exec("PRAGMA synchronous = NORMAL;");
|
|
22
|
+
db.exec("PRAGMA foreign_keys = ON;");
|
|
23
|
+
_db = db;
|
|
24
|
+
migrate(db);
|
|
25
|
+
return db;
|
|
26
|
+
}
|
|
27
|
+
function columnExists(db, table, column) {
|
|
28
|
+
const cols = db.prepare(`PRAGMA table_info(${table})`).all();
|
|
29
|
+
return cols.some((c) => c.name === column);
|
|
30
|
+
}
|
|
31
|
+
function migrate(db) {
|
|
32
|
+
db.exec(`
|
|
33
|
+
CREATE TABLE IF NOT EXISTS documents (
|
|
34
|
+
id TEXT PRIMARY KEY,
|
|
35
|
+
title TEXT NOT NULL,
|
|
36
|
+
source TEXT,
|
|
37
|
+
content_type TEXT DEFAULT 'text',
|
|
38
|
+
metadata TEXT DEFAULT '{}',
|
|
39
|
+
chunk_count INTEGER DEFAULT 0,
|
|
40
|
+
created_at INTEGER NOT NULL,
|
|
41
|
+
updated_at INTEGER NOT NULL
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
CREATE TABLE IF NOT EXISTS chunks (
|
|
45
|
+
id TEXT PRIMARY KEY,
|
|
46
|
+
doc_id TEXT NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
|
|
47
|
+
idx INTEGER NOT NULL,
|
|
48
|
+
content TEXT NOT NULL,
|
|
49
|
+
embedding BLOB,
|
|
50
|
+
token_count INTEGER DEFAULT 0,
|
|
51
|
+
created_at INTEGER NOT NULL
|
|
52
|
+
);
|
|
53
|
+
CREATE INDEX IF NOT EXISTS idx_chunks_doc_id ON chunks(doc_id);
|
|
54
|
+
|
|
55
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
56
|
+
id TEXT PRIMARY KEY,
|
|
57
|
+
type TEXT DEFAULT 'fact',
|
|
58
|
+
content TEXT NOT NULL,
|
|
59
|
+
importance REAL DEFAULT 0.5,
|
|
60
|
+
embedding BLOB,
|
|
61
|
+
tags TEXT DEFAULT '[]',
|
|
62
|
+
recall_count INTEGER DEFAULT 0,
|
|
63
|
+
last_recalled INTEGER,
|
|
64
|
+
created_at INTEGER NOT NULL,
|
|
65
|
+
updated_at INTEGER NOT NULL
|
|
66
|
+
);
|
|
67
|
+
CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(type);
|
|
68
|
+
CREATE INDEX IF NOT EXISTS idx_memories_prune ON memories(importance, recall_count, created_at);
|
|
69
|
+
|
|
70
|
+
CREATE TABLE IF NOT EXISTS meta (
|
|
71
|
+
key TEXT PRIMARY KEY,
|
|
72
|
+
value TEXT
|
|
73
|
+
);
|
|
74
|
+
`);
|
|
75
|
+
// Content-hash dedup for documents (unique partial index — NULLs allowed).
|
|
76
|
+
if (!columnExists(db, "documents", "content_hash")) {
|
|
77
|
+
db.exec("ALTER TABLE documents ADD COLUMN content_hash TEXT;");
|
|
78
|
+
}
|
|
79
|
+
db.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_documents_content_hash ON documents(content_hash) WHERE content_hash IS NOT NULL;");
|
|
80
|
+
// FTS5 full-text index over chunks for hybrid (BM25 + vector) search.
|
|
81
|
+
const hasFts = db
|
|
82
|
+
.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'chunks_fts'")
|
|
83
|
+
.get();
|
|
84
|
+
if (!hasFts) {
|
|
85
|
+
db.exec("CREATE VIRTUAL TABLE chunks_fts USING fts5(content, tokenize = 'unicode61');");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** SHA-256 hex of raw content — used for document dedup. */
|
|
89
|
+
export function contentHash(content) {
|
|
90
|
+
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
91
|
+
}
|
|
92
|
+
export function newId() {
|
|
93
|
+
return randomUUID();
|
|
94
|
+
}
|
|
95
|
+
/** Serialize a Float64Array embedding to a SQLite BLOB. */
|
|
96
|
+
export function packVector(v) {
|
|
97
|
+
return new Uint8Array(v.buffer.slice(0));
|
|
98
|
+
}
|
|
99
|
+
/** Deserialize a BLOB back into a Float64Array. */
|
|
100
|
+
export function unpackVector(blob) {
|
|
101
|
+
if (!blob || blob.byteLength === 0)
|
|
102
|
+
return null;
|
|
103
|
+
return new Float64Array(blob.buffer.slice(blob.byteOffset, blob.byteOffset + blob.byteLength));
|
|
104
|
+
}
|
|
105
|
+
export function nowMs() {
|
|
106
|
+
return Date.now();
|
|
107
|
+
}
|
|
108
|
+
export function closeDB() {
|
|
109
|
+
_db?.close();
|
|
110
|
+
_db = null;
|
|
111
|
+
}
|
|
112
|
+
/* ------------------------------------------------------------------ */
|
|
113
|
+
/* Embedding dimension metadata */
|
|
114
|
+
/* ------------------------------------------------------------------ */
|
|
115
|
+
export function getEmbedDim() {
|
|
116
|
+
const r = getDB().prepare("SELECT value FROM meta WHERE key = 'embed_dim'").get();
|
|
117
|
+
return r ? Number(r.value) : 0;
|
|
118
|
+
}
|
|
119
|
+
export function setEmbedMeta(dim, model) {
|
|
120
|
+
const db = getDB();
|
|
121
|
+
db.prepare("INSERT INTO meta(key, value) VALUES ('embed_dim', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(String(dim));
|
|
122
|
+
db.prepare("INSERT INTO meta(key, value) VALUES ('embed_model', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(model);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Lock the store to one embedding dimension. Call after producing a vector.
|
|
126
|
+
* Prevents silently mixing vectors of different models/providers.
|
|
127
|
+
*/
|
|
128
|
+
export function ensureDim(vec, model) {
|
|
129
|
+
const current = getEmbedDim();
|
|
130
|
+
if (current === 0) {
|
|
131
|
+
setEmbedMeta(vec.length, model);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (current !== vec.length) {
|
|
135
|
+
throw new Error(`Embedding dimension mismatch: DB stores ${current} (model: ${getEmbedModel()}), ` +
|
|
136
|
+
`new vectors are ${vec.length} (model: ${model}). ` +
|
|
137
|
+
`Use a consistent EMBEDDING_PROVIDER or wipe ${STORAGE_DIR}.`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
export function getEmbedModel() {
|
|
141
|
+
const r = getDB().prepare("SELECT value FROM meta WHERE key = 'embed_model'").get();
|
|
142
|
+
return r ? r.value : "";
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=database.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"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"}
|