@199-bio/engram 0.1.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/.env.example +19 -0
- package/LICENSE +21 -0
- package/LIVING_PLAN.md +180 -0
- package/PLAN.md +514 -0
- package/README.md +304 -0
- package/dist/graph/extractor.d.ts.map +1 -0
- package/dist/graph/index.d.ts.map +1 -0
- package/dist/graph/knowledge-graph.d.ts.map +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +473 -0
- package/dist/retrieval/colbert.d.ts.map +1 -0
- package/dist/retrieval/hybrid.d.ts.map +1 -0
- package/dist/retrieval/index.d.ts.map +1 -0
- package/dist/storage/database.d.ts.map +1 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/package.json +62 -0
- package/src/graph/extractor.ts +441 -0
- package/src/graph/index.ts +2 -0
- package/src/graph/knowledge-graph.ts +263 -0
- package/src/index.ts +558 -0
- package/src/retrieval/colbert-bridge.py +222 -0
- package/src/retrieval/colbert.ts +317 -0
- package/src/retrieval/hybrid.ts +218 -0
- package/src/retrieval/index.ts +2 -0
- package/src/storage/database.ts +527 -0
- package/src/storage/index.ts +1 -0
- package/tests/test-interactive.js +218 -0
- package/tests/test-mcp.sh +81 -0
- package/tsconfig.json +20 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Engram - High-quality personal memory for AI assistants
|
|
4
|
+
*
|
|
5
|
+
* Local-first MCP server with ColBERT + BM25 hybrid search
|
|
6
|
+
* and a lightweight knowledge graph.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
10
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
11
|
+
import {
|
|
12
|
+
CallToolRequestSchema,
|
|
13
|
+
ListToolsRequestSchema,
|
|
14
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
15
|
+
import path from "path";
|
|
16
|
+
import os from "os";
|
|
17
|
+
|
|
18
|
+
import { EngramDatabase } from "./storage/database.js";
|
|
19
|
+
import { KnowledgeGraph } from "./graph/knowledge-graph.js";
|
|
20
|
+
import { createRetriever } from "./retrieval/colbert.js";
|
|
21
|
+
import { HybridSearch } from "./retrieval/hybrid.js";
|
|
22
|
+
|
|
23
|
+
// ============ Configuration ============
|
|
24
|
+
|
|
25
|
+
const DB_PATH = process.env.ENGRAM_DB_PATH
|
|
26
|
+
? path.resolve(process.env.ENGRAM_DB_PATH.replace("~", os.homedir()))
|
|
27
|
+
: path.join(os.homedir(), ".engram");
|
|
28
|
+
|
|
29
|
+
const DB_FILE = path.join(DB_PATH, "engram.db");
|
|
30
|
+
|
|
31
|
+
// ============ Initialize Components ============
|
|
32
|
+
|
|
33
|
+
let db: EngramDatabase;
|
|
34
|
+
let graph: KnowledgeGraph;
|
|
35
|
+
let search: HybridSearch;
|
|
36
|
+
|
|
37
|
+
async function initialize(): Promise<void> {
|
|
38
|
+
console.error(`[Engram] Initializing with database at ${DB_FILE}`);
|
|
39
|
+
|
|
40
|
+
db = new EngramDatabase(DB_FILE);
|
|
41
|
+
graph = new KnowledgeGraph(db);
|
|
42
|
+
|
|
43
|
+
const retriever = await createRetriever(DB_PATH);
|
|
44
|
+
search = new HybridSearch(db, graph, retriever);
|
|
45
|
+
|
|
46
|
+
// Rebuild index with existing memories
|
|
47
|
+
const stats = db.getStats();
|
|
48
|
+
if (stats.memories > 0) {
|
|
49
|
+
console.error(`[Engram] Indexing ${stats.memories} existing memories...`);
|
|
50
|
+
await search.rebuildIndex();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
console.error(`[Engram] Ready. Stats: ${JSON.stringify(stats)}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ============ MCP Server ============
|
|
57
|
+
|
|
58
|
+
const server = new Server(
|
|
59
|
+
{
|
|
60
|
+
name: "engram",
|
|
61
|
+
version: "0.1.0",
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
capabilities: {
|
|
65
|
+
tools: {},
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
// Tool definitions
|
|
71
|
+
const TOOLS = [
|
|
72
|
+
{
|
|
73
|
+
name: "remember",
|
|
74
|
+
description:
|
|
75
|
+
"Store a new memory. Automatically extracts entities and relationships for the knowledge graph.",
|
|
76
|
+
inputSchema: {
|
|
77
|
+
type: "object" as const,
|
|
78
|
+
properties: {
|
|
79
|
+
content: {
|
|
80
|
+
type: "string",
|
|
81
|
+
description: "The memory content to store",
|
|
82
|
+
},
|
|
83
|
+
source: {
|
|
84
|
+
type: "string",
|
|
85
|
+
description: "Source of the memory (e.g., 'conversation', 'note', 'import')",
|
|
86
|
+
default: "conversation",
|
|
87
|
+
},
|
|
88
|
+
importance: {
|
|
89
|
+
type: "number",
|
|
90
|
+
description: "Importance score from 0 to 1 (higher = more important)",
|
|
91
|
+
minimum: 0,
|
|
92
|
+
maximum: 1,
|
|
93
|
+
default: 0.5,
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
required: ["content"],
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: "recall",
|
|
101
|
+
description:
|
|
102
|
+
"Retrieve relevant memories using hybrid search (BM25 keywords + semantic + knowledge graph). Returns the most relevant memories for a query.",
|
|
103
|
+
inputSchema: {
|
|
104
|
+
type: "object" as const,
|
|
105
|
+
properties: {
|
|
106
|
+
query: {
|
|
107
|
+
type: "string",
|
|
108
|
+
description: "What to search for - can be a question, keywords, or natural language",
|
|
109
|
+
},
|
|
110
|
+
limit: {
|
|
111
|
+
type: "number",
|
|
112
|
+
description: "Maximum number of memories to return",
|
|
113
|
+
default: 5,
|
|
114
|
+
},
|
|
115
|
+
include_graph: {
|
|
116
|
+
type: "boolean",
|
|
117
|
+
description: "Whether to expand search using knowledge graph connections",
|
|
118
|
+
default: true,
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
required: ["query"],
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: "forget",
|
|
126
|
+
description: "Remove a specific memory by its ID",
|
|
127
|
+
inputSchema: {
|
|
128
|
+
type: "object" as const,
|
|
129
|
+
properties: {
|
|
130
|
+
id: {
|
|
131
|
+
type: "string",
|
|
132
|
+
description: "The memory ID to remove",
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
required: ["id"],
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: "create_entity",
|
|
140
|
+
description: "Create a new entity in the knowledge graph (person, place, concept, etc.)",
|
|
141
|
+
inputSchema: {
|
|
142
|
+
type: "object" as const,
|
|
143
|
+
properties: {
|
|
144
|
+
name: {
|
|
145
|
+
type: "string",
|
|
146
|
+
description: "Entity name (e.g., 'John', 'Paris', 'Machine Learning')",
|
|
147
|
+
},
|
|
148
|
+
type: {
|
|
149
|
+
type: "string",
|
|
150
|
+
enum: ["person", "place", "concept", "event", "organization"],
|
|
151
|
+
description: "Type of entity",
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
required: ["name", "type"],
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: "observe",
|
|
159
|
+
description: "Add an observation or fact about an entity",
|
|
160
|
+
inputSchema: {
|
|
161
|
+
type: "object" as const,
|
|
162
|
+
properties: {
|
|
163
|
+
entity: {
|
|
164
|
+
type: "string",
|
|
165
|
+
description: "Entity name to add observation to",
|
|
166
|
+
},
|
|
167
|
+
observation: {
|
|
168
|
+
type: "string",
|
|
169
|
+
description: "The fact or observation to record",
|
|
170
|
+
},
|
|
171
|
+
confidence: {
|
|
172
|
+
type: "number",
|
|
173
|
+
description: "Confidence in this observation (0-1)",
|
|
174
|
+
default: 1.0,
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
required: ["entity", "observation"],
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
name: "relate",
|
|
182
|
+
description: "Create a relationship between two entities in the knowledge graph",
|
|
183
|
+
inputSchema: {
|
|
184
|
+
type: "object" as const,
|
|
185
|
+
properties: {
|
|
186
|
+
from: {
|
|
187
|
+
type: "string",
|
|
188
|
+
description: "Source entity name",
|
|
189
|
+
},
|
|
190
|
+
to: {
|
|
191
|
+
type: "string",
|
|
192
|
+
description: "Target entity name",
|
|
193
|
+
},
|
|
194
|
+
relation: {
|
|
195
|
+
type: "string",
|
|
196
|
+
description: "Type of relationship (e.g., 'sibling', 'works_at', 'knows', 'located_in')",
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
required: ["from", "to", "relation"],
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
name: "query_entity",
|
|
204
|
+
description: "Get detailed information about an entity including all observations and relationships",
|
|
205
|
+
inputSchema: {
|
|
206
|
+
type: "object" as const,
|
|
207
|
+
properties: {
|
|
208
|
+
entity: {
|
|
209
|
+
type: "string",
|
|
210
|
+
description: "Entity name to query",
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
required: ["entity"],
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
name: "list_entities",
|
|
218
|
+
description: "List all entities, optionally filtered by type",
|
|
219
|
+
inputSchema: {
|
|
220
|
+
type: "object" as const,
|
|
221
|
+
properties: {
|
|
222
|
+
type: {
|
|
223
|
+
type: "string",
|
|
224
|
+
enum: ["person", "place", "concept", "event", "organization"],
|
|
225
|
+
description: "Filter by entity type (optional)",
|
|
226
|
+
},
|
|
227
|
+
limit: {
|
|
228
|
+
type: "number",
|
|
229
|
+
description: "Maximum number of entities to return",
|
|
230
|
+
default: 50,
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
name: "stats",
|
|
237
|
+
description: "Get memory statistics (counts of memories, entities, relations, observations)",
|
|
238
|
+
inputSchema: {
|
|
239
|
+
type: "object" as const,
|
|
240
|
+
properties: {},
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
];
|
|
244
|
+
|
|
245
|
+
// List available tools
|
|
246
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
247
|
+
tools: TOOLS,
|
|
248
|
+
}));
|
|
249
|
+
|
|
250
|
+
// Handle tool calls
|
|
251
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
252
|
+
const { name, arguments: args } = request.params;
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
switch (name) {
|
|
256
|
+
case "remember": {
|
|
257
|
+
const { content, source = "conversation", importance = 0.5 } = args as {
|
|
258
|
+
content: string;
|
|
259
|
+
source?: string;
|
|
260
|
+
importance?: number;
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// Create memory
|
|
264
|
+
const memory = db.createMemory(content, source, importance);
|
|
265
|
+
|
|
266
|
+
// Index for semantic search
|
|
267
|
+
await search.indexMemory(memory);
|
|
268
|
+
|
|
269
|
+
// Extract and store entities/relationships
|
|
270
|
+
const { entities, observations } = graph.extractAndStore(content, memory.id);
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
content: [
|
|
274
|
+
{
|
|
275
|
+
type: "text" as const,
|
|
276
|
+
text: JSON.stringify({
|
|
277
|
+
success: true,
|
|
278
|
+
memory_id: memory.id,
|
|
279
|
+
entities_extracted: entities.map((e) => e.name),
|
|
280
|
+
observations_created: observations.length,
|
|
281
|
+
}, null, 2),
|
|
282
|
+
},
|
|
283
|
+
],
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
case "recall": {
|
|
288
|
+
const { query, limit = 5, include_graph = true } = args as {
|
|
289
|
+
query: string;
|
|
290
|
+
limit?: number;
|
|
291
|
+
include_graph?: boolean;
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const results = await search.search(query, {
|
|
295
|
+
limit,
|
|
296
|
+
includeGraph: include_graph,
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
const formatted = results.map((r) => ({
|
|
300
|
+
id: r.memory.id,
|
|
301
|
+
content: r.memory.content,
|
|
302
|
+
source: r.memory.source,
|
|
303
|
+
timestamp: r.memory.timestamp.toISOString(),
|
|
304
|
+
relevance_score: r.score.toFixed(4),
|
|
305
|
+
matched_via: Object.entries(r.sources)
|
|
306
|
+
.filter(([, v]) => v !== undefined)
|
|
307
|
+
.map(([k]) => k)
|
|
308
|
+
.join(", "),
|
|
309
|
+
}));
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
content: [
|
|
313
|
+
{
|
|
314
|
+
type: "text" as const,
|
|
315
|
+
text: JSON.stringify({
|
|
316
|
+
query,
|
|
317
|
+
results: formatted,
|
|
318
|
+
count: formatted.length,
|
|
319
|
+
}, null, 2),
|
|
320
|
+
},
|
|
321
|
+
],
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
case "forget": {
|
|
326
|
+
const { id } = args as { id: string };
|
|
327
|
+
|
|
328
|
+
const memory = db.getMemory(id);
|
|
329
|
+
if (!memory) {
|
|
330
|
+
return {
|
|
331
|
+
content: [
|
|
332
|
+
{
|
|
333
|
+
type: "text" as const,
|
|
334
|
+
text: JSON.stringify({ success: false, error: "Memory not found" }),
|
|
335
|
+
},
|
|
336
|
+
],
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Remove from semantic index
|
|
341
|
+
await search.removeFromIndex(id);
|
|
342
|
+
|
|
343
|
+
// Delete from database
|
|
344
|
+
db.deleteMemory(id);
|
|
345
|
+
|
|
346
|
+
return {
|
|
347
|
+
content: [
|
|
348
|
+
{
|
|
349
|
+
type: "text" as const,
|
|
350
|
+
text: JSON.stringify({ success: true, deleted_id: id }),
|
|
351
|
+
},
|
|
352
|
+
],
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
case "create_entity": {
|
|
357
|
+
const { name: entityName, type } = args as {
|
|
358
|
+
name: string;
|
|
359
|
+
type: "person" | "place" | "concept" | "event" | "organization";
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
const entity = graph.getOrCreateEntity(entityName, type);
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
content: [
|
|
366
|
+
{
|
|
367
|
+
type: "text" as const,
|
|
368
|
+
text: JSON.stringify({
|
|
369
|
+
success: true,
|
|
370
|
+
entity: {
|
|
371
|
+
id: entity.id,
|
|
372
|
+
name: entity.name,
|
|
373
|
+
type: entity.type,
|
|
374
|
+
},
|
|
375
|
+
}, null, 2),
|
|
376
|
+
},
|
|
377
|
+
],
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
case "observe": {
|
|
382
|
+
const { entity: entityName, observation, confidence = 1.0 } = args as {
|
|
383
|
+
entity: string;
|
|
384
|
+
observation: string;
|
|
385
|
+
confidence?: number;
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
// Ensure entity exists
|
|
389
|
+
const entity = graph.getOrCreateEntity(entityName, "person");
|
|
390
|
+
|
|
391
|
+
// Add observation
|
|
392
|
+
const obs = graph.addObservation(entity.id, observation, undefined, confidence);
|
|
393
|
+
|
|
394
|
+
return {
|
|
395
|
+
content: [
|
|
396
|
+
{
|
|
397
|
+
type: "text" as const,
|
|
398
|
+
text: JSON.stringify({
|
|
399
|
+
success: true,
|
|
400
|
+
entity: entity.name,
|
|
401
|
+
observation_id: obs.id,
|
|
402
|
+
}, null, 2),
|
|
403
|
+
},
|
|
404
|
+
],
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
case "relate": {
|
|
409
|
+
const { from, to, relation } = args as {
|
|
410
|
+
from: string;
|
|
411
|
+
to: string;
|
|
412
|
+
relation: string;
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
// Ensure both entities exist
|
|
416
|
+
const fromEntity = graph.getOrCreateEntity(from, "person");
|
|
417
|
+
const toEntity = graph.getOrCreateEntity(to, "person");
|
|
418
|
+
|
|
419
|
+
// Create relation
|
|
420
|
+
const rel = graph.relate(fromEntity.id, toEntity.id, relation);
|
|
421
|
+
|
|
422
|
+
return {
|
|
423
|
+
content: [
|
|
424
|
+
{
|
|
425
|
+
type: "text" as const,
|
|
426
|
+
text: JSON.stringify({
|
|
427
|
+
success: true,
|
|
428
|
+
relation: {
|
|
429
|
+
id: rel.id,
|
|
430
|
+
from: fromEntity.name,
|
|
431
|
+
to: toEntity.name,
|
|
432
|
+
type: rel.type,
|
|
433
|
+
},
|
|
434
|
+
}, null, 2),
|
|
435
|
+
},
|
|
436
|
+
],
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
case "query_entity": {
|
|
441
|
+
const { entity: entityName } = args as { entity: string };
|
|
442
|
+
|
|
443
|
+
const details = graph.getEntityDetails(entityName);
|
|
444
|
+
|
|
445
|
+
if (!details) {
|
|
446
|
+
return {
|
|
447
|
+
content: [
|
|
448
|
+
{
|
|
449
|
+
type: "text" as const,
|
|
450
|
+
text: JSON.stringify({ success: false, error: "Entity not found" }),
|
|
451
|
+
},
|
|
452
|
+
],
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return {
|
|
457
|
+
content: [
|
|
458
|
+
{
|
|
459
|
+
type: "text" as const,
|
|
460
|
+
text: JSON.stringify({
|
|
461
|
+
entity: {
|
|
462
|
+
id: details.id,
|
|
463
|
+
name: details.name,
|
|
464
|
+
type: details.type,
|
|
465
|
+
created_at: details.created_at.toISOString(),
|
|
466
|
+
},
|
|
467
|
+
observations: details.observations.map((o) => ({
|
|
468
|
+
content: o.content.substring(0, 200) + (o.content.length > 200 ? "..." : ""),
|
|
469
|
+
confidence: o.confidence,
|
|
470
|
+
valid_from: o.valid_from.toISOString(),
|
|
471
|
+
})),
|
|
472
|
+
relations_from: details.relationsFrom.map((r) => ({
|
|
473
|
+
type: r.type,
|
|
474
|
+
to: r.targetEntity.name,
|
|
475
|
+
})),
|
|
476
|
+
relations_to: details.relationsTo.map((r) => ({
|
|
477
|
+
type: r.type,
|
|
478
|
+
from: r.sourceEntity.name,
|
|
479
|
+
})),
|
|
480
|
+
}, null, 2),
|
|
481
|
+
},
|
|
482
|
+
],
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
case "list_entities": {
|
|
487
|
+
const { type, limit = 50 } = args as {
|
|
488
|
+
type?: "person" | "place" | "concept" | "event" | "organization";
|
|
489
|
+
limit?: number;
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
const entities = graph.listEntities(type, limit);
|
|
493
|
+
|
|
494
|
+
return {
|
|
495
|
+
content: [
|
|
496
|
+
{
|
|
497
|
+
type: "text" as const,
|
|
498
|
+
text: JSON.stringify({
|
|
499
|
+
entities: entities.map((e) => ({
|
|
500
|
+
id: e.id,
|
|
501
|
+
name: e.name,
|
|
502
|
+
type: e.type,
|
|
503
|
+
})),
|
|
504
|
+
count: entities.length,
|
|
505
|
+
}, null, 2),
|
|
506
|
+
},
|
|
507
|
+
],
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
case "stats": {
|
|
512
|
+
const stats = db.getStats();
|
|
513
|
+
|
|
514
|
+
return {
|
|
515
|
+
content: [
|
|
516
|
+
{
|
|
517
|
+
type: "text" as const,
|
|
518
|
+
text: JSON.stringify({
|
|
519
|
+
...stats,
|
|
520
|
+
database_path: DB_FILE,
|
|
521
|
+
}, null, 2),
|
|
522
|
+
},
|
|
523
|
+
],
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
default:
|
|
528
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
529
|
+
}
|
|
530
|
+
} catch (error) {
|
|
531
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
532
|
+
return {
|
|
533
|
+
content: [
|
|
534
|
+
{
|
|
535
|
+
type: "text" as const,
|
|
536
|
+
text: JSON.stringify({ success: false, error: message }),
|
|
537
|
+
},
|
|
538
|
+
],
|
|
539
|
+
isError: true,
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
// ============ Main ============
|
|
545
|
+
|
|
546
|
+
async function main() {
|
|
547
|
+
await initialize();
|
|
548
|
+
|
|
549
|
+
const transport = new StdioServerTransport();
|
|
550
|
+
await server.connect(transport);
|
|
551
|
+
|
|
552
|
+
console.error("[Engram] MCP server running on stdio");
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
main().catch((error) => {
|
|
556
|
+
console.error("[Engram] Fatal error:", error);
|
|
557
|
+
process.exit(1);
|
|
558
|
+
});
|