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.
- package/CHANGELOG.md +34 -0
- package/CONTRIBUTING.md +59 -0
- package/LICENSE +21 -0
- package/README.md +434 -0
- package/SECURITY.md +29 -0
- package/config.example.json +52 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/db.d.ts +16 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +360 -0
- package/dist/db.js.map +1 -0
- package/dist/embedder.d.ts +22 -0
- package/dist/embedder.d.ts.map +1 -0
- package/dist/embedder.js +109 -0
- package/dist/embedder.js.map +1 -0
- package/dist/import/cli.d.ts +12 -0
- package/dist/import/cli.d.ts.map +1 -0
- package/dist/import/cli.js +105 -0
- package/dist/import/cli.js.map +1 -0
- package/dist/import/config-loader.d.ts +29 -0
- package/dist/import/config-loader.d.ts.map +1 -0
- package/dist/import/config-loader.js +140 -0
- package/dist/import/config-loader.js.map +1 -0
- package/dist/import/kb-config.d.ts +27 -0
- package/dist/import/kb-config.d.ts.map +1 -0
- package/dist/import/kb-config.js +10 -0
- package/dist/import/kb-config.js.map +1 -0
- package/dist/import/kb-import.d.ts +45 -0
- package/dist/import/kb-import.d.ts.map +1 -0
- package/dist/import/kb-import.js +285 -0
- package/dist/import/kb-import.js.map +1 -0
- package/dist/import/md-parser.d.ts +35 -0
- package/dist/import/md-parser.d.ts.map +1 -0
- package/dist/import/md-parser.js +104 -0
- package/dist/import/md-parser.js.map +1 -0
- package/dist/index-http.d.ts +12 -0
- package/dist/index-http.d.ts.map +1 -0
- package/dist/index-http.js +202 -0
- package/dist/index-http.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +56 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +16 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +341 -0
- package/dist/server.js.map +1 -0
- package/dist/stemmer.d.ts +29 -0
- package/dist/stemmer.d.ts.map +1 -0
- package/dist/stemmer.js +68 -0
- package/dist/stemmer.js.map +1 -0
- package/dist/stop-words.d.ts +12 -0
- package/dist/stop-words.d.ts.map +1 -0
- package/dist/stop-words.js +112 -0
- package/dist/stop-words.js.map +1 -0
- package/dist/tools/memory-add.d.ts +15 -0
- package/dist/tools/memory-add.d.ts.map +1 -0
- package/dist/tools/memory-add.js +120 -0
- package/dist/tools/memory-add.js.map +1 -0
- package/dist/tools/memory-delete.d.ts +10 -0
- package/dist/tools/memory-delete.d.ts.map +1 -0
- package/dist/tools/memory-delete.js +39 -0
- package/dist/tools/memory-delete.js.map +1 -0
- package/dist/tools/memory-export.d.ts +10 -0
- package/dist/tools/memory-export.d.ts.map +1 -0
- package/dist/tools/memory-export.js +102 -0
- package/dist/tools/memory-export.js.map +1 -0
- package/dist/tools/memory-health.d.ts +11 -0
- package/dist/tools/memory-health.d.ts.map +1 -0
- package/dist/tools/memory-health.js +79 -0
- package/dist/tools/memory-health.js.map +1 -0
- package/dist/tools/memory-inspect.d.ts +10 -0
- package/dist/tools/memory-inspect.d.ts.map +1 -0
- package/dist/tools/memory-inspect.js +139 -0
- package/dist/tools/memory-inspect.js.map +1 -0
- package/dist/tools/memory-search.d.ts +16 -0
- package/dist/tools/memory-search.d.ts.map +1 -0
- package/dist/tools/memory-search.js +459 -0
- package/dist/tools/memory-search.js.map +1 -0
- package/dist/tools/memory-update.d.ts +11 -0
- package/dist/tools/memory-update.d.ts.map +1 -0
- package/dist/tools/memory-update.js +142 -0
- package/dist/tools/memory-update.js.map +1 -0
- package/dist/tools/style-extract.d.ts +40 -0
- package/dist/tools/style-extract.d.ts.map +1 -0
- package/dist/tools/style-extract.js +43 -0
- package/dist/tools/style-extract.js.map +1 -0
- package/dist/tools/utils.d.ts +28 -0
- package/dist/tools/utils.d.ts.map +1 -0
- package/dist/tools/utils.js +33 -0
- package/dist/tools/utils.js.map +1 -0
- package/dist/types.d.ts +216 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/validation.d.ts +122 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +96 -0
- package/dist/validation.js.map +1 -0
- package/dist/vector.d.ts +30 -0
- package/dist/vector.d.ts.map +1 -0
- package/dist/vector.js +90 -0
- package/dist/vector.js.map +1 -0
- 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":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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"}
|
package/dist/server.d.ts
ADDED
|
@@ -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
|