mnemon-mcp 1.0.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 (104) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/CONTRIBUTING.md +59 -0
  3. package/LICENSE +21 -0
  4. package/README.md +434 -0
  5. package/SECURITY.md +29 -0
  6. package/config.example.json +52 -0
  7. package/dist/.tsbuildinfo +1 -0
  8. package/dist/db.d.ts +16 -0
  9. package/dist/db.d.ts.map +1 -0
  10. package/dist/db.js +360 -0
  11. package/dist/db.js.map +1 -0
  12. package/dist/embedder.d.ts +22 -0
  13. package/dist/embedder.d.ts.map +1 -0
  14. package/dist/embedder.js +109 -0
  15. package/dist/embedder.js.map +1 -0
  16. package/dist/import/cli.d.ts +12 -0
  17. package/dist/import/cli.d.ts.map +1 -0
  18. package/dist/import/cli.js +105 -0
  19. package/dist/import/cli.js.map +1 -0
  20. package/dist/import/config-loader.d.ts +29 -0
  21. package/dist/import/config-loader.d.ts.map +1 -0
  22. package/dist/import/config-loader.js +140 -0
  23. package/dist/import/config-loader.js.map +1 -0
  24. package/dist/import/kb-config.d.ts +27 -0
  25. package/dist/import/kb-config.d.ts.map +1 -0
  26. package/dist/import/kb-config.js +10 -0
  27. package/dist/import/kb-config.js.map +1 -0
  28. package/dist/import/kb-import.d.ts +45 -0
  29. package/dist/import/kb-import.d.ts.map +1 -0
  30. package/dist/import/kb-import.js +285 -0
  31. package/dist/import/kb-import.js.map +1 -0
  32. package/dist/import/md-parser.d.ts +35 -0
  33. package/dist/import/md-parser.d.ts.map +1 -0
  34. package/dist/import/md-parser.js +104 -0
  35. package/dist/import/md-parser.js.map +1 -0
  36. package/dist/index-http.d.ts +12 -0
  37. package/dist/index-http.d.ts.map +1 -0
  38. package/dist/index-http.js +202 -0
  39. package/dist/index-http.js.map +1 -0
  40. package/dist/index.d.ts +9 -0
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.js +56 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/server.d.ts +16 -0
  45. package/dist/server.d.ts.map +1 -0
  46. package/dist/server.js +341 -0
  47. package/dist/server.js.map +1 -0
  48. package/dist/stemmer.d.ts +29 -0
  49. package/dist/stemmer.d.ts.map +1 -0
  50. package/dist/stemmer.js +68 -0
  51. package/dist/stemmer.js.map +1 -0
  52. package/dist/stop-words.d.ts +12 -0
  53. package/dist/stop-words.d.ts.map +1 -0
  54. package/dist/stop-words.js +112 -0
  55. package/dist/stop-words.js.map +1 -0
  56. package/dist/tools/memory-add.d.ts +15 -0
  57. package/dist/tools/memory-add.d.ts.map +1 -0
  58. package/dist/tools/memory-add.js +120 -0
  59. package/dist/tools/memory-add.js.map +1 -0
  60. package/dist/tools/memory-delete.d.ts +10 -0
  61. package/dist/tools/memory-delete.d.ts.map +1 -0
  62. package/dist/tools/memory-delete.js +39 -0
  63. package/dist/tools/memory-delete.js.map +1 -0
  64. package/dist/tools/memory-export.d.ts +10 -0
  65. package/dist/tools/memory-export.d.ts.map +1 -0
  66. package/dist/tools/memory-export.js +102 -0
  67. package/dist/tools/memory-export.js.map +1 -0
  68. package/dist/tools/memory-health.d.ts +11 -0
  69. package/dist/tools/memory-health.d.ts.map +1 -0
  70. package/dist/tools/memory-health.js +79 -0
  71. package/dist/tools/memory-health.js.map +1 -0
  72. package/dist/tools/memory-inspect.d.ts +10 -0
  73. package/dist/tools/memory-inspect.d.ts.map +1 -0
  74. package/dist/tools/memory-inspect.js +139 -0
  75. package/dist/tools/memory-inspect.js.map +1 -0
  76. package/dist/tools/memory-search.d.ts +16 -0
  77. package/dist/tools/memory-search.d.ts.map +1 -0
  78. package/dist/tools/memory-search.js +459 -0
  79. package/dist/tools/memory-search.js.map +1 -0
  80. package/dist/tools/memory-update.d.ts +11 -0
  81. package/dist/tools/memory-update.d.ts.map +1 -0
  82. package/dist/tools/memory-update.js +142 -0
  83. package/dist/tools/memory-update.js.map +1 -0
  84. package/dist/tools/style-extract.d.ts +40 -0
  85. package/dist/tools/style-extract.d.ts.map +1 -0
  86. package/dist/tools/style-extract.js +43 -0
  87. package/dist/tools/style-extract.js.map +1 -0
  88. package/dist/tools/utils.d.ts +28 -0
  89. package/dist/tools/utils.d.ts.map +1 -0
  90. package/dist/tools/utils.js +33 -0
  91. package/dist/tools/utils.js.map +1 -0
  92. package/dist/types.d.ts +216 -0
  93. package/dist/types.d.ts.map +1 -0
  94. package/dist/types.js +6 -0
  95. package/dist/types.js.map +1 -0
  96. package/dist/validation.d.ts +122 -0
  97. package/dist/validation.d.ts.map +1 -0
  98. package/dist/validation.js +96 -0
  99. package/dist/validation.js.map +1 -0
  100. package/dist/vector.d.ts +30 -0
  101. package/dist/vector.d.ts.map +1 -0
  102. package/dist/vector.js +90 -0
  103. package/dist/vector.js.map +1 -0
  104. package/package.json +75 -0
@@ -0,0 +1,202 @@
1
+ /**
2
+ * mnemon-mcp: HTTP transport entry point.
3
+ *
4
+ * Exposes the same tools as index.ts via StreamableHTTP transport.
5
+ * Use for remote / multi-server deployments instead of stdio.
6
+ *
7
+ * Environment variables:
8
+ * MNEMON_PORT - Listening port (default: 3000)
9
+ * MNEMON_AUTH_TOKEN - Bearer token for all requests (optional but recommended)
10
+ */
11
+ import { createServer } from "node:http";
12
+ import { timingSafeEqual } from "node:crypto";
13
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
14
+ import { openDatabase } from "./db.js";
15
+ import { createMcpServer, loadExtraStopWords, version } from "./server.js";
16
+ import { createEmbedder } from "./embedder.js";
17
+ import { loadSqliteVec, createVecTable } from "./vector.js";
18
+ // ---------------------------------------------------------------------------
19
+ // Database + config
20
+ // ---------------------------------------------------------------------------
21
+ let db;
22
+ try {
23
+ db = openDatabase();
24
+ }
25
+ catch (err) {
26
+ console.error(`[mnemon-mcp http] Failed to open database: ${err instanceof Error ? err.message : String(err)}`);
27
+ process.exit(1);
28
+ }
29
+ loadExtraStopWords();
30
+ // Optional: load sqlite-vec extension and create vector table
31
+ const vecAvailable = loadSqliteVec(db);
32
+ let embedder = null;
33
+ try {
34
+ embedder = createEmbedder();
35
+ if (embedder && vecAvailable) {
36
+ createVecTable(db, embedder.dimensions);
37
+ }
38
+ }
39
+ catch {
40
+ // Embedder creation is best-effort
41
+ }
42
+ // ---------------------------------------------------------------------------
43
+ // Auth — timing-safe comparison to prevent token extraction via timing attack
44
+ // ---------------------------------------------------------------------------
45
+ const AUTH_TOKEN = process.env["MNEMON_AUTH_TOKEN"];
46
+ const MAX_BODY_BYTES = 1_048_576; // 1 MB
47
+ const CORS_ORIGIN = process.env["MNEMON_CORS_ORIGIN"] ?? "*";
48
+ // ---------------------------------------------------------------------------
49
+ // Rate limiting — simple token bucket per IP
50
+ // ---------------------------------------------------------------------------
51
+ const RATE_LIMIT = parseInt(process.env["MNEMON_RATE_LIMIT"] ?? "100", 10); // requests per window
52
+ const RATE_WINDOW_MS = 60_000; // 1 minute
53
+ const rateBuckets = new Map();
54
+ function isRateLimited(ip) {
55
+ if (RATE_LIMIT <= 0)
56
+ return false; // disabled
57
+ const now = Date.now();
58
+ const entry = rateBuckets.get(ip);
59
+ if (!entry || now >= entry.resetAt) {
60
+ rateBuckets.set(ip, { count: 1, resetAt: now + RATE_WINDOW_MS });
61
+ return false;
62
+ }
63
+ entry.count++;
64
+ return entry.count > RATE_LIMIT;
65
+ }
66
+ // Periodic cleanup of stale buckets (every 5 minutes)
67
+ setInterval(() => {
68
+ const now = Date.now();
69
+ for (const [ip, entry] of rateBuckets) {
70
+ if (now >= entry.resetAt)
71
+ rateBuckets.delete(ip);
72
+ }
73
+ }, 5 * 60_000).unref();
74
+ function isAuthorized(req) {
75
+ if (!AUTH_TOKEN)
76
+ return true;
77
+ const header = req.headers["authorization"] ?? "";
78
+ const expected = `Bearer ${AUTH_TOKEN}`;
79
+ if (header.length !== expected.length)
80
+ return false;
81
+ return timingSafeEqual(Buffer.from(header), Buffer.from(expected));
82
+ }
83
+ function rejectUnauthorized(res) {
84
+ res.writeHead(401, { "Content-Type": "application/json", "WWW-Authenticate": 'Bearer realm="mnemon-mcp"' });
85
+ res.end(JSON.stringify({ error: "Unauthorized" }));
86
+ }
87
+ // ---------------------------------------------------------------------------
88
+ // HTTP server — stateless mode: new transport + server per request
89
+ // ---------------------------------------------------------------------------
90
+ function setCorsHeaders(res) {
91
+ res.setHeader("Access-Control-Allow-Origin", CORS_ORIGIN);
92
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
93
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
94
+ res.setHeader("Access-Control-Max-Age", "86400");
95
+ }
96
+ async function handleHttpRequest(req, res) {
97
+ setCorsHeaders(res);
98
+ // CORS preflight
99
+ if (req.method === "OPTIONS") {
100
+ res.writeHead(204);
101
+ res.end();
102
+ return;
103
+ }
104
+ // Rate limiting
105
+ // Use socket IP directly — x-forwarded-for is trivially spoofable without a trusted proxy
106
+ const clientIp = req.socket.remoteAddress ?? "unknown";
107
+ if (isRateLimited(clientIp)) {
108
+ res.writeHead(429, { "Content-Type": "application/json" });
109
+ res.end(JSON.stringify({ error: "Too many requests. Try again later." }));
110
+ return;
111
+ }
112
+ if (!isAuthorized(req)) {
113
+ rejectUnauthorized(res);
114
+ return;
115
+ }
116
+ const url = new URL(req.url ?? "/", `http://localhost`);
117
+ // Health check endpoint
118
+ if (url.pathname === "/health" && req.method === "GET") {
119
+ res.writeHead(200, { "Content-Type": "application/json" });
120
+ res.end(JSON.stringify({ status: "ok", version }));
121
+ return;
122
+ }
123
+ if (url.pathname !== "/mcp") {
124
+ res.writeHead(404, { "Content-Type": "application/json" });
125
+ res.end(JSON.stringify({ error: "Not found. Use POST /mcp or GET /health" }));
126
+ return;
127
+ }
128
+ // Body size limit — check header first, then enforce on actual body
129
+ const contentLength = parseInt(req.headers["content-length"] ?? "0", 10);
130
+ if (contentLength > MAX_BODY_BYTES) {
131
+ res.writeHead(413, { "Content-Type": "application/json" });
132
+ res.end(JSON.stringify({ error: `Request body too large (max ${MAX_BODY_BYTES} bytes)` }));
133
+ return;
134
+ }
135
+ // Enforce body size limit on actual stream (handles chunked transfer encoding).
136
+ // The data listener runs concurrently with transport.handleRequest — if the body
137
+ // exceeds the limit, req.destroy() aborts the stream and the transport will fail.
138
+ let receivedBytes = 0;
139
+ const onData = (chunk) => {
140
+ receivedBytes += chunk.length;
141
+ if (receivedBytes > MAX_BODY_BYTES) {
142
+ req.removeListener("data", onData);
143
+ if (!res.headersSent) {
144
+ res.writeHead(413, { "Content-Type": "application/json" });
145
+ res.end(JSON.stringify({ error: `Request body too large (max ${MAX_BODY_BYTES} bytes)` }));
146
+ }
147
+ req.destroy();
148
+ }
149
+ };
150
+ req.on("data", onData);
151
+ const transport = new StreamableHTTPServerTransport({});
152
+ const server = createMcpServer(db, embedder);
153
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
154
+ await server.connect(transport);
155
+ try {
156
+ await transport.handleRequest(req, res);
157
+ }
158
+ finally {
159
+ req.removeListener("data", onData);
160
+ await server.close();
161
+ }
162
+ }
163
+ // ---------------------------------------------------------------------------
164
+ // Start + graceful shutdown
165
+ // ---------------------------------------------------------------------------
166
+ const portRaw = parseInt(process.env["MNEMON_PORT"] ?? "3000", 10);
167
+ if (Number.isNaN(portRaw) || portRaw < 1 || portRaw > 65535) {
168
+ console.error(`[mnemon-mcp http] Invalid MNEMON_PORT: "${process.env["MNEMON_PORT"]}". Must be 1-65535.`);
169
+ process.exit(1);
170
+ }
171
+ const PORT = portRaw;
172
+ const httpServer = createServer((req, res) => {
173
+ handleHttpRequest(req, res).catch((err) => {
174
+ console.error(`[mnemon-mcp http] Unhandled error: ${err instanceof Error ? err.message : String(err)}`);
175
+ if (!res.headersSent) {
176
+ res.writeHead(500, { "Content-Type": "application/json" });
177
+ res.end(JSON.stringify({ error: "Internal server error" }));
178
+ }
179
+ });
180
+ });
181
+ function shutdown() {
182
+ console.error("[mnemon-mcp http] Shutting down...");
183
+ httpServer.close(() => {
184
+ try {
185
+ db.close();
186
+ }
187
+ catch {
188
+ // Best-effort
189
+ }
190
+ process.exit(0);
191
+ });
192
+ // Force exit after 5s if graceful shutdown stalls
193
+ setTimeout(() => process.exit(1), 5000).unref();
194
+ }
195
+ process.on("SIGTERM", shutdown);
196
+ process.on("SIGINT", shutdown);
197
+ httpServer.listen(PORT, () => {
198
+ console.error(`[mnemon-mcp http] v${version} listening on port ${PORT}${AUTH_TOKEN ? " (auth enabled)" : " (no auth — set MNEMON_AUTH_TOKEN for production)"}`);
199
+ console.error(`[mnemon-mcp http] MCP endpoint: POST http://localhost:${PORT}/mcp`);
200
+ console.error(`[mnemon-mcp http] Health check: GET http://localhost:${PORT}/health`);
201
+ });
202
+ //# sourceMappingURL=index-http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-http.js","sourceRoot":"","sources":["../src/index-http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,YAAY,EAAmC,MAAM,WAAW,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAG9C,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AAEnG,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAC3E,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE5D,8EAA8E;AAC9E,oBAAoB;AACpB,8EAA8E;AAE9E,IAAI,EAAmC,CAAC;AAExC,IAAI,CAAC;IACH,EAAE,GAAG,YAAY,EAAE,CAAC;AACtB,CAAC;AAAC,OAAO,GAAG,EAAE,CAAC;IACb,OAAO,CAAC,KAAK,CAAC,8CAA8C,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChH,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,kBAAkB,EAAE,CAAC;AAErB,8DAA8D;AAC9D,MAAM,YAAY,GAAG,aAAa,CAAC,EAAE,CAAC,CAAC;AAEvC,IAAI,QAAQ,GAAsC,IAAI,CAAC;AACvD,IAAI,CAAC;IACH,QAAQ,GAAG,cAAc,EAAE,CAAC;IAC5B,IAAI,QAAQ,IAAI,YAAY,EAAE,CAAC;QAC7B,cAAc,CAAC,EAAE,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;IAC1C,CAAC;AACH,CAAC;AAAC,MAAM,CAAC;IACP,mCAAmC;AACrC,CAAC;AAED,8EAA8E;AAC9E,8EAA8E;AAC9E,8EAA8E;AAE9E,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;AACpD,MAAM,cAAc,GAAG,SAAS,CAAC,CAAC,OAAO;AACzC,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,IAAI,GAAG,CAAC;AAE7D,8EAA8E;AAC9E,6CAA6C;AAC7C,8EAA8E;AAE9E,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,IAAI,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,sBAAsB;AAClG,MAAM,cAAc,GAAG,MAAM,CAAC,CAAC,WAAW;AAO1C,MAAM,WAAW,GAAG,IAAI,GAAG,EAAuB,CAAC;AAEnD,SAAS,aAAa,CAAC,EAAU;IAC/B,IAAI,UAAU,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC,CAAC,WAAW;IAC9C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAClC,IAAI,CAAC,KAAK,IAAI,GAAG,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QACnC,WAAW,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,GAAG,cAAc,EAAE,CAAC,CAAC;QACjE,OAAO,KAAK,CAAC;IACf,CAAC;IACD,KAAK,CAAC,KAAK,EAAE,CAAC;IACd,OAAO,KAAK,CAAC,KAAK,GAAG,UAAU,CAAC;AAClC,CAAC;AAED,sDAAsD;AACtD,WAAW,CAAC,GAAG,EAAE;IACf,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,KAAK,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,WAAW,EAAE,CAAC;QACtC,IAAI,GAAG,IAAI,KAAK,CAAC,OAAO;YAAE,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACnD,CAAC;AACH,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,KAAK,EAAE,CAAC;AAEvB,SAAS,YAAY,CAAC,GAAoB;IACxC,IAAI,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC;IAC7B,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC;IAClD,MAAM,QAAQ,GAAG,UAAU,UAAU,EAAE,CAAC;IACxC,IAAI,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IACpD,OAAO,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;AACrE,CAAC;AAED,SAAS,kBAAkB,CAAC,GAAmB;IAC7C,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,2BAA2B,EAAE,CAAC,CAAC;IAC5G,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC;AACrD,CAAC;AAED,8EAA8E;AAC9E,mEAAmE;AACnE,8EAA8E;AAE9E,SAAS,cAAc,CAAC,GAAmB;IACzC,GAAG,CAAC,SAAS,CAAC,6BAA6B,EAAE,WAAW,CAAC,CAAC;IAC1D,GAAG,CAAC,SAAS,CAAC,8BAA8B,EAAE,oBAAoB,CAAC,CAAC;IACpE,GAAG,CAAC,SAAS,CAAC,8BAA8B,EAAE,6BAA6B,CAAC,CAAC;IAC7E,GAAG,CAAC,SAAS,CAAC,wBAAwB,EAAE,OAAO,CAAC,CAAC;AACnD,CAAC;AAED,KAAK,UAAU,iBAAiB,CAAC,GAAoB,EAAE,GAAmB;IACxE,cAAc,CAAC,GAAG,CAAC,CAAC;IAEpB,iBAAiB;IACjB,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAC7B,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QACnB,GAAG,CAAC,GAAG,EAAE,CAAC;QACV,OAAO;IACT,CAAC;IAED,gBAAgB;IAChB,0FAA0F;IAC1F,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,aAAa,IAAI,SAAS,CAAC;IACvD,IAAI,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5B,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,qCAAqC,EAAE,CAAC,CAAC,CAAC;QAC1E,OAAO;IACT,CAAC;IAED,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,kBAAkB,CAAC,GAAG,CAAC,CAAC;QACxB,OAAO;IACT,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,kBAAkB,CAAC,CAAC;IAExD,wBAAwB;IACxB,IAAI,GAAG,CAAC,QAAQ,KAAK,SAAS,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;QACvD,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;QACnD,OAAO;IACT,CAAC;IAED,IAAI,GAAG,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;QAC5B,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,yCAAyC,EAAE,CAAC,CAAC,CAAC;QAC9E,OAAO;IACT,CAAC;IAED,oEAAoE;IACpE,MAAM,aAAa,GAAG,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAC,IAAI,GAAG,EAAE,EAAE,CAAC,CAAC;IACzE,IAAI,aAAa,GAAG,cAAc,EAAE,CAAC;QACnC,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,+BAA+B,cAAc,SAAS,EAAE,CAAC,CAAC,CAAC;QAC3F,OAAO;IACT,CAAC;IAED,gFAAgF;IAChF,iFAAiF;IACjF,kFAAkF;IAClF,IAAI,aAAa,GAAG,CAAC,CAAC;IACtB,MAAM,MAAM,GAAG,CAAC,KAAa,EAAQ,EAAE;QACrC,aAAa,IAAI,KAAK,CAAC,MAAM,CAAC;QAC9B,IAAI,aAAa,GAAG,cAAc,EAAE,CAAC;YACnC,GAAG,CAAC,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YACnC,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;gBACrB,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;gBAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,+BAA+B,cAAc,SAAS,EAAE,CAAC,CAAC,CAAC;YAC7F,CAAC;YACD,GAAG,CAAC,OAAO,EAAE,CAAC;QAChB,CAAC;IACH,CAAC,CAAC;IACF,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEvB,MAAM,SAAS,GAAG,IAAI,6BAA6B,CAAC,EAAE,CAAC,CAAC;IACxD,MAAM,MAAM,GAAG,eAAe,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;IAC7C,8DAA8D;IAC9D,MAAM,MAAM,CAAC,OAAO,CAAC,SAAgB,CAAC,CAAC;IAEvC,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAC1C,CAAC;YAAS,CAAC;QACT,GAAG,CAAC,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACnC,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,4BAA4B;AAC5B,8EAA8E;AAE9E,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,MAAM,EAAE,EAAE,CAAC,CAAC;AACnE,IAAI,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,OAAO,GAAG,CAAC,IAAI,OAAO,GAAG,KAAK,EAAE,CAAC;IAC5D,OAAO,CAAC,KAAK,CAAC,2CAA2C,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,qBAAqB,CAAC,CAAC;IAC1G,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AACD,MAAM,IAAI,GAAG,OAAO,CAAC;AAErB,MAAM,UAAU,GAAG,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;IAC3C,iBAAiB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QACxC,OAAO,CAAC,KAAK,CAAC,sCAAsC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACxG,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;YACrB,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;YAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,uBAAuB,EAAE,CAAC,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,SAAS,QAAQ;IACf,OAAO,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACpD,UAAU,CAAC,KAAK,CAAC,GAAG,EAAE;QACpB,IAAI,CAAC;YACH,EAAE,CAAC,KAAK,EAAE,CAAC;QACb,CAAC;QAAC,MAAM,CAAC;YACP,cAAc;QAChB,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IACH,kDAAkD;IAClD,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;AAClD,CAAC;AAED,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAChC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAE/B,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;IAC3B,OAAO,CAAC,KAAK,CAAC,sBAAsB,OAAO,sBAAsB,IAAI,GAAG,UAAU,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,mDAAmD,EAAE,CAAC,CAAC;IAChK,OAAO,CAAC,KAAK,CAAC,yDAAyD,IAAI,MAAM,CAAC,CAAC;IACnF,OAAO,CAAC,KAAK,CAAC,wDAAwD,IAAI,SAAS,CAAC,CAAC;AACvF,CAAC,CAAC,CAAC"}
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * mnemon-mcp: MCP server entry point (stdio transport).
4
+ *
5
+ * Suitable for Claude Code MCP config.
6
+ * Database: ~/.mnemon-mcp/memory.db (SQLite + FTS5, WAL mode).
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;GAKG"}
package/dist/index.js ADDED
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * mnemon-mcp: MCP server entry point (stdio transport).
4
+ *
5
+ * Suitable for Claude Code MCP config.
6
+ * Database: ~/.mnemon-mcp/memory.db (SQLite + FTS5, WAL mode).
7
+ */
8
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
9
+ import { openDatabase } from "./db.js";
10
+ import { createMcpServer, loadExtraStopWords } from "./server.js";
11
+ import { createEmbedder } from "./embedder.js";
12
+ import { loadSqliteVec, createVecTable } from "./vector.js";
13
+ let db;
14
+ try {
15
+ db = openDatabase();
16
+ }
17
+ catch (err) {
18
+ process.stderr.write(`Failed to open database: ${err instanceof Error ? err.message : String(err)}\n`);
19
+ process.exit(1);
20
+ }
21
+ loadExtraStopWords();
22
+ // Optional: load sqlite-vec extension and create vector table
23
+ const vecAvailable = loadSqliteVec(db);
24
+ // Optional: create embedder from env vars
25
+ let embedder = null;
26
+ try {
27
+ embedder = createEmbedder();
28
+ if (embedder && vecAvailable) {
29
+ createVecTable(db, embedder.dimensions);
30
+ }
31
+ }
32
+ catch {
33
+ // Embedder creation is best-effort
34
+ }
35
+ const server = createMcpServer(db, embedder);
36
+ // Graceful shutdown: close DB and checkpoint WAL
37
+ function shutdown() {
38
+ try {
39
+ db.close();
40
+ }
41
+ catch {
42
+ // Best-effort on shutdown
43
+ }
44
+ process.exit(0);
45
+ }
46
+ process.on("SIGTERM", shutdown);
47
+ process.on("SIGINT", shutdown);
48
+ async function main() {
49
+ const transport = new StdioServerTransport();
50
+ await server.connect(transport);
51
+ }
52
+ main().catch((err) => {
53
+ process.stderr.write(`Fatal error: ${err instanceof Error ? err.message : String(err)}\n`);
54
+ process.exit(1);
55
+ });
56
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;GAKG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE5D,IAAI,EAAmC,CAAC;AAExC,IAAI,CAAC;IACH,EAAE,GAAG,YAAY,EAAE,CAAC;AACtB,CAAC;AAAC,OAAO,GAAG,EAAE,CAAC;IACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,4BAA4B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACvG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,kBAAkB,EAAE,CAAC;AAErB,8DAA8D;AAC9D,MAAM,YAAY,GAAG,aAAa,CAAC,EAAE,CAAC,CAAC;AAEvC,0CAA0C;AAC1C,IAAI,QAAQ,GAAsC,IAAI,CAAC;AACvD,IAAI,CAAC;IACH,QAAQ,GAAG,cAAc,EAAE,CAAC;IAC5B,IAAI,QAAQ,IAAI,YAAY,EAAE,CAAC;QAC7B,cAAc,CAAC,EAAE,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;IAC1C,CAAC;AACH,CAAC;AAAC,MAAM,CAAC;IACP,mCAAmC;AACrC,CAAC;AAED,MAAM,MAAM,GAAG,eAAe,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;AAE7C,iDAAiD;AACjD,SAAS,QAAQ;IACf,IAAI,CAAC;QACH,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;IAAC,MAAM,CAAC;QACP,0BAA0B;IAC5B,CAAC;IACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAChC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAE/B,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gBAAgB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC3F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Shared MCP server factory — used by both stdio and HTTP entry points.
3
+ *
4
+ * Centralizes tool registration, dispatch, and config loading so that
5
+ * index.ts and index-http.ts only handle transport-specific concerns.
6
+ */
7
+ import type Database from "better-sqlite3";
8
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
9
+ import type { Embedder } from "./embedder.js";
10
+ declare const version: string;
11
+ export { version };
12
+ /** Load extra stop words from config (best-effort, non-fatal). */
13
+ export declare function loadExtraStopWords(): void;
14
+ /** Create an MCP server with all memory tools registered. */
15
+ export declare function createMcpServer(db: Database.Database, embedder?: Embedder | null): Server;
16
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAG3C,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AA8CnE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAO9C,QAAA,MAAQ,OAAO,QAAsD,CAAC;AAEtE,OAAO,EAAE,OAAO,EAAE,CAAC;AAEnB,kEAAkE;AAClE,wBAAgB,kBAAkB,IAAI,IAAI,CASzC;AAED,6DAA6D;AAC7D,wBAAgB,eAAe,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,QAAQ,GAAG,IAAI,GAAG,MAAM,CAkVzF"}
package/dist/server.js ADDED
@@ -0,0 +1,341 @@
1
+ /**
2
+ * Shared MCP server factory — used by both stdio and HTTP entry points.
3
+ *
4
+ * Centralizes tool registration, dispatch, and config loading so that
5
+ * index.ts and index-http.ts only handle transport-specific concerns.
6
+ */
7
+ import { createRequire } from "node:module";
8
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
9
+ import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
10
+ import { memoryAdd } from "./tools/memory-add.js";
11
+ import { memorySearch } from "./tools/memory-search.js";
12
+ import { memoryUpdate } from "./tools/memory-update.js";
13
+ import { memoryInspect } from "./tools/memory-inspect.js";
14
+ import { memoryExport } from "./tools/memory-export.js";
15
+ import { memoryDelete } from "./tools/memory-delete.js";
16
+ import { memoryHealth } from "./tools/memory-health.js";
17
+ import { MemoryAddSchema, MemorySearchSchema, MemoryUpdateSchema, MemoryInspectSchema, MemoryExportSchema, MemoryDeleteSchema, MemoryHealthSchema, memoryAddToolSchema, memorySearchToolSchema, memoryUpdateToolSchema, memoryInspectToolSchema, memoryExportToolSchema, memoryDeleteToolSchema, memoryHealthToolSchema, } from "./validation.js";
18
+ import { upsertVec, deleteVec, isVecLoaded } from "./vector.js";
19
+ import { loadConfig } from "./import/config-loader.js";
20
+ import { addExtraStopWords } from "./stop-words.js";
21
+ const require = createRequire(import.meta.url);
22
+ const { version } = require("../package.json");
23
+ export { version };
24
+ /** Load extra stop words from config (best-effort, non-fatal). */
25
+ export function loadExtraStopWords() {
26
+ try {
27
+ const config = loadConfig();
28
+ if (config.extraStopWords.length > 0) {
29
+ addExtraStopWords(config.extraStopWords);
30
+ }
31
+ }
32
+ catch {
33
+ // Config loading is best-effort for the MCP server
34
+ }
35
+ }
36
+ /** Create an MCP server with all memory tools registered. */
37
+ export function createMcpServer(db, embedder) {
38
+ const server = new Server({ name: "mnemon-mcp", version }, { capabilities: { tools: {}, resources: {}, prompts: {} } });
39
+ server.setRequestHandler(ListToolsRequestSchema, () => ({
40
+ tools: [
41
+ {
42
+ name: "memory_add",
43
+ description: "Add a new memory to the persistent store. Supports 4 cognitive layers: episodic (events/sessions), semantic (facts/concepts), procedural (rules/workflows), resource (reference material). Automatically supersedes previous entries from the same source_file.",
44
+ inputSchema: memoryAddToolSchema,
45
+ },
46
+ {
47
+ name: "memory_search",
48
+ description: "Full-text search across all memory layers using FTS5. Supports layer/entity/date/scope filtering. Returns scored results with snippets. Superseded entries excluded by default.",
49
+ inputSchema: memorySearchToolSchema,
50
+ },
51
+ {
52
+ name: "memory_update",
53
+ description: "Update an existing memory. Use supersede=true to create a versioned replacement (preserves history chain). Use supersede=false (default) to update fields in place.",
54
+ inputSchema: memoryUpdateToolSchema,
55
+ },
56
+ {
57
+ name: "memory_delete",
58
+ description: "Permanently delete a memory by ID. Cleans up superseding chain references: re-activates predecessor if one exists.",
59
+ inputSchema: memoryDeleteToolSchema,
60
+ },
61
+ {
62
+ name: "memory_inspect",
63
+ description: "Inspect memory details or layer statistics. Without id: returns aggregate stats per layer (total, active, superseded, avg_confidence, top_entities). With id: returns the full memory row and optionally its history chain.",
64
+ inputSchema: memoryInspectToolSchema,
65
+ },
66
+ {
67
+ name: "memory_export",
68
+ description: "Export memories to JSON, Markdown, or claude-md (compact LLM-optimized) format. Supports filtering by layer, scope, date range. Returns the exported content as a string.",
69
+ inputSchema: memoryExportToolSchema,
70
+ },
71
+ {
72
+ name: "memory_health",
73
+ description: "Diagnostic health report on the memory store. Returns expired entries, orphaned superseding chains, stale/never-accessed memories, low-confidence entries, and per-layer stats. Use cleanup=true to garbage-collect expired entries.",
74
+ inputSchema: memoryHealthToolSchema,
75
+ },
76
+ ],
77
+ }));
78
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
79
+ const { name, arguments: args } = request.params;
80
+ try {
81
+ switch (name) {
82
+ case "memory_add": {
83
+ const input = MemoryAddSchema.parse(args);
84
+ const result = memoryAdd(db, input);
85
+ // Auto-embed if embedder configured and sqlite-vec loaded
86
+ if (embedder && isVecLoaded()) {
87
+ try {
88
+ const textToEmbed = input.title
89
+ ? `${input.title}\n\n${input.content}`
90
+ : input.content;
91
+ const embedding = await embedder.embed(textToEmbed);
92
+ upsertVec(db, result.id, embedding);
93
+ }
94
+ catch {
95
+ // Embedding is best-effort — don't fail the add
96
+ }
97
+ }
98
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
99
+ }
100
+ case "memory_search": {
101
+ const input = MemorySearchSchema.parse(args);
102
+ const result = await memorySearch(db, input, embedder);
103
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
104
+ }
105
+ case "memory_update": {
106
+ const input = MemoryUpdateSchema.parse(args);
107
+ const result = memoryUpdate(db, input);
108
+ // Re-embed updated or new (superseded) memory
109
+ if (embedder && isVecLoaded()) {
110
+ try {
111
+ const activeId = result.new_id ?? result.updated_id;
112
+ const row = db.prepare("SELECT title, content FROM memories WHERE id = ?").get(activeId);
113
+ if (row) {
114
+ const text = row.title ? `${row.title}\n\n${row.content}` : row.content;
115
+ const embedding = await embedder.embed(text);
116
+ upsertVec(db, activeId, embedding);
117
+ }
118
+ // Remove stale vector for superseded entry
119
+ if (result.new_id) {
120
+ deleteVec(db, result.updated_id);
121
+ }
122
+ }
123
+ catch {
124
+ // Best-effort
125
+ }
126
+ }
127
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
128
+ }
129
+ case "memory_delete": {
130
+ const input = MemoryDeleteSchema.parse(args);
131
+ const result = memoryDelete(db, input);
132
+ // Remove vector for deleted memory
133
+ if (isVecLoaded()) {
134
+ deleteVec(db, input.id);
135
+ }
136
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
137
+ }
138
+ case "memory_inspect": {
139
+ const input = MemoryInspectSchema.parse(args);
140
+ const result = memoryInspect(db, input);
141
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
142
+ }
143
+ case "memory_export": {
144
+ const input = MemoryExportSchema.parse(args);
145
+ const result = memoryExport(db, input);
146
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
147
+ }
148
+ case "memory_health": {
149
+ const input = MemoryHealthSchema.parse(args ?? {});
150
+ const result = memoryHealth(db, input);
151
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
152
+ }
153
+ default:
154
+ throw new Error(`Unknown tool: ${name}`);
155
+ }
156
+ }
157
+ catch (err) {
158
+ const message = err instanceof Error ? err.message : String(err);
159
+ return {
160
+ content: [{ type: "text", text: `Error: ${message}` }],
161
+ isError: true,
162
+ };
163
+ }
164
+ });
165
+ // -----------------------------------------------------------------------
166
+ // MCP Resources — read-only data endpoints for memory browsing
167
+ // -----------------------------------------------------------------------
168
+ server.setRequestHandler(ListResourcesRequestSchema, () => ({
169
+ resources: [
170
+ {
171
+ uri: "memory://stats",
172
+ name: "Memory Statistics",
173
+ description: "Aggregate statistics per memory layer: totals, active count, avg confidence/importance, top entities",
174
+ mimeType: "application/json",
175
+ },
176
+ {
177
+ uri: "memory://recent",
178
+ name: "Recent Memories",
179
+ description: "Memories created or updated in the last 24 hours",
180
+ mimeType: "application/json",
181
+ },
182
+ ],
183
+ }));
184
+ server.setRequestHandler(ListResourceTemplatesRequestSchema, () => ({
185
+ resourceTemplates: [
186
+ {
187
+ uriTemplate: "memory://layer/{layer}",
188
+ name: "Memories by Layer",
189
+ description: "List active memories in a specific layer (episodic, semantic, procedural, resource)",
190
+ mimeType: "application/json",
191
+ },
192
+ {
193
+ uriTemplate: "memory://entity/{name}",
194
+ name: "Memories by Entity",
195
+ description: "List active memories about a specific entity",
196
+ mimeType: "application/json",
197
+ },
198
+ ],
199
+ }));
200
+ server.setRequestHandler(ReadResourceRequestSchema, (request) => {
201
+ const { uri } = request.params;
202
+ if (uri === "memory://stats") {
203
+ const result = memoryInspect(db, {});
204
+ return {
205
+ contents: [{
206
+ uri,
207
+ mimeType: "application/json",
208
+ text: JSON.stringify(result, null, 2),
209
+ }],
210
+ };
211
+ }
212
+ if (uri === "memory://recent") {
213
+ const since = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString().replace(/\.\d{3}Z$/, "Z");
214
+ const rows = db.prepare(`SELECT id, layer, title, content, created_at FROM memories
215
+ WHERE superseded_by IS NULL AND COALESCE(updated_at, created_at) >= ?
216
+ ORDER BY COALESCE(updated_at, created_at) DESC LIMIT 50`).all(since);
217
+ return {
218
+ contents: [{
219
+ uri,
220
+ mimeType: "application/json",
221
+ text: JSON.stringify({ since, count: rows.length, memories: rows }, null, 2),
222
+ }],
223
+ };
224
+ }
225
+ const VALID_LAYERS = ["episodic", "semantic", "procedural", "resource"];
226
+ const layerMatch = uri.match(/^memory:\/\/layer\/(\w+)$/);
227
+ if (layerMatch) {
228
+ const layer = layerMatch[1];
229
+ if (!VALID_LAYERS.includes(layer)) {
230
+ throw new Error(`Invalid layer: "${layer}". Must be one of: ${VALID_LAYERS.join(", ")}`);
231
+ }
232
+ const rows = db.prepare(`SELECT id, title, content, entity_name, importance, created_at FROM memories
233
+ WHERE layer = ? AND superseded_by IS NULL
234
+ ORDER BY importance DESC, created_at DESC LIMIT 100`).all(layer);
235
+ return {
236
+ contents: [{
237
+ uri,
238
+ mimeType: "application/json",
239
+ text: JSON.stringify({ layer, count: rows.length, memories: rows }, null, 2),
240
+ }],
241
+ };
242
+ }
243
+ const entityMatch = uri.match(/^memory:\/\/entity\/(.+)$/);
244
+ if (entityMatch) {
245
+ const name = decodeURIComponent(entityMatch[1]);
246
+ const rows = db.prepare(`SELECT id, layer, title, content, importance, created_at FROM memories
247
+ WHERE entity_name = ? AND superseded_by IS NULL
248
+ ORDER BY importance DESC, created_at DESC LIMIT 100`).all(name);
249
+ return {
250
+ contents: [{
251
+ uri,
252
+ mimeType: "application/json",
253
+ text: JSON.stringify({ entity_name: name, count: rows.length, memories: rows }, null, 2),
254
+ }],
255
+ };
256
+ }
257
+ throw new Error(`Unknown resource URI: ${uri}`);
258
+ });
259
+ // -----------------------------------------------------------------------
260
+ // MCP Prompts — pre-built prompt templates for common memory operations
261
+ // -----------------------------------------------------------------------
262
+ server.setRequestHandler(ListPromptsRequestSchema, () => ({
263
+ prompts: [
264
+ {
265
+ name: "recall",
266
+ description: "Recall everything known about a topic from memory",
267
+ arguments: [
268
+ { name: "topic", description: "What to recall (e.g. 'human design', 'project architecture')", required: true },
269
+ ],
270
+ },
271
+ {
272
+ name: "context-load",
273
+ description: "Load relevant context for a task into the conversation",
274
+ arguments: [
275
+ { name: "task", description: "The task you're about to work on", required: true },
276
+ { name: "scope", description: "Optional scope filter (e.g. 'mnemon-mcp', 'personal')", required: false },
277
+ ],
278
+ },
279
+ {
280
+ name: "journal",
281
+ description: "Create a structured journal/session entry from a summary",
282
+ arguments: [
283
+ { name: "summary", description: "What happened in this session", required: true },
284
+ ],
285
+ },
286
+ ],
287
+ }));
288
+ server.setRequestHandler(GetPromptRequestSchema, (request) => {
289
+ const { name, arguments: args } = request.params;
290
+ switch (name) {
291
+ case "recall": {
292
+ const topic = args?.["topic"] ?? "general";
293
+ return {
294
+ messages: [
295
+ {
296
+ role: "user",
297
+ content: {
298
+ type: "text",
299
+ text: `Search your memory for everything related to: "${topic}"\n\nUse memory_search with different queries and filters to find all relevant information. Try:\n1. FTS search for the topic directly\n2. Search with entity_name if it's about a specific person/concept/project\n3. Search across different layers (episodic for events, semantic for facts, procedural for rules)\n\nSynthesize the results into a comprehensive answer. If memories conflict, note the most recent version.`,
300
+ },
301
+ },
302
+ ],
303
+ };
304
+ }
305
+ case "context-load": {
306
+ const task = args?.["task"] ?? "current task";
307
+ const scope = args?.["scope"];
308
+ const scopeHint = scope ? `\nFilter by scope: "${scope}"` : "";
309
+ return {
310
+ messages: [
311
+ {
312
+ role: "user",
313
+ content: {
314
+ type: "text",
315
+ text: `Load relevant context for this task: "${task}"${scopeHint}\n\nSearch memory for:\n1. Procedural rules and conventions related to this task\n2. Semantic facts about the entities involved\n3. Recent episodic context (sessions, decisions, discussions)\n4. Relevant resources (references, documentation)\n\nPresent the loaded context as a structured briefing.`,
316
+ },
317
+ },
318
+ ],
319
+ };
320
+ }
321
+ case "journal": {
322
+ const summary = args?.["summary"] ?? "";
323
+ return {
324
+ messages: [
325
+ {
326
+ role: "user",
327
+ content: {
328
+ type: "text",
329
+ text: `Create a journal entry from this session summary:\n\n${summary}\n\nUse memory_add with:\n- layer: "episodic"\n- entity_type: "user"\n- event_at: current ISO timestamp\n- importance: 0.6\n- confidence: 0.9\n\nExtract any new facts, decisions, or preferences mentioned and store them as separate semantic memories.`,
330
+ },
331
+ },
332
+ ],
333
+ };
334
+ }
335
+ default:
336
+ throw new Error(`Unknown prompt: ${name}`);
337
+ }
338
+ });
339
+ return server;
340
+ }
341
+ //# sourceMappingURL=server.js.map