mcp-simple-memory 0.2.0 → 0.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/README.md +50 -0
- package/dist/cli.d.ts +5 -2
- package/dist/cli.js +261 -8
- package/dist/index.js +4 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -149,6 +149,56 @@ mem_search({ query: "authentication problem", tag: "bug" })
|
|
|
149
149
|
3. **Auto mode**: Keyword first. If < 3 results, falls back to vector search.
|
|
150
150
|
4. **Tags**: Lightweight categorization. Normalized to lowercase.
|
|
151
151
|
|
|
152
|
+
## Import Existing Knowledge
|
|
153
|
+
|
|
154
|
+
Already have notes, docs, or session logs? Import them directly:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
# Import a single file
|
|
158
|
+
npx mcp-simple-memory import CLAUDE.md --project my-app --tags setup
|
|
159
|
+
|
|
160
|
+
# Import all .md files in a directory
|
|
161
|
+
npx mcp-simple-memory import ./docs --tags documentation
|
|
162
|
+
|
|
163
|
+
# Preview what would be imported (no changes)
|
|
164
|
+
npx mcp-simple-memory import ./notes --dry-run
|
|
165
|
+
|
|
166
|
+
# With semantic search (auto-generates embeddings during import)
|
|
167
|
+
GEMINI_API_KEY=your-key npx mcp-simple-memory import memory.md
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The importer splits markdown files by headings (`#`, `##`, `###`) into individual memories. Each section becomes a searchable memory entry.
|
|
171
|
+
|
|
172
|
+
### Options
|
|
173
|
+
|
|
174
|
+
| Flag | Description |
|
|
175
|
+
|------|-------------|
|
|
176
|
+
| `--project <name>` | Set project name (default: filename) |
|
|
177
|
+
| `--tags <t1,t2>` | Add tags to all imported memories |
|
|
178
|
+
| `--dry-run` | Preview without saving |
|
|
179
|
+
|
|
180
|
+
## Database Stats
|
|
181
|
+
|
|
182
|
+
Check what's in your memory:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
npx mcp-simple-memory stats
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Output:
|
|
189
|
+
```
|
|
190
|
+
mcp-simple-memory stats
|
|
191
|
+
DB: ~/.mcp-simple-memory/memory.db
|
|
192
|
+
|
|
193
|
+
Memories: 42
|
|
194
|
+
Embeddings: 42/42 (100%)
|
|
195
|
+
Tags: 15 unique
|
|
196
|
+
|
|
197
|
+
Projects:
|
|
198
|
+
my-app: 30
|
|
199
|
+
default: 12
|
|
200
|
+
```
|
|
201
|
+
|
|
152
202
|
## Data Storage
|
|
153
203
|
|
|
154
204
|
All data stays local:
|
package/dist/cli.d.ts
CHANGED
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
* CLI for mcp-simple-memory
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
|
-
* npx mcp-simple-memory init
|
|
7
|
-
* npx mcp-simple-memory serve
|
|
6
|
+
* npx mcp-simple-memory init — Add to .mcp.json
|
|
7
|
+
* npx mcp-simple-memory serve — Start MCP server (used by Claude Code)
|
|
8
|
+
* npx mcp-simple-memory import <file> — Import .md file as memories
|
|
9
|
+
* npx mcp-simple-memory import <dir> — Import all .md files in directory
|
|
10
|
+
* npx mcp-simple-memory stats — Show memory database stats
|
|
8
11
|
*/
|
|
9
12
|
export {};
|
package/dist/cli.js
CHANGED
|
@@ -3,28 +3,50 @@
|
|
|
3
3
|
* CLI for mcp-simple-memory
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
|
-
* npx mcp-simple-memory init
|
|
7
|
-
* npx mcp-simple-memory serve
|
|
6
|
+
* npx mcp-simple-memory init — Add to .mcp.json
|
|
7
|
+
* npx mcp-simple-memory serve — Start MCP server (used by Claude Code)
|
|
8
|
+
* npx mcp-simple-memory import <file> — Import .md file as memories
|
|
9
|
+
* npx mcp-simple-memory import <dir> — Import all .md files in directory
|
|
10
|
+
* npx mcp-simple-memory stats — Show memory database stats
|
|
8
11
|
*/
|
|
9
|
-
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
10
|
-
import { join } from "path";
|
|
12
|
+
import { readFileSync, writeFileSync, existsSync, readdirSync, statSync, mkdirSync } from "fs";
|
|
13
|
+
import { join, basename, extname, resolve } from "path";
|
|
14
|
+
import { homedir } from "os";
|
|
15
|
+
import initSqlJs from "sql.js";
|
|
16
|
+
// Config - must be before command dispatch
|
|
17
|
+
const DATA_DIR = process.env.MCP_MEMORY_DIR || join(homedir(), ".mcp-simple-memory");
|
|
18
|
+
const DB_PATH = join(DATA_DIR, "memory.db");
|
|
19
|
+
const GEMINI_API_KEY = process.env.GEMINI_API_KEY || "";
|
|
20
|
+
const GEMINI_MODEL = "gemini-embedding-001";
|
|
11
21
|
const args = process.argv.slice(2);
|
|
12
22
|
const command = args[0] || "serve";
|
|
13
23
|
if (command === "init") {
|
|
14
24
|
init();
|
|
15
25
|
}
|
|
16
26
|
else if (command === "serve") {
|
|
17
|
-
// Import and run the server
|
|
18
27
|
await import("./index.js");
|
|
19
28
|
}
|
|
29
|
+
else if (command === "import") {
|
|
30
|
+
await importMemories(args[1], args.slice(2));
|
|
31
|
+
}
|
|
32
|
+
else if (command === "stats") {
|
|
33
|
+
await showStats();
|
|
34
|
+
}
|
|
20
35
|
else if (command === "help" || command === "--help" || command === "-h") {
|
|
21
36
|
console.log(`
|
|
22
37
|
mcp-simple-memory — Persistent memory for Claude Code
|
|
23
38
|
|
|
24
39
|
Commands:
|
|
25
|
-
init
|
|
26
|
-
serve
|
|
27
|
-
|
|
40
|
+
init Add mcp-simple-memory to .mcp.json in current directory
|
|
41
|
+
serve Start the MCP server (default, used by Claude Code)
|
|
42
|
+
import <file|dir> Import .md files as memories
|
|
43
|
+
stats Show database statistics
|
|
44
|
+
help Show this help
|
|
45
|
+
|
|
46
|
+
Import options:
|
|
47
|
+
--project <name> Set project name (default: filename without extension)
|
|
48
|
+
--tags <t1,t2> Add tags to all imported memories
|
|
49
|
+
--dry-run Show what would be imported without saving
|
|
28
50
|
|
|
29
51
|
Environment variables:
|
|
30
52
|
GEMINI_API_KEY Enable semantic search (optional, free tier works)
|
|
@@ -35,6 +57,100 @@ else {
|
|
|
35
57
|
console.error(`Unknown command: ${command}. Run 'mcp-simple-memory help' for usage.`);
|
|
36
58
|
process.exit(1);
|
|
37
59
|
}
|
|
60
|
+
// ---- Database helpers (shared with index.ts) --------------------------------
|
|
61
|
+
async function openDb() {
|
|
62
|
+
if (!existsSync(DATA_DIR))
|
|
63
|
+
mkdirSync(DATA_DIR, { recursive: true });
|
|
64
|
+
const SQL = await initSqlJs();
|
|
65
|
+
let db;
|
|
66
|
+
if (existsSync(DB_PATH)) {
|
|
67
|
+
db = new SQL.Database(readFileSync(DB_PATH));
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
db = new SQL.Database();
|
|
71
|
+
}
|
|
72
|
+
db.run(`CREATE TABLE IF NOT EXISTS memories (
|
|
73
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
74
|
+
title TEXT, content TEXT NOT NULL,
|
|
75
|
+
type TEXT DEFAULT 'memory', project TEXT DEFAULT 'default',
|
|
76
|
+
created_at INTEGER NOT NULL, created_iso TEXT NOT NULL,
|
|
77
|
+
updated_at INTEGER, updated_iso TEXT
|
|
78
|
+
)`);
|
|
79
|
+
db.run(`CREATE TABLE IF NOT EXISTS embeddings (
|
|
80
|
+
memory_id INTEGER PRIMARY KEY, vector BLOB NOT NULL
|
|
81
|
+
)`);
|
|
82
|
+
db.run(`CREATE TABLE IF NOT EXISTS tags (
|
|
83
|
+
memory_id INTEGER NOT NULL, tag TEXT NOT NULL,
|
|
84
|
+
PRIMARY KEY (memory_id, tag)
|
|
85
|
+
)`);
|
|
86
|
+
return db;
|
|
87
|
+
}
|
|
88
|
+
function persistDb(db) {
|
|
89
|
+
writeFileSync(DB_PATH, Buffer.from(db.export()));
|
|
90
|
+
}
|
|
91
|
+
function queryAll(db, sql, params = []) {
|
|
92
|
+
const stmt = db.prepare(sql);
|
|
93
|
+
if (params.length)
|
|
94
|
+
stmt.bind(params);
|
|
95
|
+
const rows = [];
|
|
96
|
+
while (stmt.step())
|
|
97
|
+
rows.push(stmt.getAsObject());
|
|
98
|
+
stmt.free();
|
|
99
|
+
return rows;
|
|
100
|
+
}
|
|
101
|
+
async function getEmbedding(text) {
|
|
102
|
+
if (!GEMINI_API_KEY)
|
|
103
|
+
return null;
|
|
104
|
+
try {
|
|
105
|
+
const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_MODEL}:embedContent?key=${GEMINI_API_KEY}`, {
|
|
106
|
+
method: "POST",
|
|
107
|
+
headers: { "Content-Type": "application/json" },
|
|
108
|
+
body: JSON.stringify({
|
|
109
|
+
model: `models/${GEMINI_MODEL}`,
|
|
110
|
+
content: { parts: [{ text }] },
|
|
111
|
+
}),
|
|
112
|
+
});
|
|
113
|
+
if (!res.ok)
|
|
114
|
+
return null;
|
|
115
|
+
const data = (await res.json());
|
|
116
|
+
return new Float32Array(data.embedding.values);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function chunkMarkdown(text) {
|
|
123
|
+
const lines = text.split("\n");
|
|
124
|
+
const chunks = [];
|
|
125
|
+
let currentTitle = "";
|
|
126
|
+
let currentContent = [];
|
|
127
|
+
let currentLevel = 0;
|
|
128
|
+
for (const line of lines) {
|
|
129
|
+
const headingMatch = line.match(/^(#{1,3})\s+(.+)/);
|
|
130
|
+
if (headingMatch) {
|
|
131
|
+
// Save previous chunk
|
|
132
|
+
if (currentContent.length > 0 || currentTitle) {
|
|
133
|
+
const content = currentContent.join("\n").trim();
|
|
134
|
+
if (content.length > 10) {
|
|
135
|
+
chunks.push({ title: currentTitle, content, level: currentLevel });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
currentTitle = headingMatch[2].trim();
|
|
139
|
+
currentLevel = headingMatch[1].length;
|
|
140
|
+
currentContent = [];
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
currentContent.push(line);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Last chunk
|
|
147
|
+
const content = currentContent.join("\n").trim();
|
|
148
|
+
if (content.length > 10) {
|
|
149
|
+
chunks.push({ title: currentTitle, content, level: currentLevel });
|
|
150
|
+
}
|
|
151
|
+
return chunks;
|
|
152
|
+
}
|
|
153
|
+
// ---- Commands ---------------------------------------------------------------
|
|
38
154
|
function init() {
|
|
39
155
|
const mcpPath = join(process.cwd(), ".mcp.json");
|
|
40
156
|
let config = {};
|
|
@@ -68,3 +184,140 @@ function init() {
|
|
|
68
184
|
console.log("Example with semantic search:");
|
|
69
185
|
console.log(' "env": { "GEMINI_API_KEY": "your-key-here" }');
|
|
70
186
|
}
|
|
187
|
+
async function importMemories(target, flags) {
|
|
188
|
+
if (!target) {
|
|
189
|
+
console.error("Usage: mcp-simple-memory import <file.md|directory> [--project name] [--tags t1,t2] [--dry-run]");
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
// Parse flags
|
|
193
|
+
let project;
|
|
194
|
+
let extraTags = [];
|
|
195
|
+
let dryRun = false;
|
|
196
|
+
for (let i = 0; i < flags.length; i++) {
|
|
197
|
+
if (flags[i] === "--project" && flags[i + 1]) {
|
|
198
|
+
project = flags[++i];
|
|
199
|
+
}
|
|
200
|
+
else if (flags[i] === "--tags" && flags[i + 1]) {
|
|
201
|
+
extraTags = flags[++i].split(",").map((t) => t.trim().toLowerCase()).filter(Boolean);
|
|
202
|
+
}
|
|
203
|
+
else if (flags[i] === "--dry-run") {
|
|
204
|
+
dryRun = true;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const resolvedTarget = resolve(target);
|
|
208
|
+
// Collect files
|
|
209
|
+
let files = [];
|
|
210
|
+
if (!existsSync(resolvedTarget)) {
|
|
211
|
+
console.error(`Not found: ${resolvedTarget}`);
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
if (statSync(resolvedTarget).isDirectory()) {
|
|
215
|
+
files = readdirSync(resolvedTarget)
|
|
216
|
+
.filter((f) => extname(f).toLowerCase() === ".md")
|
|
217
|
+
.map((f) => join(resolvedTarget, f))
|
|
218
|
+
.sort();
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
files = [resolvedTarget];
|
|
222
|
+
}
|
|
223
|
+
if (!files.length) {
|
|
224
|
+
console.error("No .md files found.");
|
|
225
|
+
process.exit(1);
|
|
226
|
+
}
|
|
227
|
+
console.log(`Found ${files.length} file(s) to import.\n`);
|
|
228
|
+
const db = await openDb();
|
|
229
|
+
let totalChunks = 0;
|
|
230
|
+
let totalEmbedded = 0;
|
|
231
|
+
for (const file of files) {
|
|
232
|
+
const name = basename(file, ".md");
|
|
233
|
+
const proj = project || name;
|
|
234
|
+
const raw = readFileSync(file, "utf-8");
|
|
235
|
+
const chunks = chunkMarkdown(raw);
|
|
236
|
+
console.log(`${name}.md → ${chunks.length} chunks (project: ${proj})`);
|
|
237
|
+
for (const chunk of chunks) {
|
|
238
|
+
const tags = [...extraTags];
|
|
239
|
+
// Auto-tag from heading level
|
|
240
|
+
if (chunk.level === 1)
|
|
241
|
+
tags.push("section");
|
|
242
|
+
if (chunk.level === 2)
|
|
243
|
+
tags.push("subsection");
|
|
244
|
+
if (dryRun) {
|
|
245
|
+
const preview = chunk.content.substring(0, 60).replace(/\n/g, " ");
|
|
246
|
+
console.log(` [dry-run] "${chunk.title}" (${chunk.content.length} chars) ${preview}...`);
|
|
247
|
+
totalChunks++;
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
const now = Date.now();
|
|
251
|
+
db.run(`INSERT INTO memories (title, content, type, project, created_at, created_iso) VALUES (?, ?, ?, ?, ?, ?)`, [chunk.title || name, chunk.content, "memory", proj, now, new Date(now).toISOString()]);
|
|
252
|
+
const id = queryAll(db, `SELECT last_insert_rowid() as id`)[0]?.id ?? 0;
|
|
253
|
+
// Tags
|
|
254
|
+
for (const tag of tags) {
|
|
255
|
+
if (tag)
|
|
256
|
+
db.run(`INSERT OR IGNORE INTO tags (memory_id, tag) VALUES (?, ?)`, [id, tag.toLowerCase()]);
|
|
257
|
+
}
|
|
258
|
+
// Embedding
|
|
259
|
+
if (GEMINI_API_KEY) {
|
|
260
|
+
const vec = await getEmbedding(`${chunk.title}\n${chunk.content}`);
|
|
261
|
+
if (vec) {
|
|
262
|
+
db.run(`INSERT OR REPLACE INTO embeddings (memory_id, vector) VALUES (?, ?)`, [
|
|
263
|
+
id,
|
|
264
|
+
new Uint8Array(vec.buffer),
|
|
265
|
+
]);
|
|
266
|
+
totalEmbedded++;
|
|
267
|
+
}
|
|
268
|
+
// Rate limit
|
|
269
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
270
|
+
}
|
|
271
|
+
const tagStr = tags.length ? ` [${tags.join(", ")}]` : "";
|
|
272
|
+
console.log(` #${id} "${chunk.title}" (${chunk.content.length} chars)${tagStr}`);
|
|
273
|
+
totalChunks++;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (!dryRun) {
|
|
277
|
+
persistDb(db);
|
|
278
|
+
}
|
|
279
|
+
db.close();
|
|
280
|
+
console.log(`\n${dryRun ? "[DRY RUN] " : ""}Imported ${totalChunks} memories from ${files.length} file(s).`);
|
|
281
|
+
if (totalEmbedded > 0) {
|
|
282
|
+
console.log(`Generated ${totalEmbedded} embeddings.`);
|
|
283
|
+
}
|
|
284
|
+
else if (!GEMINI_API_KEY && !dryRun) {
|
|
285
|
+
console.log("Tip: Set GEMINI_API_KEY to auto-generate embeddings during import.");
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
async function showStats() {
|
|
289
|
+
if (!existsSync(DB_PATH)) {
|
|
290
|
+
console.log("No database found. Run 'mcp-simple-memory init' first.");
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const db = await openDb();
|
|
294
|
+
const memCount = queryAll(db, `SELECT COUNT(*) as c FROM memories`)[0]?.c ?? 0;
|
|
295
|
+
const embCount = queryAll(db, `SELECT COUNT(*) as c FROM embeddings`)[0]?.c ?? 0;
|
|
296
|
+
const tagCount = queryAll(db, `SELECT COUNT(DISTINCT tag) as c FROM tags`)[0]?.c ?? 0;
|
|
297
|
+
const projects = queryAll(db, `SELECT project, COUNT(*) as c FROM memories GROUP BY project ORDER BY c DESC`);
|
|
298
|
+
const types = queryAll(db, `SELECT type, COUNT(*) as c FROM memories GROUP BY type ORDER BY c DESC`);
|
|
299
|
+
const topTags = queryAll(db, `SELECT tag, COUNT(*) as c FROM tags GROUP BY tag ORDER BY c DESC LIMIT 10`);
|
|
300
|
+
console.log(`mcp-simple-memory stats`);
|
|
301
|
+
console.log(` DB: ${DB_PATH}\n`);
|
|
302
|
+
console.log(` Memories: ${memCount}`);
|
|
303
|
+
console.log(` Embeddings: ${embCount}/${memCount} (${memCount ? Math.round(embCount / memCount * 100) : 0}%)`);
|
|
304
|
+
console.log(` Tags: ${tagCount} unique\n`);
|
|
305
|
+
if (projects.length) {
|
|
306
|
+
console.log(` Projects:`);
|
|
307
|
+
for (const p of projects)
|
|
308
|
+
console.log(` ${p.project}: ${p.c}`);
|
|
309
|
+
console.log("");
|
|
310
|
+
}
|
|
311
|
+
if (types.length) {
|
|
312
|
+
console.log(` Types:`);
|
|
313
|
+
for (const t of types)
|
|
314
|
+
console.log(` ${t.type}: ${t.c}`);
|
|
315
|
+
console.log("");
|
|
316
|
+
}
|
|
317
|
+
if (topTags.length) {
|
|
318
|
+
console.log(` Top tags:`);
|
|
319
|
+
for (const t of topTags)
|
|
320
|
+
console.log(` ${t.tag}: ${t.c}`);
|
|
321
|
+
}
|
|
322
|
+
db.close();
|
|
323
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -217,7 +217,7 @@ async function handleSave(args) {
|
|
|
217
217
|
if (GEMINI_API_KEY) {
|
|
218
218
|
getEmbedding(`${title}\n${content}`).then((vec) => {
|
|
219
219
|
if (vec) {
|
|
220
|
-
runSql(`INSERT OR REPLACE INTO embeddings (memory_id, vector) VALUES (?, ?)`, [id, vec.buffer]);
|
|
220
|
+
runSql(`INSERT OR REPLACE INTO embeddings (memory_id, vector) VALUES (?, ?)`, [id, new Uint8Array(vec.buffer)]);
|
|
221
221
|
persist();
|
|
222
222
|
}
|
|
223
223
|
});
|
|
@@ -425,7 +425,7 @@ async function handleUpdate(args) {
|
|
|
425
425
|
const newTitle = args.title || existing[0].title;
|
|
426
426
|
getEmbedding(`${newTitle}\n${newContent}`).then((vec) => {
|
|
427
427
|
if (vec) {
|
|
428
|
-
runSql(`INSERT OR REPLACE INTO embeddings (memory_id, vector) VALUES (?, ?)`, [id, vec.buffer]);
|
|
428
|
+
runSql(`INSERT OR REPLACE INTO embeddings (memory_id, vector) VALUES (?, ?)`, [id, new Uint8Array(vec.buffer)]);
|
|
429
429
|
persist();
|
|
430
430
|
}
|
|
431
431
|
});
|
|
@@ -462,7 +462,7 @@ async function handleTags(args) {
|
|
|
462
462
|
return ok(`# Tags (${rows.length})\n\n${lines.join("\n")}`);
|
|
463
463
|
}
|
|
464
464
|
// ---- MCP Server -----------------------------------------------------------
|
|
465
|
-
const server = new Server({ name: "mcp-simple-memory", version: "0.
|
|
465
|
+
const server = new Server({ name: "mcp-simple-memory", version: "0.3.0" }, { capabilities: { tools: {} } });
|
|
466
466
|
const tools = [
|
|
467
467
|
{
|
|
468
468
|
name: "mem_save",
|
|
@@ -620,7 +620,7 @@ async function main() {
|
|
|
620
620
|
await initDb();
|
|
621
621
|
const transport = new StdioServerTransport();
|
|
622
622
|
await server.connect(transport);
|
|
623
|
-
console.error(`[mcp-simple-memory] v0.
|
|
623
|
+
console.error(`[mcp-simple-memory] v0.3.0 | DB: ${DB_PATH} | Embeddings: ${GEMINI_API_KEY ? "ON" : "OFF"}`);
|
|
624
624
|
}
|
|
625
625
|
main().catch((e) => {
|
|
626
626
|
console.error(`[mcp-simple-memory] Fatal: ${e}`);
|
package/package.json
CHANGED