@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.
- package/.agents/skills/mcp-builder/LICENSE.txt +202 -0
- package/.agents/skills/mcp-builder/SKILL.md +236 -0
- package/.agents/skills/mcp-builder/reference/evaluation.md +602 -0
- package/.agents/skills/mcp-builder/reference/mcp_best_practices.md +249 -0
- package/.agents/skills/mcp-builder/reference/node_mcp_server.md +970 -0
- package/.agents/skills/mcp-builder/reference/python_mcp_server.md +719 -0
- package/.agents/skills/mcp-builder/scripts/connections.py +151 -0
- package/.agents/skills/mcp-builder/scripts/evaluation.py +373 -0
- package/.agents/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
- package/.agents/skills/mcp-builder/scripts/requirements.txt +2 -0
- package/.env.example +26 -0
- package/Implementation Plan.md +358 -0
- package/README.md +187 -0
- package/dist/constants.d.ts +34 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +40 -0
- package/dist/constants.js.map +1 -0
- package/dist/encoding/embedder.d.ts +12 -0
- package/dist/encoding/embedder.d.ts.map +1 -0
- package/dist/encoding/embedder.js +85 -0
- package/dist/encoding/embedder.js.map +1 -0
- package/dist/encoding/pipeline.d.ts +28 -0
- package/dist/encoding/pipeline.d.ts.map +1 -0
- package/dist/encoding/pipeline.js +146 -0
- package/dist/encoding/pipeline.js.map +1 -0
- package/dist/evolution/consolidator.d.ts +12 -0
- package/dist/evolution/consolidator.d.ts.map +1 -0
- package/dist/evolution/consolidator.js +212 -0
- package/dist/evolution/consolidator.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +53 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/openai-provider.d.ts +23 -0
- package/dist/llm/openai-provider.d.ts.map +1 -0
- package/dist/llm/openai-provider.js +141 -0
- package/dist/llm/openai-provider.js.map +1 -0
- package/dist/llm/prompts.d.ts +10 -0
- package/dist/llm/prompts.d.ts.map +1 -0
- package/dist/llm/prompts.js +63 -0
- package/dist/llm/prompts.js.map +1 -0
- package/dist/llm/provider.d.ts +7 -0
- package/dist/llm/provider.d.ts.map +1 -0
- package/dist/llm/provider.js +25 -0
- package/dist/llm/provider.js.map +1 -0
- package/dist/resources/context-resource.d.ts +8 -0
- package/dist/resources/context-resource.d.ts.map +1 -0
- package/dist/resources/context-resource.js +51 -0
- package/dist/resources/context-resource.js.map +1 -0
- package/dist/retrieval/search.d.ts +24 -0
- package/dist/retrieval/search.d.ts.map +1 -0
- package/dist/retrieval/search.js +143 -0
- package/dist/retrieval/search.js.map +1 -0
- package/dist/storage/factory.d.ts +10 -0
- package/dist/storage/factory.d.ts.map +1 -0
- package/dist/storage/factory.js +35 -0
- package/dist/storage/factory.js.map +1 -0
- package/dist/storage/json-store.d.ts +34 -0
- package/dist/storage/json-store.d.ts.map +1 -0
- package/dist/storage/json-store.js +248 -0
- package/dist/storage/json-store.js.map +1 -0
- package/dist/storage/neo4j-store.d.ts +31 -0
- package/dist/storage/neo4j-store.d.ts.map +1 -0
- package/dist/storage/neo4j-store.js +440 -0
- package/dist/storage/neo4j-store.js.map +1 -0
- package/dist/tools/memory-tools.d.ts +4 -0
- package/dist/tools/memory-tools.d.ts.map +1 -0
- package/dist/tools/memory-tools.js +873 -0
- package/dist/tools/memory-tools.js.map +1 -0
- package/dist/types.d.ts +129 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/implementation_plan.md.resolved.md +322 -0
- package/package.json +43 -0
- package/src/constants.ts +52 -0
- package/src/encoding/embedder.ts +93 -0
- package/src/encoding/pipeline.ts +197 -0
- package/src/evolution/consolidator.ts +281 -0
- package/src/index.ts +67 -0
- package/src/llm/openai-provider.ts +208 -0
- package/src/llm/prompts.ts +66 -0
- package/src/llm/provider.ts +37 -0
- package/src/resources/context-resource.ts +74 -0
- package/src/retrieval/search.ts +203 -0
- package/src/storage/factory.ts +48 -0
- package/src/storage/json-store.ts +325 -0
- package/src/storage/neo4j-store.ts +564 -0
- package/src/tools/memory-tools.ts +1067 -0
- package/src/types.ts +207 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// JSON File Storage Backend
|
|
3
|
+
// =============================================================================
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
writeFileSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
existsSync,
|
|
9
|
+
mkdirSync,
|
|
10
|
+
renameSync,
|
|
11
|
+
} from "node:fs";
|
|
12
|
+
import { dirname } from "node:path";
|
|
13
|
+
import type {
|
|
14
|
+
StorageBackend,
|
|
15
|
+
MemoryNode,
|
|
16
|
+
MemoryEdge,
|
|
17
|
+
ScoredNode,
|
|
18
|
+
Subgraph,
|
|
19
|
+
NodeFilter,
|
|
20
|
+
GraphStats,
|
|
21
|
+
} from "../types.js";
|
|
22
|
+
import { cosineSimilarity } from "../encoding/embedder.js";
|
|
23
|
+
|
|
24
|
+
interface MemoryFileData {
|
|
25
|
+
version: string;
|
|
26
|
+
lastConsolidated?: string;
|
|
27
|
+
nodes: MemoryNode[];
|
|
28
|
+
edges: MemoryEdge[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class JsonStore implements StorageBackend {
|
|
32
|
+
private nodes: Map<string, MemoryNode> = new Map();
|
|
33
|
+
private edges: Map<string, MemoryEdge> = new Map();
|
|
34
|
+
private lastConsolidated?: string;
|
|
35
|
+
private filePath: string;
|
|
36
|
+
|
|
37
|
+
constructor(filePath: string) {
|
|
38
|
+
this.filePath = filePath;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async initialize(): Promise<void> {
|
|
42
|
+
if (existsSync(this.filePath)) {
|
|
43
|
+
const raw = readFileSync(this.filePath, "utf-8");
|
|
44
|
+
const data: MemoryFileData = JSON.parse(raw);
|
|
45
|
+
this.lastConsolidated = data.lastConsolidated;
|
|
46
|
+
for (const node of data.nodes) {
|
|
47
|
+
this.nodes.set(node.id, node);
|
|
48
|
+
}
|
|
49
|
+
for (const edge of data.edges) {
|
|
50
|
+
this.edges.set(edge.id, edge);
|
|
51
|
+
}
|
|
52
|
+
console.error(
|
|
53
|
+
`[open-memory] Loaded ${this.nodes.size} nodes, ${this.edges.size} edges from ${this.filePath}`,
|
|
54
|
+
);
|
|
55
|
+
} else {
|
|
56
|
+
console.error(
|
|
57
|
+
`[open-memory] No existing memory file found. Starting fresh at ${this.filePath}`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private persist(): void {
|
|
63
|
+
const data: MemoryFileData = {
|
|
64
|
+
version: "1.0.0",
|
|
65
|
+
lastConsolidated: this.lastConsolidated,
|
|
66
|
+
nodes: Array.from(this.nodes.values()),
|
|
67
|
+
edges: Array.from(this.edges.values()),
|
|
68
|
+
};
|
|
69
|
+
const dir = dirname(this.filePath);
|
|
70
|
+
if (!existsSync(dir)) {
|
|
71
|
+
mkdirSync(dir, { recursive: true });
|
|
72
|
+
}
|
|
73
|
+
// Atomic write: write to temp file, then rename
|
|
74
|
+
const tmpPath = this.filePath + ".tmp";
|
|
75
|
+
writeFileSync(tmpPath, JSON.stringify(data, null, 2), "utf-8");
|
|
76
|
+
renameSync(tmpPath, this.filePath);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// -- Node Operations -------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
async addNode(node: MemoryNode): Promise<void> {
|
|
82
|
+
this.nodes.set(node.id, node);
|
|
83
|
+
this.persist();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async updateNode(
|
|
87
|
+
id: string,
|
|
88
|
+
updates: Partial<MemoryNode>,
|
|
89
|
+
): Promise<MemoryNode | null> {
|
|
90
|
+
const existing = this.nodes.get(id);
|
|
91
|
+
if (!existing) return null;
|
|
92
|
+
const updated = {
|
|
93
|
+
...existing,
|
|
94
|
+
...updates,
|
|
95
|
+
id: existing.id, // prevent ID overwrite
|
|
96
|
+
updatedAt: new Date().toISOString(),
|
|
97
|
+
};
|
|
98
|
+
this.nodes.set(id, updated);
|
|
99
|
+
this.persist();
|
|
100
|
+
return updated;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async deleteNode(id: string): Promise<boolean> {
|
|
104
|
+
const existed = this.nodes.delete(id);
|
|
105
|
+
if (existed) {
|
|
106
|
+
// Remove all edges connected to this node
|
|
107
|
+
for (const [edgeId, edge] of this.edges) {
|
|
108
|
+
if (edge.source === id || edge.target === id) {
|
|
109
|
+
this.edges.delete(edgeId);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
this.persist();
|
|
113
|
+
}
|
|
114
|
+
return existed;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async getNode(id: string): Promise<MemoryNode | null> {
|
|
118
|
+
return this.nodes.get(id) ?? null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async getNodeByName(name: string): Promise<MemoryNode | null> {
|
|
122
|
+
const nameLower = name.toLowerCase();
|
|
123
|
+
for (const node of this.nodes.values()) {
|
|
124
|
+
if (node.name.toLowerCase() === nameLower) {
|
|
125
|
+
return node;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async findNodes(filter: NodeFilter): Promise<MemoryNode[]> {
|
|
132
|
+
let results = Array.from(this.nodes.values());
|
|
133
|
+
|
|
134
|
+
if (filter.type) {
|
|
135
|
+
results = results.filter((n) => n.type === filter.type);
|
|
136
|
+
}
|
|
137
|
+
if (filter.source) {
|
|
138
|
+
results = results.filter((n) => n.source === filter.source);
|
|
139
|
+
}
|
|
140
|
+
if (filter.nameContains) {
|
|
141
|
+
const q = filter.nameContains.toLowerCase();
|
|
142
|
+
results = results.filter((n) => n.name.toLowerCase().includes(q));
|
|
143
|
+
}
|
|
144
|
+
if (filter.createdAfter) {
|
|
145
|
+
results = results.filter((n) => n.createdAt >= filter.createdAfter!);
|
|
146
|
+
}
|
|
147
|
+
if (filter.createdBefore) {
|
|
148
|
+
results = results.filter((n) => n.createdAt <= filter.createdBefore!);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return results;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async getAllNodes(
|
|
155
|
+
limit: number,
|
|
156
|
+
offset: number,
|
|
157
|
+
): Promise<{ nodes: MemoryNode[]; total: number }> {
|
|
158
|
+
const all = Array.from(this.nodes.values());
|
|
159
|
+
return {
|
|
160
|
+
nodes: all.slice(offset, offset + limit),
|
|
161
|
+
total: all.length,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async findNodesByEmbedding(
|
|
166
|
+
embedding: number[],
|
|
167
|
+
topK: number,
|
|
168
|
+
): Promise<ScoredNode[]> {
|
|
169
|
+
const scored: ScoredNode[] = [];
|
|
170
|
+
for (const node of this.nodes.values()) {
|
|
171
|
+
if (node.embedding && node.embedding.length > 0) {
|
|
172
|
+
const score = cosineSimilarity(embedding, node.embedding);
|
|
173
|
+
scored.push({ node, score, matchType: "semantic" });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
scored.sort((a, b) => b.score - a.score);
|
|
177
|
+
return scored.slice(0, topK);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// -- Edge Operations --------------------------------------------------------
|
|
181
|
+
|
|
182
|
+
async addEdge(edge: MemoryEdge): Promise<void> {
|
|
183
|
+
this.edges.set(edge.id, edge);
|
|
184
|
+
this.persist();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async updateEdge(
|
|
188
|
+
id: string,
|
|
189
|
+
updates: Partial<MemoryEdge>,
|
|
190
|
+
): Promise<MemoryEdge | null> {
|
|
191
|
+
const existing = this.edges.get(id);
|
|
192
|
+
if (!existing) return null;
|
|
193
|
+
const updated = {
|
|
194
|
+
...existing,
|
|
195
|
+
...updates,
|
|
196
|
+
id: existing.id,
|
|
197
|
+
updatedAt: new Date().toISOString(),
|
|
198
|
+
};
|
|
199
|
+
this.edges.set(id, updated);
|
|
200
|
+
this.persist();
|
|
201
|
+
return updated;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async deleteEdge(id: string): Promise<boolean> {
|
|
205
|
+
const existed = this.edges.delete(id);
|
|
206
|
+
if (existed) this.persist();
|
|
207
|
+
return existed;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async getEdge(id: string): Promise<MemoryEdge | null> {
|
|
211
|
+
return this.edges.get(id) ?? null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async getEdgesForNode(
|
|
215
|
+
nodeId: string,
|
|
216
|
+
direction: "in" | "out" | "both" = "both",
|
|
217
|
+
): Promise<MemoryEdge[]> {
|
|
218
|
+
const results: MemoryEdge[] = [];
|
|
219
|
+
for (const edge of this.edges.values()) {
|
|
220
|
+
if (direction === "out" && edge.source === nodeId) results.push(edge);
|
|
221
|
+
else if (direction === "in" && edge.target === nodeId) results.push(edge);
|
|
222
|
+
else if (
|
|
223
|
+
direction === "both" &&
|
|
224
|
+
(edge.source === nodeId || edge.target === nodeId)
|
|
225
|
+
)
|
|
226
|
+
results.push(edge);
|
|
227
|
+
}
|
|
228
|
+
return results;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async getEdgesBetween(
|
|
232
|
+
sourceId: string,
|
|
233
|
+
targetId: string,
|
|
234
|
+
): Promise<MemoryEdge[]> {
|
|
235
|
+
const results: MemoryEdge[] = [];
|
|
236
|
+
for (const edge of this.edges.values()) {
|
|
237
|
+
if (
|
|
238
|
+
(edge.source === sourceId && edge.target === targetId) ||
|
|
239
|
+
(edge.source === targetId && edge.target === sourceId)
|
|
240
|
+
) {
|
|
241
|
+
results.push(edge);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return results;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// -- Graph Traversal --------------------------------------------------------
|
|
248
|
+
|
|
249
|
+
async getNeighborhood(nodeId: string, depth: number): Promise<Subgraph> {
|
|
250
|
+
const visitedNodes = new Set<string>();
|
|
251
|
+
const visitedEdges = new Set<string>();
|
|
252
|
+
const resultNodes: MemoryNode[] = [];
|
|
253
|
+
const resultEdges: MemoryEdge[] = [];
|
|
254
|
+
|
|
255
|
+
const queue: { id: string; currentDepth: number }[] = [
|
|
256
|
+
{ id: nodeId, currentDepth: 0 },
|
|
257
|
+
];
|
|
258
|
+
|
|
259
|
+
while (queue.length > 0) {
|
|
260
|
+
const item = queue.shift()!;
|
|
261
|
+
if (visitedNodes.has(item.id)) continue;
|
|
262
|
+
visitedNodes.add(item.id);
|
|
263
|
+
|
|
264
|
+
const node = this.nodes.get(item.id);
|
|
265
|
+
if (node) resultNodes.push(node);
|
|
266
|
+
|
|
267
|
+
if (item.currentDepth < depth) {
|
|
268
|
+
for (const edge of this.edges.values()) {
|
|
269
|
+
if (edge.source === item.id || edge.target === item.id) {
|
|
270
|
+
if (!visitedEdges.has(edge.id)) {
|
|
271
|
+
visitedEdges.add(edge.id);
|
|
272
|
+
resultEdges.push(edge);
|
|
273
|
+
}
|
|
274
|
+
const neighborId =
|
|
275
|
+
edge.source === item.id ? edge.target : edge.source;
|
|
276
|
+
if (!visitedNodes.has(neighborId)) {
|
|
277
|
+
queue.push({
|
|
278
|
+
id: neighborId,
|
|
279
|
+
currentDepth: item.currentDepth + 1,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return { nodes: resultNodes, edges: resultEdges };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// -- Stats & Lifecycle ------------------------------------------------------
|
|
291
|
+
|
|
292
|
+
async getStats(): Promise<GraphStats> {
|
|
293
|
+
const nodesByType: Record<string, number> = {};
|
|
294
|
+
for (const node of this.nodes.values()) {
|
|
295
|
+
nodesByType[node.type] = (nodesByType[node.type] ?? 0) + 1;
|
|
296
|
+
}
|
|
297
|
+
return {
|
|
298
|
+
totalNodes: this.nodes.size,
|
|
299
|
+
totalEdges: this.edges.size,
|
|
300
|
+
nodesByType,
|
|
301
|
+
storageBackend: "json",
|
|
302
|
+
lastConsolidated: this.lastConsolidated,
|
|
303
|
+
memoryFilePath: this.filePath,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async close(): Promise<void> {
|
|
308
|
+
this.persist();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// -- Internal helpers for consolidation -------------------------------------
|
|
312
|
+
|
|
313
|
+
getNodesMap(): Map<string, MemoryNode> {
|
|
314
|
+
return this.nodes;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
getEdgesMap(): Map<string, MemoryEdge> {
|
|
318
|
+
return this.edges;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
setLastConsolidated(timestamp: string): void {
|
|
322
|
+
this.lastConsolidated = timestamp;
|
|
323
|
+
this.persist();
|
|
324
|
+
}
|
|
325
|
+
}
|