th-memory-mcp 1.2.2 → 2.1.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.
Files changed (43) hide show
  1. package/ARCHITECTURE_v2.md +1582 -0
  2. package/README.md +214 -187
  3. package/README.th.md +28 -7
  4. package/dist/core/consolidation-engine.js +87 -0
  5. package/dist/core/context-engine.js +50 -0
  6. package/dist/core/graph-engine.js +67 -0
  7. package/dist/core/lifecycle-engine.js +76 -0
  8. package/dist/core/retrieval-engine.js +40 -0
  9. package/dist/core/temporal-engine.js +73 -0
  10. package/dist/db/index.js +111 -0
  11. package/dist/db/migrations.js +160 -0
  12. package/dist/db/repositories/memories.js +52 -0
  13. package/dist/db.js +3 -0
  14. package/dist/index.js +42 -0
  15. package/dist/lib/embed.js +8 -5
  16. package/dist/memory/conflict-resolver.js +125 -0
  17. package/dist/memory/decay.js +30 -0
  18. package/dist/memory/deduplicator.js +51 -0
  19. package/dist/memory/scorer.js +44 -0
  20. package/dist/memory/source-weights.js +13 -0
  21. package/dist/memory/types.js +41 -0
  22. package/dist/retrieval/fts.js +22 -0
  23. package/dist/retrieval/fusion.js +11 -0
  24. package/dist/retrieval/scorer.js +19 -0
  25. package/dist/retrieval/vector.js +29 -0
  26. package/dist/tools/consolidate.js +63 -0
  27. package/dist/tools/context.js +55 -0
  28. package/dist/tools/export_memory.js +1 -1
  29. package/dist/tools/extract_memories.js +90 -0
  30. package/dist/tools/forget.js +1 -1
  31. package/dist/tools/history.js +1 -1
  32. package/dist/tools/import_memory.js +95 -0
  33. package/dist/tools/lesson.js +1 -1
  34. package/dist/tools/link_memory.js +31 -0
  35. package/dist/tools/memory_stats.js +1 -1
  36. package/dist/tools/merge_memory.js +49 -0
  37. package/dist/tools/profile.js +1 -1
  38. package/dist/tools/recall.js +1 -1
  39. package/dist/tools/recent_interactions.js +1 -1
  40. package/dist/tools/remember.js +1 -1
  41. package/dist/tools/update_memory.js +98 -0
  42. package/package.json +46 -46
  43. package/design.md +0 -308
@@ -0,0 +1,90 @@
1
+ import { z } from "zod";
2
+ import { db, ok, err } from "../db/index.js";
3
+ import { createMemory } from "../db/repositories/memories.js";
4
+ import { deduplicate } from "../memory/deduplicator.js";
5
+ // Deterministic, LLM-free intent heuristics (spec §19 "optional extraction").
6
+ // Scans captured interactions and proposes memory candidates. Safe by default:
7
+ // dry-run proposes; pass apply=true to actually create memories (source=captured).
8
+ const INTENT_PATTERNS = [
9
+ { re: /(?:remember|จำไว้ว่า|บันทึกว่า)\s+(?:that\s+)?(.+)/i, type: "FACT" },
10
+ {
11
+ re: /(?:i prefer|my preference is|ผมชอบ|ฉันชอบ|เราชอบ)\s+(.+)/i,
12
+ type: "PREFERENCE",
13
+ },
14
+ {
15
+ re: /(?:i use|เราใช้|ฉันใช้)\s+([^\s,]+)\s+(?:for|ในการ|เพื่อ)\s+(.+)/i,
16
+ type: "PREFERENCE",
17
+ },
18
+ {
19
+ re: /(?:don'?t use|ห้ามใช้|อย่าใช้)\s+(.+?)(?:,\s*(?:use|ใช้)\s+(.+))?/i,
20
+ type: "LESSON",
21
+ },
22
+ {
23
+ re: /(?:instead|use)\s+(.+?)\s+(?:rather than|แทน)\s+(.+)/i,
24
+ type: "LESSON",
25
+ },
26
+ ];
27
+ export const extractMemoriesInput = {
28
+ limit: z
29
+ .number()
30
+ .int()
31
+ .min(1)
32
+ .max(200)
33
+ .optional()
34
+ .describe("Max recent interactions to scan (default 50)"),
35
+ kind: z
36
+ .enum(["prompt", "tool_call", "error"])
37
+ .optional()
38
+ .describe("Interaction kind to scan (default prompt)"),
39
+ apply: z
40
+ .boolean()
41
+ .optional()
42
+ .describe("Create the proposed memories (default false = propose only)"),
43
+ };
44
+ export function extractMemoriesHandler(args) {
45
+ try {
46
+ const limit = args.limit ?? 50;
47
+ const kind = args.kind ?? "prompt";
48
+ const rows = db
49
+ .prepare("SELECT id, content FROM interactions WHERE kind = ? ORDER BY id DESC LIMIT ?")
50
+ .all(kind, limit);
51
+ const candidates = [];
52
+ for (const r of rows) {
53
+ for (const p of INTENT_PATTERNS) {
54
+ const m = r.content.match(p.re);
55
+ if (m) {
56
+ const clause = (m[1] || "").trim();
57
+ if (clause.length >= 3) {
58
+ candidates.push({
59
+ interactionId: r.id,
60
+ type: p.type,
61
+ content: clause,
62
+ });
63
+ }
64
+ break;
65
+ }
66
+ }
67
+ }
68
+ const distinct = candidates.filter((c) => deduplicate(c.type, c.content).verdict === "distinct");
69
+ if (args.apply === true) {
70
+ let n = 0;
71
+ for (const c of distinct) {
72
+ createMemory({
73
+ type: c.type,
74
+ content: c.content,
75
+ source: "captured",
76
+ });
77
+ n++;
78
+ }
79
+ return ok(`extracted and created ${n} memories from interactions (${candidates.length} candidates, ${candidates.length - distinct.length} duplicates skipped)`);
80
+ }
81
+ const preview = distinct
82
+ .slice(0, 20)
83
+ .map((c) => `[${c.type}] ${c.content}`)
84
+ .join("\n") || "(no candidates)";
85
+ return ok(`proposed ${distinct.length} memory candidate(s) from ${rows.length} ${kind} interactions (dry-run; pass apply=true to create):\n${preview}`);
86
+ }
87
+ catch (e) {
88
+ return err(e instanceof Error ? e.message : String(e));
89
+ }
90
+ }
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { db, removeSearchIndex, removeEmbedding, ok, err, } from "../db.js";
2
+ import { db, removeSearchIndex, removeEmbedding, ok, err, } from "../db/index.js";
3
3
  export const forgetInput = {
4
4
  target_id: z
5
5
  .number()
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { db, escapeLike, truncate, ok, err, } from "../db.js";
2
+ import { db, escapeLike, truncate, ok, err, } from "../db/index.js";
3
3
  export const searchHistoryInput = {
4
4
  query: z.string().min(1).max(500).describe("Text to search in past prompts"),
5
5
  limit: z
@@ -0,0 +1,95 @@
1
+ import { z } from "zod";
2
+ import { readFileSync } from "node:fs";
3
+ import { join, dirname, resolve, sep } from "node:path";
4
+ import { DB_PATH, ok, err } from "../db/index.js";
5
+ import { createMemory } from "../db/repositories/memories.js";
6
+ import { deduplicate } from "../memory/deduplicator.js";
7
+ import { MEMORY_TYPES } from "../memory/types.js";
8
+ import { EXPORTS_DIRNAME } from "../lib/config.js";
9
+ export const importMemoryInput = {
10
+ file: z
11
+ .string()
12
+ .optional()
13
+ .describe("Path to a .json export file (must be inside data/exports/)"),
14
+ json: z
15
+ .string()
16
+ .optional()
17
+ .describe("Inline JSON: an array of memory objects, or { memories: [...] }"),
18
+ apply: z
19
+ .boolean()
20
+ .optional()
21
+ .describe("Actually insert memories (default false = dry run, just report)"),
22
+ };
23
+ export function importMemoryHandler(args) {
24
+ try {
25
+ let raw;
26
+ if (args.json) {
27
+ raw = args.json;
28
+ }
29
+ else if (args.file) {
30
+ const exportDir = join(dirname(DB_PATH), EXPORTS_DIRNAME);
31
+ const full = resolve(args.file);
32
+ const allowed = resolve(exportDir);
33
+ if (!full.startsWith(allowed + sep))
34
+ return err(`file must be inside ${exportDir}`);
35
+ if (!full.endsWith(".json"))
36
+ return err("file must end with .json");
37
+ raw = readFileSync(full, "utf8");
38
+ }
39
+ else {
40
+ return err("provide either file or json");
41
+ }
42
+ let parsed;
43
+ try {
44
+ parsed = JSON.parse(raw);
45
+ }
46
+ catch {
47
+ return err("invalid JSON");
48
+ }
49
+ const items = Array.isArray(parsed)
50
+ ? parsed
51
+ : Array.isArray(parsed.memories)
52
+ ? (parsed.memories)
53
+ : [];
54
+ let wouldImport = 0;
55
+ let skipped = 0;
56
+ let invalid = 0;
57
+ const log = [];
58
+ for (const it of items) {
59
+ if (!it ||
60
+ typeof it.content !== "string" ||
61
+ !MEMORY_TYPES.includes(it.type)) {
62
+ invalid++;
63
+ continue;
64
+ }
65
+ const dup = deduplicate(it.type, it.content);
66
+ if (dup.verdict === "duplicate") {
67
+ skipped++;
68
+ log.push(`skip duplicate -> existing ${dup.existingId}`);
69
+ continue;
70
+ }
71
+ wouldImport++;
72
+ if (args.apply === true) {
73
+ createMemory({
74
+ type: it.type,
75
+ content: it.content,
76
+ summary: typeof it.summary === "string" ? it.summary : null,
77
+ source: it.source ?? "imported",
78
+ confidence: typeof it.confidence === "number" ? it.confidence : 0.7,
79
+ importance: typeof it.importance === "number" ? it.importance : 0.5,
80
+ projectId: typeof it.projectId === "string" ? it.projectId : null,
81
+ sessionId: typeof it.sessionId === "string" ? it.sessionId : null,
82
+ validFrom: typeof it.validFrom === "string" ? it.validFrom : null,
83
+ validUntil: typeof it.validUntil === "string" ? it.validUntil : null,
84
+ metadata: it.metadata ?? null,
85
+ });
86
+ }
87
+ }
88
+ const mode = args.apply === true ? "applied" : "dry-run";
89
+ const summary = `import ${mode}: ${wouldImport} to import, ${skipped} duplicate(s) skipped, ${invalid} invalid`;
90
+ return ok(log.length ? `${summary}\n${log.join("\n")}` : summary);
91
+ }
92
+ catch (e) {
93
+ return err(e instanceof Error ? e.message : String(e));
94
+ }
95
+ }
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { db, nowISO, syncSearchIndex, upsertEmbedding, ok, err, } from "../db.js";
2
+ import { db, nowISO, syncSearchIndex, upsertEmbedding, ok, err, } from "../db/index.js";
3
3
  import { embed } from "../lib/embed.js";
4
4
  export const saveLessonInput = {
5
5
  situation: z.string().min(1).max(1000).describe("The original situation/context"),
@@ -0,0 +1,31 @@
1
+ import { z } from "zod";
2
+ import { ok, err } from "../db/index.js";
3
+ import { linkMemories } from "../core/graph-engine.js";
4
+ import { getMemoryById } from "../db/repositories/memories.js";
5
+ import { LINK_RELATIONS } from "../memory/types.js";
6
+ export const linkMemoryInput = {
7
+ sourceId: z.number().int().describe("Source memory id"),
8
+ targetId: z.number().int().describe("Target memory id"),
9
+ relation: z
10
+ .enum(LINK_RELATIONS)
11
+ .describe("Link relation (supports/contradicts/supersedes/derived_from/related_to/caused_by/depends_on)"),
12
+ };
13
+ export function linkMemoryHandler(args) {
14
+ try {
15
+ const src = getMemoryById(args.sourceId);
16
+ const tgt = getMemoryById(args.targetId);
17
+ if (!src)
18
+ return err(`source memory ${args.sourceId} not found`);
19
+ if (!tgt)
20
+ return err(`target memory ${args.targetId} not found`);
21
+ if (src.status === "deleted" || tgt.status === "deleted")
22
+ return err("cannot link deleted memories");
23
+ if (src.id === tgt.id)
24
+ return err("cannot link a memory to itself");
25
+ linkMemories(args.sourceId, args.targetId, args.relation);
26
+ return ok(`linked memory ${args.sourceId} -[${args.relation}]-> ${args.targetId}`);
27
+ }
28
+ catch (e) {
29
+ return err(e instanceof Error ? e.message : String(e));
30
+ }
31
+ }
@@ -1,5 +1,5 @@
1
1
  import { statSync } from "node:fs";
2
- import { db, DB_PATH, truncate, ok, err, } from "../db.js";
2
+ import { db, DB_PATH, truncate, ok, err, } from "../db/index.js";
3
3
  import { CAPTURE_KINDS } from "../lib/capture-core.js";
4
4
  export const STATS_BUDGET = 1500;
5
5
  const kindCounts = db.prepare("SELECT kind, COUNT(*) AS n FROM interactions GROUP BY kind");
@@ -0,0 +1,49 @@
1
+ import { z } from "zod";
2
+ import { db, nowISO, ok, err } from "../db/index.js";
3
+ import { getMemoryById } from "../db/repositories/memories.js";
4
+ import { supersede } from "../core/lifecycle-engine.js";
5
+ export const mergeMemoryInput = {
6
+ sourceId: z
7
+ .number()
8
+ .int()
9
+ .describe("Memory to merge away (becomes superseded)"),
10
+ targetId: z
11
+ .number()
12
+ .int()
13
+ .describe("Canonical memory to keep (becomes active)"),
14
+ };
15
+ function mergeMetadata(existing, mergedId) {
16
+ let obj = {};
17
+ if (existing) {
18
+ try {
19
+ obj = JSON.parse(existing);
20
+ }
21
+ catch {
22
+ obj = {};
23
+ }
24
+ }
25
+ const from = Array.isArray(obj.merged_from) ? obj.merged_from : [];
26
+ from.push(mergedId);
27
+ obj.merged_from = from;
28
+ return JSON.stringify(obj);
29
+ }
30
+ export function mergeMemoryHandler(args) {
31
+ try {
32
+ const src = getMemoryById(args.sourceId);
33
+ const tgt = getMemoryById(args.targetId);
34
+ if (!src)
35
+ return err(`source memory ${args.sourceId} not found`);
36
+ if (!tgt)
37
+ return err(`target memory ${args.targetId} not found`);
38
+ if (src.id === tgt.id)
39
+ return err("cannot merge a memory into itself");
40
+ if (src.status === "deleted" || tgt.status === "deleted")
41
+ return err("cannot merge deleted memories");
42
+ db.prepare("UPDATE memories SET metadata = ?, updated_at = ? WHERE id = ?").run(mergeMetadata(tgt.metadata, src.id), nowISO(), tgt.id);
43
+ supersede(src.id, tgt.id);
44
+ return ok(`merged memory ${src.id} into ${tgt.id} (source superseded, provenance recorded in metadata.merged_from)`);
45
+ }
46
+ catch (e) {
47
+ return err(e instanceof Error ? e.message : String(e));
48
+ }
49
+ }
@@ -1,4 +1,4 @@
1
- import { db, truncate, ok, err, } from "../db.js";
1
+ import { db, truncate, ok, err, } from "../db/index.js";
2
2
  export const PROFILE_BUDGET = 3000;
3
3
  const PROFILE_SECTION_MAX = 400;
4
4
  const PREF_CATEGORY_MAX = 30;
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { db, buildFtsMatch, escapeLike, truncate, getAllEmbeddings, ok, err, } from "../db.js";
2
+ import { db, buildFtsMatch, escapeLike, truncate, getAllEmbeddings, ok, err, } from "../db/index.js";
3
3
  import { embed, cosine, deserialize } from "../lib/embed.js";
4
4
  export const recallInput = {
5
5
  topic: z.string().min(1).max(500).describe("Topic to recall from memory"),
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { db, truncate, ok, err } from "../db.js";
2
+ import { db, truncate, ok, err } from "../db/index.js";
3
3
  import { CAPTURE_KINDS, } from "../lib/capture-core.js";
4
4
  export const recentInteractionsInput = {
5
5
  limit: z
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { db, nowISO, syncSearchIndex, upsertEmbedding, ok, err, } from "../db.js";
2
+ import { db, nowISO, syncSearchIndex, upsertEmbedding, ok, err, } from "../db/index.js";
3
3
  import { embed } from "../lib/embed.js";
4
4
  export const rememberInput = {
5
5
  category: z
@@ -0,0 +1,98 @@
1
+ import { z } from "zod";
2
+ import { db, nowISO, ok, err, } from "../db/index.js";
3
+ import { getMemoryById, createMemory, syncMemoryIndex, } from "../db/repositories/memories.js";
4
+ import { supersede } from "../core/lifecycle-engine.js";
5
+ export const updateMemoryInput = {
6
+ id: z.number().int().describe("Memory id to update"),
7
+ content: z
8
+ .string()
9
+ .max(2000)
10
+ .optional()
11
+ .describe("New content. When provided, a superseding memory is created (supersede=true) unless supersede=false."),
12
+ summary: z.string().max(2000).nullable().optional().describe("New summary"),
13
+ importance: z.number().min(0).max(1).optional(),
14
+ confidence: z.number().min(0).max(1).optional(),
15
+ validUntil: z
16
+ .string()
17
+ .nullable()
18
+ .optional()
19
+ .describe("ISO timestamp or null to clear"),
20
+ metadata: z.unknown().optional().describe("New metadata object (replaces)"),
21
+ supersede: z
22
+ .boolean()
23
+ .optional()
24
+ .describe("If content changes, create a superseding memory instead of editing in place (default true)"),
25
+ };
26
+ export function updateMemoryHandler(args) {
27
+ try {
28
+ const mem = getMemoryById(args.id);
29
+ if (!mem)
30
+ return err(`memory ${args.id} not found`);
31
+ if (mem.status === "deleted")
32
+ return err("cannot update a deleted memory");
33
+ const supersedeContent = args.content !== undefined && (args.supersede ?? true);
34
+ if (supersedeContent) {
35
+ const newId = createMemory({
36
+ type: mem.type,
37
+ content: args.content,
38
+ summary: args.summary ?? mem.summary,
39
+ source: mem.source,
40
+ confidence: args.confidence ?? mem.confidence,
41
+ importance: args.importance ?? mem.importance,
42
+ salience: mem.salience,
43
+ projectId: mem.project_id,
44
+ sessionId: mem.session_id,
45
+ validFrom: mem.valid_from,
46
+ validUntil: args.validUntil !== undefined ? args.validUntil : mem.valid_until,
47
+ metadata: args.metadata !== undefined
48
+ ? args.metadata
49
+ : mem.metadata
50
+ ? JSON.parse(mem.metadata)
51
+ : null,
52
+ });
53
+ supersede(mem.id, newId);
54
+ return ok(`created superseding memory id=${newId} for old id=${mem.id} (old now superseded)`);
55
+ }
56
+ const sets = [];
57
+ const vals = [];
58
+ if (args.summary !== undefined) {
59
+ sets.push("summary = ?");
60
+ vals.push(args.summary);
61
+ }
62
+ if (args.importance !== undefined) {
63
+ sets.push("importance = ?");
64
+ vals.push(args.importance);
65
+ }
66
+ if (args.confidence !== undefined) {
67
+ sets.push("confidence = ?");
68
+ vals.push(args.confidence);
69
+ }
70
+ if (args.validUntil !== undefined) {
71
+ sets.push("valid_until = ?");
72
+ vals.push(args.validUntil);
73
+ }
74
+ if (args.metadata !== undefined) {
75
+ sets.push("metadata = ?");
76
+ vals.push(JSON.stringify(args.metadata));
77
+ }
78
+ if (args.content !== undefined) {
79
+ sets.push("content = ?");
80
+ vals.push(args.content);
81
+ }
82
+ if (sets.length === 0)
83
+ return ok(`no mutable fields provided; memory ${mem.id} unchanged`);
84
+ sets.push("updated_at = ?");
85
+ vals.push(nowISO());
86
+ vals.push(mem.id);
87
+ db.prepare(`UPDATE memories SET ${sets.join(", ")} WHERE id = ?`).run(...vals);
88
+ if (args.content !== undefined) {
89
+ const m = getMemoryById(mem.id);
90
+ if (m)
91
+ syncMemoryIndex(m.id, m.type, m.content);
92
+ }
93
+ return ok(`updated memory ${mem.id} in place`);
94
+ }
95
+ catch (e) {
96
+ return err(e instanceof Error ? e.message : String(e));
97
+ }
98
+ }
package/package.json CHANGED
@@ -1,46 +1,46 @@
1
- {
2
- "name": "th-memory-mcp",
3
- "version": "1.2.2",
4
- "mcpName": "io.github.worakorn-prince/th-memory-mcp",
5
- "description": "Adaptive Memory MCP server - SQLite-backed memory for OpenCode",
6
- "author": "worakorn-prince",
7
- "repository": {
8
- "type": "git",
9
- "url": "https://github.com/worakorn-prince/th-memory-mcp.git"
10
- },
11
- "type": "module",
12
- "main": "dist/index.js",
13
- "bin": {
14
- "th-memory-mcp": "dist/index.js"
15
- },
16
- "files": [
17
- "dist",
18
- "README.md",
19
- "LICENSE",
20
- "design.md",
21
- "opencode.example.json",
22
- "AGENTS.memory.example.md"
23
- ],
24
- "scripts": {
25
- "build": "tsc",
26
- "prepublishOnly": "npm run build",
27
- "start": "node dist/index.js",
28
- "inspect": "npx @modelcontextprotocol/inspector node dist/index.js",
29
- "test": "npm run build && node test/capture.test.mjs && node test/distill.test.mjs && node test/smoke.mjs",
30
- "distill": "node dist/distill.js",
31
- "quickstart": "npm run build && node scripts/quickstart.mjs"
32
- },
33
- "engines": {
34
- "node": ">=20"
35
- },
36
- "dependencies": {
37
- "@modelcontextprotocol/sdk": "^1.0.0",
38
- "better-sqlite3": "^12.0.0",
39
- "zod": "^3.25.0"
40
- },
41
- "devDependencies": {
42
- "@types/better-sqlite3": "^7.6.0",
43
- "@types/node": "^22.0.0",
44
- "typescript": "^5.6.0"
45
- }
46
- }
1
+ {
2
+ "name": "th-memory-mcp",
3
+ "version": "2.1.0",
4
+ "mcpName": "io.github.worakorn-prince/th-memory-mcp",
5
+ "description": "Adaptive Memory MCP server - SQLite-backed memory for OpenCode",
6
+ "author": "worakorn-prince",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/worakorn-prince/th-memory-mcp.git"
10
+ },
11
+ "type": "module",
12
+ "main": "dist/index.js",
13
+ "bin": {
14
+ "th-memory-mcp": "dist/index.js"
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md",
19
+ "LICENSE",
20
+ "ARCHITECTURE_v2.md",
21
+ "opencode.example.json",
22
+ "AGENTS.memory.example.md"
23
+ ],
24
+ "scripts": {
25
+ "build": "tsc",
26
+ "prepublishOnly": "npm run build",
27
+ "start": "node dist/index.js",
28
+ "inspect": "npx @modelcontextprotocol/inspector node dist/index.js",
29
+ "test": "npm run build && node test/capture.test.mjs && node test/distill.test.mjs && node test/lifecycle.test.mjs && node test/temporal.test.mjs && node test/conflict.test.mjs && node test/retrieval.test.mjs && node test/graph.test.mjs && node test/context.test.mjs && node test/consolidation.test.mjs && node test/benchmark.test.mjs && node test/security.test.mjs && node test/tools_v21.test.mjs && node test/smoke.mjs",
30
+ "distill": "node dist/distill.js",
31
+ "quickstart": "npm run build && node scripts/quickstart.mjs"
32
+ },
33
+ "engines": {
34
+ "node": ">=20"
35
+ },
36
+ "dependencies": {
37
+ "@modelcontextprotocol/sdk": "^1.0.0",
38
+ "better-sqlite3": "^12.0.0",
39
+ "zod": "^3.25.0"
40
+ },
41
+ "devDependencies": {
42
+ "@types/better-sqlite3": "^7.6.0",
43
+ "@types/node": "^22.0.0",
44
+ "typescript": "^5.6.0"
45
+ }
46
+ }