@vheins/local-memory-mcp 0.6.1 → 0.7.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/bin/mcp-memory-server.js +5 -3
- package/dist/{chunk-EQTLHCFK.js → chunk-3I4VJIFK.js} +645 -549
- package/dist/dashboard/public/assets/{index-Df97JpLg.js → index-W8_M2hX3.js} +6 -6
- package/dist/dashboard/public/index.html +1 -1
- package/dist/dashboard/server.js +22 -9
- package/dist/mcp/server.js +235 -107
- package/package.json +2 -3
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
9
9
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
10
10
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
11
|
-
<script type="module" crossorigin src="/assets/index-
|
|
11
|
+
<script type="module" crossorigin src="/assets/index-W8_M2hX3.js"></script>
|
|
12
12
|
<link rel="stylesheet" crossorigin href="/assets/index-Bd7v94SO.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
package/dist/dashboard/server.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
TOOL_DEFINITIONS,
|
|
7
7
|
listResources,
|
|
8
8
|
logger
|
|
9
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-3I4VJIFK.js";
|
|
10
10
|
|
|
11
11
|
// src/dashboard/server.ts
|
|
12
12
|
import express from "express";
|
|
@@ -189,7 +189,7 @@ function sleep(ms) {
|
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
// src/dashboard/lib/context.ts
|
|
192
|
-
var db =
|
|
192
|
+
var db = await SQLiteStore.create();
|
|
193
193
|
var mcpClient = new MCPClient();
|
|
194
194
|
var startTime = Date.now();
|
|
195
195
|
|
|
@@ -253,7 +253,8 @@ var pkg = { version: "0.0.0" };
|
|
|
253
253
|
try {
|
|
254
254
|
const pkgPath = path2.join(__dirname2, "../../../package.json");
|
|
255
255
|
if (fs.existsSync(pkgPath)) {
|
|
256
|
-
|
|
256
|
+
const data = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
257
|
+
if (data.version) pkg.version = data.version;
|
|
257
258
|
}
|
|
258
259
|
} catch {
|
|
259
260
|
}
|
|
@@ -324,11 +325,22 @@ var SystemController = class {
|
|
|
324
325
|
}
|
|
325
326
|
}
|
|
326
327
|
static getCapabilities(req, res) {
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
};
|
|
328
|
+
const tools = (TOOL_DEFINITIONS || []).map((tool) => ({
|
|
329
|
+
type: "tool",
|
|
330
|
+
id: tool.name,
|
|
331
|
+
attributes: tool
|
|
332
|
+
}));
|
|
333
|
+
const resources = (listResources().resources || []).map((resource) => ({
|
|
334
|
+
type: "resource",
|
|
335
|
+
id: resource.uri,
|
|
336
|
+
attributes: resource
|
|
337
|
+
}));
|
|
338
|
+
const prompts = (Object.values(PROMPTS) || []).map((prompt) => ({
|
|
339
|
+
type: "prompt",
|
|
340
|
+
id: prompt.name,
|
|
341
|
+
attributes: prompt
|
|
342
|
+
}));
|
|
343
|
+
const caps = { tools, resources, prompts };
|
|
332
344
|
res.json(jsonApiRes(caps, "capability"));
|
|
333
345
|
}
|
|
334
346
|
static getExport(req, res) {
|
|
@@ -783,7 +795,8 @@ var pkg2 = { version: "0.0.0" };
|
|
|
783
795
|
try {
|
|
784
796
|
const pkgPath = path3.join(__dirname3, "../../package.json");
|
|
785
797
|
if (fs2.existsSync(pkgPath)) {
|
|
786
|
-
|
|
798
|
+
const data = JSON.parse(fs2.readFileSync(pkgPath, "utf8"));
|
|
799
|
+
if (data.version) pkg2.version = data.version;
|
|
787
800
|
}
|
|
788
801
|
} catch {
|
|
789
802
|
}
|
package/dist/mcp/server.js
CHANGED
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
setLogLevel,
|
|
43
43
|
updateSessionFromInitialize,
|
|
44
44
|
updateSessionRoots
|
|
45
|
-
} from "../chunk-
|
|
45
|
+
} from "../chunk-3I4VJIFK.js";
|
|
46
46
|
|
|
47
47
|
// src/mcp/server.ts
|
|
48
48
|
import readline from "readline";
|
|
@@ -299,6 +299,14 @@ function hasMetadataLikeTitle(title) {
|
|
|
299
299
|
const normalized = title.trim();
|
|
300
300
|
return /^\[[^\]]{0,200}(agent:|role:|model:|\d{4}-\d{2}-\d{2}|source_)[^\]]*\]/i.test(normalized);
|
|
301
301
|
}
|
|
302
|
+
function generateShortCode() {
|
|
303
|
+
const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
|
304
|
+
let code = "";
|
|
305
|
+
for (let i = 0; i < 6; i++) {
|
|
306
|
+
code += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
307
|
+
}
|
|
308
|
+
return code;
|
|
309
|
+
}
|
|
302
310
|
async function handleMemoryStore(params, db2, vectors2) {
|
|
303
311
|
const validated = MemoryStoreSchema.parse(params);
|
|
304
312
|
if (hasMetadataLikeTitle(validated.title)) {
|
|
@@ -352,6 +360,7 @@ async function handleMemoryStore(params, db2, vectors2) {
|
|
|
352
360
|
}
|
|
353
361
|
const entry = {
|
|
354
362
|
id: randomUUID(),
|
|
363
|
+
code: validated.code || generateShortCode(),
|
|
355
364
|
type: validated.type,
|
|
356
365
|
title: validated.title,
|
|
357
366
|
content: validated.content,
|
|
@@ -390,13 +399,15 @@ async function handleMemoryStore(params, db2, vectors2) {
|
|
|
390
399
|
{
|
|
391
400
|
success: true,
|
|
392
401
|
id: entry.id,
|
|
402
|
+
code: entry.code,
|
|
393
403
|
repo: entry.scope.repo,
|
|
394
404
|
type: entry.type,
|
|
395
405
|
title: entry.title
|
|
396
406
|
},
|
|
397
|
-
`Stored
|
|
407
|
+
`Stored [${entry.code}] "${entry.title}" in repo "${entry.scope.repo}".`,
|
|
398
408
|
{
|
|
399
|
-
|
|
409
|
+
contentSummary: `Stored [${entry.code}] "${entry.title}" in repo "${entry.scope.repo}".`,
|
|
410
|
+
structuredContentPathHint: "code",
|
|
400
411
|
resourceLinks: [
|
|
401
412
|
{
|
|
402
413
|
uri: `memory://${entry.id}`,
|
|
@@ -542,19 +553,13 @@ async function handleMemorySearch(params, db2, vectors2) {
|
|
|
542
553
|
const currentBranch = validated.scope?.branch;
|
|
543
554
|
candidates = candidates.map((c) => {
|
|
544
555
|
let boost = 0;
|
|
545
|
-
if (currentBranch && c.memory.scope.branch === currentBranch)
|
|
546
|
-
|
|
547
|
-
}
|
|
548
|
-
if (currentPath && c.memory.scope.folder && currentPath.includes(c.memory.scope.folder.toLowerCase())) {
|
|
549
|
-
boost += 0.15;
|
|
550
|
-
}
|
|
556
|
+
if (currentBranch && c.memory.scope.branch === currentBranch) boost += 0.1;
|
|
557
|
+
if (currentPath && c.memory.scope.folder && currentPath.includes(c.memory.scope.folder.toLowerCase())) boost += 0.15;
|
|
551
558
|
if (currentPath && c.memory.scope.language) {
|
|
552
559
|
const ext = currentPath.split(".").pop();
|
|
553
560
|
if (ext && ext.includes(c.memory.scope.language.toLowerCase())) boost += 0.1;
|
|
554
561
|
}
|
|
555
|
-
if (currentTags.length > 0 && c.memory.tags.some((t) => currentTags.includes(t.toLowerCase())))
|
|
556
|
-
boost += 0.2;
|
|
557
|
-
}
|
|
562
|
+
if (currentTags.length > 0 && c.memory.tags.some((t) => currentTags.includes(t.toLowerCase()))) boost += 0.2;
|
|
558
563
|
return { ...c, similarityScore: Math.min(1, c.similarityScore + boost) };
|
|
559
564
|
});
|
|
560
565
|
}
|
|
@@ -597,9 +602,7 @@ async function handleMemorySearch(params, db2, vectors2) {
|
|
|
597
602
|
scoredMemories.sort((a, b) => b.finalScore - a.finalScore);
|
|
598
603
|
const threshold = scoredMemories.length <= 5 ? 0.1 : 0.4;
|
|
599
604
|
let allMatches = scoredMemories.filter((sm) => sm.finalScore >= threshold).map((sm) => sm.memory);
|
|
600
|
-
if (allMatches.length === 0 && scoredMemories.length > 0)
|
|
601
|
-
allMatches = [scoredMemories[0].memory];
|
|
602
|
-
}
|
|
605
|
+
if (allMatches.length === 0 && scoredMemories.length > 0) allMatches = [scoredMemories[0].memory];
|
|
603
606
|
const total = allMatches.length;
|
|
604
607
|
const paginatedResults = allMatches.slice(validated.offset, validated.offset + validated.limit);
|
|
605
608
|
db2.memories.incrementHitCounts(paginatedResults.map((m) => m.id));
|
|
@@ -610,9 +613,34 @@ async function handleMemorySearch(params, db2, vectors2) {
|
|
|
610
613
|
offset: validated.offset,
|
|
611
614
|
returned: paginatedResults.length
|
|
612
615
|
});
|
|
613
|
-
const COLUMNS = ["id", "title", "type", "importance"];
|
|
614
|
-
const rows = paginatedResults.map((m) => [m.id, m.title ?? "Untitled", m.type, m.importance]);
|
|
615
|
-
const
|
|
616
|
+
const COLUMNS = ["id", "code", "title", "type", "importance"];
|
|
617
|
+
const rows = paginatedResults.map((m) => [m.id, m.code || "-", m.title ?? "Untitled", m.type, m.importance]);
|
|
618
|
+
const memoriesByType = {};
|
|
619
|
+
for (const m of paginatedResults) {
|
|
620
|
+
const typeLabel = m.type || "unknown";
|
|
621
|
+
if (!memoriesByType[typeLabel]) memoriesByType[typeLabel] = [];
|
|
622
|
+
memoriesByType[typeLabel].push(m);
|
|
623
|
+
}
|
|
624
|
+
let contentSummary;
|
|
625
|
+
if (!validated.structured) {
|
|
626
|
+
if (paginatedResults.length > 0) {
|
|
627
|
+
const parts = [];
|
|
628
|
+
for (const [memType, items] of Object.entries(memoriesByType)) {
|
|
629
|
+
parts.push(`${capitalize(memType)}:`);
|
|
630
|
+
parts.push("- code|importance|title");
|
|
631
|
+
for (const m of items) {
|
|
632
|
+
const code = m.code || "-";
|
|
633
|
+
parts.push(`- ${code}|${m.importance}|${m.title}`);
|
|
634
|
+
}
|
|
635
|
+
parts.push("");
|
|
636
|
+
}
|
|
637
|
+
parts.push("Use memory-detail with memory_id (or code) for full content.");
|
|
638
|
+
contentSummary = parts.join("\n").trim();
|
|
639
|
+
} else {
|
|
640
|
+
contentSummary = `No memories found for "${validated.query}" in repo "${validated.repo}".`;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
const structuredData = {
|
|
616
644
|
schema: "memory-search",
|
|
617
645
|
query: validated.query,
|
|
618
646
|
count: paginatedResults.length,
|
|
@@ -624,13 +652,14 @@ async function handleMemorySearch(params, db2, vectors2) {
|
|
|
624
652
|
rows
|
|
625
653
|
}
|
|
626
654
|
};
|
|
627
|
-
|
|
628
|
-
const contentSummary = paginatedResults.length > 0 ? `Found ${total} memories for "${validated.query}" (showing ${paginatedResults.length} at offset ${validated.offset}): ${memoryList}. Use memory-detail to read full content.` : `No memories found for "${validated.query}" in repo "${validated.repo}".`;
|
|
629
|
-
return createMcpResponse(structuredContent, contentSummary, {
|
|
655
|
+
return createMcpResponse(structuredData, contentSummary || "", {
|
|
630
656
|
contentSummary,
|
|
631
657
|
includeSerializedStructuredContent: false
|
|
632
658
|
});
|
|
633
659
|
}
|
|
660
|
+
function capitalize(str) {
|
|
661
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
662
|
+
}
|
|
634
663
|
|
|
635
664
|
// src/mcp/tools/memory.summarize.ts
|
|
636
665
|
async function handleMemorySummarize(params, db2) {
|
|
@@ -639,30 +668,13 @@ async function handleMemorySummarize(params, db2) {
|
|
|
639
668
|
const fullSummary = `Project summary:
|
|
640
669
|
- ${summary}`;
|
|
641
670
|
db2.summaries.upsertSummary(validated.repo, fullSummary);
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
`Updated summary for repo "${validated.repo}" with ${validated.signals.length} signals.`,
|
|
650
|
-
{
|
|
651
|
-
structuredContentPathHint: "summary",
|
|
652
|
-
resourceLinks: [
|
|
653
|
-
{
|
|
654
|
-
uri: `repository://${encodeURIComponent(validated.repo)}/summary`,
|
|
655
|
-
name: `Repository Summary (${validated.repo})`,
|
|
656
|
-
description: "Repository summary resource",
|
|
657
|
-
mimeType: "text/plain",
|
|
658
|
-
annotations: {
|
|
659
|
-
audience: ["assistant"],
|
|
660
|
-
priority: 0.9
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
]
|
|
664
|
-
}
|
|
665
|
-
);
|
|
671
|
+
const content = `Updated summary for repo "${validated.repo}" with ${validated.signals.length} signals:
|
|
672
|
+
|
|
673
|
+
${fullSummary}`;
|
|
674
|
+
return createMcpResponse(null, content, {
|
|
675
|
+
contentSummary: content,
|
|
676
|
+
includeSerializedStructuredContent: false
|
|
677
|
+
});
|
|
666
678
|
}
|
|
667
679
|
|
|
668
680
|
// src/mcp/sampling.ts
|
|
@@ -701,36 +713,68 @@ async function handleMemoryRecap(params, db2) {
|
|
|
701
713
|
const rows = db2.memories.getRecentMemories(validated.repo, validated.limit, validated.offset, false, [
|
|
702
714
|
"task_archive"
|
|
703
715
|
]);
|
|
704
|
-
const COLUMNS = ["id", "title", "type", "importance"];
|
|
705
|
-
const topRows = rows.map((row) => [row.id, row.title ?? "Untitled", row.type, row.importance]);
|
|
716
|
+
const COLUMNS = ["id", "code", "title", "type", "importance"];
|
|
717
|
+
const topRows = rows.map((row) => [row.id, row.code || "-", row.title ?? "Untitled", row.type, row.importance]);
|
|
706
718
|
const byType = {};
|
|
707
719
|
for (const [type, count] of Object.entries(stats.byType)) {
|
|
708
720
|
if (type !== "task_archive") {
|
|
709
721
|
byType[type] = count;
|
|
710
722
|
}
|
|
711
723
|
}
|
|
712
|
-
|
|
724
|
+
let contentSummary;
|
|
725
|
+
if (!validated.structured) {
|
|
726
|
+
if (total > 0) {
|
|
727
|
+
const parts = [];
|
|
728
|
+
for (const [memType, count] of Object.entries(byType)) {
|
|
729
|
+
if (count > 0) {
|
|
730
|
+
parts.push(`${capitalize2(memType)}: ${count}`);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
const memoriesByType = {};
|
|
734
|
+
for (const row of rows) {
|
|
735
|
+
const typeLabel = row.type || "unknown";
|
|
736
|
+
if (!memoriesByType[typeLabel]) {
|
|
737
|
+
memoriesByType[typeLabel] = [];
|
|
738
|
+
}
|
|
739
|
+
memoriesByType[typeLabel].push(row);
|
|
740
|
+
}
|
|
741
|
+
for (const [memType, items] of Object.entries(memoriesByType)) {
|
|
742
|
+
parts.push("");
|
|
743
|
+
parts.push(`${capitalize2(memType)}:`);
|
|
744
|
+
parts.push("- code|importance|title");
|
|
745
|
+
for (const row of items) {
|
|
746
|
+
const code = row.code || "-";
|
|
747
|
+
parts.push(`- ${code}|${row.importance}|${row.title}`);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
parts.push("");
|
|
751
|
+
parts.push("Use memory-detail with memory_id (or code) for full content.");
|
|
752
|
+
contentSummary = parts.join("\n").trim();
|
|
753
|
+
} else {
|
|
754
|
+
contentSummary = `No memories found for repo "${validated.repo}".`;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
const structuredData = {
|
|
713
758
|
schema: "memory-recap",
|
|
714
759
|
repo: validated.repo,
|
|
715
760
|
count: rows.length,
|
|
716
761
|
total,
|
|
717
762
|
offset: validated.offset,
|
|
718
763
|
limit: validated.limit,
|
|
719
|
-
stats: {
|
|
720
|
-
by_type: byType
|
|
721
|
-
},
|
|
764
|
+
stats: { byType },
|
|
722
765
|
top: {
|
|
723
766
|
columns: [...COLUMNS],
|
|
724
767
|
rows: topRows
|
|
725
768
|
}
|
|
726
769
|
};
|
|
727
|
-
|
|
728
|
-
const contentSummary = total > 0 ? `Repo "${validated.repo}" has ${total} active memories. Showing ${rows.length} at offset ${validated.offset}: ${memoryList}. Use memory-detail to read full content.` : `No memories found for repo "${validated.repo}".`;
|
|
729
|
-
return createMcpResponse(structuredContent, contentSummary, {
|
|
770
|
+
return createMcpResponse(structuredData, contentSummary || "", {
|
|
730
771
|
contentSummary,
|
|
731
772
|
includeSerializedStructuredContent: false
|
|
732
773
|
});
|
|
733
774
|
}
|
|
775
|
+
function capitalize2(str) {
|
|
776
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
777
|
+
}
|
|
734
778
|
|
|
735
779
|
// src/mcp/tools/task.manage.ts
|
|
736
780
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
@@ -750,20 +794,36 @@ function describeTaskListFilter(status) {
|
|
|
750
794
|
if (labels.length === 2) return `${labels[0]} and ${labels[1]}`;
|
|
751
795
|
return `${labels.slice(0, -1).join(", ")}, and ${labels[labels.length - 1]}`;
|
|
752
796
|
}
|
|
753
|
-
function buildTaskListSummary(repo, count, status, phase, search,
|
|
797
|
+
function buildTaskListSummary(repo, count, status, phase, search, tasksByStatus) {
|
|
754
798
|
const filterLabel = describeTaskListFilter(status);
|
|
755
799
|
const taskLabel = count === 1 ? "task" : "tasks";
|
|
756
|
-
const parts = [
|
|
757
|
-
if (
|
|
758
|
-
parts.push(
|
|
800
|
+
const parts = [];
|
|
801
|
+
if (tasksByStatus && Object.keys(tasksByStatus).length > 0) {
|
|
802
|
+
parts.push("Current Available Tasks:");
|
|
803
|
+
for (const [taskStatus, items] of Object.entries(tasksByStatus)) {
|
|
804
|
+
if (items.length > 0) {
|
|
805
|
+
parts.push("");
|
|
806
|
+
parts.push(`${capitalize3(taskStatus)}:`);
|
|
807
|
+
parts.push("- code|status|priority|title");
|
|
808
|
+
for (const t of items) {
|
|
809
|
+
parts.push(`- ${t.task_code}|${t.status}|${t.priority}|${t.title}`);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
} else {
|
|
814
|
+
parts.push(`Found ${count} ${filterLabel} ${taskLabel} in repo "${repo}".`);
|
|
759
815
|
}
|
|
760
|
-
if (search) {
|
|
761
|
-
parts.push(
|
|
816
|
+
if (phase || search) {
|
|
817
|
+
parts.push("");
|
|
818
|
+
if (phase) parts.push(`Phase filter: ${phase}.`);
|
|
819
|
+
if (search) parts.push(`Search filter: "${search}".`);
|
|
762
820
|
}
|
|
763
|
-
parts.push(
|
|
764
|
-
parts.push(
|
|
765
|
-
parts.
|
|
766
|
-
|
|
821
|
+
parts.push("");
|
|
822
|
+
parts.push("See task-detail with task_code for details.");
|
|
823
|
+
return parts.join("\n").trim();
|
|
824
|
+
}
|
|
825
|
+
function capitalize3(str) {
|
|
826
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
767
827
|
}
|
|
768
828
|
function deriveTaskStatusTimestamps(status, now, existingTask) {
|
|
769
829
|
const timestamps = {
|
|
@@ -817,7 +877,6 @@ Comments & History:
|
|
|
817
877
|
title: truncatedTitle,
|
|
818
878
|
content,
|
|
819
879
|
importance: Math.min(5, task.priority + 1),
|
|
820
|
-
// Slightly higher importance for archived tasks
|
|
821
880
|
agent: task.agent || "system",
|
|
822
881
|
role: task.role || "unknown",
|
|
823
882
|
model: "system",
|
|
@@ -833,14 +892,16 @@ Comments & History:
|
|
|
833
892
|
}
|
|
834
893
|
}
|
|
835
894
|
async function handleTaskList(args, storage) {
|
|
895
|
+
const validated = TaskListSchema.parse(args);
|
|
836
896
|
const {
|
|
837
897
|
repo,
|
|
838
898
|
status = "backlog,pending,in_progress,blocked",
|
|
839
899
|
phase,
|
|
840
900
|
query,
|
|
841
901
|
limit,
|
|
842
|
-
offset
|
|
843
|
-
|
|
902
|
+
offset,
|
|
903
|
+
structured: isStructuredRequest
|
|
904
|
+
} = validated;
|
|
844
905
|
let statuses = [];
|
|
845
906
|
if (status !== "all") {
|
|
846
907
|
statuses = status.split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -856,7 +917,7 @@ async function handleTaskList(args, storage) {
|
|
|
856
917
|
t.priority,
|
|
857
918
|
t.comments_count || 0
|
|
858
919
|
]);
|
|
859
|
-
const
|
|
920
|
+
const structuredData = {
|
|
860
921
|
schema: "task-list",
|
|
861
922
|
tasks: {
|
|
862
923
|
columns: [...COLUMNS],
|
|
@@ -865,11 +926,20 @@ async function handleTaskList(args, storage) {
|
|
|
865
926
|
count: rows.length,
|
|
866
927
|
offset
|
|
867
928
|
};
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
929
|
+
let contentSummary;
|
|
930
|
+
if (!isStructuredRequest) {
|
|
931
|
+
const tasksByStatus = {};
|
|
932
|
+
for (const t of filteredTasks) {
|
|
933
|
+
const statusLabel = t.status === "in_progress" ? "In Progress" : capitalize3(t.status);
|
|
934
|
+
if (!tasksByStatus[statusLabel]) {
|
|
935
|
+
tasksByStatus[statusLabel] = [];
|
|
936
|
+
}
|
|
937
|
+
tasksByStatus[statusLabel].push(t);
|
|
938
|
+
}
|
|
939
|
+
contentSummary = buildTaskListSummary(repo, rows.length, status, phase, query, tasksByStatus);
|
|
940
|
+
}
|
|
941
|
+
return createMcpResponse(structuredData, contentSummary || "", {
|
|
942
|
+
contentSummary,
|
|
873
943
|
includeSerializedStructuredContent: false
|
|
874
944
|
});
|
|
875
945
|
}
|
|
@@ -890,7 +960,9 @@ async function handleTaskCreate(args, storage) {
|
|
|
890
960
|
codesInRequest.add(taskData.task_code);
|
|
891
961
|
const normalizedStatus = taskData.status || "backlog";
|
|
892
962
|
if (normalizedStatus !== "backlog" && normalizedStatus !== "pending") {
|
|
893
|
-
throw new Error(
|
|
963
|
+
throw new Error(
|
|
964
|
+
`New tasks must be 'backlog' or 'pending'. Task '${taskData.task_code}' has status '${normalizedStatus}'.`
|
|
965
|
+
);
|
|
894
966
|
}
|
|
895
967
|
if (normalizedStatus === "pending") {
|
|
896
968
|
const stats = storage.tasks.getTaskStats(repo);
|
|
@@ -899,7 +971,9 @@ async function handleTaskCreate(args, storage) {
|
|
|
899
971
|
return t?.status === "pending";
|
|
900
972
|
}).length;
|
|
901
973
|
if (stats.todo + pendingInRequest >= 10) {
|
|
902
|
-
throw new Error(
|
|
974
|
+
throw new Error(
|
|
975
|
+
`Cannot create task '${taskData.task_code}' as 'pending'. Maximum of 10 pending tasks reached.`
|
|
976
|
+
);
|
|
903
977
|
}
|
|
904
978
|
}
|
|
905
979
|
const statusTimestamps2 = deriveTaskStatusTimestamps(normalizedStatus, now2);
|
|
@@ -934,7 +1008,22 @@ async function handleTaskCreate(args, storage) {
|
|
|
934
1008
|
`Created ${bulkTasks.length} tasks in repo "${repo}".`
|
|
935
1009
|
);
|
|
936
1010
|
}
|
|
937
|
-
const {
|
|
1011
|
+
const {
|
|
1012
|
+
task_code,
|
|
1013
|
+
phase,
|
|
1014
|
+
title,
|
|
1015
|
+
description,
|
|
1016
|
+
status,
|
|
1017
|
+
priority,
|
|
1018
|
+
agent,
|
|
1019
|
+
role,
|
|
1020
|
+
doc_path,
|
|
1021
|
+
tags,
|
|
1022
|
+
metadata,
|
|
1023
|
+
parent_id,
|
|
1024
|
+
depends_on,
|
|
1025
|
+
est_tokens
|
|
1026
|
+
} = singleTask;
|
|
938
1027
|
if (!task_code || !phase || !title || !description) {
|
|
939
1028
|
throw new Error("Missing required fields for single task creation (task_code, phase, title, description)");
|
|
940
1029
|
}
|
|
@@ -1112,7 +1201,9 @@ async function handleTaskUpdate(args, storage, vectors2) {
|
|
|
1112
1201
|
throw new Error("comment is required when changing task status");
|
|
1113
1202
|
}
|
|
1114
1203
|
if ((existingTask.status === "backlog" || existingTask.status === "pending" || existingTask.status === "blocked") && updates.status === "completed") {
|
|
1115
|
-
throw new Error(
|
|
1204
|
+
throw new Error(
|
|
1205
|
+
`Cannot transition task ${targetId} from '${existingTask.status}' directly to 'completed'. Must be 'in_progress' first.`
|
|
1206
|
+
);
|
|
1116
1207
|
}
|
|
1117
1208
|
}
|
|
1118
1209
|
if (updates.status === "completed" && isStatusChanging && updates.est_tokens === void 0) {
|
|
@@ -1124,7 +1215,8 @@ async function handleTaskUpdate(args, storage, vectors2) {
|
|
|
1124
1215
|
const finalUpdates = { ...updates };
|
|
1125
1216
|
if (updates.status === "completed") finalUpdates.finished_at = now;
|
|
1126
1217
|
else if (updates.status === "canceled") finalUpdates.canceled_at = now;
|
|
1127
|
-
else if (updates.status === "in_progress" && existingTask.status !== "in_progress")
|
|
1218
|
+
else if (updates.status === "in_progress" && existingTask.status !== "in_progress")
|
|
1219
|
+
finalUpdates.in_progress_at = now;
|
|
1128
1220
|
storage.tasks.updateTask(targetId, finalUpdates);
|
|
1129
1221
|
if (comment !== void 0 || isStatusChanging) {
|
|
1130
1222
|
storage.tasks.insertTaskComment({
|
|
@@ -1499,29 +1591,41 @@ async function handleMemoryAcknowledge(params, db2) {
|
|
|
1499
1591
|
|
|
1500
1592
|
// src/mcp/tools/memory.detail.ts
|
|
1501
1593
|
async function handleMemoryDetail(args, storage) {
|
|
1502
|
-
const { id } = MemoryDetailSchema.parse(args);
|
|
1503
|
-
|
|
1594
|
+
const { id, code } = MemoryDetailSchema.parse(args);
|
|
1595
|
+
let memory;
|
|
1596
|
+
if (id) {
|
|
1597
|
+
memory = storage.memories.getById(id);
|
|
1598
|
+
} else if (code) {
|
|
1599
|
+
memory = storage.memories.getByCode(code);
|
|
1600
|
+
}
|
|
1504
1601
|
if (!memory) {
|
|
1505
|
-
throw new Error(`Memory not found: ${id}`);
|
|
1506
|
-
}
|
|
1507
|
-
storage.memories.incrementHitCount(id);
|
|
1508
|
-
const
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1602
|
+
throw new Error(`Memory not found: ${id || code}`);
|
|
1603
|
+
}
|
|
1604
|
+
storage.memories.incrementHitCount(memory.id);
|
|
1605
|
+
const lines = [
|
|
1606
|
+
`Code: ${memory.code || "-"}`,
|
|
1607
|
+
`ID: ${memory.id}`,
|
|
1608
|
+
`Title: ${memory.title}`,
|
|
1609
|
+
`Type: ${memory.type}`,
|
|
1610
|
+
`Importance: ${memory.importance}`,
|
|
1611
|
+
`Created: ${memory.created_at}`
|
|
1612
|
+
];
|
|
1613
|
+
if (memory.scope?.repo) lines.push(`Repo: ${memory.scope.repo}`);
|
|
1614
|
+
if (memory.scope?.folder) lines.push(`Folder: ${memory.scope.folder}`);
|
|
1615
|
+
if (memory.content) {
|
|
1616
|
+
lines.push("", "--- Content ---", memory.content);
|
|
1617
|
+
}
|
|
1618
|
+
const content = lines.join("\n");
|
|
1619
|
+
return createMcpResponse(memory, content, {
|
|
1620
|
+
contentSummary: content,
|
|
1621
|
+
includeSerializedStructuredContent: false
|
|
1519
1622
|
});
|
|
1520
1623
|
}
|
|
1521
1624
|
|
|
1522
1625
|
// src/mcp/tools/task.get.ts
|
|
1523
1626
|
async function handleTaskGet(args, storage) {
|
|
1524
|
-
const
|
|
1627
|
+
const validated = TaskGetSchema.parse(args);
|
|
1628
|
+
const { repo, id, task_code, structured: isStructuredRequest } = validated;
|
|
1525
1629
|
let task;
|
|
1526
1630
|
if (id) {
|
|
1527
1631
|
task = storage.tasks.getTaskById(id);
|
|
@@ -1533,17 +1637,40 @@ async function handleTaskGet(args, storage) {
|
|
|
1533
1637
|
if (!task) {
|
|
1534
1638
|
throw new Error(`Task not found: ${id || task_code} in repo ${repo}`);
|
|
1535
1639
|
}
|
|
1536
|
-
const
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
{
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1640
|
+
const comments = storage.tasks.getTaskCommentsByTaskId(task.id);
|
|
1641
|
+
let contentSummary;
|
|
1642
|
+
if (!isStructuredRequest) {
|
|
1643
|
+
const lines = [
|
|
1644
|
+
`Task: ${task.title}`,
|
|
1645
|
+
`Code: ${task.task_code}`,
|
|
1646
|
+
`Status: ${task.status}`,
|
|
1647
|
+
`Priority: ${task.priority}`,
|
|
1648
|
+
`ID: ${task.id}`
|
|
1649
|
+
];
|
|
1650
|
+
if (task.phase) lines.push(`Phase: ${task.phase}`);
|
|
1651
|
+
if (task.description) lines.push(`Description: ${task.description}`);
|
|
1652
|
+
if (task.metadata) lines.push(`Metadata: ${JSON.stringify(task.metadata)}`);
|
|
1653
|
+
lines.push(`Created: ${task.created_at}`);
|
|
1654
|
+
if (task.updated_at) lines.push(`Updated: ${task.updated_at}`);
|
|
1655
|
+
if (task.in_progress_at) lines.push(`Started: ${task.in_progress_at}`);
|
|
1656
|
+
if (task.finished_at) lines.push(`Finished: ${task.finished_at}`);
|
|
1657
|
+
if (comments.length > 0) {
|
|
1658
|
+
lines.push("", "--- History ---");
|
|
1659
|
+
for (const c of comments) {
|
|
1660
|
+
const statusChange = c.previous_status || c.next_status ? ` [${c.previous_status || "?"} \u2192 ${c.next_status || "?"}]` : "";
|
|
1661
|
+
const agentInfo = c.agent ? ` (${c.agent})` : "";
|
|
1662
|
+
lines.push(`- ${c.created_at}${statusChange}${agentInfo}: ${c.comment}`);
|
|
1545
1663
|
}
|
|
1546
|
-
|
|
1664
|
+
}
|
|
1665
|
+
contentSummary = lines.join("\n");
|
|
1666
|
+
}
|
|
1667
|
+
const structuredData = {
|
|
1668
|
+
...task,
|
|
1669
|
+
comments
|
|
1670
|
+
};
|
|
1671
|
+
return createMcpResponse(structuredData, contentSummary || "", {
|
|
1672
|
+
contentSummary,
|
|
1673
|
+
includeSerializedStructuredContent: false
|
|
1547
1674
|
});
|
|
1548
1675
|
}
|
|
1549
1676
|
|
|
@@ -1843,9 +1970,10 @@ var RealVectorStore = class {
|
|
|
1843
1970
|
|
|
1844
1971
|
// src/mcp/server.ts
|
|
1845
1972
|
import fs2 from "fs";
|
|
1973
|
+
process.env.MCP_SERVER = "true";
|
|
1846
1974
|
if (process.argv.includes("doctor")) {
|
|
1847
1975
|
process.stderr.write("\n\u{1F3E5} MCP Local Memory - System Diagnosis\n\n");
|
|
1848
|
-
const db2 =
|
|
1976
|
+
const db2 = await SQLiteStore.create();
|
|
1849
1977
|
const dbPath = db2.getDbPath();
|
|
1850
1978
|
process.stderr.write(`\u{1F4C2} Database Path: ${dbPath}
|
|
1851
1979
|
`);
|
|
@@ -1871,7 +1999,7 @@ if (process.argv.includes("doctor")) {
|
|
|
1871
1999
|
process.stderr.write("\n\u2728 Diagnosis complete.\n\n");
|
|
1872
2000
|
process.exit(0);
|
|
1873
2001
|
}
|
|
1874
|
-
var db =
|
|
2002
|
+
var db = await SQLiteStore.create();
|
|
1875
2003
|
var vectors = new RealVectorStore(db);
|
|
1876
2004
|
vectors.initialize().catch((err) => {
|
|
1877
2005
|
logger.warn("[Server] Initial vector model loading failed. Will retry on first use.", { error: String(err) });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vheins/local-memory-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "MCP Local Memory Service for coding copilot agents",
|
|
5
5
|
"mcpName": "io.github.vheins/local-memory-mcp",
|
|
6
6
|
"type": "module",
|
|
@@ -44,20 +44,19 @@
|
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@xenova/transformers": "^2.17.2",
|
|
47
|
-
"better-sqlite3": "^12.6.2",
|
|
48
47
|
"chart.js": "^4.5.1",
|
|
49
48
|
"dompurify": "^3.3.3",
|
|
50
49
|
"express": "^5.2.1",
|
|
51
50
|
"gray-matter": "^4.0.3",
|
|
52
51
|
"marked": "^18.0.0",
|
|
53
52
|
"qs": "^6.15.0",
|
|
53
|
+
"sql.js": "^1.14.1",
|
|
54
54
|
"zod": "^4.3.5"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@eslint/js": "^10.0.1",
|
|
58
58
|
"@fast-check/vitest": "^0.3.0",
|
|
59
59
|
"@sveltejs/vite-plugin-svelte": "^7.0.0",
|
|
60
|
-
"@types/better-sqlite3": "^7.6.13",
|
|
61
60
|
"@types/dompurify": "^3.0.5",
|
|
62
61
|
"@types/express": "^5.0.6",
|
|
63
62
|
"@types/marked": "^5.0.2",
|