toon-memory 1.8.0 → 2.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/mcp/server.js +113 -10
- package/package.json +1 -1
- package/src/mcp/server.ts +136 -3
package/mcp/server.js
CHANGED
|
@@ -16506,8 +16506,8 @@ var require_resolve = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
16506
16506
|
}
|
|
16507
16507
|
return count;
|
|
16508
16508
|
}
|
|
16509
|
-
function getFullPath(resolver, id = "",
|
|
16510
|
-
if (
|
|
16509
|
+
function getFullPath(resolver, id = "", normalize2) {
|
|
16510
|
+
if (normalize2 !== false) id = normalizeId(id);
|
|
16511
16511
|
return _getFullPath(resolver, resolver.parse(id));
|
|
16512
16512
|
}
|
|
16513
16513
|
exports.getFullPath = getFullPath;
|
|
@@ -17602,7 +17602,7 @@ var require_schemes = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
17602
17602
|
var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
17603
17603
|
const { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizeComponentEncoding, isIPv4, nonSimpleDomain } = require_utils();
|
|
17604
17604
|
const { SCHEMES, getSchemeHandler } = require_schemes();
|
|
17605
|
-
function
|
|
17605
|
+
function normalize2(uri, options) {
|
|
17606
17606
|
if (typeof uri === "string") uri = serialize(parse3(uri, options), options);
|
|
17607
17607
|
else if (typeof uri === "object") uri = parse3(serialize(uri, options), options);
|
|
17608
17608
|
return uri;
|
|
@@ -17780,7 +17780,7 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
17780
17780
|
}
|
|
17781
17781
|
const fastUri = {
|
|
17782
17782
|
SCHEMES,
|
|
17783
|
-
normalize,
|
|
17783
|
+
normalize: normalize2,
|
|
17784
17784
|
resolve,
|
|
17785
17785
|
resolveComponent,
|
|
17786
17786
|
equal,
|
|
@@ -27796,6 +27796,27 @@ function parseRelativeDate(since) {
|
|
|
27796
27796
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
27797
27797
|
return today;
|
|
27798
27798
|
}
|
|
27799
|
+
var normalize = (s) => s.toLowerCase().replace(/[-_]/g, " ").replace(/\s+/g, " ").trim();
|
|
27800
|
+
function findRelatedEntries(text, excludeKey = "", limit = 3) {
|
|
27801
|
+
const data = readMemory();
|
|
27802
|
+
const lines = data.split("\n").filter((l) => l.startsWith(" ") && l.includes("|") && !l.startsWith(" summaries:"));
|
|
27803
|
+
const queryTokens = normalize(text).split(" ").filter(Boolean);
|
|
27804
|
+
const scored = lines.map((line) => {
|
|
27805
|
+
const trimmed = line.trim();
|
|
27806
|
+
const parts = trimmed.split("|");
|
|
27807
|
+
if (parts.length < 7) return null;
|
|
27808
|
+
const [id, cat, key, content, file2, tags, date5] = parts;
|
|
27809
|
+
if (key === excludeKey) return null;
|
|
27810
|
+
const searchStr = normalize(`${id} ${cat} ${key} ${content} ${file2} ${tags}`);
|
|
27811
|
+
let score = 0;
|
|
27812
|
+
for (const token of queryTokens) {
|
|
27813
|
+
if (searchStr.includes(token)) score++;
|
|
27814
|
+
}
|
|
27815
|
+
if (score === 0) return null;
|
|
27816
|
+
return { id, cat, key, content, file: file2, tags, date: date5, score };
|
|
27817
|
+
}).filter(Boolean).sort((a, b) => b.score - a.score).slice(0, limit);
|
|
27818
|
+
return scored;
|
|
27819
|
+
}
|
|
27799
27820
|
function archiveOldEntries() {
|
|
27800
27821
|
const data = readMemory();
|
|
27801
27822
|
const lines = data.split("\n");
|
|
@@ -27852,8 +27873,58 @@ archived:
|
|
|
27852
27873
|
return { archived: toArchive.length, kept: toKeep.length };
|
|
27853
27874
|
}
|
|
27854
27875
|
var server = new McpServer(
|
|
27855
|
-
{ name: "toon-memory", version: "
|
|
27856
|
-
{ capabilities: { tools: { listChanged: true } } }
|
|
27876
|
+
{ name: "toon-memory", version: "2.0.0" },
|
|
27877
|
+
{ capabilities: { tools: { listChanged: true }, resources: { listChanged: true } } }
|
|
27878
|
+
);
|
|
27879
|
+
server.registerResource(
|
|
27880
|
+
"memory-entries",
|
|
27881
|
+
"toon://memory/entries",
|
|
27882
|
+
{ title: "Memory Entries", mimeType: "text/plain" },
|
|
27883
|
+
async (uri) => {
|
|
27884
|
+
const data = readMemory();
|
|
27885
|
+
return { contents: [{ uri: uri.href, text: data }] };
|
|
27886
|
+
}
|
|
27887
|
+
);
|
|
27888
|
+
server.registerResource(
|
|
27889
|
+
"memory-stats",
|
|
27890
|
+
"toon://memory/stats",
|
|
27891
|
+
{ title: "Memory Stats", mimeType: "text/plain" },
|
|
27892
|
+
async (uri) => {
|
|
27893
|
+
const data = readMemory();
|
|
27894
|
+
const lines = data.split("\n").filter((l) => l.startsWith(" ") && l.includes("|") && !l.startsWith(" summaries:"));
|
|
27895
|
+
const entries = lines.map((l) => {
|
|
27896
|
+
const parts = l.trim().split("|");
|
|
27897
|
+
return { category: parts[1] || "unknown", ttl: parts[7] || "" };
|
|
27898
|
+
});
|
|
27899
|
+
const byCategory = {};
|
|
27900
|
+
for (const e of entries) {
|
|
27901
|
+
byCategory[e.category] = (byCategory[e.category] || 0) + 1;
|
|
27902
|
+
}
|
|
27903
|
+
const withTtl = entries.filter((e) => e.ttl).length;
|
|
27904
|
+
const expired = entries.filter((e) => e.ttl && isExpired(e.ttl)).length;
|
|
27905
|
+
const text = [
|
|
27906
|
+
`Entradas totales: ${entries.length}`,
|
|
27907
|
+
`TTL: ${withTtl} con expiraci\xF3n, ${expired} expiradas`,
|
|
27908
|
+
"Por categor\xEDa:",
|
|
27909
|
+
...Object.entries(byCategory).map(([k, v]) => ` ${k}: ${v}`)
|
|
27910
|
+
].join("\n");
|
|
27911
|
+
return { contents: [{ uri: uri.href, text }] };
|
|
27912
|
+
}
|
|
27913
|
+
);
|
|
27914
|
+
server.registerResource(
|
|
27915
|
+
"memory-summaries",
|
|
27916
|
+
"toon://memory/summaries",
|
|
27917
|
+
{ title: "Memory Summaries", mimeType: "text/plain" },
|
|
27918
|
+
async (uri) => {
|
|
27919
|
+
const data = readMemory();
|
|
27920
|
+
const lines = data.split("\n");
|
|
27921
|
+
const summaryIdx = lines.findIndex((l) => l.trim().startsWith("summaries:"));
|
|
27922
|
+
if (summaryIdx === -1) {
|
|
27923
|
+
return { contents: [{ uri: uri.href, text: "No hay res\xFAmenes guardados" }] };
|
|
27924
|
+
}
|
|
27925
|
+
const summaryText = lines.slice(summaryIdx + 1).filter((l) => l.includes(":")).join("\n");
|
|
27926
|
+
return { contents: [{ uri: uri.href, text: summaryText || "No hay res\xFAmenes guardados" }] };
|
|
27927
|
+
}
|
|
27857
27928
|
);
|
|
27858
27929
|
server.registerTool(
|
|
27859
27930
|
"memory_remember",
|
|
@@ -27922,9 +27993,18 @@ server.registerTool(
|
|
|
27922
27993
|
\u23F0 TTL: ${resolvedTtl}` : "";
|
|
27923
27994
|
const inferredMsg = tagsInferred ? `
|
|
27924
27995
|
\u{1F3F7}\uFE0F Tags inferidos: ${resolvedTags}` : "";
|
|
27996
|
+
const related = findRelatedEntries(`${key} ${content} ${resolvedTags}`, key, 3);
|
|
27997
|
+
let relatedMsg = "";
|
|
27998
|
+
if (related.length > 0) {
|
|
27999
|
+
const items = related.map((r) => ` [${r.cat}] ${r.key} \u2014 ${r.content.slice(0, 80)}`).join("\n");
|
|
28000
|
+
relatedMsg = `
|
|
28001
|
+
|
|
28002
|
+
\u{1F517} Entradas relacionadas:
|
|
28003
|
+
${items}`;
|
|
28004
|
+
}
|
|
27925
28005
|
return {
|
|
27926
28006
|
content: [{ type: "text", text: `\u{1F9E0} ${action}: ${category}/${key} (${entryId})
|
|
27927
|
-
${content}${ttlMsg}${inferredMsg}${archiveMsg}` }]
|
|
28007
|
+
${content}${ttlMsg}${inferredMsg}${archiveMsg}${relatedMsg}` }]
|
|
27928
28008
|
};
|
|
27929
28009
|
}
|
|
27930
28010
|
);
|
|
@@ -27943,8 +28023,8 @@ server.registerTool(
|
|
|
27943
28023
|
async ({ query, category, from_date, to_date }) => {
|
|
27944
28024
|
const data = readMemory();
|
|
27945
28025
|
const lines = data.split("\n").filter((l) => l.startsWith(" ") && l.includes("|") && !l.startsWith("summaries:"));
|
|
27946
|
-
const
|
|
27947
|
-
const queryTokens =
|
|
28026
|
+
const normalize2 = (s) => s.toLowerCase().replace(/[-_]/g, " ").replace(/\s+/g, " ").trim();
|
|
28027
|
+
const queryTokens = normalize2(query).split(" ").filter(Boolean);
|
|
27948
28028
|
const results = lines.map((line) => {
|
|
27949
28029
|
const trimmed = line.trim();
|
|
27950
28030
|
const parts = trimmed.split("|");
|
|
@@ -27954,7 +28034,7 @@ server.registerTool(
|
|
|
27954
28034
|
if (from_date && date5 < from_date) return null;
|
|
27955
28035
|
if (to_date && date5 > to_date) return null;
|
|
27956
28036
|
if (ttl && isExpired(ttl)) return null;
|
|
27957
|
-
const searchStr =
|
|
28037
|
+
const searchStr = normalize2(`${id} ${cat} ${key} ${content} ${file2} ${tags}`);
|
|
27958
28038
|
if (!queryTokens.every((token) => searchStr.includes(token))) return null;
|
|
27959
28039
|
return { id, cat, key, content, file: file2, tags, date: date5 };
|
|
27960
28040
|
}).filter(Boolean);
|
|
@@ -28088,6 +28168,29 @@ server.registerTool(
|
|
|
28088
28168
|
return { content: [{ type: "text", text: sections.join("\n") }] };
|
|
28089
28169
|
}
|
|
28090
28170
|
);
|
|
28171
|
+
server.registerTool(
|
|
28172
|
+
"memory_suggest",
|
|
28173
|
+
{
|
|
28174
|
+
title: "Suggest Related Memories",
|
|
28175
|
+
description: "Sugiere entradas de memoria relacionadas con un contexto dado. \xDAtil para obtener contexto antes de una tarea.",
|
|
28176
|
+
inputSchema: {
|
|
28177
|
+
context: external_exports.string().describe("Texto o contexto para buscar sugerencias"),
|
|
28178
|
+
limit: external_exports.number().optional().default(5).describe("M\xE1ximo de sugerencias")
|
|
28179
|
+
}
|
|
28180
|
+
},
|
|
28181
|
+
async ({ context, limit }) => {
|
|
28182
|
+
const related = findRelatedEntries(context, "", limit);
|
|
28183
|
+
if (related.length === 0) {
|
|
28184
|
+
return { content: [{ type: "text", text: `No se encontraron entradas relacionadas con "${context}"` }] };
|
|
28185
|
+
}
|
|
28186
|
+
const formatted = related.map((r) => `[${r.cat}] ${r.key} (${r.id})
|
|
28187
|
+
${r.content}
|
|
28188
|
+
File: ${r.file} | Tags: ${r.tags} | Date: ${r.date}`).join("\n\n");
|
|
28189
|
+
return { content: [{ type: "text", text: `\u{1F50D} Sugerencias para "${context}":
|
|
28190
|
+
|
|
28191
|
+
${formatted}` }] };
|
|
28192
|
+
}
|
|
28193
|
+
);
|
|
28091
28194
|
server.registerTool(
|
|
28092
28195
|
"memory_summary",
|
|
28093
28196
|
{
|
package/package.json
CHANGED
package/src/mcp/server.ts
CHANGED
|
@@ -298,6 +298,39 @@ function parseRelativeDate(since: string): string {
|
|
|
298
298
|
return today
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
+
const normalize = (s: string) => s.toLowerCase().replace(/[-_]/g, " ").replace(/\s+/g, " ").trim()
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Find entries related to the given text by fuzzy matching.
|
|
305
|
+
* Returns top N results ranked by match quality.
|
|
306
|
+
*/
|
|
307
|
+
function findRelatedEntries(text: string, excludeKey: string = "", limit: number = 3): Array<{ id: string; cat: string; key: string; content: string; file: string; tags: string; date: string; score: number }> {
|
|
308
|
+
const data = readMemory()
|
|
309
|
+
const lines = data.split("\n").filter((l) => l.startsWith(" ") && l.includes("|") && !l.startsWith(" summaries:"))
|
|
310
|
+
const queryTokens = normalize(text).split(" ").filter(Boolean)
|
|
311
|
+
|
|
312
|
+
const scored = lines
|
|
313
|
+
.map((line) => {
|
|
314
|
+
const trimmed = line.trim()
|
|
315
|
+
const parts = trimmed.split("|")
|
|
316
|
+
if (parts.length < 7) return null
|
|
317
|
+
const [id, cat, key, content, file, tags, date] = parts
|
|
318
|
+
if (key === excludeKey) return null
|
|
319
|
+
const searchStr = normalize(`${id} ${cat} ${key} ${content} ${file} ${tags}`)
|
|
320
|
+
let score = 0
|
|
321
|
+
for (const token of queryTokens) {
|
|
322
|
+
if (searchStr.includes(token)) score++
|
|
323
|
+
}
|
|
324
|
+
if (score === 0) return null
|
|
325
|
+
return { id, cat, key, content, file, tags, date, score }
|
|
326
|
+
})
|
|
327
|
+
.filter(Boolean)
|
|
328
|
+
.sort((a, b) => b!.score - a!.score)
|
|
329
|
+
.slice(0, limit)
|
|
330
|
+
|
|
331
|
+
return scored as Array<{ id: string; cat: string; key: string; content: string; file: string; tags: string; date: string; score: number }>
|
|
332
|
+
}
|
|
333
|
+
|
|
301
334
|
/**
|
|
302
335
|
* Archive entries older than ARCHIVE_DAYS to archive.toon.
|
|
303
336
|
*
|
|
@@ -381,8 +414,71 @@ function archiveOldEntries(): { archived: number; kept: number } {
|
|
|
381
414
|
}
|
|
382
415
|
|
|
383
416
|
const server = new McpServer(
|
|
384
|
-
{ name: "toon-memory", version: "
|
|
385
|
-
{ capabilities: { tools: { listChanged: true } } }
|
|
417
|
+
{ name: "toon-memory", version: "2.0.0" },
|
|
418
|
+
{ capabilities: { tools: { listChanged: true }, resources: { listChanged: true } } }
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Register memory entries as an MCP resource.
|
|
423
|
+
* Agents can read this as context in the system prompt.
|
|
424
|
+
*/
|
|
425
|
+
server.registerResource(
|
|
426
|
+
"memory-entries",
|
|
427
|
+
"toon://memory/entries",
|
|
428
|
+
{ title: "Memory Entries", mimeType: "text/plain" },
|
|
429
|
+
async (uri) => {
|
|
430
|
+
const data = readMemory()
|
|
431
|
+
return { contents: [{ uri: uri.href, text: data }] }
|
|
432
|
+
}
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Register memory stats as an MCP resource.
|
|
437
|
+
*/
|
|
438
|
+
server.registerResource(
|
|
439
|
+
"memory-stats",
|
|
440
|
+
"toon://memory/stats",
|
|
441
|
+
{ title: "Memory Stats", mimeType: "text/plain" },
|
|
442
|
+
async (uri) => {
|
|
443
|
+
const data = readMemory()
|
|
444
|
+
const lines = data.split("\n").filter((l) => l.startsWith(" ") && l.includes("|") && !l.startsWith(" summaries:"))
|
|
445
|
+
const entries = lines.map((l) => {
|
|
446
|
+
const parts = l.trim().split("|")
|
|
447
|
+
return { category: parts[1] || "unknown", ttl: parts[7] || "" }
|
|
448
|
+
})
|
|
449
|
+
const byCategory: Record<string, number> = {}
|
|
450
|
+
for (const e of entries) {
|
|
451
|
+
byCategory[e.category] = (byCategory[e.category] || 0) + 1
|
|
452
|
+
}
|
|
453
|
+
const withTtl = entries.filter((e) => e.ttl).length
|
|
454
|
+
const expired = entries.filter((e) => e.ttl && isExpired(e.ttl)).length
|
|
455
|
+
const text = [
|
|
456
|
+
`Entradas totales: ${entries.length}`,
|
|
457
|
+
`TTL: ${withTtl} con expiración, ${expired} expiradas`,
|
|
458
|
+
"Por categoría:",
|
|
459
|
+
...Object.entries(byCategory).map(([k, v]) => ` ${k}: ${v}`),
|
|
460
|
+
].join("\n")
|
|
461
|
+
return { contents: [{ uri: uri.href, text }] }
|
|
462
|
+
}
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Register memory summaries as an MCP resource.
|
|
467
|
+
*/
|
|
468
|
+
server.registerResource(
|
|
469
|
+
"memory-summaries",
|
|
470
|
+
"toon://memory/summaries",
|
|
471
|
+
{ title: "Memory Summaries", mimeType: "text/plain" },
|
|
472
|
+
async (uri) => {
|
|
473
|
+
const data = readMemory()
|
|
474
|
+
const lines = data.split("\n")
|
|
475
|
+
const summaryIdx = lines.findIndex((l) => l.trim().startsWith("summaries:"))
|
|
476
|
+
if (summaryIdx === -1) {
|
|
477
|
+
return { contents: [{ uri: uri.href, text: "No hay resúmenes guardados" }] }
|
|
478
|
+
}
|
|
479
|
+
const summaryText = lines.slice(summaryIdx + 1).filter((l) => l.includes(":")).join("\n")
|
|
480
|
+
return { contents: [{ uri: uri.href, text: summaryText || "No hay resúmenes guardados" }] }
|
|
481
|
+
}
|
|
386
482
|
)
|
|
387
483
|
|
|
388
484
|
/**
|
|
@@ -474,8 +570,16 @@ server.registerTool(
|
|
|
474
570
|
|
|
475
571
|
const ttlMsg = resolvedTtl ? `\n⏰ TTL: ${resolvedTtl}` : ""
|
|
476
572
|
const inferredMsg = tagsInferred ? `\n🏷️ Tags inferidos: ${resolvedTags}` : ""
|
|
573
|
+
|
|
574
|
+
const related = findRelatedEntries(`${key} ${content} ${resolvedTags}`, key, 3)
|
|
575
|
+
let relatedMsg = ""
|
|
576
|
+
if (related.length > 0) {
|
|
577
|
+
const items = related.map((r) => ` [${r.cat}] ${r.key} — ${r.content.slice(0, 80)}`).join("\n")
|
|
578
|
+
relatedMsg = `\n\n🔗 Entradas relacionadas:\n${items}`
|
|
579
|
+
}
|
|
580
|
+
|
|
477
581
|
return {
|
|
478
|
-
content: [{ type: "text" as const, text: `🧠 ${action}: ${category}/${key} (${entryId})\n${content}${ttlMsg}${inferredMsg}${archiveMsg}` }],
|
|
582
|
+
content: [{ type: "text" as const, text: `🧠 ${action}: ${category}/${key} (${entryId})\n${content}${ttlMsg}${inferredMsg}${archiveMsg}${relatedMsg}` }],
|
|
479
583
|
}
|
|
480
584
|
}
|
|
481
585
|
)
|
|
@@ -704,6 +808,35 @@ server.registerTool(
|
|
|
704
808
|
}
|
|
705
809
|
)
|
|
706
810
|
|
|
811
|
+
/**
|
|
812
|
+
* Register the memory_suggest tool.
|
|
813
|
+
* Suggest related entries for a given text context.
|
|
814
|
+
*/
|
|
815
|
+
server.registerTool(
|
|
816
|
+
"memory_suggest",
|
|
817
|
+
{
|
|
818
|
+
title: "Suggest Related Memories",
|
|
819
|
+
description: "Sugiere entradas de memoria relacionadas con un contexto dado. Útil para obtener contexto antes de una tarea.",
|
|
820
|
+
inputSchema: {
|
|
821
|
+
context: z.string().describe("Texto o contexto para buscar sugerencias"),
|
|
822
|
+
limit: z.number().optional().default(5).describe("Máximo de sugerencias"),
|
|
823
|
+
},
|
|
824
|
+
},
|
|
825
|
+
async ({ context, limit }) => {
|
|
826
|
+
const related = findRelatedEntries(context, "", limit)
|
|
827
|
+
|
|
828
|
+
if (related.length === 0) {
|
|
829
|
+
return { content: [{ type: "text" as const, text: `No se encontraron entradas relacionadas con "${context}"` }] }
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
const formatted = related
|
|
833
|
+
.map((r) => `[${r.cat}] ${r.key} (${r.id})\n ${r.content}\n File: ${r.file} | Tags: ${r.tags} | Date: ${r.date}`)
|
|
834
|
+
.join("\n\n")
|
|
835
|
+
|
|
836
|
+
return { content: [{ type: "text" as const, text: `🔍 Sugerencias para "${context}":\n\n${formatted}` }] }
|
|
837
|
+
}
|
|
838
|
+
)
|
|
839
|
+
|
|
707
840
|
/**
|
|
708
841
|
* Register the memory_summary tool.
|
|
709
842
|
* Get or set file summaries to save tokens when reading large files.
|