@coreidentitylabs/open-graph-memory-mcp 1.0.1

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.
Files changed (91) hide show
  1. package/.agents/skills/mcp-builder/LICENSE.txt +202 -0
  2. package/.agents/skills/mcp-builder/SKILL.md +236 -0
  3. package/.agents/skills/mcp-builder/reference/evaluation.md +602 -0
  4. package/.agents/skills/mcp-builder/reference/mcp_best_practices.md +249 -0
  5. package/.agents/skills/mcp-builder/reference/node_mcp_server.md +970 -0
  6. package/.agents/skills/mcp-builder/reference/python_mcp_server.md +719 -0
  7. package/.agents/skills/mcp-builder/scripts/connections.py +151 -0
  8. package/.agents/skills/mcp-builder/scripts/evaluation.py +373 -0
  9. package/.agents/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
  10. package/.agents/skills/mcp-builder/scripts/requirements.txt +2 -0
  11. package/.env.example +26 -0
  12. package/Implementation Plan.md +358 -0
  13. package/README.md +187 -0
  14. package/dist/constants.d.ts +34 -0
  15. package/dist/constants.d.ts.map +1 -0
  16. package/dist/constants.js +40 -0
  17. package/dist/constants.js.map +1 -0
  18. package/dist/encoding/embedder.d.ts +12 -0
  19. package/dist/encoding/embedder.d.ts.map +1 -0
  20. package/dist/encoding/embedder.js +85 -0
  21. package/dist/encoding/embedder.js.map +1 -0
  22. package/dist/encoding/pipeline.d.ts +28 -0
  23. package/dist/encoding/pipeline.d.ts.map +1 -0
  24. package/dist/encoding/pipeline.js +146 -0
  25. package/dist/encoding/pipeline.js.map +1 -0
  26. package/dist/evolution/consolidator.d.ts +12 -0
  27. package/dist/evolution/consolidator.d.ts.map +1 -0
  28. package/dist/evolution/consolidator.js +212 -0
  29. package/dist/evolution/consolidator.js.map +1 -0
  30. package/dist/index.d.ts +3 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +53 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/llm/openai-provider.d.ts +23 -0
  35. package/dist/llm/openai-provider.d.ts.map +1 -0
  36. package/dist/llm/openai-provider.js +141 -0
  37. package/dist/llm/openai-provider.js.map +1 -0
  38. package/dist/llm/prompts.d.ts +10 -0
  39. package/dist/llm/prompts.d.ts.map +1 -0
  40. package/dist/llm/prompts.js +63 -0
  41. package/dist/llm/prompts.js.map +1 -0
  42. package/dist/llm/provider.d.ts +7 -0
  43. package/dist/llm/provider.d.ts.map +1 -0
  44. package/dist/llm/provider.js +25 -0
  45. package/dist/llm/provider.js.map +1 -0
  46. package/dist/resources/context-resource.d.ts +8 -0
  47. package/dist/resources/context-resource.d.ts.map +1 -0
  48. package/dist/resources/context-resource.js +51 -0
  49. package/dist/resources/context-resource.js.map +1 -0
  50. package/dist/retrieval/search.d.ts +24 -0
  51. package/dist/retrieval/search.d.ts.map +1 -0
  52. package/dist/retrieval/search.js +143 -0
  53. package/dist/retrieval/search.js.map +1 -0
  54. package/dist/storage/factory.d.ts +10 -0
  55. package/dist/storage/factory.d.ts.map +1 -0
  56. package/dist/storage/factory.js +35 -0
  57. package/dist/storage/factory.js.map +1 -0
  58. package/dist/storage/json-store.d.ts +34 -0
  59. package/dist/storage/json-store.d.ts.map +1 -0
  60. package/dist/storage/json-store.js +248 -0
  61. package/dist/storage/json-store.js.map +1 -0
  62. package/dist/storage/neo4j-store.d.ts +31 -0
  63. package/dist/storage/neo4j-store.d.ts.map +1 -0
  64. package/dist/storage/neo4j-store.js +440 -0
  65. package/dist/storage/neo4j-store.js.map +1 -0
  66. package/dist/tools/memory-tools.d.ts +4 -0
  67. package/dist/tools/memory-tools.d.ts.map +1 -0
  68. package/dist/tools/memory-tools.js +873 -0
  69. package/dist/tools/memory-tools.js.map +1 -0
  70. package/dist/types.d.ts +129 -0
  71. package/dist/types.d.ts.map +1 -0
  72. package/dist/types.js +5 -0
  73. package/dist/types.js.map +1 -0
  74. package/implementation_plan.md.resolved.md +322 -0
  75. package/package.json +43 -0
  76. package/src/constants.ts +52 -0
  77. package/src/encoding/embedder.ts +93 -0
  78. package/src/encoding/pipeline.ts +197 -0
  79. package/src/evolution/consolidator.ts +281 -0
  80. package/src/index.ts +67 -0
  81. package/src/llm/openai-provider.ts +208 -0
  82. package/src/llm/prompts.ts +66 -0
  83. package/src/llm/provider.ts +37 -0
  84. package/src/resources/context-resource.ts +74 -0
  85. package/src/retrieval/search.ts +203 -0
  86. package/src/storage/factory.ts +48 -0
  87. package/src/storage/json-store.ts +325 -0
  88. package/src/storage/neo4j-store.ts +564 -0
  89. package/src/tools/memory-tools.ts +1067 -0
  90. package/src/types.ts +207 -0
  91. package/tsconfig.json +21 -0
@@ -0,0 +1,873 @@
1
+ // =============================================================================
2
+ // MCP Tools — Agent-Driven Memory Operations
3
+ // =============================================================================
4
+ import { z } from "zod";
5
+ import { v4 as uuidv4 } from "uuid";
6
+ import { generateLocalEmbedding } from "../encoding/embedder.js";
7
+ import { hybridSearch, getContextForTopic } from "../retrieval/search.js";
8
+ import { consolidateMemory } from "../evolution/consolidator.js";
9
+ import { encodeText } from "../encoding/pipeline.js";
10
+ import { DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE, CHARACTER_LIMIT, } from "../constants.js";
11
+ // =============================================================================
12
+ // Tool Registration
13
+ // =============================================================================
14
+ export function registerMemoryTools(server, store, llm) {
15
+ // ---- WRITE TOOLS ----
16
+ registerAddEntities(server, store);
17
+ registerAddRelations(server, store);
18
+ registerSaveConversation(server, store);
19
+ // ---- READ TOOLS ----
20
+ registerSearch(server, store);
21
+ registerGetEntity(server, store);
22
+ registerListEntities(server, store);
23
+ registerGetRelations(server, store);
24
+ registerGetContext(server, store);
25
+ // ---- MANAGEMENT TOOLS ----
26
+ registerDeleteEntity(server, store);
27
+ registerConsolidate(server, store);
28
+ registerStatus(server, store);
29
+ // ---- OPTIONAL SERVER-SIDE ENCODING ----
30
+ if (llm) {
31
+ registerEncodeText(server, store, llm);
32
+ console.error(`[open-memory] memory_encode_text tool enabled (LLM configured)`);
33
+ }
34
+ }
35
+ // =============================================================================
36
+ // Write Tools
37
+ // =============================================================================
38
+ function registerAddEntities(server, store) {
39
+ const EntitySchema = z.object({
40
+ name: z
41
+ .string()
42
+ .min(1)
43
+ .describe("Entity name (e.g., 'React Query', 'UserService')"),
44
+ type: z
45
+ .enum([
46
+ "entity",
47
+ "concept",
48
+ "event",
49
+ "code_pattern",
50
+ "decision",
51
+ "conversation",
52
+ ])
53
+ .default("entity")
54
+ .describe("Node type"),
55
+ description: z.string().min(1).describe("Description of the entity"),
56
+ metadata: z
57
+ .record(z.unknown())
58
+ .optional()
59
+ .describe("Optional key-value metadata"),
60
+ });
61
+ const InputSchema = z.object({
62
+ entities: z
63
+ .array(EntitySchema)
64
+ .min(1)
65
+ .max(50)
66
+ .describe("Array of entities to add to memory"),
67
+ });
68
+ server.registerTool("memory_add_entities", {
69
+ title: "Add Entities to Memory",
70
+ description: `Store entities (people, tools, concepts, code patterns, decisions) in the memory graph.
71
+ The agent extracts entities from conversation and passes structured data here.
72
+ Server auto-generates embeddings for semantic search.
73
+
74
+ Returns: IDs and names of stored entities.`,
75
+ inputSchema: InputSchema,
76
+ annotations: {
77
+ readOnlyHint: false,
78
+ destructiveHint: false,
79
+ idempotentHint: false,
80
+ openWorldHint: false,
81
+ },
82
+ }, async (params) => {
83
+ try {
84
+ const results = [];
85
+ for (const entity of params.entities) {
86
+ // Check for existing entity by name
87
+ const existing = await store.getNodeByName(entity.name);
88
+ if (existing) {
89
+ // Update existing entity
90
+ await store.updateNode(existing.id, {
91
+ description: entity.description,
92
+ metadata: { ...existing.metadata, ...entity.metadata },
93
+ embedding: generateLocalEmbedding(`${entity.name} ${entity.description}`),
94
+ });
95
+ results.push({
96
+ id: existing.id,
97
+ name: entity.name,
98
+ status: "updated",
99
+ });
100
+ }
101
+ else {
102
+ // Create new entity
103
+ const now = new Date().toISOString();
104
+ const node = {
105
+ id: uuidv4(),
106
+ name: entity.name,
107
+ type: entity.type,
108
+ description: entity.description,
109
+ embedding: generateLocalEmbedding(`${entity.name} ${entity.description}`),
110
+ metadata: entity.metadata ?? {},
111
+ createdAt: now,
112
+ updatedAt: now,
113
+ source: "agent",
114
+ accessCount: 0,
115
+ };
116
+ await store.addNode(node);
117
+ results.push({ id: node.id, name: entity.name, status: "created" });
118
+ }
119
+ }
120
+ const output = { stored: results.length, entities: results };
121
+ return {
122
+ content: [
123
+ { type: "text", text: JSON.stringify(output, null, 2) },
124
+ ],
125
+ };
126
+ }
127
+ catch (error) {
128
+ return errorResponse(error);
129
+ }
130
+ });
131
+ }
132
+ function registerAddRelations(server, store) {
133
+ const RelationSchema = z.object({
134
+ source: z.string().min(1).describe("Source entity name or ID"),
135
+ target: z.string().min(1).describe("Target entity name or ID"),
136
+ relation: z
137
+ .string()
138
+ .min(1)
139
+ .describe("Relationship type (e.g., 'uses', 'depends_on', 'decided_to')"),
140
+ description: z
141
+ .string()
142
+ .optional()
143
+ .describe("Description of the relationship"),
144
+ weight: z
145
+ .number()
146
+ .min(0)
147
+ .max(1)
148
+ .default(0.8)
149
+ .describe("Confidence weight (0-1)"),
150
+ });
151
+ const InputSchema = z.object({
152
+ relations: z
153
+ .array(RelationSchema)
154
+ .min(1)
155
+ .max(50)
156
+ .describe("Array of relationships to add"),
157
+ });
158
+ server.registerTool("memory_add_relations", {
159
+ title: "Add Relations to Memory",
160
+ description: `Store relationships between entities in the memory graph.
161
+ Source and target can be entity names (resolved automatically) or IDs.
162
+
163
+ Returns: IDs and details of stored edges.`,
164
+ inputSchema: InputSchema,
165
+ annotations: {
166
+ readOnlyHint: false,
167
+ destructiveHint: false,
168
+ idempotentHint: false,
169
+ openWorldHint: false,
170
+ },
171
+ }, async (params) => {
172
+ try {
173
+ const results = [];
174
+ for (const rel of params.relations) {
175
+ // Resolve source and target by name or ID
176
+ const sourceNode = (await store.getNode(rel.source)) ??
177
+ (await store.getNodeByName(rel.source));
178
+ const targetNode = (await store.getNode(rel.target)) ??
179
+ (await store.getNodeByName(rel.target));
180
+ if (!sourceNode) {
181
+ results.push({
182
+ id: "",
183
+ source: rel.source,
184
+ target: rel.target,
185
+ relation: rel.relation,
186
+ status: `error: source '${rel.source}' not found`,
187
+ });
188
+ continue;
189
+ }
190
+ if (!targetNode) {
191
+ results.push({
192
+ id: "",
193
+ source: rel.source,
194
+ target: rel.target,
195
+ relation: rel.relation,
196
+ status: `error: target '${rel.target}' not found`,
197
+ });
198
+ continue;
199
+ }
200
+ const now = new Date().toISOString();
201
+ const edge = {
202
+ id: uuidv4(),
203
+ source: sourceNode.id,
204
+ target: targetNode.id,
205
+ relation: rel.relation,
206
+ description: rel.description ??
207
+ `${sourceNode.name} ${rel.relation} ${targetNode.name}`,
208
+ weight: rel.weight,
209
+ metadata: {},
210
+ createdAt: now,
211
+ updatedAt: now,
212
+ };
213
+ await store.addEdge(edge);
214
+ results.push({
215
+ id: edge.id,
216
+ source: sourceNode.name,
217
+ target: targetNode.name,
218
+ relation: rel.relation,
219
+ status: "created",
220
+ });
221
+ }
222
+ const output = {
223
+ stored: results.filter((r) => r.status === "created").length,
224
+ relations: results,
225
+ };
226
+ return {
227
+ content: [
228
+ { type: "text", text: JSON.stringify(output, null, 2) },
229
+ ],
230
+ };
231
+ }
232
+ catch (error) {
233
+ return errorResponse(error);
234
+ }
235
+ });
236
+ }
237
+ function registerSaveConversation(server, store) {
238
+ const MessageSchema = z.object({
239
+ role: z.string().describe("Message role (user, assistant, system)"),
240
+ content: z.string().describe("Message content"),
241
+ });
242
+ const InputSchema = z.object({
243
+ messages: z
244
+ .array(MessageSchema)
245
+ .min(1)
246
+ .describe("Conversation messages to store"),
247
+ context: z
248
+ .string()
249
+ .optional()
250
+ .describe("Optional summary or context label for this conversation"),
251
+ });
252
+ server.registerTool("memory_save_conversation", {
253
+ title: "Save Conversation to Memory",
254
+ description: `Store a conversation snapshot as an event node in the memory graph.
255
+ Useful for preserving conversation history across sessions.
256
+
257
+ Returns: ID of the stored conversation node.`,
258
+ inputSchema: InputSchema,
259
+ annotations: {
260
+ readOnlyHint: false,
261
+ destructiveHint: false,
262
+ idempotentHint: false,
263
+ openWorldHint: false,
264
+ },
265
+ }, async (params) => {
266
+ try {
267
+ const now = new Date().toISOString();
268
+ const content = params.messages
269
+ .map((m) => `[${m.role}]: ${m.content}`)
270
+ .join("\n");
271
+ const summary = params.context ?? `Conversation at ${now}`;
272
+ const node = {
273
+ id: uuidv4(),
274
+ name: summary,
275
+ type: "conversation",
276
+ description: content.length > 2000
277
+ ? content.substring(0, 2000) + "..."
278
+ : content,
279
+ embedding: generateLocalEmbedding(`${summary} ${content.substring(0, 500)}`),
280
+ metadata: {
281
+ messageCount: params.messages.length,
282
+ fullContent: content,
283
+ },
284
+ createdAt: now,
285
+ updatedAt: now,
286
+ source: "conversation",
287
+ accessCount: 0,
288
+ };
289
+ await store.addNode(node);
290
+ return {
291
+ content: [
292
+ {
293
+ type: "text",
294
+ text: JSON.stringify({
295
+ id: node.id,
296
+ name: node.name,
297
+ messageCount: params.messages.length,
298
+ status: "saved",
299
+ }, null, 2),
300
+ },
301
+ ],
302
+ };
303
+ }
304
+ catch (error) {
305
+ return errorResponse(error);
306
+ }
307
+ });
308
+ }
309
+ // =============================================================================
310
+ // Read Tools
311
+ // =============================================================================
312
+ function registerSearch(server, store) {
313
+ const InputSchema = z.object({
314
+ query: z.string().min(1).describe("Search query (natural language)"),
315
+ topK: z.number().int().min(1).max(50).default(10).describe("Max results"),
316
+ type: z
317
+ .enum([
318
+ "entity",
319
+ "concept",
320
+ "event",
321
+ "code_pattern",
322
+ "decision",
323
+ "conversation",
324
+ ])
325
+ .optional()
326
+ .describe("Filter by node type"),
327
+ timeRange: z
328
+ .object({
329
+ after: z
330
+ .string()
331
+ .optional()
332
+ .describe("ISO timestamp — only results after this"),
333
+ before: z
334
+ .string()
335
+ .optional()
336
+ .describe("ISO timestamp — only results before this"),
337
+ })
338
+ .optional()
339
+ .describe("Time range filter"),
340
+ });
341
+ server.registerTool("memory_search", {
342
+ title: "Search Memory",
343
+ description: `Hybrid search across the memory graph: combines text matching, semantic similarity, and graph traversal.
344
+ Returns entities, their scores, and related edges.
345
+
346
+ Use this to find relevant context before making decisions or writing code.`,
347
+ inputSchema: InputSchema,
348
+ annotations: {
349
+ readOnlyHint: true,
350
+ destructiveHint: false,
351
+ idempotentHint: true,
352
+ openWorldHint: false,
353
+ },
354
+ }, async (params) => {
355
+ try {
356
+ const results = await hybridSearch(store, {
357
+ query: params.query,
358
+ topK: params.topK,
359
+ type: params.type,
360
+ timeRange: params.timeRange,
361
+ });
362
+ const output = {
363
+ totalMatches: results.totalNodes,
364
+ returnedCount: results.nodes.length,
365
+ results: results.nodes.map((r) => ({
366
+ id: r.node.id,
367
+ name: r.node.name,
368
+ type: r.node.type,
369
+ description: r.node.description,
370
+ score: Math.round(r.score * 1000) / 1000,
371
+ matchType: r.matchType,
372
+ updatedAt: r.node.updatedAt,
373
+ })),
374
+ relatedEdges: results.relatedEdges.slice(0, 20).map((e) => ({
375
+ source: e.source,
376
+ target: e.target,
377
+ relation: e.relation,
378
+ description: e.description,
379
+ })),
380
+ };
381
+ let text = JSON.stringify(output, null, 2);
382
+ if (text.length > CHARACTER_LIMIT) {
383
+ output.results = output.results.slice(0, 5);
384
+ output.relatedEdges = output.relatedEdges.slice(0, 5);
385
+ text = JSON.stringify({ ...output, truncated: true }, null, 2);
386
+ }
387
+ return {
388
+ content: [{ type: "text", text }],
389
+ };
390
+ }
391
+ catch (error) {
392
+ return errorResponse(error);
393
+ }
394
+ });
395
+ }
396
+ function registerGetEntity(server, store) {
397
+ const InputSchema = z
398
+ .object({
399
+ name: z.string().optional().describe("Entity name to look up"),
400
+ id: z.string().optional().describe("Entity ID to look up"),
401
+ })
402
+ .refine((data) => data.name || data.id, {
403
+ message: "Either 'name' or 'id' must be provided",
404
+ });
405
+ server.registerTool("memory_get_entity", {
406
+ title: "Get Entity Details",
407
+ description: `Get full details of a specific entity including all its relationships.
408
+ Provide either entity name or ID.`,
409
+ inputSchema: InputSchema,
410
+ annotations: {
411
+ readOnlyHint: true,
412
+ destructiveHint: false,
413
+ idempotentHint: true,
414
+ openWorldHint: false,
415
+ },
416
+ }, async (params) => {
417
+ try {
418
+ let node = null;
419
+ if (params.id) {
420
+ node = await store.getNode(params.id);
421
+ }
422
+ else if (params.name) {
423
+ node = await store.getNodeByName(params.name);
424
+ }
425
+ if (!node) {
426
+ return {
427
+ content: [
428
+ {
429
+ type: "text",
430
+ text: `Entity not found: ${params.name ?? params.id}`,
431
+ },
432
+ ],
433
+ };
434
+ }
435
+ // Update access count
436
+ await store.updateNode(node.id, {
437
+ accessCount: node.accessCount + 1,
438
+ lastAccessedAt: new Date().toISOString(),
439
+ });
440
+ // Get relationships
441
+ const edges = await store.getEdgesForNode(node.id);
442
+ // Resolve edge node names
443
+ const edgeDetails = await Promise.all(edges.map(async (e) => {
444
+ const otherNodeId = e.source === node.id ? e.target : e.source;
445
+ const otherNode = await store.getNode(otherNodeId);
446
+ return {
447
+ relation: e.relation,
448
+ direction: e.source === node.id ? "outgoing" : "incoming",
449
+ relatedEntity: otherNode?.name ?? otherNodeId,
450
+ relatedEntityType: otherNode?.type,
451
+ description: e.description,
452
+ weight: e.weight,
453
+ };
454
+ }));
455
+ const output = {
456
+ id: node.id,
457
+ name: node.name,
458
+ type: node.type,
459
+ description: node.description,
460
+ metadata: node.metadata,
461
+ createdAt: node.createdAt,
462
+ updatedAt: node.updatedAt,
463
+ validFrom: node.validFrom,
464
+ validUntil: node.validUntil,
465
+ accessCount: node.accessCount,
466
+ relationships: edgeDetails,
467
+ };
468
+ return {
469
+ content: [
470
+ { type: "text", text: JSON.stringify(output, null, 2) },
471
+ ],
472
+ };
473
+ }
474
+ catch (error) {
475
+ return errorResponse(error);
476
+ }
477
+ });
478
+ }
479
+ function registerListEntities(server, store) {
480
+ const InputSchema = z.object({
481
+ type: z
482
+ .enum([
483
+ "entity",
484
+ "concept",
485
+ "event",
486
+ "code_pattern",
487
+ "decision",
488
+ "conversation",
489
+ ])
490
+ .optional()
491
+ .describe("Filter by node type"),
492
+ limit: z
493
+ .number()
494
+ .int()
495
+ .min(1)
496
+ .max(MAX_PAGE_SIZE)
497
+ .default(DEFAULT_PAGE_SIZE)
498
+ .describe("Max results per page"),
499
+ offset: z.number().int().min(0).default(0).describe("Pagination offset"),
500
+ nameContains: z.string().optional().describe("Filter by name substring"),
501
+ });
502
+ server.registerTool("memory_list_entities", {
503
+ title: "List Memory Entities",
504
+ description: `List all entities in the memory graph with optional filtering and pagination.`,
505
+ inputSchema: InputSchema,
506
+ annotations: {
507
+ readOnlyHint: true,
508
+ destructiveHint: false,
509
+ idempotentHint: true,
510
+ openWorldHint: false,
511
+ },
512
+ }, async (params) => {
513
+ try {
514
+ let results;
515
+ if (params.type || params.nameContains) {
516
+ const allMatches = await store.findNodes({
517
+ type: params.type,
518
+ nameContains: params.nameContains,
519
+ });
520
+ results = {
521
+ nodes: allMatches.slice(params.offset, params.offset + params.limit),
522
+ total: allMatches.length,
523
+ };
524
+ }
525
+ else {
526
+ results = await store.getAllNodes(params.limit, params.offset);
527
+ }
528
+ const output = {
529
+ total: results.total,
530
+ count: results.nodes.length,
531
+ offset: params.offset,
532
+ entities: results.nodes.map((n) => ({
533
+ id: n.id,
534
+ name: n.name,
535
+ type: n.type,
536
+ description: n.description.substring(0, 200),
537
+ updatedAt: n.updatedAt,
538
+ accessCount: n.accessCount,
539
+ })),
540
+ hasMore: results.total > params.offset + results.nodes.length,
541
+ nextOffset: results.total > params.offset + results.nodes.length
542
+ ? params.offset + results.nodes.length
543
+ : undefined,
544
+ };
545
+ return {
546
+ content: [
547
+ { type: "text", text: JSON.stringify(output, null, 2) },
548
+ ],
549
+ };
550
+ }
551
+ catch (error) {
552
+ return errorResponse(error);
553
+ }
554
+ });
555
+ }
556
+ function registerGetRelations(server, store) {
557
+ const InputSchema = z.object({
558
+ entity: z.string().min(1).describe("Entity name or ID"),
559
+ relation: z.string().optional().describe("Filter by relation type"),
560
+ direction: z
561
+ .enum(["in", "out", "both"])
562
+ .default("both")
563
+ .describe("Edge direction filter"),
564
+ });
565
+ server.registerTool("memory_get_relations", {
566
+ title: "Get Entity Relations",
567
+ description: `Get all relationships for a given entity, optionally filtered by type and direction.`,
568
+ inputSchema: InputSchema,
569
+ annotations: {
570
+ readOnlyHint: true,
571
+ destructiveHint: false,
572
+ idempotentHint: true,
573
+ openWorldHint: false,
574
+ },
575
+ }, async (params) => {
576
+ try {
577
+ const node = (await store.getNode(params.entity)) ??
578
+ (await store.getNodeByName(params.entity));
579
+ if (!node) {
580
+ return {
581
+ content: [
582
+ {
583
+ type: "text",
584
+ text: `Entity not found: ${params.entity}`,
585
+ },
586
+ ],
587
+ };
588
+ }
589
+ let edges = await store.getEdgesForNode(node.id, params.direction);
590
+ if (params.relation) {
591
+ edges = edges.filter((e) => e.relation === params.relation);
592
+ }
593
+ const edgeDetails = await Promise.all(edges.map(async (e) => {
594
+ const otherNodeId = e.source === node.id ? e.target : e.source;
595
+ const otherNode = await store.getNode(otherNodeId);
596
+ return {
597
+ id: e.id,
598
+ relation: e.relation,
599
+ direction: e.source === node.id ? "outgoing" : "incoming",
600
+ relatedEntity: otherNode?.name ?? otherNodeId,
601
+ relatedEntityType: otherNode?.type,
602
+ description: e.description,
603
+ weight: e.weight,
604
+ };
605
+ }));
606
+ return {
607
+ content: [
608
+ {
609
+ type: "text",
610
+ text: JSON.stringify({
611
+ entity: node.name,
612
+ totalRelations: edgeDetails.length,
613
+ relations: edgeDetails,
614
+ }, null, 2),
615
+ },
616
+ ],
617
+ };
618
+ }
619
+ catch (error) {
620
+ return errorResponse(error);
621
+ }
622
+ });
623
+ }
624
+ function registerGetContext(server, store) {
625
+ const InputSchema = z.object({
626
+ topic: z.string().min(1).describe("Topic to retrieve context for"),
627
+ maxTokens: z
628
+ .number()
629
+ .int()
630
+ .min(100)
631
+ .max(8000)
632
+ .default(2000)
633
+ .describe("Approximate max tokens in returned context"),
634
+ });
635
+ server.registerTool("memory_get_context", {
636
+ title: "Get Context for Topic",
637
+ description: `Retrieve summarized context for a topic, formatted for direct injection into prompts.
638
+ Use before making decisions or writing code to get historical context.`,
639
+ inputSchema: InputSchema,
640
+ annotations: {
641
+ readOnlyHint: true,
642
+ destructiveHint: false,
643
+ idempotentHint: true,
644
+ openWorldHint: false,
645
+ },
646
+ }, async (params) => {
647
+ try {
648
+ const context = await getContextForTopic(store, params.topic, params.maxTokens);
649
+ return {
650
+ content: [{ type: "text", text: context }],
651
+ };
652
+ }
653
+ catch (error) {
654
+ return errorResponse(error);
655
+ }
656
+ });
657
+ }
658
+ // =============================================================================
659
+ // Management Tools
660
+ // =============================================================================
661
+ function registerDeleteEntity(server, store) {
662
+ const InputSchema = z
663
+ .object({
664
+ name: z.string().optional().describe("Entity name to delete"),
665
+ id: z.string().optional().describe("Entity ID to delete"),
666
+ })
667
+ .refine((data) => data.name || data.id, {
668
+ message: "Either 'name' or 'id' must be provided",
669
+ });
670
+ server.registerTool("memory_delete_entity", {
671
+ title: "Delete Entity from Memory",
672
+ description: `Remove an entity and all its edges from the memory graph. This is destructive.`,
673
+ inputSchema: InputSchema,
674
+ annotations: {
675
+ readOnlyHint: false,
676
+ destructiveHint: true,
677
+ idempotentHint: true,
678
+ openWorldHint: false,
679
+ },
680
+ }, async (params) => {
681
+ try {
682
+ let node = null;
683
+ if (params.id)
684
+ node = await store.getNode(params.id);
685
+ else if (params.name)
686
+ node = await store.getNodeByName(params.name);
687
+ if (!node) {
688
+ return {
689
+ content: [
690
+ {
691
+ type: "text",
692
+ text: `Entity not found: ${params.name ?? params.id}`,
693
+ },
694
+ ],
695
+ };
696
+ }
697
+ const deleted = await store.deleteNode(node.id);
698
+ return {
699
+ content: [
700
+ {
701
+ type: "text",
702
+ text: JSON.stringify({
703
+ deleted,
704
+ id: node.id,
705
+ name: node.name,
706
+ }, null, 2),
707
+ },
708
+ ],
709
+ };
710
+ }
711
+ catch (error) {
712
+ return errorResponse(error);
713
+ }
714
+ });
715
+ }
716
+ function registerConsolidate(server, store) {
717
+ const InputSchema = z.object({
718
+ strategy: z
719
+ .enum(["full", "merge_only", "prune_only", "infer_only"])
720
+ .default("full")
721
+ .describe("Consolidation strategy"),
722
+ });
723
+ server.registerTool("memory_consolidate", {
724
+ title: "Consolidate Memory",
725
+ description: `Run memory consolidation: merge duplicates, infer transitive edges, and prune stale nodes.else
726
+ Strategies: full (all), merge_only, prune_only, infer_only.`,
727
+ inputSchema: InputSchema,
728
+ annotations: {
729
+ readOnlyHint: false,
730
+ destructiveHint: false,
731
+ idempotentHint: true,
732
+ openWorldHint: false,
733
+ },
734
+ }, async (params) => {
735
+ try {
736
+ const result = await consolidateMemory(store, params.strategy);
737
+ return {
738
+ content: [
739
+ {
740
+ type: "text",
741
+ text: JSON.stringify({
742
+ ...result,
743
+ durationMs: result.duration,
744
+ status: "completed",
745
+ }, null, 2),
746
+ },
747
+ ],
748
+ };
749
+ }
750
+ catch (error) {
751
+ return errorResponse(error);
752
+ }
753
+ });
754
+ }
755
+ function registerStatus(server, store) {
756
+ server.registerTool("memory_status", {
757
+ title: "Memory Status",
758
+ description: `Get memory graph health: node/edge counts by type, storage backend, and last consolidation time.`,
759
+ inputSchema: {},
760
+ annotations: {
761
+ readOnlyHint: true,
762
+ destructiveHint: false,
763
+ idempotentHint: true,
764
+ openWorldHint: false,
765
+ },
766
+ }, async () => {
767
+ try {
768
+ const stats = await store.getStats();
769
+ return {
770
+ content: [
771
+ {
772
+ type: "text",
773
+ text: JSON.stringify(stats, null, 2),
774
+ },
775
+ ],
776
+ };
777
+ }
778
+ catch (error) {
779
+ return errorResponse(error);
780
+ }
781
+ });
782
+ }
783
+ // =============================================================================
784
+ // Optional Server-Side Encoding Tool
785
+ // =============================================================================
786
+ function registerEncodeText(server, store, llm) {
787
+ const InputSchema = z.object({
788
+ text: z
789
+ .string()
790
+ .min(1)
791
+ .describe("Raw text to process (conversation excerpt, code comment, meeting notes, etc.)"),
792
+ autoStore: z
793
+ .boolean()
794
+ .default(true)
795
+ .describe("Automatically store extracted entities and relations in memory (default: true)"),
796
+ });
797
+ server.registerTool("memory_encode_text", {
798
+ title: "Encode Text (Server-Side LLM)",
799
+ description: `Extract entities and relationships from raw text using the server-side LLM.
800
+ This is the automated alternative to manually calling memory_add_entities + memory_add_relations.
801
+ Requires LLM_API_KEY to be configured.
802
+
803
+ Pipeline: text → LLM extraction → entity resolution → embedding → storage
804
+
805
+ Set autoStore=false to preview extraction results without saving.`,
806
+ inputSchema: InputSchema,
807
+ annotations: {
808
+ readOnlyHint: false,
809
+ destructiveHint: false,
810
+ idempotentHint: false,
811
+ openWorldHint: true,
812
+ },
813
+ }, async (params) => {
814
+ try {
815
+ if (params.autoStore) {
816
+ // Full pipeline: extract + store
817
+ const result = await encodeText(params.text, store, llm);
818
+ return {
819
+ content: [
820
+ {
821
+ type: "text",
822
+ text: JSON.stringify({
823
+ status: "encoded",
824
+ entitiesCreated: result.entitiesCreated,
825
+ entitiesUpdated: result.entitiesUpdated,
826
+ relationsCreated: result.relationsCreated,
827
+ details: result.details,
828
+ }, null, 2),
829
+ },
830
+ ],
831
+ };
832
+ }
833
+ else {
834
+ // Preview mode: extract only, don't store
835
+ const { nodes: existingNodes } = await store.getAllNodes(10000, 0);
836
+ const existingNames = existingNodes.map((n) => n.name);
837
+ const extraction = await llm.extractEntitiesAndRelations(params.text, existingNames);
838
+ return {
839
+ content: [
840
+ {
841
+ type: "text",
842
+ text: JSON.stringify({
843
+ status: "preview",
844
+ message: "Extraction preview — nothing stored. Set autoStore=true to save.",
845
+ entities: extraction.entities,
846
+ relations: extraction.relations,
847
+ }, null, 2),
848
+ },
849
+ ],
850
+ };
851
+ }
852
+ }
853
+ catch (error) {
854
+ return errorResponse(error);
855
+ }
856
+ });
857
+ }
858
+ // =============================================================================
859
+ // Helpers
860
+ // =============================================================================
861
+ function errorResponse(error) {
862
+ const message = error instanceof Error ? error.message : String(error);
863
+ return {
864
+ isError: true,
865
+ content: [
866
+ {
867
+ type: "text",
868
+ text: `Error: ${message}`,
869
+ },
870
+ ],
871
+ };
872
+ }
873
+ //# sourceMappingURL=memory-tools.js.map