collective-memory-mcp 0.1.0 → 0.3.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/README.md +104 -22
- package/package.json +12 -9
- package/src/models.js +89 -0
- package/src/server.js +793 -0
- package/src/storage.js +442 -0
- package/index.js +0 -127
package/src/server.js
ADDED
|
@@ -0,0 +1,793 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collective Memory MCP Server
|
|
3
|
+
* A persistent, graph-based memory system for AI agents.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
7
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
|
+
import {
|
|
9
|
+
CallToolRequestSchema,
|
|
10
|
+
ListToolsRequestSchema,
|
|
11
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
12
|
+
import { getStorage } from "./storage.js";
|
|
13
|
+
import { Entity, Relation, ENTITY_TYPES, RELATION_TYPES } from "./models.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Create and configure the MCP server
|
|
17
|
+
*/
|
|
18
|
+
function createServer() {
|
|
19
|
+
const storage = getStorage();
|
|
20
|
+
|
|
21
|
+
const server = new Server(
|
|
22
|
+
{
|
|
23
|
+
name: "collective-memory",
|
|
24
|
+
version: "0.1.0",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
capabilities: {
|
|
28
|
+
tools: {},
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* List available tools
|
|
35
|
+
*/
|
|
36
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
37
|
+
return {
|
|
38
|
+
tools: [
|
|
39
|
+
{
|
|
40
|
+
name: "create_entities",
|
|
41
|
+
description:
|
|
42
|
+
"Create multiple new entities in the knowledge graph. " +
|
|
43
|
+
"Silently ignores entities with duplicate names. Returns list of successfully created entities. " +
|
|
44
|
+
"Valid entity types: agent, task, structure, artifact, session",
|
|
45
|
+
inputSchema: {
|
|
46
|
+
type: "object",
|
|
47
|
+
properties: {
|
|
48
|
+
entities: {
|
|
49
|
+
type: "array",
|
|
50
|
+
items: {
|
|
51
|
+
type: "object",
|
|
52
|
+
properties: {
|
|
53
|
+
name: {
|
|
54
|
+
type: "string",
|
|
55
|
+
description: "Unique entity name",
|
|
56
|
+
},
|
|
57
|
+
entityType: {
|
|
58
|
+
type: "string",
|
|
59
|
+
enum: ENTITY_TYPES,
|
|
60
|
+
description: "Type of entity",
|
|
61
|
+
},
|
|
62
|
+
observations: {
|
|
63
|
+
type: "array",
|
|
64
|
+
items: { type: "string" },
|
|
65
|
+
description: "List of atomic facts about this entity",
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
required: ["name", "entityType"],
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
required: ["entities"],
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: "create_relations",
|
|
77
|
+
description:
|
|
78
|
+
"Create multiple directed relations between entities. " +
|
|
79
|
+
"Skips duplicate relations. Fails gracefully if source or target entity doesn't exist. " +
|
|
80
|
+
"Valid relation types: executed_by, created, modified, documented, depends_on, part_of, similar_to, uses, implements",
|
|
81
|
+
inputSchema: {
|
|
82
|
+
type: "object",
|
|
83
|
+
properties: {
|
|
84
|
+
relations: {
|
|
85
|
+
type: "array",
|
|
86
|
+
items: {
|
|
87
|
+
type: "object",
|
|
88
|
+
properties: {
|
|
89
|
+
from: {
|
|
90
|
+
type: "string",
|
|
91
|
+
description: "Source entity name",
|
|
92
|
+
},
|
|
93
|
+
to: {
|
|
94
|
+
type: "string",
|
|
95
|
+
description: "Target entity name",
|
|
96
|
+
},
|
|
97
|
+
relationType: {
|
|
98
|
+
type: "string",
|
|
99
|
+
enum: RELATION_TYPES,
|
|
100
|
+
description: "Type of relation",
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
required: ["from", "to", "relationType"],
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
required: ["relations"],
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: "add_observations",
|
|
112
|
+
description:
|
|
113
|
+
"Add new observations to existing entities. " +
|
|
114
|
+
"Appends observations to existing list. Fails if entity doesn't exist. " +
|
|
115
|
+
"Returns count of added observations per entity.",
|
|
116
|
+
inputSchema: {
|
|
117
|
+
type: "object",
|
|
118
|
+
properties: {
|
|
119
|
+
observations: {
|
|
120
|
+
type: "array",
|
|
121
|
+
items: {
|
|
122
|
+
type: "object",
|
|
123
|
+
properties: {
|
|
124
|
+
entityName: {
|
|
125
|
+
type: "string",
|
|
126
|
+
description: "Name of the entity",
|
|
127
|
+
},
|
|
128
|
+
contents: {
|
|
129
|
+
type: "array",
|
|
130
|
+
items: { type: "string" },
|
|
131
|
+
description: "Observations to add",
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
required: ["entityName", "contents"],
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
required: ["observations"],
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: "delete_entities",
|
|
143
|
+
description:
|
|
144
|
+
"Remove entities and cascade delete their relations. " +
|
|
145
|
+
"Removes all relations where entity is source or target. " +
|
|
146
|
+
"Silent operation if entity doesn't exist. Irreversible operation.",
|
|
147
|
+
inputSchema: {
|
|
148
|
+
type: "object",
|
|
149
|
+
properties: {
|
|
150
|
+
entityNames: {
|
|
151
|
+
type: "array",
|
|
152
|
+
items: { type: "string" },
|
|
153
|
+
description: "Names of entities to delete",
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
required: ["entityNames"],
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
name: "delete_observations",
|
|
161
|
+
description:
|
|
162
|
+
"Remove specific observations from entities. " +
|
|
163
|
+
"Removes exact string matches. Silent if observation doesn't exist. " +
|
|
164
|
+
"Preserves other observations on the entity.",
|
|
165
|
+
inputSchema: {
|
|
166
|
+
type: "object",
|
|
167
|
+
properties: {
|
|
168
|
+
deletions: {
|
|
169
|
+
type: "array",
|
|
170
|
+
items: {
|
|
171
|
+
type: "object",
|
|
172
|
+
properties: {
|
|
173
|
+
entityName: {
|
|
174
|
+
type: "string",
|
|
175
|
+
description: "Name of the entity",
|
|
176
|
+
},
|
|
177
|
+
observations: {
|
|
178
|
+
type: "array",
|
|
179
|
+
items: { type: "string" },
|
|
180
|
+
description: "Observations to remove (exact matches)",
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
required: ["entityName", "observations"],
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
required: ["deletions"],
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
name: "delete_relations",
|
|
192
|
+
description:
|
|
193
|
+
"Remove specific relations from the graph. " +
|
|
194
|
+
"Requires exact match of all three fields. Silent if relation doesn't exist. " +
|
|
195
|
+
"Does not affect entities, only the connection.",
|
|
196
|
+
inputSchema: {
|
|
197
|
+
type: "object",
|
|
198
|
+
properties: {
|
|
199
|
+
relations: {
|
|
200
|
+
type: "array",
|
|
201
|
+
items: {
|
|
202
|
+
type: "object",
|
|
203
|
+
properties: {
|
|
204
|
+
from: { type: "string", description: "Source entity name" },
|
|
205
|
+
to: { type: "string", description: "Target entity name" },
|
|
206
|
+
relationType: { type: "string", description: "Type of relation" },
|
|
207
|
+
},
|
|
208
|
+
required: ["from", "to", "relationType"],
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
required: ["relations"],
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
name: "read_graph",
|
|
217
|
+
description:
|
|
218
|
+
"Read the entire knowledge graph. " +
|
|
219
|
+
"Returns all entities and relations. Useful for debugging, inspection, " +
|
|
220
|
+
"generating graph visualizations, and exporting memory for analysis.",
|
|
221
|
+
inputSchema: {
|
|
222
|
+
type: "object",
|
|
223
|
+
properties: {},
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
name: "search_collective_memory",
|
|
228
|
+
description:
|
|
229
|
+
"Search for relevant past work based on a natural language query. " +
|
|
230
|
+
"Search scope includes entity names, entity types, all observation content, and relation types. " +
|
|
231
|
+
"Returns entities with their immediate relations, ordered by relevance.",
|
|
232
|
+
inputSchema: {
|
|
233
|
+
type: "object",
|
|
234
|
+
properties: {
|
|
235
|
+
query: {
|
|
236
|
+
type: "string",
|
|
237
|
+
description: "Natural language search query",
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
required: ["query"],
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
name: "open_nodes",
|
|
245
|
+
description:
|
|
246
|
+
"Retrieve specific nodes by exact name match. " +
|
|
247
|
+
"Returns only requested entities and includes relations between returned entities. " +
|
|
248
|
+
"Silently skips non-existent nodes.",
|
|
249
|
+
inputSchema: {
|
|
250
|
+
type: "object",
|
|
251
|
+
properties: {
|
|
252
|
+
names: {
|
|
253
|
+
type: "array",
|
|
254
|
+
items: { type: "string" },
|
|
255
|
+
description: "Entity names to retrieve",
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
required: ["names"],
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
name: "record_task_completion",
|
|
263
|
+
description:
|
|
264
|
+
"[Primary tool for agents] Document a completed task with full context. " +
|
|
265
|
+
"Automatically creates the task entity, agent entity if needed, all artifact entities, " +
|
|
266
|
+
"and establishes all relevant relations (executed_by, created, modified, part_of). " +
|
|
267
|
+
"Returns the complete task node with all relations.",
|
|
268
|
+
inputSchema: {
|
|
269
|
+
type: "object",
|
|
270
|
+
properties: {
|
|
271
|
+
agent_name: {
|
|
272
|
+
type: "string",
|
|
273
|
+
description: "Name of the agent completing the task",
|
|
274
|
+
},
|
|
275
|
+
task_name: {
|
|
276
|
+
type: "string",
|
|
277
|
+
description: "Unique name for the task",
|
|
278
|
+
},
|
|
279
|
+
task_type: {
|
|
280
|
+
type: "string",
|
|
281
|
+
description: "Type of task (e.g., implementation, debugging, refactoring)",
|
|
282
|
+
},
|
|
283
|
+
description: {
|
|
284
|
+
type: "string",
|
|
285
|
+
description: "High-level description of what was accomplished",
|
|
286
|
+
},
|
|
287
|
+
observations: {
|
|
288
|
+
type: "array",
|
|
289
|
+
items: { type: "string" },
|
|
290
|
+
description: "Detailed observations about the task execution",
|
|
291
|
+
},
|
|
292
|
+
created_artifacts: {
|
|
293
|
+
type: "array",
|
|
294
|
+
items: {
|
|
295
|
+
type: "object",
|
|
296
|
+
properties: {
|
|
297
|
+
name: { type: "string" },
|
|
298
|
+
observations: {
|
|
299
|
+
type: "array",
|
|
300
|
+
items: { type: "string" },
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
required: ["name"],
|
|
304
|
+
},
|
|
305
|
+
description: "Artifacts created during the task",
|
|
306
|
+
},
|
|
307
|
+
modified_structures: {
|
|
308
|
+
type: "array",
|
|
309
|
+
items: {
|
|
310
|
+
type: "object",
|
|
311
|
+
properties: {
|
|
312
|
+
name: { type: "string" },
|
|
313
|
+
observations: {
|
|
314
|
+
type: "array",
|
|
315
|
+
items: { type: "string" },
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
required: ["name"],
|
|
319
|
+
},
|
|
320
|
+
description: "Structures modified during the task",
|
|
321
|
+
},
|
|
322
|
+
session_id: {
|
|
323
|
+
type: "string",
|
|
324
|
+
description: "Optional session identifier",
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
required: ["agent_name", "task_name"],
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
name: "find_similar_procedures",
|
|
332
|
+
description:
|
|
333
|
+
"Search for tasks that match a procedural query and return their full implementation details. " +
|
|
334
|
+
"Searches task entities and observations. Returns complete subgraphs (tasks + related artifacts + structures). " +
|
|
335
|
+
"Sorted by relevance and recency.",
|
|
336
|
+
inputSchema: {
|
|
337
|
+
type: "object",
|
|
338
|
+
properties: {
|
|
339
|
+
query: {
|
|
340
|
+
type: "string",
|
|
341
|
+
description: "Procedural search query (e.g., 'authentication implementation')",
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
required: ["query"],
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
],
|
|
348
|
+
};
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Handle tool calls
|
|
353
|
+
*/
|
|
354
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
355
|
+
const { name, arguments: args } = request.params;
|
|
356
|
+
|
|
357
|
+
try {
|
|
358
|
+
switch (name) {
|
|
359
|
+
case "create_entities":
|
|
360
|
+
return { content: [{ type: "text", text: JSON.stringify(createEntities(args), null, 2) }] };
|
|
361
|
+
|
|
362
|
+
case "create_relations":
|
|
363
|
+
return { content: [{ type: "text", text: JSON.stringify(createRelations(args), null, 2) }] };
|
|
364
|
+
|
|
365
|
+
case "add_observations":
|
|
366
|
+
return { content: [{ type: "text", text: JSON.stringify(addObservations(args), null, 2) }] };
|
|
367
|
+
|
|
368
|
+
case "delete_entities":
|
|
369
|
+
return { content: [{ type: "text", text: JSON.stringify(deleteEntities(args), null, 2) }] };
|
|
370
|
+
|
|
371
|
+
case "delete_observations":
|
|
372
|
+
return { content: [{ type: "text", text: JSON.stringify(deleteObservations(args), null, 2) }] };
|
|
373
|
+
|
|
374
|
+
case "delete_relations":
|
|
375
|
+
return { content: [{ type: "text", text: JSON.stringify(deleteRelations(args), null, 2) }] };
|
|
376
|
+
|
|
377
|
+
case "read_graph":
|
|
378
|
+
return { content: [{ type: "text", text: JSON.stringify(readGraph(), null, 2) }] };
|
|
379
|
+
|
|
380
|
+
case "search_collective_memory":
|
|
381
|
+
return { content: [{ type: "text", text: JSON.stringify(searchCollectiveMemory(args), null, 2) }] };
|
|
382
|
+
|
|
383
|
+
case "open_nodes":
|
|
384
|
+
return { content: [{ type: "text", text: JSON.stringify(openNodes(args), null, 2) }] };
|
|
385
|
+
|
|
386
|
+
case "record_task_completion":
|
|
387
|
+
return { content: [{ type: "text", text: JSON.stringify(recordTaskCompletion(args), null, 2) }] };
|
|
388
|
+
|
|
389
|
+
case "find_similar_procedures":
|
|
390
|
+
return { content: [{ type: "text", text: JSON.stringify(findSimilarProcedures(args), null, 2) }] };
|
|
391
|
+
|
|
392
|
+
default:
|
|
393
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
394
|
+
}
|
|
395
|
+
} catch (error) {
|
|
396
|
+
return {
|
|
397
|
+
content: [
|
|
398
|
+
{
|
|
399
|
+
type: "text",
|
|
400
|
+
text: JSON.stringify({
|
|
401
|
+
status: "error",
|
|
402
|
+
message: error.message,
|
|
403
|
+
}),
|
|
404
|
+
},
|
|
405
|
+
],
|
|
406
|
+
isError: true,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
// ========== Tool Implementations ==========
|
|
412
|
+
|
|
413
|
+
function createEntities({ entities = [] }) {
|
|
414
|
+
const created = [];
|
|
415
|
+
|
|
416
|
+
for (const data of entities) {
|
|
417
|
+
const { name, entityType, observations = [] } = data;
|
|
418
|
+
|
|
419
|
+
if (!name || !entityType) continue;
|
|
420
|
+
if (!ENTITY_TYPES.includes(entityType)) continue;
|
|
421
|
+
|
|
422
|
+
const entity = new Entity({ name, entityType, observations });
|
|
423
|
+
if (storage.createEntity(entity)) {
|
|
424
|
+
created.push(name);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return { status: "success", created_entities: created, count: created.length };
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function createRelations({ relations = [] }) {
|
|
432
|
+
const created = [];
|
|
433
|
+
|
|
434
|
+
for (const data of relations) {
|
|
435
|
+
const { from, to, relationType } = data;
|
|
436
|
+
|
|
437
|
+
if (!from || !to || !relationType) continue;
|
|
438
|
+
|
|
439
|
+
// Verify entities exist
|
|
440
|
+
if (!storage.entityExists(from) || !storage.entityExists(to)) continue;
|
|
441
|
+
|
|
442
|
+
const relation = new Relation({ from, to, relationType });
|
|
443
|
+
if (storage.createRelation(relation)) {
|
|
444
|
+
created.push(`${from} -> ${to} (${relationType})`);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return { status: "success", created_relations: created, count: created.length };
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function addObservations({ observations = [] }) {
|
|
452
|
+
const results = {};
|
|
453
|
+
|
|
454
|
+
for (const data of observations) {
|
|
455
|
+
const { entityName, contents = [] } = data;
|
|
456
|
+
|
|
457
|
+
if (!entityName) {
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const entity = storage.getEntity(entityName);
|
|
462
|
+
if (!entity) {
|
|
463
|
+
results[entityName] = { error: "Entity not found" };
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Add new observations (avoid duplicates)
|
|
468
|
+
const newObs = contents.filter((o) => !entity.observations.includes(o));
|
|
469
|
+
entity.observations.push(...newObs);
|
|
470
|
+
|
|
471
|
+
if (storage.updateEntity(entityName, { observations: entity.observations })) {
|
|
472
|
+
results[entityName] = { added: newObs.length };
|
|
473
|
+
} else {
|
|
474
|
+
results[entityName] = { error: "Failed to update" };
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return { status: "success", results };
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function deleteEntities({ entityNames = [] }) {
|
|
482
|
+
const count = storage.deleteEntities(entityNames);
|
|
483
|
+
return { status: "success", deleted_count: count };
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function deleteObservations({ deletions = [] }) {
|
|
487
|
+
const results = {};
|
|
488
|
+
|
|
489
|
+
for (const data of deletions) {
|
|
490
|
+
const { entityName, observations: toRemove = [] } = data;
|
|
491
|
+
|
|
492
|
+
const entity = storage.getEntity(entityName);
|
|
493
|
+
if (!entity) {
|
|
494
|
+
results[entityName] = { error: "Entity not found" };
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Remove exact matches
|
|
499
|
+
const originalCount = entity.observations.length;
|
|
500
|
+
entity.observations = entity.observations.filter((o) => !toRemove.includes(o));
|
|
501
|
+
const removedCount = originalCount - entity.observations.length;
|
|
502
|
+
|
|
503
|
+
storage.updateEntity(entityName, { observations: entity.observations });
|
|
504
|
+
results[entityName] = { removed: removedCount };
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
return { status: "success", results };
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function deleteRelations({ relations = [] }) {
|
|
511
|
+
const toDelete = relations.map((r) => [r.from, r.to, r.relationType]);
|
|
512
|
+
const count = storage.deleteRelations(toDelete);
|
|
513
|
+
return { status: "success", deleted_count: count };
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function readGraph() {
|
|
517
|
+
const entities = storage.getAllEntities().map((e) => e.toJSON());
|
|
518
|
+
const relations = storage.getAllRelations().map((r) => r.toJSON());
|
|
519
|
+
|
|
520
|
+
return {
|
|
521
|
+
entities,
|
|
522
|
+
relations,
|
|
523
|
+
summary: {
|
|
524
|
+
entity_count: entities.length,
|
|
525
|
+
relation_count: relations.length,
|
|
526
|
+
},
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function searchCollectiveMemory({ query = "" }) {
|
|
531
|
+
const matchingEntities = storage.searchEntities(query);
|
|
532
|
+
|
|
533
|
+
const results = matchingEntities.map((entity) => {
|
|
534
|
+
const related = storage.getRelatedEntities(entity.name);
|
|
535
|
+
return {
|
|
536
|
+
name: entity.name,
|
|
537
|
+
entityType: entity.entityType,
|
|
538
|
+
observations: entity.observations,
|
|
539
|
+
createdAt: entity.createdAt,
|
|
540
|
+
related_entities: related.connected.map((e) => ({
|
|
541
|
+
name: e.name,
|
|
542
|
+
entityType: e.entityType,
|
|
543
|
+
})),
|
|
544
|
+
};
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
return { matching_entities: results, count: results.length };
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function openNodes({ names = [] }) {
|
|
551
|
+
const entities = [];
|
|
552
|
+
const entityNamesSet = new Set();
|
|
553
|
+
|
|
554
|
+
for (const name of names) {
|
|
555
|
+
const entity = storage.getEntity(name);
|
|
556
|
+
if (entity) {
|
|
557
|
+
entities.push(entity.toJSON());
|
|
558
|
+
entityNamesSet.add(name);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Get relations between requested entities
|
|
563
|
+
const allRelations = storage.getAllRelations();
|
|
564
|
+
const filteredRelations = allRelations
|
|
565
|
+
.filter((r) => entityNamesSet.has(r.from) && entityNamesSet.has(r.to))
|
|
566
|
+
.map((r) => r.toJSON());
|
|
567
|
+
|
|
568
|
+
return {
|
|
569
|
+
entities,
|
|
570
|
+
relations_between_requested_nodes: filteredRelations,
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function recordTaskCompletion({
|
|
575
|
+
agent_name,
|
|
576
|
+
task_name,
|
|
577
|
+
task_type = "task",
|
|
578
|
+
description = "",
|
|
579
|
+
observations = [],
|
|
580
|
+
created_artifacts = [],
|
|
581
|
+
modified_structures = [],
|
|
582
|
+
session_id = null,
|
|
583
|
+
}) {
|
|
584
|
+
if (!agent_name || !task_name) {
|
|
585
|
+
return {
|
|
586
|
+
status: "error",
|
|
587
|
+
message: "agent_name and task_name are required",
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// Ensure agent entity exists
|
|
592
|
+
let agentEntity = storage.getEntity(agent_name);
|
|
593
|
+
if (!agentEntity) {
|
|
594
|
+
agentEntity = new Entity({
|
|
595
|
+
name: agent_name,
|
|
596
|
+
entityType: "agent",
|
|
597
|
+
observations: [`Created during task ${task_name}`],
|
|
598
|
+
});
|
|
599
|
+
storage.createEntity(agentEntity);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// Create task entity
|
|
603
|
+
const taskObservations = [...observations];
|
|
604
|
+
if (description) {
|
|
605
|
+
taskObservations.unshift(`Description: ${description}`);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const taskEntity = new Entity({
|
|
609
|
+
name: task_name,
|
|
610
|
+
entityType: "task",
|
|
611
|
+
observations: taskObservations,
|
|
612
|
+
metadata: { task_type },
|
|
613
|
+
});
|
|
614
|
+
storage.createEntity(taskEntity);
|
|
615
|
+
|
|
616
|
+
// Create executed_by relation
|
|
617
|
+
storage.createRelation(
|
|
618
|
+
new Relation({
|
|
619
|
+
from: task_name,
|
|
620
|
+
to: agent_name,
|
|
621
|
+
relationType: "executed_by",
|
|
622
|
+
})
|
|
623
|
+
);
|
|
624
|
+
|
|
625
|
+
// Handle session
|
|
626
|
+
if (session_id) {
|
|
627
|
+
let sessionEntity = storage.getEntity(session_id);
|
|
628
|
+
if (!sessionEntity) {
|
|
629
|
+
sessionEntity = new Entity({
|
|
630
|
+
name: session_id,
|
|
631
|
+
entityType: "session",
|
|
632
|
+
observations: [`Started for task ${task_name}`],
|
|
633
|
+
});
|
|
634
|
+
storage.createEntity(sessionEntity);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
storage.createRelation(
|
|
638
|
+
new Relation({
|
|
639
|
+
from: task_name,
|
|
640
|
+
to: session_id,
|
|
641
|
+
relationType: "part_of",
|
|
642
|
+
})
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Create artifacts
|
|
647
|
+
const createdArtifactNames = [];
|
|
648
|
+
for (const artifactData of created_artifacts) {
|
|
649
|
+
const { name: artifactName, observations: artifactObs = [] } = artifactData;
|
|
650
|
+
if (!artifactName) continue;
|
|
651
|
+
|
|
652
|
+
let artifactEntity;
|
|
653
|
+
if (!storage.entityExists(artifactName)) {
|
|
654
|
+
artifactEntity = new Entity({
|
|
655
|
+
name: artifactName,
|
|
656
|
+
entityType: "artifact",
|
|
657
|
+
observations: artifactObs,
|
|
658
|
+
});
|
|
659
|
+
storage.createEntity(artifactEntity);
|
|
660
|
+
} else {
|
|
661
|
+
artifactEntity = storage.getEntity(artifactName);
|
|
662
|
+
const newObs = artifactObs.filter((o) => !artifactEntity.observations.includes(o));
|
|
663
|
+
artifactEntity.observations.push(...newObs);
|
|
664
|
+
storage.updateEntity(artifactName, { observations: artifactEntity.observations });
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
storage.createRelation(
|
|
668
|
+
new Relation({
|
|
669
|
+
from: task_name,
|
|
670
|
+
to: artifactName,
|
|
671
|
+
relationType: "created",
|
|
672
|
+
})
|
|
673
|
+
);
|
|
674
|
+
createdArtifactNames.push(artifactName);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// Handle modified structures
|
|
678
|
+
for (const structureData of modified_structures) {
|
|
679
|
+
const { name: structureName, observations: structureObs = [] } = structureData;
|
|
680
|
+
if (!structureName) continue;
|
|
681
|
+
|
|
682
|
+
let relationType;
|
|
683
|
+
if (!storage.entityExists(structureName)) {
|
|
684
|
+
const structureEntity = new Entity({
|
|
685
|
+
name: structureName,
|
|
686
|
+
entityType: "structure",
|
|
687
|
+
observations: structureObs,
|
|
688
|
+
});
|
|
689
|
+
storage.createEntity(structureEntity);
|
|
690
|
+
relationType = "documented";
|
|
691
|
+
} else {
|
|
692
|
+
const existing = storage.getEntity(structureName);
|
|
693
|
+
const newObs = structureObs.filter((o) => !existing.observations.includes(o));
|
|
694
|
+
if (newObs.length > 0) {
|
|
695
|
+
existing.observations.push(...newObs);
|
|
696
|
+
storage.updateEntity(structureName, { observations: existing.observations });
|
|
697
|
+
}
|
|
698
|
+
relationType = "modified";
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
storage.createRelation(
|
|
702
|
+
new Relation({
|
|
703
|
+
from: task_name,
|
|
704
|
+
to: structureName,
|
|
705
|
+
relationType,
|
|
706
|
+
})
|
|
707
|
+
);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// Build response
|
|
711
|
+
const taskRelations = storage.getRelations({ fromEntity: task_name });
|
|
712
|
+
|
|
713
|
+
return {
|
|
714
|
+
status: "success",
|
|
715
|
+
task: taskEntity.toJSON(),
|
|
716
|
+
relations: taskRelations.map((r) => r.toJSON()),
|
|
717
|
+
created_artifacts: createdArtifactNames,
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
function findSimilarProcedures({ query = "" }) {
|
|
722
|
+
const searchQuery = query.toLowerCase();
|
|
723
|
+
|
|
724
|
+
// Search for matching task entities
|
|
725
|
+
const allEntities = storage.getAllEntities();
|
|
726
|
+
const matchingTasks = allEntities.filter(
|
|
727
|
+
(e) =>
|
|
728
|
+
e.entityType === "task" &&
|
|
729
|
+
(e.name.toLowerCase().includes(searchQuery) ||
|
|
730
|
+
e.observations.some((obs) => obs.toLowerCase().includes(searchQuery)))
|
|
731
|
+
);
|
|
732
|
+
|
|
733
|
+
const results = [];
|
|
734
|
+
for (const task of matchingTasks) {
|
|
735
|
+
const taskRelations = storage.getRelations({ fromEntity: task.name });
|
|
736
|
+
|
|
737
|
+
const artifacts = [];
|
|
738
|
+
const structures = [];
|
|
739
|
+
|
|
740
|
+
for (const rel of taskRelations) {
|
|
741
|
+
if (rel.relationType === "created" || rel.relationType === "uses") {
|
|
742
|
+
const artifact = storage.getEntity(rel.to);
|
|
743
|
+
if (artifact) artifacts.push(artifact.toJSON());
|
|
744
|
+
} else if (rel.relationType === "modified" || rel.relationType === "documented") {
|
|
745
|
+
const structure = storage.getEntity(rel.to);
|
|
746
|
+
if (structure) structures.push(structure.toJSON());
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// Get execution context (agent info)
|
|
751
|
+
let agentName = null;
|
|
752
|
+
for (const rel of taskRelations) {
|
|
753
|
+
if (rel.relationType === "executed_by") {
|
|
754
|
+
agentName = rel.to;
|
|
755
|
+
break;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
const executionContext = {};
|
|
760
|
+
if (agentName) {
|
|
761
|
+
const agent = storage.getEntity(agentName);
|
|
762
|
+
if (agent) {
|
|
763
|
+
executionContext.agent = agent.toJSON();
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
results.push({
|
|
768
|
+
task: task.toJSON(),
|
|
769
|
+
artifacts,
|
|
770
|
+
structures,
|
|
771
|
+
execution_context: executionContext,
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
return { similar_tasks: results, count: results.length };
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
return server;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
/**
|
|
782
|
+
* Main entry point
|
|
783
|
+
*/
|
|
784
|
+
async function main() {
|
|
785
|
+
const server = createServer();
|
|
786
|
+
const transport = new StdioServerTransport();
|
|
787
|
+
await server.connect(transport);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
main().catch((err) => {
|
|
791
|
+
console.error("Fatal error:", err);
|
|
792
|
+
process.exit(1);
|
|
793
|
+
});
|