@danielsimonjr/memory-mcp 0.7.2 → 0.41.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/dist/__tests__/edge-cases/edge-cases.test.js +406 -0
- package/dist/__tests__/file-path.test.js +5 -5
- package/dist/__tests__/integration/workflows.test.js +449 -0
- package/dist/__tests__/knowledge-graph.test.js +8 -3
- package/dist/__tests__/performance/benchmarks.test.js +411 -0
- package/dist/__tests__/unit/core/EntityManager.test.js +334 -0
- package/dist/__tests__/unit/core/GraphStorage.test.js +205 -0
- package/dist/__tests__/unit/core/RelationManager.test.js +274 -0
- package/dist/__tests__/unit/features/CompressionManager.test.js +350 -0
- package/dist/__tests__/unit/search/BasicSearch.test.js +311 -0
- package/dist/__tests__/unit/search/BooleanSearch.test.js +432 -0
- package/dist/__tests__/unit/search/FuzzySearch.test.js +448 -0
- package/dist/__tests__/unit/search/RankedSearch.test.js +379 -0
- package/dist/__tests__/unit/utils/levenshtein.test.js +77 -0
- package/dist/core/EntityManager.js +554 -0
- package/dist/core/GraphStorage.js +172 -0
- package/dist/core/KnowledgeGraphManager.js +400 -0
- package/dist/core/ObservationManager.js +129 -0
- package/dist/core/RelationManager.js +186 -0
- package/dist/core/TransactionManager.js +389 -0
- package/dist/core/index.js +9 -0
- package/dist/features/AnalyticsManager.js +222 -0
- package/dist/features/ArchiveManager.js +74 -0
- package/dist/features/BackupManager.js +311 -0
- package/dist/features/CompressionManager.js +310 -0
- package/dist/features/ExportManager.js +305 -0
- package/dist/features/HierarchyManager.js +219 -0
- package/dist/features/ImportExportManager.js +50 -0
- package/dist/features/ImportManager.js +328 -0
- package/dist/features/TagManager.js +210 -0
- package/dist/features/index.js +12 -0
- package/dist/index.js +13 -996
- package/dist/memory.jsonl +225 -0
- package/dist/search/BasicSearch.js +161 -0
- package/dist/search/BooleanSearch.js +304 -0
- package/dist/search/FuzzySearch.js +115 -0
- package/dist/search/RankedSearch.js +206 -0
- package/dist/search/SavedSearchManager.js +145 -0
- package/dist/search/SearchManager.js +305 -0
- package/dist/search/SearchSuggestions.js +57 -0
- package/dist/search/TFIDFIndexManager.js +217 -0
- package/dist/search/index.js +10 -0
- package/dist/server/MCPServer.js +889 -0
- package/dist/types/analytics.types.js +6 -0
- package/dist/types/entity.types.js +7 -0
- package/dist/types/import-export.types.js +7 -0
- package/dist/types/index.js +12 -0
- package/dist/types/search.types.js +7 -0
- package/dist/types/tag.types.js +6 -0
- package/dist/utils/constants.js +127 -0
- package/dist/utils/dateUtils.js +89 -0
- package/dist/utils/errors.js +121 -0
- package/dist/utils/index.js +13 -0
- package/dist/utils/levenshtein.js +62 -0
- package/dist/utils/logger.js +33 -0
- package/dist/utils/pathUtils.js +115 -0
- package/dist/utils/schemas.js +184 -0
- package/dist/utils/searchCache.js +209 -0
- package/dist/utils/tfidf.js +90 -0
- package/dist/utils/validationUtils.js +109 -0
- package/package.json +50 -48
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
-
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
2
|
import { promises as fs } from 'fs';
|
|
6
3
|
import path from 'path';
|
|
7
4
|
import { fileURLToPath } from 'url';
|
|
5
|
+
import { logger } from './utils/logger.js';
|
|
6
|
+
import { KnowledgeGraphManager } from './core/KnowledgeGraphManager.js';
|
|
7
|
+
import { MCPServer } from './server/MCPServer.js';
|
|
8
8
|
// Define memory file path using environment variable with fallback
|
|
9
9
|
export const defaultMemoryPath = path.join(path.dirname(fileURLToPath(import.meta.url)), 'memory.jsonl');
|
|
10
10
|
// Handle backward compatibility: migrate memory.json to memory.jsonl if needed
|
|
@@ -28,9 +28,9 @@ export async function ensureMemoryFilePath() {
|
|
|
28
28
|
}
|
|
29
29
|
catch {
|
|
30
30
|
// Old file exists, new file doesn't - migrate
|
|
31
|
-
|
|
31
|
+
logger.info('Found legacy memory.json file, migrating to memory.jsonl for JSONL format compatibility');
|
|
32
32
|
await fs.rename(oldMemoryPath, newMemoryPath);
|
|
33
|
-
|
|
33
|
+
logger.info('Successfully migrated memory.json to memory.jsonl');
|
|
34
34
|
return newMemoryPath;
|
|
35
35
|
}
|
|
36
36
|
}
|
|
@@ -39,1002 +39,19 @@ export async function ensureMemoryFilePath() {
|
|
|
39
39
|
return newMemoryPath;
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
// The KnowledgeGraphManager class contains all operations to interact with the knowledge graph
|
|
45
|
-
export class KnowledgeGraphManager {
|
|
46
|
-
memoryFilePath;
|
|
47
|
-
constructor(memoryFilePath) {
|
|
48
|
-
this.memoryFilePath = memoryFilePath;
|
|
49
|
-
}
|
|
50
|
-
async loadGraph() {
|
|
51
|
-
try {
|
|
52
|
-
const data = await fs.readFile(this.memoryFilePath, "utf-8");
|
|
53
|
-
const lines = data.split("\n").filter(line => line.trim() !== "");
|
|
54
|
-
return lines.reduce((graph, line) => {
|
|
55
|
-
const item = JSON.parse(line);
|
|
56
|
-
if (item.type === "entity") {
|
|
57
|
-
// Add createdAt if missing for backward compatibility
|
|
58
|
-
if (!item.createdAt)
|
|
59
|
-
item.createdAt = new Date().toISOString();
|
|
60
|
-
// Add lastModified if missing for backward compatibility
|
|
61
|
-
if (!item.lastModified)
|
|
62
|
-
item.lastModified = item.createdAt;
|
|
63
|
-
// Phase 3: Backward compatibility for tags and importance
|
|
64
|
-
// These fields are optional and will be undefined if not present
|
|
65
|
-
graph.entities.push(item);
|
|
66
|
-
}
|
|
67
|
-
if (item.type === "relation") {
|
|
68
|
-
// Add createdAt if missing for backward compatibility
|
|
69
|
-
if (!item.createdAt)
|
|
70
|
-
item.createdAt = new Date().toISOString();
|
|
71
|
-
// Add lastModified if missing for backward compatibility
|
|
72
|
-
if (!item.lastModified)
|
|
73
|
-
item.lastModified = item.createdAt;
|
|
74
|
-
graph.relations.push(item);
|
|
75
|
-
}
|
|
76
|
-
return graph;
|
|
77
|
-
}, { entities: [], relations: [] });
|
|
78
|
-
}
|
|
79
|
-
catch (error) {
|
|
80
|
-
if (error instanceof Error && 'code' in error && error.code === "ENOENT") {
|
|
81
|
-
return { entities: [], relations: [] };
|
|
82
|
-
}
|
|
83
|
-
throw error;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
async saveGraph(graph) {
|
|
87
|
-
const lines = [
|
|
88
|
-
...graph.entities.map(e => {
|
|
89
|
-
const entityData = {
|
|
90
|
-
type: "entity",
|
|
91
|
-
name: e.name,
|
|
92
|
-
entityType: e.entityType,
|
|
93
|
-
observations: e.observations,
|
|
94
|
-
createdAt: e.createdAt,
|
|
95
|
-
lastModified: e.lastModified
|
|
96
|
-
};
|
|
97
|
-
// Phase 3: Only include tags and importance if they exist
|
|
98
|
-
if (e.tags !== undefined)
|
|
99
|
-
entityData.tags = e.tags;
|
|
100
|
-
if (e.importance !== undefined)
|
|
101
|
-
entityData.importance = e.importance;
|
|
102
|
-
return JSON.stringify(entityData);
|
|
103
|
-
}),
|
|
104
|
-
...graph.relations.map(r => JSON.stringify({
|
|
105
|
-
type: "relation",
|
|
106
|
-
from: r.from,
|
|
107
|
-
to: r.to,
|
|
108
|
-
relationType: r.relationType,
|
|
109
|
-
createdAt: r.createdAt,
|
|
110
|
-
lastModified: r.lastModified
|
|
111
|
-
})),
|
|
112
|
-
];
|
|
113
|
-
await fs.writeFile(this.memoryFilePath, lines.join("\n"));
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Phase 4: Create multiple entities in a single batch operation.
|
|
117
|
-
* Batch optimization: All entities are processed and saved in a single saveGraph() call,
|
|
118
|
-
* minimizing disk I/O. This is significantly more efficient than creating entities one at a time.
|
|
119
|
-
*/
|
|
120
|
-
async createEntities(entities) {
|
|
121
|
-
const graph = await this.loadGraph();
|
|
122
|
-
const timestamp = new Date().toISOString();
|
|
123
|
-
const newEntities = entities
|
|
124
|
-
.filter(e => !graph.entities.some(existingEntity => existingEntity.name === e.name))
|
|
125
|
-
.map(e => {
|
|
126
|
-
const entity = {
|
|
127
|
-
...e,
|
|
128
|
-
createdAt: e.createdAt || timestamp,
|
|
129
|
-
lastModified: e.lastModified || timestamp
|
|
130
|
-
};
|
|
131
|
-
// Phase 3: Normalize tags to lowercase if provided
|
|
132
|
-
if (e.tags) {
|
|
133
|
-
entity.tags = e.tags.map(tag => tag.toLowerCase());
|
|
134
|
-
}
|
|
135
|
-
// Phase 3: Validate importance if provided
|
|
136
|
-
if (e.importance !== undefined) {
|
|
137
|
-
if (e.importance < 0 || e.importance > 10) {
|
|
138
|
-
throw new Error(`Importance must be between 0 and 10, got ${e.importance}`);
|
|
139
|
-
}
|
|
140
|
-
entity.importance = e.importance;
|
|
141
|
-
}
|
|
142
|
-
return entity;
|
|
143
|
-
});
|
|
144
|
-
graph.entities.push(...newEntities);
|
|
145
|
-
// Phase 4: Single save operation for all entities ensures batch efficiency
|
|
146
|
-
await this.saveGraph(graph);
|
|
147
|
-
return newEntities;
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Phase 4: Create multiple relations in a single batch operation.
|
|
151
|
-
* Batch optimization: All relations are processed and saved in a single saveGraph() call,
|
|
152
|
-
* minimizing disk I/O. This is significantly more efficient than creating relations one at a time.
|
|
153
|
-
*/
|
|
154
|
-
async createRelations(relations) {
|
|
155
|
-
const graph = await this.loadGraph();
|
|
156
|
-
const timestamp = new Date().toISOString();
|
|
157
|
-
const newRelations = relations
|
|
158
|
-
.filter(r => !graph.relations.some(existingRelation => existingRelation.from === r.from &&
|
|
159
|
-
existingRelation.to === r.to &&
|
|
160
|
-
existingRelation.relationType === r.relationType))
|
|
161
|
-
.map(r => ({ ...r, createdAt: r.createdAt || timestamp, lastModified: r.lastModified || timestamp }));
|
|
162
|
-
graph.relations.push(...newRelations);
|
|
163
|
-
// Phase 4: Single save operation for all relations ensures batch efficiency
|
|
164
|
-
await this.saveGraph(graph);
|
|
165
|
-
return newRelations;
|
|
166
|
-
}
|
|
167
|
-
async addObservations(observations) {
|
|
168
|
-
const graph = await this.loadGraph();
|
|
169
|
-
const timestamp = new Date().toISOString();
|
|
170
|
-
const results = observations.map(o => {
|
|
171
|
-
const entity = graph.entities.find(e => e.name === o.entityName);
|
|
172
|
-
if (!entity) {
|
|
173
|
-
throw new Error(`Entity with name ${o.entityName} not found`);
|
|
174
|
-
}
|
|
175
|
-
const newObservations = o.contents.filter(content => !entity.observations.includes(content));
|
|
176
|
-
entity.observations.push(...newObservations);
|
|
177
|
-
// Update lastModified timestamp if observations were added
|
|
178
|
-
if (newObservations.length > 0) {
|
|
179
|
-
entity.lastModified = timestamp;
|
|
180
|
-
}
|
|
181
|
-
return { entityName: o.entityName, addedObservations: newObservations };
|
|
182
|
-
});
|
|
183
|
-
await this.saveGraph(graph);
|
|
184
|
-
return results;
|
|
185
|
-
}
|
|
186
|
-
async deleteEntities(entityNames) {
|
|
187
|
-
const graph = await this.loadGraph();
|
|
188
|
-
graph.entities = graph.entities.filter(e => !entityNames.includes(e.name));
|
|
189
|
-
graph.relations = graph.relations.filter(r => !entityNames.includes(r.from) && !entityNames.includes(r.to));
|
|
190
|
-
await this.saveGraph(graph);
|
|
191
|
-
}
|
|
192
|
-
async deleteObservations(deletions) {
|
|
193
|
-
const graph = await this.loadGraph();
|
|
194
|
-
const timestamp = new Date().toISOString();
|
|
195
|
-
deletions.forEach(d => {
|
|
196
|
-
const entity = graph.entities.find(e => e.name === d.entityName);
|
|
197
|
-
if (entity) {
|
|
198
|
-
const originalLength = entity.observations.length;
|
|
199
|
-
entity.observations = entity.observations.filter(o => !d.observations.includes(o));
|
|
200
|
-
// Update lastModified timestamp if observations were deleted
|
|
201
|
-
if (entity.observations.length < originalLength) {
|
|
202
|
-
entity.lastModified = timestamp;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
});
|
|
206
|
-
await this.saveGraph(graph);
|
|
207
|
-
}
|
|
208
|
-
async deleteRelations(relations) {
|
|
209
|
-
const graph = await this.loadGraph();
|
|
210
|
-
const timestamp = new Date().toISOString();
|
|
211
|
-
// Track which entities are affected by relation deletions
|
|
212
|
-
const affectedEntityNames = new Set();
|
|
213
|
-
relations.forEach(rel => {
|
|
214
|
-
affectedEntityNames.add(rel.from);
|
|
215
|
-
affectedEntityNames.add(rel.to);
|
|
216
|
-
});
|
|
217
|
-
graph.relations = graph.relations.filter(r => !relations.some(delRelation => r.from === delRelation.from &&
|
|
218
|
-
r.to === delRelation.to &&
|
|
219
|
-
r.relationType === delRelation.relationType));
|
|
220
|
-
// Update lastModified for affected entities
|
|
221
|
-
graph.entities.forEach(entity => {
|
|
222
|
-
if (affectedEntityNames.has(entity.name)) {
|
|
223
|
-
entity.lastModified = timestamp;
|
|
224
|
-
}
|
|
225
|
-
});
|
|
226
|
-
await this.saveGraph(graph);
|
|
227
|
-
}
|
|
228
|
-
async readGraph() {
|
|
229
|
-
return this.loadGraph();
|
|
230
|
-
}
|
|
231
|
-
// Phase 3: Enhanced search function with tags and importance filters
|
|
232
|
-
async searchNodes(query, tags, minImportance, maxImportance) {
|
|
233
|
-
const graph = await this.loadGraph();
|
|
234
|
-
// Normalize tags to lowercase for case-insensitive matching
|
|
235
|
-
const normalizedTags = tags?.map(tag => tag.toLowerCase());
|
|
236
|
-
// Filter entities
|
|
237
|
-
const filteredEntities = graph.entities.filter(e => {
|
|
238
|
-
// Text search
|
|
239
|
-
const matchesQuery = e.name.toLowerCase().includes(query.toLowerCase()) ||
|
|
240
|
-
e.entityType.toLowerCase().includes(query.toLowerCase()) ||
|
|
241
|
-
e.observations.some(o => o.toLowerCase().includes(query.toLowerCase()));
|
|
242
|
-
if (!matchesQuery)
|
|
243
|
-
return false;
|
|
244
|
-
// Phase 3: Tag filter
|
|
245
|
-
if (normalizedTags && normalizedTags.length > 0) {
|
|
246
|
-
if (!e.tags || e.tags.length === 0)
|
|
247
|
-
return false;
|
|
248
|
-
const entityTags = e.tags.map(tag => tag.toLowerCase());
|
|
249
|
-
const hasMatchingTag = normalizedTags.some(tag => entityTags.includes(tag));
|
|
250
|
-
if (!hasMatchingTag)
|
|
251
|
-
return false;
|
|
252
|
-
}
|
|
253
|
-
// Phase 3: Importance filter
|
|
254
|
-
if (minImportance !== undefined && (e.importance === undefined || e.importance < minImportance)) {
|
|
255
|
-
return false;
|
|
256
|
-
}
|
|
257
|
-
if (maxImportance !== undefined && (e.importance === undefined || e.importance > maxImportance)) {
|
|
258
|
-
return false;
|
|
259
|
-
}
|
|
260
|
-
return true;
|
|
261
|
-
});
|
|
262
|
-
// Create a Set of filtered entity names for quick lookup
|
|
263
|
-
const filteredEntityNames = new Set(filteredEntities.map(e => e.name));
|
|
264
|
-
// Filter relations to only include those between filtered entities
|
|
265
|
-
const filteredRelations = graph.relations.filter(r => filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to));
|
|
266
|
-
const filteredGraph = {
|
|
267
|
-
entities: filteredEntities,
|
|
268
|
-
relations: filteredRelations,
|
|
269
|
-
};
|
|
270
|
-
return filteredGraph;
|
|
271
|
-
}
|
|
272
|
-
async openNodes(names) {
|
|
273
|
-
const graph = await this.loadGraph();
|
|
274
|
-
// Filter entities
|
|
275
|
-
const filteredEntities = graph.entities.filter(e => names.includes(e.name));
|
|
276
|
-
// Create a Set of filtered entity names for quick lookup
|
|
277
|
-
const filteredEntityNames = new Set(filteredEntities.map(e => e.name));
|
|
278
|
-
// Filter relations to only include those between filtered entities
|
|
279
|
-
const filteredRelations = graph.relations.filter(r => filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to));
|
|
280
|
-
const filteredGraph = {
|
|
281
|
-
entities: filteredEntities,
|
|
282
|
-
relations: filteredRelations,
|
|
283
|
-
};
|
|
284
|
-
return filteredGraph;
|
|
285
|
-
}
|
|
286
|
-
// Phase 3: Enhanced searchByDateRange with tags filter
|
|
287
|
-
async searchByDateRange(startDate, endDate, entityType, tags) {
|
|
288
|
-
const graph = await this.loadGraph();
|
|
289
|
-
const start = startDate ? new Date(startDate) : null;
|
|
290
|
-
const end = endDate ? new Date(endDate) : null;
|
|
291
|
-
// Normalize tags to lowercase for case-insensitive matching
|
|
292
|
-
const normalizedTags = tags?.map(tag => tag.toLowerCase());
|
|
293
|
-
// Filter entities by date range and optionally by entity type and tags
|
|
294
|
-
const filteredEntities = graph.entities.filter(e => {
|
|
295
|
-
// Check entity type filter
|
|
296
|
-
if (entityType && e.entityType !== entityType) {
|
|
297
|
-
return false;
|
|
298
|
-
}
|
|
299
|
-
// Phase 3: Tag filter
|
|
300
|
-
if (normalizedTags && normalizedTags.length > 0) {
|
|
301
|
-
if (!e.tags || e.tags.length === 0)
|
|
302
|
-
return false;
|
|
303
|
-
const entityTags = e.tags.map(tag => tag.toLowerCase());
|
|
304
|
-
const hasMatchingTag = normalizedTags.some(tag => entityTags.includes(tag));
|
|
305
|
-
if (!hasMatchingTag)
|
|
306
|
-
return false;
|
|
307
|
-
}
|
|
308
|
-
// Check date range using createdAt or lastModified
|
|
309
|
-
const entityDate = new Date(e.lastModified || e.createdAt || '');
|
|
310
|
-
if (start && entityDate < start) {
|
|
311
|
-
return false;
|
|
312
|
-
}
|
|
313
|
-
if (end && entityDate > end) {
|
|
314
|
-
return false;
|
|
315
|
-
}
|
|
316
|
-
return true;
|
|
317
|
-
});
|
|
318
|
-
// Create a Set of filtered entity names for quick lookup
|
|
319
|
-
const filteredEntityNames = new Set(filteredEntities.map(e => e.name));
|
|
320
|
-
// Filter relations by date range and only include those between filtered entities
|
|
321
|
-
const filteredRelations = graph.relations.filter(r => {
|
|
322
|
-
// Must be between filtered entities
|
|
323
|
-
if (!filteredEntityNames.has(r.from) || !filteredEntityNames.has(r.to)) {
|
|
324
|
-
return false;
|
|
325
|
-
}
|
|
326
|
-
// Check date range using createdAt or lastModified
|
|
327
|
-
const relationDate = new Date(r.lastModified || r.createdAt || '');
|
|
328
|
-
if (start && relationDate < start) {
|
|
329
|
-
return false;
|
|
330
|
-
}
|
|
331
|
-
if (end && relationDate > end) {
|
|
332
|
-
return false;
|
|
333
|
-
}
|
|
334
|
-
return true;
|
|
335
|
-
});
|
|
336
|
-
return {
|
|
337
|
-
entities: filteredEntities,
|
|
338
|
-
relations: filteredRelations,
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
async getGraphStats() {
|
|
342
|
-
const graph = await this.loadGraph();
|
|
343
|
-
// Calculate entity type counts
|
|
344
|
-
const entityTypesCounts = {};
|
|
345
|
-
graph.entities.forEach(e => {
|
|
346
|
-
entityTypesCounts[e.entityType] = (entityTypesCounts[e.entityType] || 0) + 1;
|
|
347
|
-
});
|
|
348
|
-
// Calculate relation type counts
|
|
349
|
-
const relationTypesCounts = {};
|
|
350
|
-
graph.relations.forEach(r => {
|
|
351
|
-
relationTypesCounts[r.relationType] = (relationTypesCounts[r.relationType] || 0) + 1;
|
|
352
|
-
});
|
|
353
|
-
// Find oldest and newest entities
|
|
354
|
-
let oldestEntity;
|
|
355
|
-
let newestEntity;
|
|
356
|
-
let earliestEntityDate = null;
|
|
357
|
-
let latestEntityDate = null;
|
|
358
|
-
graph.entities.forEach(e => {
|
|
359
|
-
const date = new Date(e.createdAt || '');
|
|
360
|
-
if (!earliestEntityDate || date < earliestEntityDate) {
|
|
361
|
-
earliestEntityDate = date;
|
|
362
|
-
oldestEntity = { name: e.name, date: e.createdAt || '' };
|
|
363
|
-
}
|
|
364
|
-
if (!latestEntityDate || date > latestEntityDate) {
|
|
365
|
-
latestEntityDate = date;
|
|
366
|
-
newestEntity = { name: e.name, date: e.createdAt || '' };
|
|
367
|
-
}
|
|
368
|
-
});
|
|
369
|
-
// Find oldest and newest relations
|
|
370
|
-
let oldestRelation;
|
|
371
|
-
let newestRelation;
|
|
372
|
-
let earliestRelationDate = null;
|
|
373
|
-
let latestRelationDate = null;
|
|
374
|
-
graph.relations.forEach(r => {
|
|
375
|
-
const date = new Date(r.createdAt || '');
|
|
376
|
-
if (!earliestRelationDate || date < earliestRelationDate) {
|
|
377
|
-
earliestRelationDate = date;
|
|
378
|
-
oldestRelation = { from: r.from, to: r.to, relationType: r.relationType, date: r.createdAt || '' };
|
|
379
|
-
}
|
|
380
|
-
if (!latestRelationDate || date > latestRelationDate) {
|
|
381
|
-
latestRelationDate = date;
|
|
382
|
-
newestRelation = { from: r.from, to: r.to, relationType: r.relationType, date: r.createdAt || '' };
|
|
383
|
-
}
|
|
384
|
-
});
|
|
385
|
-
return {
|
|
386
|
-
totalEntities: graph.entities.length,
|
|
387
|
-
totalRelations: graph.relations.length,
|
|
388
|
-
entityTypesCounts,
|
|
389
|
-
relationTypesCounts,
|
|
390
|
-
oldestEntity,
|
|
391
|
-
newestEntity,
|
|
392
|
-
oldestRelation,
|
|
393
|
-
newestRelation,
|
|
394
|
-
entityDateRange: earliestEntityDate && latestEntityDate ? {
|
|
395
|
-
earliest: earliestEntityDate.toISOString(),
|
|
396
|
-
latest: latestEntityDate.toISOString()
|
|
397
|
-
} : undefined,
|
|
398
|
-
relationDateRange: earliestRelationDate && latestRelationDate ? {
|
|
399
|
-
earliest: earliestRelationDate.toISOString(),
|
|
400
|
-
latest: latestRelationDate.toISOString()
|
|
401
|
-
} : undefined,
|
|
402
|
-
};
|
|
403
|
-
}
|
|
404
|
-
// Phase 3: Add tags to an entity
|
|
405
|
-
async addTags(entityName, tags) {
|
|
406
|
-
const graph = await this.loadGraph();
|
|
407
|
-
const timestamp = new Date().toISOString();
|
|
408
|
-
const entity = graph.entities.find(e => e.name === entityName);
|
|
409
|
-
if (!entity) {
|
|
410
|
-
throw new Error(`Entity with name ${entityName} not found`);
|
|
411
|
-
}
|
|
412
|
-
// Initialize tags array if it doesn't exist
|
|
413
|
-
if (!entity.tags) {
|
|
414
|
-
entity.tags = [];
|
|
415
|
-
}
|
|
416
|
-
// Normalize tags to lowercase and filter out duplicates
|
|
417
|
-
const normalizedTags = tags.map(tag => tag.toLowerCase());
|
|
418
|
-
const newTags = normalizedTags.filter(tag => !entity.tags.includes(tag));
|
|
419
|
-
entity.tags.push(...newTags);
|
|
420
|
-
// Update lastModified timestamp if tags were added
|
|
421
|
-
if (newTags.length > 0) {
|
|
422
|
-
entity.lastModified = timestamp;
|
|
423
|
-
}
|
|
424
|
-
await this.saveGraph(graph);
|
|
425
|
-
return { entityName, addedTags: newTags };
|
|
426
|
-
}
|
|
427
|
-
// Phase 3: Remove tags from an entity
|
|
428
|
-
async removeTags(entityName, tags) {
|
|
429
|
-
const graph = await this.loadGraph();
|
|
430
|
-
const timestamp = new Date().toISOString();
|
|
431
|
-
const entity = graph.entities.find(e => e.name === entityName);
|
|
432
|
-
if (!entity) {
|
|
433
|
-
throw new Error(`Entity with name ${entityName} not found`);
|
|
434
|
-
}
|
|
435
|
-
if (!entity.tags) {
|
|
436
|
-
return { entityName, removedTags: [] };
|
|
437
|
-
}
|
|
438
|
-
// Normalize tags to lowercase
|
|
439
|
-
const normalizedTags = tags.map(tag => tag.toLowerCase());
|
|
440
|
-
const originalLength = entity.tags.length;
|
|
441
|
-
// Filter out the tags to remove
|
|
442
|
-
entity.tags = entity.tags.filter(tag => !normalizedTags.includes(tag.toLowerCase()));
|
|
443
|
-
const removedTags = normalizedTags.filter(tag => originalLength > entity.tags.length ||
|
|
444
|
-
!entity.tags.map(t => t.toLowerCase()).includes(tag));
|
|
445
|
-
// Update lastModified timestamp if tags were removed
|
|
446
|
-
if (entity.tags.length < originalLength) {
|
|
447
|
-
entity.lastModified = timestamp;
|
|
448
|
-
}
|
|
449
|
-
await this.saveGraph(graph);
|
|
450
|
-
return { entityName, removedTags };
|
|
451
|
-
}
|
|
452
|
-
// Phase 3: Set importance level for an entity
|
|
453
|
-
async setImportance(entityName, importance) {
|
|
454
|
-
const graph = await this.loadGraph();
|
|
455
|
-
const timestamp = new Date().toISOString();
|
|
456
|
-
// Validate importance range
|
|
457
|
-
if (importance < 0 || importance > 10) {
|
|
458
|
-
throw new Error(`Importance must be between 0 and 10, got ${importance}`);
|
|
459
|
-
}
|
|
460
|
-
const entity = graph.entities.find(e => e.name === entityName);
|
|
461
|
-
if (!entity) {
|
|
462
|
-
throw new Error(`Entity with name ${entityName} not found`);
|
|
463
|
-
}
|
|
464
|
-
entity.importance = importance;
|
|
465
|
-
entity.lastModified = timestamp;
|
|
466
|
-
await this.saveGraph(graph);
|
|
467
|
-
return { entityName, importance };
|
|
468
|
-
}
|
|
469
|
-
// Phase 4: Export graph in various formats
|
|
470
|
-
/**
|
|
471
|
-
* Export the knowledge graph in the specified format with optional filtering.
|
|
472
|
-
* Supports JSON, CSV, and GraphML formats for different use cases.
|
|
473
|
-
*
|
|
474
|
-
* @param format - Export format: 'json', 'csv', or 'graphml'
|
|
475
|
-
* @param filter - Optional filter object with same structure as searchByDateRange
|
|
476
|
-
* @returns Exported graph data as a formatted string
|
|
477
|
-
*/
|
|
478
|
-
async exportGraph(format, filter) {
|
|
479
|
-
// Get filtered or full graph based on filter parameter
|
|
480
|
-
let graph;
|
|
481
|
-
if (filter) {
|
|
482
|
-
graph = await this.searchByDateRange(filter.startDate, filter.endDate, filter.entityType, filter.tags);
|
|
483
|
-
}
|
|
484
|
-
else {
|
|
485
|
-
graph = await this.loadGraph();
|
|
486
|
-
}
|
|
487
|
-
switch (format) {
|
|
488
|
-
case 'json':
|
|
489
|
-
return this.exportAsJson(graph);
|
|
490
|
-
case 'csv':
|
|
491
|
-
return this.exportAsCsv(graph);
|
|
492
|
-
case 'graphml':
|
|
493
|
-
return this.exportAsGraphML(graph);
|
|
494
|
-
default:
|
|
495
|
-
throw new Error(`Unsupported export format: ${format}`);
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
/**
|
|
499
|
-
* Export graph as pretty-printed JSON with all entity and relation data
|
|
500
|
-
*/
|
|
501
|
-
exportAsJson(graph) {
|
|
502
|
-
return JSON.stringify(graph, null, 2);
|
|
503
|
-
}
|
|
504
|
-
/**
|
|
505
|
-
* Export graph as CSV with two sections: entities and relations
|
|
506
|
-
* Uses proper escaping for fields containing commas, quotes, and newlines
|
|
507
|
-
*/
|
|
508
|
-
exportAsCsv(graph) {
|
|
509
|
-
const lines = [];
|
|
510
|
-
// Helper function to escape CSV fields
|
|
511
|
-
const escapeCsvField = (field) => {
|
|
512
|
-
if (field === undefined || field === null)
|
|
513
|
-
return '';
|
|
514
|
-
const str = String(field);
|
|
515
|
-
// Escape quotes by doubling them and wrap in quotes if contains comma, quote, or newline
|
|
516
|
-
if (str.includes(',') || str.includes('"') || str.includes('\n')) {
|
|
517
|
-
return `"${str.replace(/"/g, '""')}"`;
|
|
518
|
-
}
|
|
519
|
-
return str;
|
|
520
|
-
};
|
|
521
|
-
// Entities section
|
|
522
|
-
lines.push('# ENTITIES');
|
|
523
|
-
lines.push('name,entityType,observations,createdAt,lastModified,tags,importance');
|
|
524
|
-
for (const entity of graph.entities) {
|
|
525
|
-
const observationsStr = entity.observations.join('; ');
|
|
526
|
-
const tagsStr = entity.tags ? entity.tags.join('; ') : '';
|
|
527
|
-
const importanceStr = entity.importance !== undefined ? String(entity.importance) : '';
|
|
528
|
-
lines.push([
|
|
529
|
-
escapeCsvField(entity.name),
|
|
530
|
-
escapeCsvField(entity.entityType),
|
|
531
|
-
escapeCsvField(observationsStr),
|
|
532
|
-
escapeCsvField(entity.createdAt),
|
|
533
|
-
escapeCsvField(entity.lastModified),
|
|
534
|
-
escapeCsvField(tagsStr),
|
|
535
|
-
escapeCsvField(importanceStr)
|
|
536
|
-
].join(','));
|
|
537
|
-
}
|
|
538
|
-
// Relations section
|
|
539
|
-
lines.push('');
|
|
540
|
-
lines.push('# RELATIONS');
|
|
541
|
-
lines.push('from,to,relationType,createdAt,lastModified');
|
|
542
|
-
for (const relation of graph.relations) {
|
|
543
|
-
lines.push([
|
|
544
|
-
escapeCsvField(relation.from),
|
|
545
|
-
escapeCsvField(relation.to),
|
|
546
|
-
escapeCsvField(relation.relationType),
|
|
547
|
-
escapeCsvField(relation.createdAt),
|
|
548
|
-
escapeCsvField(relation.lastModified)
|
|
549
|
-
].join(','));
|
|
550
|
-
}
|
|
551
|
-
return lines.join('\n');
|
|
552
|
-
}
|
|
553
|
-
/**
|
|
554
|
-
* Export graph as GraphML XML format for graph visualization tools
|
|
555
|
-
* Compatible with Gephi, Cytoscape, yEd, and other graph analysis tools
|
|
556
|
-
*/
|
|
557
|
-
exportAsGraphML(graph) {
|
|
558
|
-
const lines = [];
|
|
559
|
-
// Helper function to escape XML special characters
|
|
560
|
-
const escapeXml = (str) => {
|
|
561
|
-
if (str === undefined || str === null)
|
|
562
|
-
return '';
|
|
563
|
-
return String(str)
|
|
564
|
-
.replace(/&/g, '&')
|
|
565
|
-
.replace(/</g, '<')
|
|
566
|
-
.replace(/>/g, '>')
|
|
567
|
-
.replace(/"/g, '"')
|
|
568
|
-
.replace(/'/g, ''');
|
|
569
|
-
};
|
|
570
|
-
// GraphML header
|
|
571
|
-
lines.push('<?xml version="1.0" encoding="UTF-8"?>');
|
|
572
|
-
lines.push('<graphml xmlns="http://graphml.graphdrawing.org/xmlns"');
|
|
573
|
-
lines.push(' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"');
|
|
574
|
-
lines.push(' xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns');
|
|
575
|
-
lines.push(' http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">');
|
|
576
|
-
// Define node attributes (keys)
|
|
577
|
-
lines.push(' <!-- Node attributes -->');
|
|
578
|
-
lines.push(' <key id="d0" for="node" attr.name="entityType" attr.type="string"/>');
|
|
579
|
-
lines.push(' <key id="d1" for="node" attr.name="observations" attr.type="string"/>');
|
|
580
|
-
lines.push(' <key id="d2" for="node" attr.name="createdAt" attr.type="string"/>');
|
|
581
|
-
lines.push(' <key id="d3" for="node" attr.name="lastModified" attr.type="string"/>');
|
|
582
|
-
lines.push(' <key id="d4" for="node" attr.name="tags" attr.type="string"/>');
|
|
583
|
-
lines.push(' <key id="d5" for="node" attr.name="importance" attr.type="double"/>');
|
|
584
|
-
// Define edge attributes (keys)
|
|
585
|
-
lines.push(' <!-- Edge attributes -->');
|
|
586
|
-
lines.push(' <key id="e0" for="edge" attr.name="relationType" attr.type="string"/>');
|
|
587
|
-
lines.push(' <key id="e1" for="edge" attr.name="createdAt" attr.type="string"/>');
|
|
588
|
-
lines.push(' <key id="e2" for="edge" attr.name="lastModified" attr.type="string"/>');
|
|
589
|
-
// Start graph (directed graph)
|
|
590
|
-
lines.push(' <graph id="G" edgedefault="directed">');
|
|
591
|
-
// Add nodes (entities)
|
|
592
|
-
for (const entity of graph.entities) {
|
|
593
|
-
// Use entity name as node ID (escape for XML attribute)
|
|
594
|
-
const nodeId = escapeXml(entity.name);
|
|
595
|
-
lines.push(` <node id="${nodeId}">`);
|
|
596
|
-
lines.push(` <data key="d0">${escapeXml(entity.entityType)}</data>`);
|
|
597
|
-
lines.push(` <data key="d1">${escapeXml(entity.observations.join('; '))}</data>`);
|
|
598
|
-
if (entity.createdAt) {
|
|
599
|
-
lines.push(` <data key="d2">${escapeXml(entity.createdAt)}</data>`);
|
|
600
|
-
}
|
|
601
|
-
if (entity.lastModified) {
|
|
602
|
-
lines.push(` <data key="d3">${escapeXml(entity.lastModified)}</data>`);
|
|
603
|
-
}
|
|
604
|
-
if (entity.tags && entity.tags.length > 0) {
|
|
605
|
-
lines.push(` <data key="d4">${escapeXml(entity.tags.join('; '))}</data>`);
|
|
606
|
-
}
|
|
607
|
-
if (entity.importance !== undefined) {
|
|
608
|
-
lines.push(` <data key="d5">${entity.importance}</data>`);
|
|
609
|
-
}
|
|
610
|
-
lines.push(' </node>');
|
|
611
|
-
}
|
|
612
|
-
// Add edges (relations)
|
|
613
|
-
let edgeId = 0;
|
|
614
|
-
for (const relation of graph.relations) {
|
|
615
|
-
const sourceId = escapeXml(relation.from);
|
|
616
|
-
const targetId = escapeXml(relation.to);
|
|
617
|
-
lines.push(` <edge id="e${edgeId}" source="${sourceId}" target="${targetId}">`);
|
|
618
|
-
lines.push(` <data key="e0">${escapeXml(relation.relationType)}</data>`);
|
|
619
|
-
if (relation.createdAt) {
|
|
620
|
-
lines.push(` <data key="e1">${escapeXml(relation.createdAt)}</data>`);
|
|
621
|
-
}
|
|
622
|
-
if (relation.lastModified) {
|
|
623
|
-
lines.push(` <data key="e2">${escapeXml(relation.lastModified)}</data>`);
|
|
624
|
-
}
|
|
625
|
-
lines.push(' </edge>');
|
|
626
|
-
edgeId++;
|
|
627
|
-
}
|
|
628
|
-
// Close graph and graphml
|
|
629
|
-
lines.push(' </graph>');
|
|
630
|
-
lines.push('</graphml>');
|
|
631
|
-
return lines.join('\n');
|
|
632
|
-
}
|
|
633
|
-
}
|
|
42
|
+
// Re-export KnowledgeGraphManager for backward compatibility
|
|
43
|
+
export { KnowledgeGraphManager };
|
|
634
44
|
let knowledgeGraphManager;
|
|
635
|
-
// The server instance and tools exposed to Claude
|
|
636
|
-
const server = new Server({
|
|
637
|
-
name: "memory-server",
|
|
638
|
-
version: "0.7.0",
|
|
639
|
-
}, {
|
|
640
|
-
capabilities: {
|
|
641
|
-
tools: {},
|
|
642
|
-
},
|
|
643
|
-
});
|
|
644
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
645
|
-
return {
|
|
646
|
-
tools: [
|
|
647
|
-
{
|
|
648
|
-
name: "create_entities",
|
|
649
|
-
description: "Create multiple new entities in the knowledge graph",
|
|
650
|
-
inputSchema: {
|
|
651
|
-
type: "object",
|
|
652
|
-
properties: {
|
|
653
|
-
entities: {
|
|
654
|
-
type: "array",
|
|
655
|
-
items: {
|
|
656
|
-
type: "object",
|
|
657
|
-
properties: {
|
|
658
|
-
name: { type: "string", description: "The name of the entity" },
|
|
659
|
-
entityType: { type: "string", description: "The type of the entity" },
|
|
660
|
-
observations: {
|
|
661
|
-
type: "array",
|
|
662
|
-
items: { type: "string" },
|
|
663
|
-
description: "An array of observation contents associated with the entity"
|
|
664
|
-
},
|
|
665
|
-
},
|
|
666
|
-
required: ["name", "entityType", "observations"],
|
|
667
|
-
additionalProperties: false,
|
|
668
|
-
},
|
|
669
|
-
},
|
|
670
|
-
},
|
|
671
|
-
required: ["entities"],
|
|
672
|
-
additionalProperties: false,
|
|
673
|
-
},
|
|
674
|
-
},
|
|
675
|
-
{
|
|
676
|
-
name: "create_relations",
|
|
677
|
-
description: "Create multiple new relations between entities in the knowledge graph. Relations should be in active voice",
|
|
678
|
-
inputSchema: {
|
|
679
|
-
type: "object",
|
|
680
|
-
properties: {
|
|
681
|
-
relations: {
|
|
682
|
-
type: "array",
|
|
683
|
-
items: {
|
|
684
|
-
type: "object",
|
|
685
|
-
properties: {
|
|
686
|
-
from: { type: "string", description: "The name of the entity where the relation starts" },
|
|
687
|
-
to: { type: "string", description: "The name of the entity where the relation ends" },
|
|
688
|
-
relationType: { type: "string", description: "The type of the relation" },
|
|
689
|
-
},
|
|
690
|
-
required: ["from", "to", "relationType"],
|
|
691
|
-
additionalProperties: false,
|
|
692
|
-
},
|
|
693
|
-
},
|
|
694
|
-
},
|
|
695
|
-
required: ["relations"],
|
|
696
|
-
additionalProperties: false,
|
|
697
|
-
},
|
|
698
|
-
},
|
|
699
|
-
{
|
|
700
|
-
name: "add_observations",
|
|
701
|
-
description: "Add new observations to existing entities in the knowledge graph",
|
|
702
|
-
inputSchema: {
|
|
703
|
-
type: "object",
|
|
704
|
-
properties: {
|
|
705
|
-
observations: {
|
|
706
|
-
type: "array",
|
|
707
|
-
items: {
|
|
708
|
-
type: "object",
|
|
709
|
-
properties: {
|
|
710
|
-
entityName: { type: "string", description: "The name of the entity to add the observations to" },
|
|
711
|
-
contents: {
|
|
712
|
-
type: "array",
|
|
713
|
-
items: { type: "string" },
|
|
714
|
-
description: "An array of observation contents to add"
|
|
715
|
-
},
|
|
716
|
-
},
|
|
717
|
-
required: ["entityName", "contents"],
|
|
718
|
-
additionalProperties: false,
|
|
719
|
-
},
|
|
720
|
-
},
|
|
721
|
-
},
|
|
722
|
-
required: ["observations"],
|
|
723
|
-
additionalProperties: false,
|
|
724
|
-
},
|
|
725
|
-
},
|
|
726
|
-
{
|
|
727
|
-
name: "delete_entities",
|
|
728
|
-
description: "Delete multiple entities and their associated relations from the knowledge graph",
|
|
729
|
-
inputSchema: {
|
|
730
|
-
type: "object",
|
|
731
|
-
properties: {
|
|
732
|
-
entityNames: {
|
|
733
|
-
type: "array",
|
|
734
|
-
items: { type: "string" },
|
|
735
|
-
description: "An array of entity names to delete"
|
|
736
|
-
},
|
|
737
|
-
},
|
|
738
|
-
required: ["entityNames"],
|
|
739
|
-
additionalProperties: false,
|
|
740
|
-
},
|
|
741
|
-
},
|
|
742
|
-
{
|
|
743
|
-
name: "delete_observations",
|
|
744
|
-
description: "Delete specific observations from entities in the knowledge graph",
|
|
745
|
-
inputSchema: {
|
|
746
|
-
type: "object",
|
|
747
|
-
properties: {
|
|
748
|
-
deletions: {
|
|
749
|
-
type: "array",
|
|
750
|
-
items: {
|
|
751
|
-
type: "object",
|
|
752
|
-
properties: {
|
|
753
|
-
entityName: { type: "string", description: "The name of the entity containing the observations" },
|
|
754
|
-
observations: {
|
|
755
|
-
type: "array",
|
|
756
|
-
items: { type: "string" },
|
|
757
|
-
description: "An array of observations to delete"
|
|
758
|
-
},
|
|
759
|
-
},
|
|
760
|
-
required: ["entityName", "observations"],
|
|
761
|
-
additionalProperties: false,
|
|
762
|
-
},
|
|
763
|
-
},
|
|
764
|
-
},
|
|
765
|
-
required: ["deletions"],
|
|
766
|
-
additionalProperties: false,
|
|
767
|
-
},
|
|
768
|
-
},
|
|
769
|
-
{
|
|
770
|
-
name: "delete_relations",
|
|
771
|
-
description: "Delete multiple relations from the knowledge graph",
|
|
772
|
-
inputSchema: {
|
|
773
|
-
type: "object",
|
|
774
|
-
properties: {
|
|
775
|
-
relations: {
|
|
776
|
-
type: "array",
|
|
777
|
-
items: {
|
|
778
|
-
type: "object",
|
|
779
|
-
properties: {
|
|
780
|
-
from: { type: "string", description: "The name of the entity where the relation starts" },
|
|
781
|
-
to: { type: "string", description: "The name of the entity where the relation ends" },
|
|
782
|
-
relationType: { type: "string", description: "The type of the relation" },
|
|
783
|
-
},
|
|
784
|
-
required: ["from", "to", "relationType"],
|
|
785
|
-
additionalProperties: false,
|
|
786
|
-
},
|
|
787
|
-
description: "An array of relations to delete"
|
|
788
|
-
},
|
|
789
|
-
},
|
|
790
|
-
required: ["relations"],
|
|
791
|
-
additionalProperties: false,
|
|
792
|
-
},
|
|
793
|
-
},
|
|
794
|
-
{
|
|
795
|
-
name: "read_graph",
|
|
796
|
-
description: "Read the entire knowledge graph",
|
|
797
|
-
inputSchema: {
|
|
798
|
-
type: "object",
|
|
799
|
-
properties: {},
|
|
800
|
-
additionalProperties: false,
|
|
801
|
-
},
|
|
802
|
-
},
|
|
803
|
-
{
|
|
804
|
-
name: "search_nodes",
|
|
805
|
-
description: "Search for nodes in the knowledge graph based on a query, with optional filters for tags and importance",
|
|
806
|
-
inputSchema: {
|
|
807
|
-
type: "object",
|
|
808
|
-
properties: {
|
|
809
|
-
query: { type: "string", description: "The search query to match against entity names, types, and observation content" },
|
|
810
|
-
tags: {
|
|
811
|
-
type: "array",
|
|
812
|
-
items: { type: "string" },
|
|
813
|
-
description: "Optional array of tags to filter by (case-insensitive)"
|
|
814
|
-
},
|
|
815
|
-
minImportance: {
|
|
816
|
-
type: "number",
|
|
817
|
-
description: "Optional minimum importance level (0-10)"
|
|
818
|
-
},
|
|
819
|
-
maxImportance: {
|
|
820
|
-
type: "number",
|
|
821
|
-
description: "Optional maximum importance level (0-10)"
|
|
822
|
-
},
|
|
823
|
-
},
|
|
824
|
-
required: ["query"],
|
|
825
|
-
additionalProperties: false,
|
|
826
|
-
},
|
|
827
|
-
},
|
|
828
|
-
{
|
|
829
|
-
name: "open_nodes",
|
|
830
|
-
description: "Open specific nodes in the knowledge graph by their names",
|
|
831
|
-
inputSchema: {
|
|
832
|
-
type: "object",
|
|
833
|
-
properties: {
|
|
834
|
-
names: {
|
|
835
|
-
type: "array",
|
|
836
|
-
items: { type: "string" },
|
|
837
|
-
description: "An array of entity names to retrieve",
|
|
838
|
-
},
|
|
839
|
-
},
|
|
840
|
-
required: ["names"],
|
|
841
|
-
additionalProperties: false,
|
|
842
|
-
},
|
|
843
|
-
},
|
|
844
|
-
{
|
|
845
|
-
name: "search_by_date_range",
|
|
846
|
-
description: "Search for entities and relations within a specific date range, optionally filtered by entity type and tags. Uses createdAt or lastModified timestamps.",
|
|
847
|
-
inputSchema: {
|
|
848
|
-
type: "object",
|
|
849
|
-
properties: {
|
|
850
|
-
startDate: {
|
|
851
|
-
type: "string",
|
|
852
|
-
description: "ISO 8601 start date (optional). If not provided, no lower bound is applied."
|
|
853
|
-
},
|
|
854
|
-
endDate: {
|
|
855
|
-
type: "string",
|
|
856
|
-
description: "ISO 8601 end date (optional). If not provided, no upper bound is applied."
|
|
857
|
-
},
|
|
858
|
-
entityType: {
|
|
859
|
-
type: "string",
|
|
860
|
-
description: "Filter by specific entity type (optional)"
|
|
861
|
-
},
|
|
862
|
-
tags: {
|
|
863
|
-
type: "array",
|
|
864
|
-
items: { type: "string" },
|
|
865
|
-
description: "Optional array of tags to filter by (case-insensitive)"
|
|
866
|
-
},
|
|
867
|
-
},
|
|
868
|
-
additionalProperties: false,
|
|
869
|
-
},
|
|
870
|
-
},
|
|
871
|
-
{
|
|
872
|
-
name: "get_graph_stats",
|
|
873
|
-
description: "Get comprehensive statistics about the knowledge graph including counts, types, and date ranges",
|
|
874
|
-
inputSchema: {
|
|
875
|
-
type: "object",
|
|
876
|
-
properties: {},
|
|
877
|
-
additionalProperties: false,
|
|
878
|
-
},
|
|
879
|
-
},
|
|
880
|
-
{
|
|
881
|
-
name: "add_tags",
|
|
882
|
-
description: "Add tags to an existing entity in the knowledge graph. Tags are stored as lowercase for case-insensitive matching.",
|
|
883
|
-
inputSchema: {
|
|
884
|
-
type: "object",
|
|
885
|
-
properties: {
|
|
886
|
-
entityName: {
|
|
887
|
-
type: "string",
|
|
888
|
-
description: "The name of the entity to add tags to"
|
|
889
|
-
},
|
|
890
|
-
tags: {
|
|
891
|
-
type: "array",
|
|
892
|
-
items: { type: "string" },
|
|
893
|
-
description: "An array of tags to add to the entity"
|
|
894
|
-
},
|
|
895
|
-
},
|
|
896
|
-
required: ["entityName", "tags"],
|
|
897
|
-
additionalProperties: false,
|
|
898
|
-
},
|
|
899
|
-
},
|
|
900
|
-
{
|
|
901
|
-
name: "remove_tags",
|
|
902
|
-
description: "Remove tags from an existing entity in the knowledge graph",
|
|
903
|
-
inputSchema: {
|
|
904
|
-
type: "object",
|
|
905
|
-
properties: {
|
|
906
|
-
entityName: {
|
|
907
|
-
type: "string",
|
|
908
|
-
description: "The name of the entity to remove tags from"
|
|
909
|
-
},
|
|
910
|
-
tags: {
|
|
911
|
-
type: "array",
|
|
912
|
-
items: { type: "string" },
|
|
913
|
-
description: "An array of tags to remove from the entity"
|
|
914
|
-
},
|
|
915
|
-
},
|
|
916
|
-
required: ["entityName", "tags"],
|
|
917
|
-
additionalProperties: false,
|
|
918
|
-
},
|
|
919
|
-
},
|
|
920
|
-
{
|
|
921
|
-
name: "set_importance",
|
|
922
|
-
description: "Set the importance level for an entity. Importance must be a number between 0 and 10.",
|
|
923
|
-
inputSchema: {
|
|
924
|
-
type: "object",
|
|
925
|
-
properties: {
|
|
926
|
-
entityName: {
|
|
927
|
-
type: "string",
|
|
928
|
-
description: "The name of the entity to set importance for"
|
|
929
|
-
},
|
|
930
|
-
importance: {
|
|
931
|
-
type: "number",
|
|
932
|
-
description: "The importance level (0-10, where 0 is least important and 10 is most important)",
|
|
933
|
-
minimum: 0,
|
|
934
|
-
maximum: 10
|
|
935
|
-
},
|
|
936
|
-
},
|
|
937
|
-
required: ["entityName", "importance"],
|
|
938
|
-
additionalProperties: false,
|
|
939
|
-
},
|
|
940
|
-
},
|
|
941
|
-
{
|
|
942
|
-
name: "export_graph",
|
|
943
|
-
description: "Export the knowledge graph in various formats (JSON, CSV, or GraphML) with optional filtering. GraphML format is compatible with graph visualization tools like Gephi and Cytoscape.",
|
|
944
|
-
inputSchema: {
|
|
945
|
-
type: "object",
|
|
946
|
-
properties: {
|
|
947
|
-
format: {
|
|
948
|
-
type: "string",
|
|
949
|
-
enum: ["json", "csv", "graphml"],
|
|
950
|
-
description: "Export format: 'json' for pretty-printed JSON, 'csv' for comma-separated values with entities and relations sections, 'graphml' for GraphML XML format"
|
|
951
|
-
},
|
|
952
|
-
filter: {
|
|
953
|
-
type: "object",
|
|
954
|
-
properties: {
|
|
955
|
-
startDate: {
|
|
956
|
-
type: "string",
|
|
957
|
-
description: "ISO 8601 start date for filtering (optional)"
|
|
958
|
-
},
|
|
959
|
-
endDate: {
|
|
960
|
-
type: "string",
|
|
961
|
-
description: "ISO 8601 end date for filtering (optional)"
|
|
962
|
-
},
|
|
963
|
-
entityType: {
|
|
964
|
-
type: "string",
|
|
965
|
-
description: "Filter by specific entity type (optional)"
|
|
966
|
-
},
|
|
967
|
-
tags: {
|
|
968
|
-
type: "array",
|
|
969
|
-
items: { type: "string" },
|
|
970
|
-
description: "Filter by tags (optional, case-insensitive)"
|
|
971
|
-
}
|
|
972
|
-
},
|
|
973
|
-
description: "Optional filter to export a subset of the graph"
|
|
974
|
-
}
|
|
975
|
-
},
|
|
976
|
-
required: ["format"],
|
|
977
|
-
additionalProperties: false,
|
|
978
|
-
},
|
|
979
|
-
}
|
|
980
|
-
],
|
|
981
|
-
};
|
|
982
|
-
});
|
|
983
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
984
|
-
const { name, arguments: args } = request.params;
|
|
985
|
-
if (name === "read_graph") {
|
|
986
|
-
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.readGraph(), null, 2) }] };
|
|
987
|
-
}
|
|
988
|
-
if (name === "get_graph_stats") {
|
|
989
|
-
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.getGraphStats(), null, 2) }] };
|
|
990
|
-
}
|
|
991
|
-
if (!args) {
|
|
992
|
-
throw new Error(`No arguments provided for tool: ${name}`);
|
|
993
|
-
}
|
|
994
|
-
switch (name) {
|
|
995
|
-
case "create_entities":
|
|
996
|
-
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.createEntities(args.entities), null, 2) }] };
|
|
997
|
-
case "create_relations":
|
|
998
|
-
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.createRelations(args.relations), null, 2) }] };
|
|
999
|
-
case "add_observations":
|
|
1000
|
-
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.addObservations(args.observations), null, 2) }] };
|
|
1001
|
-
case "delete_entities":
|
|
1002
|
-
await knowledgeGraphManager.deleteEntities(args.entityNames);
|
|
1003
|
-
return { content: [{ type: "text", text: "Entities deleted successfully" }] };
|
|
1004
|
-
case "delete_observations":
|
|
1005
|
-
await knowledgeGraphManager.deleteObservations(args.deletions);
|
|
1006
|
-
return { content: [{ type: "text", text: "Observations deleted successfully" }] };
|
|
1007
|
-
case "delete_relations":
|
|
1008
|
-
await knowledgeGraphManager.deleteRelations(args.relations);
|
|
1009
|
-
return { content: [{ type: "text", text: "Relations deleted successfully" }] };
|
|
1010
|
-
case "search_nodes":
|
|
1011
|
-
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.searchNodes(args.query, args.tags, args.minImportance, args.maxImportance), null, 2) }] };
|
|
1012
|
-
case "open_nodes":
|
|
1013
|
-
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.openNodes(args.names), null, 2) }] };
|
|
1014
|
-
case "search_by_date_range":
|
|
1015
|
-
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.searchByDateRange(args.startDate, args.endDate, args.entityType, args.tags), null, 2) }] };
|
|
1016
|
-
case "add_tags":
|
|
1017
|
-
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.addTags(args.entityName, args.tags), null, 2) }] };
|
|
1018
|
-
case "remove_tags":
|
|
1019
|
-
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.removeTags(args.entityName, args.tags), null, 2) }] };
|
|
1020
|
-
case "set_importance":
|
|
1021
|
-
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.setImportance(args.entityName, args.importance), null, 2) }] };
|
|
1022
|
-
case "export_graph":
|
|
1023
|
-
return { content: [{ type: "text", text: await knowledgeGraphManager.exportGraph(args.format, args.filter) }] };
|
|
1024
|
-
default:
|
|
1025
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
1026
|
-
}
|
|
1027
|
-
});
|
|
1028
45
|
async function main() {
|
|
1029
46
|
// Initialize memory file path with backward compatibility
|
|
1030
|
-
|
|
47
|
+
const memoryFilePath = await ensureMemoryFilePath();
|
|
1031
48
|
// Initialize knowledge graph manager with the memory file path
|
|
1032
|
-
knowledgeGraphManager = new KnowledgeGraphManager(
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
49
|
+
knowledgeGraphManager = new KnowledgeGraphManager(memoryFilePath);
|
|
50
|
+
// Initialize and start MCP server
|
|
51
|
+
const server = new MCPServer(knowledgeGraphManager);
|
|
52
|
+
await server.start();
|
|
1036
53
|
}
|
|
1037
54
|
main().catch((error) => {
|
|
1038
|
-
|
|
55
|
+
logger.error("Fatal error in main():", error);
|
|
1039
56
|
process.exit(1);
|
|
1040
57
|
});
|