@vheins/local-memory-mcp 0.8.17 → 0.8.19
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.
|
@@ -1,27 +1,25 @@
|
|
|
1
1
|
// src/mcp/capabilities.ts
|
|
2
2
|
import { fileURLToPath } from "url";
|
|
3
3
|
import path from "path";
|
|
4
|
-
import fs from "fs";
|
|
5
4
|
var __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
5
|
var pkgVersion = "0.1.0";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
pkgVersion = pkg.version;
|
|
21
|
-
break;
|
|
6
|
+
if ("0.8.19") {
|
|
7
|
+
pkgVersion = "0.8.19";
|
|
8
|
+
} else {
|
|
9
|
+
let searchDir = __dirname;
|
|
10
|
+
for (let i = 0; i < 5; i++) {
|
|
11
|
+
const candidate = path.join(searchDir, "package.json");
|
|
12
|
+
try {
|
|
13
|
+
if (fs.existsSync(candidate)) {
|
|
14
|
+
const pkg = JSON.parse(fs.readFileSync(candidate, "utf8"));
|
|
15
|
+
if (pkg.name === "@vheins/local-memory-mcp" && pkg.version) {
|
|
16
|
+
pkgVersion = pkg.version;
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
22
19
|
}
|
|
20
|
+
} catch {
|
|
23
21
|
}
|
|
24
|
-
|
|
22
|
+
searchDir = path.dirname(searchDir);
|
|
25
23
|
}
|
|
26
24
|
}
|
|
27
25
|
var MCP_PROTOCOL_VERSION = "2025-11-25";
|
|
@@ -47,7 +45,7 @@ var CAPABILITIES = {
|
|
|
47
45
|
};
|
|
48
46
|
|
|
49
47
|
// src/mcp/utils/logger.ts
|
|
50
|
-
import
|
|
48
|
+
import fs from "fs";
|
|
51
49
|
var LEVELS = {
|
|
52
50
|
debug: 0,
|
|
53
51
|
info: 1,
|
|
@@ -167,11 +165,11 @@ function addLogSink(sink) {
|
|
|
167
165
|
}
|
|
168
166
|
var LOG_LEVEL_VALUES = Object.keys(LEVELS);
|
|
169
167
|
function createFileSink(logDir, maxFiles = 5) {
|
|
170
|
-
|
|
171
|
-
const existing =
|
|
168
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
169
|
+
const existing = fs.readdirSync(logDir).filter((f) => f.startsWith("mcp-") && f.endsWith(".log")).sort();
|
|
172
170
|
while (existing.length >= maxFiles) {
|
|
173
171
|
try {
|
|
174
|
-
|
|
172
|
+
fs.unlinkSync(`${logDir}/${existing.shift()}`);
|
|
175
173
|
} catch {
|
|
176
174
|
}
|
|
177
175
|
}
|
|
@@ -181,7 +179,7 @@ function createFileSink(logDir, maxFiles = 5) {
|
|
|
181
179
|
const line = `${(/* @__PURE__ */ new Date()).toISOString()} [${payload.level.toUpperCase()}] [pid:${process.pid}] ${JSON.stringify(payload.data)}
|
|
182
180
|
`;
|
|
183
181
|
try {
|
|
184
|
-
|
|
182
|
+
fs.appendFileSync(logFile, line);
|
|
185
183
|
} catch {
|
|
186
184
|
}
|
|
187
185
|
};
|
|
@@ -190,7 +188,7 @@ function createFileSink(logDir, maxFiles = 5) {
|
|
|
190
188
|
// src/mcp/storage/sqlite.ts
|
|
191
189
|
import Database from "better-sqlite3";
|
|
192
190
|
import path3 from "path";
|
|
193
|
-
import
|
|
191
|
+
import fs3 from "fs";
|
|
194
192
|
import os from "os";
|
|
195
193
|
|
|
196
194
|
// src/mcp/storage/migrations.ts
|
|
@@ -1291,15 +1289,15 @@ var MemoryEntity = class extends BaseEntity {
|
|
|
1291
1289
|
let sql = `SELECT * FROM memories WHERE (${where.join(" AND ")}) AND (expires_at IS NULL OR expires_at > ?)`;
|
|
1292
1290
|
if (!includeArchived) sql += " AND status = 'active'";
|
|
1293
1291
|
sql += ` ORDER BY CASE WHEN repo = ? THEN 0 ELSE 1 END, importance DESC, created_at DESC LIMIT 100`;
|
|
1294
|
-
const
|
|
1295
|
-
if (
|
|
1292
|
+
const candidates = this.all(sql, [...params, now.toISOString(), repo]);
|
|
1293
|
+
if (candidates.length < 5) {
|
|
1296
1294
|
const recentSql = `SELECT * FROM memories WHERE (${where.join(" OR ")}) AND status = 'active' AND (expires_at IS NULL OR expires_at > ?) ORDER BY created_at DESC LIMIT 10`;
|
|
1297
1295
|
const recent = this.all(recentSql, [...params, now.toISOString()]);
|
|
1298
1296
|
for (const r of recent) {
|
|
1299
|
-
if (!
|
|
1297
|
+
if (!candidates.find((c) => c.id === r.id)) candidates.push(r);
|
|
1300
1298
|
}
|
|
1301
1299
|
}
|
|
1302
|
-
return
|
|
1300
|
+
return candidates.map((row) => {
|
|
1303
1301
|
const memory = this.rowToMemoryEntry(row);
|
|
1304
1302
|
const isExpired = row.expires_at && new Date(row.expires_at) <= now;
|
|
1305
1303
|
const isArchived = row.status === "archived" && !includeArchived;
|
|
@@ -1912,7 +1910,7 @@ var SummaryEntity = class extends BaseEntity {
|
|
|
1912
1910
|
// src/mcp/storage/write-lock.ts
|
|
1913
1911
|
import lockfile from "proper-lockfile";
|
|
1914
1912
|
import path2 from "path";
|
|
1915
|
-
import
|
|
1913
|
+
import fs2 from "fs";
|
|
1916
1914
|
var LOCK_STALE_MS = 3e4;
|
|
1917
1915
|
var LOCK_RETRY_DELAY_MS = 200;
|
|
1918
1916
|
var LOCK_RETRY_COUNT = 250;
|
|
@@ -1921,9 +1919,9 @@ var WriteLock = class {
|
|
|
1921
1919
|
locked = false;
|
|
1922
1920
|
constructor(dbPath) {
|
|
1923
1921
|
this.lockTarget = dbPath;
|
|
1924
|
-
if (!
|
|
1925
|
-
|
|
1926
|
-
|
|
1922
|
+
if (!fs2.existsSync(dbPath)) {
|
|
1923
|
+
fs2.mkdirSync(path2.dirname(dbPath), { recursive: true });
|
|
1924
|
+
fs2.writeFileSync(dbPath, "");
|
|
1927
1925
|
}
|
|
1928
1926
|
}
|
|
1929
1927
|
/**
|
|
@@ -1977,11 +1975,11 @@ function resolveDbPath() {
|
|
|
1977
1975
|
if (process.env.MEMORY_DB_PATH) return process.env.MEMORY_DB_PATH;
|
|
1978
1976
|
const standardConfigDir = process.platform === "win32" ? path3.join(os.homedir(), ".local-memory-mcp") : process.platform === "darwin" ? path3.join(os.homedir(), "Library", "Application Support", "local-memory-mcp") : path3.join(os.homedir(), ".config", "local-memory-mcp");
|
|
1979
1977
|
const standardPath = path3.join(standardConfigDir, "memory.db");
|
|
1980
|
-
if (
|
|
1978
|
+
if (fs3.existsSync(standardPath)) return standardPath;
|
|
1981
1979
|
const legacyPath = path3.join(os.homedir(), ".config", "local-memory-mcp", "memory.db");
|
|
1982
|
-
if (
|
|
1980
|
+
if (fs3.existsSync(legacyPath)) return legacyPath;
|
|
1983
1981
|
const localCwdFile = path3.join(process.cwd(), "storage", "memory.db");
|
|
1984
|
-
if (
|
|
1982
|
+
if (fs3.existsSync(localCwdFile)) return localCwdFile;
|
|
1985
1983
|
return standardPath;
|
|
1986
1984
|
}
|
|
1987
1985
|
var DB_PATH = resolveDbPath();
|
|
@@ -1999,8 +1997,8 @@ var SQLiteStore = class _SQLiteStore {
|
|
|
1999
1997
|
this.dbPathInstance = finalPath;
|
|
2000
1998
|
if (finalPath !== ":memory:") {
|
|
2001
1999
|
const dbDir = path3.dirname(finalPath);
|
|
2002
|
-
if (!
|
|
2003
|
-
|
|
2000
|
+
if (!fs3.existsSync(dbDir)) {
|
|
2001
|
+
fs3.mkdirSync(dbDir, { recursive: true });
|
|
2004
2002
|
}
|
|
2005
2003
|
}
|
|
2006
2004
|
this.db = new Database(finalPath);
|
|
@@ -2052,12 +2050,12 @@ var SQLiteStore = class _SQLiteStore {
|
|
|
2052
2050
|
*/
|
|
2053
2051
|
_attemptRecovery(dbPath) {
|
|
2054
2052
|
const backupPath = dbPath + ".backup";
|
|
2055
|
-
if (
|
|
2053
|
+
if (fs3.existsSync(backupPath)) {
|
|
2056
2054
|
logger.warn("[SQLiteStore] Attempting recovery from backup", { backupPath });
|
|
2057
2055
|
try {
|
|
2058
2056
|
const corruptPath = `${dbPath}.corrupt_${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "").slice(0, 15)}`;
|
|
2059
|
-
|
|
2060
|
-
|
|
2057
|
+
fs3.copyFileSync(dbPath, corruptPath);
|
|
2058
|
+
fs3.copyFileSync(backupPath, dbPath);
|
|
2061
2059
|
logger.warn("[SQLiteStore] Recovery successful. Corrupt file saved to", { corruptPath });
|
|
2062
2060
|
} catch (err) {
|
|
2063
2061
|
logger.error("[SQLiteStore] Recovery failed", { error: String(err) });
|
|
@@ -2075,7 +2073,7 @@ var SQLiteStore = class _SQLiteStore {
|
|
|
2075
2073
|
try {
|
|
2076
2074
|
this.db.pragma("wal_checkpoint(PASSIVE)");
|
|
2077
2075
|
const backupPath = this.dbPathInstance + ".backup";
|
|
2078
|
-
|
|
2076
|
+
fs3.copyFileSync(this.dbPathInstance, backupPath);
|
|
2079
2077
|
} catch (err) {
|
|
2080
2078
|
logger.warn("[SQLiteStore] Backup failed", { error: String(err) });
|
|
2081
2079
|
}
|
|
@@ -2398,8 +2396,8 @@ var TaskUpdateSchema = z.object({
|
|
|
2398
2396
|
est_tokens: z.number().int().min(0).optional(),
|
|
2399
2397
|
force: z.boolean().optional(),
|
|
2400
2398
|
structured: z.boolean().default(false)
|
|
2401
|
-
}).refine((data) => data.id !== void 0 || data.ids !== void 0, {
|
|
2402
|
-
message: "Either 'id' or '
|
|
2399
|
+
}).refine((data) => data.id !== void 0 || data.ids !== void 0 || data.task_code !== void 0, {
|
|
2400
|
+
message: "Either 'id', 'ids', or 'task_code' must be provided for update"
|
|
2403
2401
|
}).refine((data) => Object.keys(data).length > 2, {
|
|
2404
2402
|
message: "At least one field besides repo and id/ids must be provided for update"
|
|
2405
2403
|
});
|
|
@@ -3276,8 +3274,8 @@ function invalidPaginationParams(message) {
|
|
|
3276
3274
|
|
|
3277
3275
|
// src/mcp/utils/completion.ts
|
|
3278
3276
|
var MAX_COMPLETION_VALUES = 100;
|
|
3279
|
-
function rankCompletionValues(
|
|
3280
|
-
const unique = [...new Set(
|
|
3277
|
+
function rankCompletionValues(candidates, input) {
|
|
3278
|
+
const unique = [...new Set(candidates.filter(Boolean))];
|
|
3281
3279
|
const needle = input.trim().toLowerCase();
|
|
3282
3280
|
if (!needle) {
|
|
3283
3281
|
return unique.slice(0, MAX_COMPLETION_VALUES);
|
|
@@ -3682,14 +3680,14 @@ function invalidCompletionParams(message) {
|
|
|
3682
3680
|
}
|
|
3683
3681
|
|
|
3684
3682
|
// src/mcp/prompts/loader.ts
|
|
3685
|
-
import
|
|
3683
|
+
import fs4 from "fs";
|
|
3686
3684
|
import path5 from "path";
|
|
3687
3685
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3688
3686
|
import matter from "gray-matter";
|
|
3689
3687
|
var __filename = fileURLToPath3(import.meta.url);
|
|
3690
3688
|
var __dirname2 = path5.dirname(__filename);
|
|
3691
3689
|
function findPromptDir() {
|
|
3692
|
-
const
|
|
3690
|
+
const candidates = [
|
|
3693
3691
|
// Production if chunked into dist/
|
|
3694
3692
|
"./prompts",
|
|
3695
3693
|
// Production if inlined into dist/mcp/
|
|
@@ -3697,9 +3695,9 @@ function findPromptDir() {
|
|
|
3697
3695
|
// Dev: /src/mcp/prompts/definitions (next to loader.ts)
|
|
3698
3696
|
"./definitions"
|
|
3699
3697
|
].map((relPath) => path5.resolve(__dirname2, relPath));
|
|
3700
|
-
for (const dir of
|
|
3701
|
-
if (
|
|
3702
|
-
const files =
|
|
3698
|
+
for (const dir of candidates) {
|
|
3699
|
+
if (fs4.existsSync(dir)) {
|
|
3700
|
+
const files = fs4.readdirSync(dir);
|
|
3703
3701
|
if (files.some((f) => f.endsWith(".md"))) {
|
|
3704
3702
|
return dir;
|
|
3705
3703
|
}
|
|
@@ -3709,15 +3707,15 @@ function findPromptDir() {
|
|
|
3709
3707
|
}
|
|
3710
3708
|
var PROMPT_DIR = findPromptDir();
|
|
3711
3709
|
function listPromptFiles() {
|
|
3712
|
-
if (!
|
|
3713
|
-
return
|
|
3710
|
+
if (!fs4.existsSync(PROMPT_DIR)) return [];
|
|
3711
|
+
return fs4.readdirSync(PROMPT_DIR).filter((file) => file.endsWith(".md")).map((file) => file.replace(/\.md$/, "")).sort();
|
|
3714
3712
|
}
|
|
3715
3713
|
function loadPromptFromMarkdown(name) {
|
|
3716
3714
|
const filePath = path5.join(PROMPT_DIR, `${name}.md`);
|
|
3717
|
-
if (!
|
|
3715
|
+
if (!fs4.existsSync(filePath)) {
|
|
3718
3716
|
throw new Error(`Prompt file not found: ${filePath}`);
|
|
3719
3717
|
}
|
|
3720
|
-
const fileContent =
|
|
3718
|
+
const fileContent = fs4.readFileSync(filePath, "utf-8");
|
|
3721
3719
|
const { data, content } = matter(fileContent);
|
|
3722
3720
|
return {
|
|
3723
3721
|
name: data.name || name,
|
package/dist/dashboard/server.js
CHANGED
package/dist/mcp/server.js
CHANGED
|
@@ -43,7 +43,7 @@ import {
|
|
|
43
43
|
setLogLevel,
|
|
44
44
|
updateSessionFromInitialize,
|
|
45
45
|
updateSessionRoots
|
|
46
|
-
} from "../chunk-
|
|
46
|
+
} from "../chunk-TR5SQKA3.js";
|
|
47
47
|
|
|
48
48
|
// src/mcp/server.ts
|
|
49
49
|
import readline from "readline";
|
|
@@ -1267,7 +1267,13 @@ function addRequiredStringField(properties, required, task, field, schema) {
|
|
|
1267
1267
|
async function handleTaskUpdate(args, storage, vectors2) {
|
|
1268
1268
|
const updateData = TaskUpdateSchema.parse(args);
|
|
1269
1269
|
const { repo, id, ids, comment, force, ...updates } = updateData;
|
|
1270
|
-
|
|
1270
|
+
let resolvedId = id;
|
|
1271
|
+
if (!resolvedId && !ids && updates.task_code) {
|
|
1272
|
+
const found = storage.tasks.getTaskByCode(repo, updates.task_code);
|
|
1273
|
+
if (!found) throw new Error(`Task not found: ${updates.task_code}`);
|
|
1274
|
+
resolvedId = found.id;
|
|
1275
|
+
}
|
|
1276
|
+
const targetIds = ids || (resolvedId ? [resolvedId] : []);
|
|
1271
1277
|
if (targetIds.length === 0) {
|
|
1272
1278
|
throw new Error("Either 'id' or 'ids' must be provided for update");
|
|
1273
1279
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vheins/local-memory-mcp",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.19",
|
|
4
4
|
"description": "MCP Local Memory Service for coding copilot agents",
|
|
5
5
|
"mcpName": "io.github.vheins/local-memory-mcp",
|
|
6
6
|
"type": "module",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"author": "Muhammad Rheza Alfin <m.rheza.alfin@gmail.com>",
|
|
28
28
|
"license": "MIT",
|
|
29
29
|
"scripts": {
|
|
30
|
-
"build": "rm -rf dist && npm run dashboard:build && tsup
|
|
30
|
+
"build": "rm -rf dist && npm run dashboard:build && tsup --config tsup.config.ts && mkdir -p bin && cp -r src/mcp/prompts/definitions dist/prompts/ && printf \"#!/usr/bin/env node\\nimport '../dist/dashboard/server.js';\\n\" > bin/mcp-memory-dashboard.js && shx chmod +x dist/mcp/server.js dist/dashboard/server.js bin/mcp-memory-server.js bin/mcp-memory-dashboard.js",
|
|
31
31
|
"dashboard:build": "vite build --config src/dashboard/ui/vite.config.ts",
|
|
32
32
|
"dashboard:dev": "vite dev --config src/dashboard/ui/vite.config.ts",
|
|
33
33
|
"prepare": "npm run build",
|