@xultrax-web/agent-memory-mcp 0.6.0 → 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/LICENSE +21 -21
- package/README.md +38 -18
- package/dist/index.js +352 -4
- package/package.json +1 -1
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 xultrax-web
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 xultrax-web
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -185,18 +185,30 @@ Custom path:
|
|
|
185
185
|
|
|
186
186
|
## Tools
|
|
187
187
|
|
|
188
|
-
| Tool | Purpose
|
|
189
|
-
| ------------------- |
|
|
190
|
-
| `save_memory` | Create or update a memory. Atomic write + locked. Validates name + type. Updates the index.
|
|
191
|
-
| `search_memories` | Fuzzy search (Fuse.js · typo-tolerant, word-order tolerant, partial matches). Returns top N with relevance 0-100 + body snippet.
|
|
192
|
-
| `relevant_memories` | Same matching as search, but returns full memory bodies as one markdown doc. Built for LLM auto-context.
|
|
193
|
-
| `get_memory` | Fetch one memory by name. Returns frontmatter + body.
|
|
194
|
-
| `list_memories` | List memories. Optional `type` filter. Paginated (default 50/page).
|
|
195
|
-
| `delete_memory` | Soft delete: moves the memory to `.trash/<ts>-<name>.md`. Recoverable until you empty `.trash/` by hand.
|
|
196
|
-
| `restore_memory` | Restore a soft-deleted memory from `.trash/`. Picks the most recent trash entry for the name.
|
|
197
|
-
| `doctor` | Storage integrity check. Reports orphans, dangling index entries, unreadable files. Pass `rebuild-index=true` to repair `MEMORY.md` from disk.
|
|
198
|
-
| `stats` | Dashboard: counts per type, total size, largest memory, audit-log size, trash count.
|
|
199
|
-
| `log_events` | Read recent entries from the audit event log. Optional `tail` (default 20) + `action` filter.
|
|
188
|
+
| Tool | Purpose |
|
|
189
|
+
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
190
|
+
| `save_memory` | Create or update a memory. Atomic write + locked. Validates name + type. Updates the index. |
|
|
191
|
+
| `search_memories` | Fuzzy search (Fuse.js · typo-tolerant, word-order tolerant, partial matches). Returns top N with relevance 0-100 + body snippet. |
|
|
192
|
+
| `relevant_memories` | Same matching as search, but returns full memory bodies as one markdown doc. Built for LLM auto-context. |
|
|
193
|
+
| `get_memory` | Fetch one memory by name. Returns frontmatter + body. |
|
|
194
|
+
| `list_memories` | List memories. Optional `type` filter. Paginated (default 50/page). |
|
|
195
|
+
| `delete_memory` | Soft delete: moves the memory to `.trash/<ts>-<name>.md`. Recoverable until you empty `.trash/` by hand. |
|
|
196
|
+
| `restore_memory` | Restore a soft-deleted memory from `.trash/`. Picks the most recent trash entry for the name. |
|
|
197
|
+
| `doctor` | Storage integrity check. Reports orphans, dangling index entries, unreadable files. Pass `rebuild-index=true` to repair `MEMORY.md` from disk. |
|
|
198
|
+
| `stats` | Dashboard: counts per type, total size, largest memory, audit-log size, trash count. |
|
|
199
|
+
| `log_events` | Read recent entries from the audit event log. Optional `tail` (default 20) + `action` filter. |
|
|
200
|
+
| `verify_memory` | Re-evaluate a memory's claims. Extracts URLs/dates/file refs, flags stale-date signals, returns type-specific verification heuristics. Pairs with the `audit_stale` prompt. |
|
|
201
|
+
|
|
202
|
+
### Prompts
|
|
203
|
+
|
|
204
|
+
The server exposes 4 built-in MCP prompts that clients (Claude Desktop, Cursor, etc.) surface as slash-commands. These turn memory into an active workflow layer, not just a passive store:
|
|
205
|
+
|
|
206
|
+
| Prompt | Arguments | What it does |
|
|
207
|
+
| ------------------ | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
208
|
+
| `extract_memories` | none | LLM scans the current conversation, proposes candidate memories, and calls `save_memory` for each one (with type + description chosen). |
|
|
209
|
+
| `summarize_topic` | `topic` | LLM pulls memories relevant to the topic via `relevant_memories` and synthesizes them into a single summary with citations. |
|
|
210
|
+
| `prepare_handoff` | `project` (optional) | LLM walks project-type memories matching the filter and assembles a structured handoff doc (current state, open items, watch-outs). |
|
|
211
|
+
| `audit_stale` | none | LLM evaluates project + reference memories for staleness and produces a triage list (likely stale / worth verifying / still fresh). |
|
|
200
212
|
|
|
201
213
|
### Memory types
|
|
202
214
|
|
|
@@ -229,6 +241,7 @@ agent-memory doctor --rebuild-index # repair MEMORY.md from disk
|
|
|
229
241
|
agent-memory stats # dashboard: counts, sizes, audit/trash
|
|
230
242
|
agent-memory log # last 20 entries from the audit log
|
|
231
243
|
agent-memory log --tail 50 --action delete # filter by action, tail size
|
|
244
|
+
agent-memory verify deploy-process # extract URLs/dates/file refs + staleness heuristics
|
|
232
245
|
```
|
|
233
246
|
|
|
234
247
|
### Audit log + structured logging
|
|
@@ -326,15 +339,22 @@ This server is built to be used daily, not to demo well once.
|
|
|
326
339
|
|
|
327
340
|
**Shipped in v0.6:**
|
|
328
341
|
|
|
329
|
-
- **Vitest test suite** —
|
|
330
|
-
- **GitHub Actions CI** — runs tests on every push/PR across Node
|
|
342
|
+
- **Vitest test suite** — 25+ blackbox tests covering CLI + MCP server paths.
|
|
343
|
+
- **GitHub Actions CI** — runs tests on every push/PR across Node 20/22/24.
|
|
331
344
|
- **[COMPATIBILITY.md](COMPATIBILITY.md)** — known-working client matrix + quirks.
|
|
332
345
|
|
|
333
|
-
**
|
|
346
|
+
**Shipped in v0.7 · the active context layer:**
|
|
334
347
|
|
|
335
|
-
-
|
|
336
|
-
-
|
|
337
|
-
-
|
|
348
|
+
- **MCP Prompts capability** — 4 built-in workflows (`extract_memories`, `summarize_topic`, `prepare_handoff`, `audit_stale`) that the client surfaces as slash-commands.
|
|
349
|
+
- **`verify_memory` tool** — static analysis of a memory's URLs/dates/file refs with type-specific staleness heuristics. Plus the matching `agent-memory verify <name>` CLI.
|
|
350
|
+
- **Conflict detection on save** — fuzzy-matches new memories against existing ones; warns on near-duplicates without blocking the save (so the LLM can decide whether to merge, rename, or proceed).
|
|
351
|
+
|
|
352
|
+
**Landing in v0.8 - v0.9:**
|
|
353
|
+
|
|
354
|
+
- Tags + wiki-links + folder support + `find_related` (organization-at-scale)
|
|
355
|
+
- TUI / web UI for browsing + editing memories in a clean interface
|
|
356
|
+
- `agent-memory sync` for git-backed multi-machine memory (the moat)
|
|
357
|
+
- Memory packs for shareable curated bundles
|
|
338
358
|
|
|
339
359
|
---
|
|
340
360
|
|
package/dist/index.js
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
25
25
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
26
|
-
import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
26
|
+
import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
27
27
|
import Fuse from "fuse.js";
|
|
28
28
|
import matter from "gray-matter";
|
|
29
29
|
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync, } from "node:fs";
|
|
@@ -255,13 +255,89 @@ function toolSaveMemory(args) {
|
|
|
255
255
|
`---\n\n`;
|
|
256
256
|
const fp = memoryFilePath(name);
|
|
257
257
|
const isUpdate = existsSync(fp);
|
|
258
|
+
// Conflict detection · only when creating a NEW memory (updates are
|
|
259
|
+
// intentional re-saves and don't need a similarity warning). Fuzzy
|
|
260
|
+
// match against existing names + descriptions; if anything scores
|
|
261
|
+
// above the conflict threshold, warn but don't block — the LLM can
|
|
262
|
+
// decide whether to merge, rename, or proceed.
|
|
263
|
+
let conflictWarning = "";
|
|
264
|
+
if (!isUpdate) {
|
|
265
|
+
const conflicts = detectConflicts({ name, description, type });
|
|
266
|
+
if (conflicts.length > 0) {
|
|
267
|
+
const list = conflicts
|
|
268
|
+
.map((c) => ` - ${c.name} [${c.type}] (${c.similarity}% similar)`)
|
|
269
|
+
.join("\n");
|
|
270
|
+
conflictWarning =
|
|
271
|
+
`\n\nWARNING · potentially similar existing memory(ies):\n${list}\n` +
|
|
272
|
+
`Consider merging or renaming. To proceed anyway, the save has already been completed.`;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
258
275
|
return withLock(() => {
|
|
259
276
|
atomicWriteFile(fp, frontmatter + content + "\n");
|
|
260
277
|
upsertIndexEntryUnlocked(name, description);
|
|
261
|
-
logEvent("save", {
|
|
278
|
+
logEvent("save", {
|
|
279
|
+
name,
|
|
280
|
+
type,
|
|
281
|
+
update: isUpdate,
|
|
282
|
+
bytes: content.length,
|
|
283
|
+
conflicts: conflictWarning ? "warned" : undefined,
|
|
284
|
+
});
|
|
262
285
|
log("debug", "save_memory", { name, type, update: isUpdate });
|
|
263
|
-
return `${isUpdate ? "Updated" : "Saved"} memory "${name}" (${type}) at ${fp}`;
|
|
286
|
+
return `${isUpdate ? "Updated" : "Saved"} memory "${name}" (${type}) at ${fp}${conflictWarning}`;
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
function detectConflicts(candidate) {
|
|
290
|
+
const names = listMemoryFiles();
|
|
291
|
+
// Skip the candidate name itself (will be an update, not a conflict)
|
|
292
|
+
const others = names.filter((n) => n !== candidate.name);
|
|
293
|
+
if (others.length === 0)
|
|
294
|
+
return [];
|
|
295
|
+
const existing = others.map((n) => readMemory(n)).filter((m) => m !== null);
|
|
296
|
+
if (existing.length === 0)
|
|
297
|
+
return [];
|
|
298
|
+
// Two separate searches catch different conflict shapes:
|
|
299
|
+
// - Name-based: matches when the new slug is very close to an
|
|
300
|
+
// existing slug (e.g. "deploy-process" vs "deployment-strategy")
|
|
301
|
+
// - Description-based: matches when descriptions are paraphrases
|
|
302
|
+
// of each other regardless of name
|
|
303
|
+
// We merge results + dedupe. Threshold 0.5 here is intentionally
|
|
304
|
+
// looser than search (0.4) because we'd rather warn on a few false
|
|
305
|
+
// positives than miss an obvious duplicate.
|
|
306
|
+
// Threshold 0.6 catches paraphrases like "deployment-strategy" vs
|
|
307
|
+
// "deploy-process" (Fuse Bitap scores those around 0.5). The 45%
|
|
308
|
+
// similarity floor below then trims out the long tail.
|
|
309
|
+
const nameFuse = new Fuse(existing, {
|
|
310
|
+
includeScore: true,
|
|
311
|
+
threshold: 0.6,
|
|
312
|
+
ignoreLocation: true,
|
|
313
|
+
minMatchCharLength: 3,
|
|
314
|
+
keys: ["name"],
|
|
264
315
|
});
|
|
316
|
+
const descFuse = new Fuse(existing, {
|
|
317
|
+
includeScore: true,
|
|
318
|
+
threshold: 0.6,
|
|
319
|
+
ignoreLocation: true,
|
|
320
|
+
minMatchCharLength: 3,
|
|
321
|
+
keys: ["description"],
|
|
322
|
+
});
|
|
323
|
+
const merged = new Map();
|
|
324
|
+
const addHit = (name, type, score) => {
|
|
325
|
+
const existingHit = merged.get(name);
|
|
326
|
+
const similarity = Math.round((1 - score) * 100);
|
|
327
|
+
if (!existingHit || similarity > existingHit.similarity) {
|
|
328
|
+
merged.set(name, { name, type, similarity });
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
for (const r of nameFuse.search(candidate.name, { limit: 3 })) {
|
|
332
|
+
addHit(r.item.name, r.item.type, r.score ?? 1);
|
|
333
|
+
}
|
|
334
|
+
for (const r of descFuse.search(candidate.description, { limit: 3 })) {
|
|
335
|
+
addHit(r.item.name, r.item.type, r.score ?? 1);
|
|
336
|
+
}
|
|
337
|
+
return Array.from(merged.values())
|
|
338
|
+
.filter((h) => h.similarity >= 45) // 45%+ similarity = worth surfacing
|
|
339
|
+
.sort((a, b) => b.similarity - a.similarity)
|
|
340
|
+
.slice(0, 3);
|
|
265
341
|
}
|
|
266
342
|
function toolGetMemory(args) {
|
|
267
343
|
const name = String(args.name ?? "").trim();
|
|
@@ -558,6 +634,90 @@ function toolDoctor(args) {
|
|
|
558
634
|
return formatDoctorReport(report, rebuild);
|
|
559
635
|
}
|
|
560
636
|
// -------------------------------------------------------------
|
|
637
|
+
// verify_memory · re-evaluate a memory's claims
|
|
638
|
+
// -------------------------------------------------------------
|
|
639
|
+
//
|
|
640
|
+
// Static analysis only (no network calls — MCP servers may run
|
|
641
|
+
// offline). Extracts URLs + dates + file paths from the body and
|
|
642
|
+
// returns a structured report the LLM can act on. Pairs with the
|
|
643
|
+
// audit_stale prompt to triage memory hygiene.
|
|
644
|
+
const URL_PATTERN = /\bhttps?:\/\/[^\s<>"')]+/g;
|
|
645
|
+
const DATE_PATTERN = /\b(20\d{2})-(\d{2})-(\d{2})\b/g;
|
|
646
|
+
const FILE_PATH_PATTERN = /\b(?:[A-Za-z]:\\|\/)?(?:[\w.-]+[\\/])+[\w.-]+\.\w+\b/g;
|
|
647
|
+
function toolVerifyMemory(args) {
|
|
648
|
+
const name = String(args.name ?? "").trim();
|
|
649
|
+
if (!SLUG_PATTERN.test(name))
|
|
650
|
+
throw new Error(`Invalid name "${name}".`);
|
|
651
|
+
const mem = readMemory(name);
|
|
652
|
+
if (!mem)
|
|
653
|
+
return `Memory "${name}" not found.`;
|
|
654
|
+
const urls = Array.from(new Set(mem.body.match(URL_PATTERN) ?? []));
|
|
655
|
+
const dates = Array.from(new Set((mem.body.match(DATE_PATTERN) ?? []).map((d) => d)));
|
|
656
|
+
const filePaths = Array.from(new Set(mem.body.match(FILE_PATH_PATTERN) ?? []));
|
|
657
|
+
// Staleness heuristic: if any date in the body is more than 60 days
|
|
658
|
+
// old AND the memory type is project, flag it for review.
|
|
659
|
+
const now = Date.now();
|
|
660
|
+
const SIXTY_DAYS = 60 * 24 * 3600 * 1000;
|
|
661
|
+
const oldDates = dates.filter((d) => {
|
|
662
|
+
const parsed = Date.parse(d);
|
|
663
|
+
return !isNaN(parsed) && now - parsed > SIXTY_DAYS;
|
|
664
|
+
});
|
|
665
|
+
const lines = [];
|
|
666
|
+
lines.push(c(ANSI.bold, `verify_memory · ${mem.name}`));
|
|
667
|
+
lines.push(`type : ${mem.type}`);
|
|
668
|
+
lines.push(`description : ${mem.description}`);
|
|
669
|
+
lines.push("");
|
|
670
|
+
lines.push(c(ANSI.bold, "Static signals:"));
|
|
671
|
+
lines.push(` URLs found : ${urls.length}`);
|
|
672
|
+
lines.push(` Dates referenced : ${dates.length}${oldDates.length > 0 ? ` (${oldDates.length} > 60 days old)` : ""}`);
|
|
673
|
+
lines.push(` File-path refs : ${filePaths.length}`);
|
|
674
|
+
if (urls.length > 0) {
|
|
675
|
+
lines.push("");
|
|
676
|
+
lines.push(c(ANSI.bold, "URLs to verify:"));
|
|
677
|
+
for (const u of urls.slice(0, 10))
|
|
678
|
+
lines.push(` - ${u}`);
|
|
679
|
+
if (urls.length > 10)
|
|
680
|
+
lines.push(` ... +${urls.length - 10} more`);
|
|
681
|
+
}
|
|
682
|
+
if (oldDates.length > 0) {
|
|
683
|
+
lines.push("");
|
|
684
|
+
lines.push(c(ANSI.yellow, "Stale-date signals (consider whether claims are still current):"));
|
|
685
|
+
for (const d of oldDates.slice(0, 5))
|
|
686
|
+
lines.push(` - ${d}`);
|
|
687
|
+
}
|
|
688
|
+
if (filePaths.length > 0 && filePaths.length <= 10) {
|
|
689
|
+
lines.push("");
|
|
690
|
+
lines.push(c(ANSI.bold, "File paths referenced:"));
|
|
691
|
+
for (const fp of filePaths)
|
|
692
|
+
lines.push(` - ${fp}`);
|
|
693
|
+
}
|
|
694
|
+
lines.push("");
|
|
695
|
+
lines.push(c(ANSI.bold, "Type-specific verification heuristics:"));
|
|
696
|
+
switch (mem.type) {
|
|
697
|
+
case "reference":
|
|
698
|
+
lines.push(" - HEAD-check each URL above (200 = alive, 404 = dead, 410 = gone)");
|
|
699
|
+
lines.push(" - Verify the resource still says what the memory claims it says");
|
|
700
|
+
break;
|
|
701
|
+
case "project":
|
|
702
|
+
lines.push(" - Check if any dates above are stale relative to current project state");
|
|
703
|
+
lines.push(" - Cross-reference any names/people mentioned against current org chart");
|
|
704
|
+
lines.push(" - Verify any deadlines or commitments haven't already passed");
|
|
705
|
+
break;
|
|
706
|
+
case "feedback":
|
|
707
|
+
lines.push(" - Confirm the rule still applies (operator hasn't changed their mind)");
|
|
708
|
+
lines.push(" - Check the **Why:** is still load-bearing — if the original reason is gone, the rule may be obsolete");
|
|
709
|
+
break;
|
|
710
|
+
case "user":
|
|
711
|
+
lines.push(" - User preferences drift over time; ask the operator if a 6+ month old memory still holds");
|
|
712
|
+
break;
|
|
713
|
+
}
|
|
714
|
+
lines.push("");
|
|
715
|
+
lines.push(c(ANSI.dim, "Memory body for review:"));
|
|
716
|
+
lines.push("");
|
|
717
|
+
lines.push(mem.body);
|
|
718
|
+
return lines.join("\n");
|
|
719
|
+
}
|
|
720
|
+
// -------------------------------------------------------------
|
|
561
721
|
// Stats · operator dashboard
|
|
562
722
|
// -------------------------------------------------------------
|
|
563
723
|
function toolStats(_args) {
|
|
@@ -667,7 +827,7 @@ function actionColor(action) {
|
|
|
667
827
|
// -------------------------------------------------------------
|
|
668
828
|
// Server wiring
|
|
669
829
|
// -------------------------------------------------------------
|
|
670
|
-
const server = new Server({ name: "agent-memory", version: "0.
|
|
830
|
+
const server = new Server({ name: "agent-memory", version: "0.7.0" }, { capabilities: { tools: {}, resources: {}, prompts: {} } });
|
|
671
831
|
// -------------------------------------------------------------
|
|
672
832
|
// Resource URI scheme
|
|
673
833
|
// -------------------------------------------------------------
|
|
@@ -727,6 +887,169 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
|
727
887
|
}
|
|
728
888
|
throw new Error(`Unknown resource URI: ${uri}. Supported: ${URI_INDEX}, ${URI_MEMORY_PREFIX}{name}`);
|
|
729
889
|
});
|
|
890
|
+
const PROMPTS = [
|
|
891
|
+
{
|
|
892
|
+
name: "extract_memories",
|
|
893
|
+
description: "Scan the current conversation for things worth remembering. Returns a structured prompt asking the LLM to identify candidate memories and call save_memory for each one.",
|
|
894
|
+
},
|
|
895
|
+
{
|
|
896
|
+
name: "summarize_topic",
|
|
897
|
+
description: "Pull memories relevant to a topic and ask the LLM to synthesize them into a single coherent summary.",
|
|
898
|
+
arguments: [
|
|
899
|
+
{ name: "topic", description: "What to summarize what's known about.", required: true },
|
|
900
|
+
],
|
|
901
|
+
},
|
|
902
|
+
{
|
|
903
|
+
name: "prepare_handoff",
|
|
904
|
+
description: "Generate a project state snapshot from all project-type memories matching a filter. Useful for rotating on-call, end-of-day handoffs, or onboarding a collaborator.",
|
|
905
|
+
arguments: [
|
|
906
|
+
{
|
|
907
|
+
name: "project",
|
|
908
|
+
description: "Substring to filter project memories by (matches name + description + tags).",
|
|
909
|
+
required: false,
|
|
910
|
+
},
|
|
911
|
+
],
|
|
912
|
+
},
|
|
913
|
+
{
|
|
914
|
+
name: "audit_stale",
|
|
915
|
+
description: "Walk recent project and reference memories and ask the LLM to flag which ones are likely stale or contradicted by current state. Pairs with verify_memory for follow-up.",
|
|
916
|
+
},
|
|
917
|
+
];
|
|
918
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => ({
|
|
919
|
+
prompts: PROMPTS,
|
|
920
|
+
}));
|
|
921
|
+
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
922
|
+
const { name, arguments: args = {} } = request.params;
|
|
923
|
+
ensureStorage();
|
|
924
|
+
switch (name) {
|
|
925
|
+
case "extract_memories":
|
|
926
|
+
return {
|
|
927
|
+
description: PROMPTS[0].description,
|
|
928
|
+
messages: [
|
|
929
|
+
{
|
|
930
|
+
role: "user",
|
|
931
|
+
content: {
|
|
932
|
+
type: "text",
|
|
933
|
+
text: [
|
|
934
|
+
"Scan the current conversation for facts, rules, preferences, decisions, or context that would be useful to remember across future sessions. Focus on things that:",
|
|
935
|
+
"",
|
|
936
|
+
"- Reflect the operator's stable preferences or working style (type=user)",
|
|
937
|
+
"- Represent rules the assistant should follow going forward (type=feedback)",
|
|
938
|
+
"- Capture current-state context not derivable from code or docs (type=project)",
|
|
939
|
+
"- Point at external resources the operator references (type=reference)",
|
|
940
|
+
"",
|
|
941
|
+
"For each candidate, call the save_memory tool with:",
|
|
942
|
+
" - name: a short kebab-case slug (a-z, 0-9, -, _)",
|
|
943
|
+
" - type: one of user | feedback | project | reference",
|
|
944
|
+
" - description: a one-line summary used in the index",
|
|
945
|
+
" - content: the memory body in markdown. For feedback/project, include `**Why:**` and `**How to apply:**` lines.",
|
|
946
|
+
"",
|
|
947
|
+
"Before saving each one, briefly explain why it's worth remembering. Skip anything that's already obvious from code or that's only relevant to the current session.",
|
|
948
|
+
"",
|
|
949
|
+
"If there's nothing worth saving, say so plainly and stop.",
|
|
950
|
+
].join("\n"),
|
|
951
|
+
},
|
|
952
|
+
},
|
|
953
|
+
],
|
|
954
|
+
};
|
|
955
|
+
case "summarize_topic": {
|
|
956
|
+
const topic = String(args.topic ?? "").trim();
|
|
957
|
+
if (!topic)
|
|
958
|
+
throw new Error("summarize_topic requires a 'topic' argument");
|
|
959
|
+
return {
|
|
960
|
+
description: PROMPTS[1].description,
|
|
961
|
+
messages: [
|
|
962
|
+
{
|
|
963
|
+
role: "user",
|
|
964
|
+
content: {
|
|
965
|
+
type: "text",
|
|
966
|
+
text: [
|
|
967
|
+
`Call the relevant_memories tool with query="${topic}" (max=10).`,
|
|
968
|
+
"",
|
|
969
|
+
"Then synthesize the returned memories into a single coherent summary covering:",
|
|
970
|
+
"",
|
|
971
|
+
"1. What's established / known",
|
|
972
|
+
"2. Open questions or unresolved tensions across the memories",
|
|
973
|
+
"3. Any stale or contradicted claims worth flagging",
|
|
974
|
+
"",
|
|
975
|
+
"Keep it tight — aim for one paragraph per section unless the material is genuinely dense. Cite the source memory names inline as `[memory-name]`.",
|
|
976
|
+
].join("\n"),
|
|
977
|
+
},
|
|
978
|
+
},
|
|
979
|
+
],
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
case "prepare_handoff": {
|
|
983
|
+
const project = String(args.project ?? "").trim();
|
|
984
|
+
const filterClause = project
|
|
985
|
+
? `filtered to project memories matching "${project}"`
|
|
986
|
+
: "across all project memories";
|
|
987
|
+
return {
|
|
988
|
+
description: PROMPTS[2].description,
|
|
989
|
+
messages: [
|
|
990
|
+
{
|
|
991
|
+
role: "user",
|
|
992
|
+
content: {
|
|
993
|
+
type: "text",
|
|
994
|
+
text: [
|
|
995
|
+
`Generate a project handoff document ${filterClause}.`,
|
|
996
|
+
"",
|
|
997
|
+
project
|
|
998
|
+
? `Start by calling list_memories with type="project", then filter the results by substring match against "${project}".`
|
|
999
|
+
: `Start by calling list_memories with type="project" to get the full set.`,
|
|
1000
|
+
"",
|
|
1001
|
+
"For each relevant memory, call get_memory to load its full body. Then produce a single handoff document with these sections:",
|
|
1002
|
+
"",
|
|
1003
|
+
"## Current state",
|
|
1004
|
+
"What's in flight or recently shipped, distilled from project memories.",
|
|
1005
|
+
"",
|
|
1006
|
+
"## Open items",
|
|
1007
|
+
"Anything explicitly noted as pending, waiting, or unresolved.",
|
|
1008
|
+
"",
|
|
1009
|
+
"## Watch-outs",
|
|
1010
|
+
"Constraints, deadlines, or hidden gotchas captured in memory.",
|
|
1011
|
+
"",
|
|
1012
|
+
"## Reference material",
|
|
1013
|
+
"Links and external resources the next person should know about (from reference memories if they're relevant to the project).",
|
|
1014
|
+
"",
|
|
1015
|
+
"Cite source memories inline as `[memory-name]`. Keep prose dense; this is meant to be read by an experienced operator, not explained from scratch.",
|
|
1016
|
+
].join("\n"),
|
|
1017
|
+
},
|
|
1018
|
+
},
|
|
1019
|
+
],
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
case "audit_stale":
|
|
1023
|
+
return {
|
|
1024
|
+
description: PROMPTS[3].description,
|
|
1025
|
+
messages: [
|
|
1026
|
+
{
|
|
1027
|
+
role: "user",
|
|
1028
|
+
content: {
|
|
1029
|
+
type: "text",
|
|
1030
|
+
text: [
|
|
1031
|
+
'Call list_memories with type="project", then list_memories with type="reference". For each memory returned, evaluate whether its claims are likely still true based on:',
|
|
1032
|
+
"",
|
|
1033
|
+
"- Dates in the body (anything more than 30 days old in a `project` memory deserves scrutiny)",
|
|
1034
|
+
"- References to people, dependencies, or systems that may have changed",
|
|
1035
|
+
"- Claims about external state (URLs, dashboards, APIs) that you can't verify without external access",
|
|
1036
|
+
"",
|
|
1037
|
+
"Produce a triage list with three buckets:",
|
|
1038
|
+
"",
|
|
1039
|
+
"**Likely stale** (high confidence they're outdated) — explain why, suggest action.",
|
|
1040
|
+
"**Worth verifying** (claims you can't evaluate without external access) — suggest using the verify_memory tool.",
|
|
1041
|
+
"**Still fresh** (nothing in the content suggests staleness).",
|
|
1042
|
+
"",
|
|
1043
|
+
"Be conservative on the 'likely stale' bucket — false positives there create work for the operator.",
|
|
1044
|
+
].join("\n"),
|
|
1045
|
+
},
|
|
1046
|
+
},
|
|
1047
|
+
],
|
|
1048
|
+
};
|
|
1049
|
+
default:
|
|
1050
|
+
throw new Error(`Unknown prompt: ${name}`);
|
|
1051
|
+
}
|
|
1052
|
+
});
|
|
730
1053
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
731
1054
|
tools: [
|
|
732
1055
|
{
|
|
@@ -869,6 +1192,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
869
1192
|
},
|
|
870
1193
|
},
|
|
871
1194
|
},
|
|
1195
|
+
{
|
|
1196
|
+
name: "verify_memory",
|
|
1197
|
+
description: "Re-evaluate a memory's claims against signals in its content. Extracts URLs, dates, and file paths from the body; flags stale-date signals on project-type memories; returns type-specific verification heuristics for the LLM or operator to act on. Pairs with the audit_stale prompt.",
|
|
1198
|
+
inputSchema: {
|
|
1199
|
+
type: "object",
|
|
1200
|
+
properties: {
|
|
1201
|
+
name: { type: "string", description: "The memory's name slug" },
|
|
1202
|
+
},
|
|
1203
|
+
required: ["name"],
|
|
1204
|
+
},
|
|
1205
|
+
},
|
|
872
1206
|
],
|
|
873
1207
|
}));
|
|
874
1208
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
@@ -906,6 +1240,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
906
1240
|
case "log_events":
|
|
907
1241
|
result = toolLogEvents(args);
|
|
908
1242
|
break;
|
|
1243
|
+
case "verify_memory":
|
|
1244
|
+
result = toolVerifyMemory(args);
|
|
1245
|
+
break;
|
|
909
1246
|
default:
|
|
910
1247
|
throw new Error(`Unknown tool: ${name}`);
|
|
911
1248
|
}
|
|
@@ -938,6 +1275,7 @@ const CLI_COMMANDS = new Set([
|
|
|
938
1275
|
"doctor",
|
|
939
1276
|
"stats",
|
|
940
1277
|
"log",
|
|
1278
|
+
"verify",
|
|
941
1279
|
"import-claude-code",
|
|
942
1280
|
"help",
|
|
943
1281
|
"--help",
|
|
@@ -1067,6 +1405,13 @@ async function cliMain(command, rest) {
|
|
|
1067
1405
|
process.stdout.write(toolStats({}) + "\n");
|
|
1068
1406
|
return 0;
|
|
1069
1407
|
}
|
|
1408
|
+
case "verify": {
|
|
1409
|
+
const name = positional[0];
|
|
1410
|
+
if (!name)
|
|
1411
|
+
throw new Error("Usage: agent-memory verify <name>");
|
|
1412
|
+
process.stdout.write(toolVerifyMemory({ name }) + "\n");
|
|
1413
|
+
return 0;
|
|
1414
|
+
}
|
|
1070
1415
|
case "log": {
|
|
1071
1416
|
process.stdout.write(toolLogEvents({
|
|
1072
1417
|
tail: flags.tail ? Number(flags.tail) : undefined,
|
|
@@ -1117,6 +1462,9 @@ COMMANDS
|
|
|
1117
1462
|
stats Dashboard: counts per type, sizes, audit/trash counts.
|
|
1118
1463
|
log [--tail N] [--action save|delete|restore]
|
|
1119
1464
|
Recent audit-log entries.
|
|
1465
|
+
verify <name> Re-evaluate a memory's claims (URLs, dates, file refs,
|
|
1466
|
+
type-specific staleness heuristics). Static analysis;
|
|
1467
|
+
no network calls.
|
|
1120
1468
|
import-claude-code [--source <path>] [--project <pat>] [--overwrite] [--dry-run]
|
|
1121
1469
|
Walk ~/.claude/projects/*/memory/ and
|
|
1122
1470
|
import each memory into the current store.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xultrax-web/agent-memory-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"mcpName": "io.github.xultrax-web/agent-memory-mcp",
|
|
5
5
|
"description": "Markdown memory for AI agents. Plain files you can read, edit, grep, and commit. The only MCP memory server that isn't a database.",
|
|
6
6
|
"type": "module",
|