@joseairosa/recall 1.2.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/index.js ADDED
@@ -0,0 +1,2293 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import {
7
+ CallToolRequestSchema,
8
+ ListResourcesRequestSchema,
9
+ ListToolsRequestSchema,
10
+ ReadResourceRequestSchema,
11
+ ListPromptsRequestSchema,
12
+ GetPromptRequestSchema
13
+ } from "@modelcontextprotocol/sdk/types.js";
14
+
15
+ // src/redis/client.ts
16
+ import Redis from "ioredis";
17
+ var redisClient = null;
18
+ function getRedisClient() {
19
+ if (!redisClient) {
20
+ const redisUrl = process.env.REDIS_URL || "redis://localhost:6379";
21
+ redisClient = new Redis(redisUrl, {
22
+ maxRetriesPerRequest: 3,
23
+ retryStrategy(times) {
24
+ const delay = Math.min(times * 50, 2e3);
25
+ return delay;
26
+ },
27
+ reconnectOnError(err) {
28
+ const targetError = "READONLY";
29
+ if (err.message.includes(targetError)) {
30
+ return true;
31
+ }
32
+ return false;
33
+ }
34
+ });
35
+ redisClient.on("error", (err) => {
36
+ console.error("Redis Client Error:", err);
37
+ });
38
+ redisClient.on("connect", () => {
39
+ console.error("Redis Client Connected");
40
+ });
41
+ redisClient.on("ready", () => {
42
+ console.error("Redis Client Ready");
43
+ });
44
+ }
45
+ return redisClient;
46
+ }
47
+ async function closeRedisClient() {
48
+ if (redisClient) {
49
+ await redisClient.quit();
50
+ redisClient = null;
51
+ }
52
+ }
53
+ async function checkRedisConnection() {
54
+ try {
55
+ const client = getRedisClient();
56
+ const result = await client.ping();
57
+ return result === "PONG";
58
+ } catch (error) {
59
+ console.error("Redis connection check failed:", error);
60
+ return false;
61
+ }
62
+ }
63
+
64
+ // src/tools/index.ts
65
+ import { z as z3 } from "zod";
66
+ import { McpError as McpError2, ErrorCode as ErrorCode2 } from "@modelcontextprotocol/sdk/types.js";
67
+
68
+ // src/redis/memory-store.ts
69
+ import { ulid } from "ulid";
70
+
71
+ // src/embeddings/generator.ts
72
+ import Anthropic from "@anthropic-ai/sdk";
73
+ var anthropicClient = null;
74
+ function getAnthropicClient() {
75
+ if (!anthropicClient) {
76
+ const apiKey = process.env.ANTHROPIC_API_KEY;
77
+ if (!apiKey) {
78
+ throw new Error("ANTHROPIC_API_KEY environment variable is required");
79
+ }
80
+ anthropicClient = new Anthropic({ apiKey });
81
+ }
82
+ return anthropicClient;
83
+ }
84
+ async function generateSemanticFingerprint(text) {
85
+ try {
86
+ const client = getAnthropicClient();
87
+ const response = await client.messages.create({
88
+ model: "claude-3-5-haiku-20241022",
89
+ // Fast, cheap model for this task
90
+ max_tokens: 200,
91
+ messages: [{
92
+ role: "user",
93
+ content: `Extract 5-10 key concepts/keywords from this text. Return ONLY a comma-separated list, no explanations:
94
+
95
+ ${text}`
96
+ }]
97
+ });
98
+ const content = response.content[0];
99
+ if (content.type === "text") {
100
+ const keywords = content.text.split(",").map((k) => k.trim().toLowerCase()).filter((k) => k.length > 0);
101
+ return keywords;
102
+ }
103
+ return [];
104
+ } catch (error) {
105
+ console.error("Error generating semantic fingerprint:", error);
106
+ throw error;
107
+ }
108
+ }
109
+ async function generateEmbedding(text) {
110
+ try {
111
+ const keywords = await generateSemanticFingerprint(text);
112
+ const vector = createSimpleVector(text, keywords);
113
+ return vector;
114
+ } catch (error) {
115
+ console.error("Error generating embedding:", error);
116
+ throw error;
117
+ }
118
+ }
119
+ function createSimpleVector(text, keywords) {
120
+ const VECTOR_SIZE = 128;
121
+ const vector = new Array(VECTOR_SIZE).fill(0);
122
+ const normalized = text.toLowerCase();
123
+ const trigrams = extractTrigrams(normalized);
124
+ for (let i = 0; i < Math.min(trigrams.length, 64); i++) {
125
+ const hash = simpleHash(trigrams[i]);
126
+ const index = hash % 64;
127
+ vector[index] += 1;
128
+ }
129
+ for (const keyword of keywords) {
130
+ const hash = simpleHash(keyword);
131
+ const index = 64 + hash % 64;
132
+ vector[index] += 2;
133
+ }
134
+ const magnitude = Math.sqrt(vector.reduce((sum, val) => sum + val * val, 0));
135
+ if (magnitude > 0) {
136
+ for (let i = 0; i < vector.length; i++) {
137
+ vector[i] /= magnitude;
138
+ }
139
+ }
140
+ return vector;
141
+ }
142
+ function extractTrigrams(text) {
143
+ const trigrams = [];
144
+ for (let i = 0; i < text.length - 2; i++) {
145
+ trigrams.push(text.substring(i, i + 3));
146
+ }
147
+ return trigrams;
148
+ }
149
+ function simpleHash(str) {
150
+ let hash = 0;
151
+ for (let i = 0; i < str.length; i++) {
152
+ const char = str.charCodeAt(i);
153
+ hash = (hash << 5) - hash + char;
154
+ hash = hash & hash;
155
+ }
156
+ return Math.abs(hash);
157
+ }
158
+ function cosineSimilarity(a, b) {
159
+ if (a.length !== b.length) {
160
+ throw new Error("Vectors must have the same length");
161
+ }
162
+ let dotProduct = 0;
163
+ let normA = 0;
164
+ let normB = 0;
165
+ for (let i = 0; i < a.length; i++) {
166
+ dotProduct += a[i] * b[i];
167
+ normA += a[i] * a[i];
168
+ normB += b[i] * b[i];
169
+ }
170
+ return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
171
+ }
172
+
173
+ // src/types.ts
174
+ import { z } from "zod";
175
+ var ContextType = z.enum([
176
+ "directive",
177
+ // Instructions or commands
178
+ "information",
179
+ // General facts or knowledge
180
+ "heading",
181
+ // Section headers or organizational markers
182
+ "decision",
183
+ // Decisions made during work
184
+ "code_pattern",
185
+ // Code patterns or conventions
186
+ "requirement",
187
+ // Project requirements
188
+ "error",
189
+ // Error encountered and solution
190
+ "todo",
191
+ // Task or todo item
192
+ "insight",
193
+ // Key insight or realization
194
+ "preference"
195
+ // User preference
196
+ ]);
197
+ var MemoryEntrySchema = z.object({
198
+ id: z.string().describe("ULID identifier"),
199
+ timestamp: z.number().describe("Unix timestamp in milliseconds"),
200
+ context_type: ContextType,
201
+ content: z.string().describe("The actual memory content"),
202
+ summary: z.string().optional().describe("Short summary for quick scanning"),
203
+ tags: z.array(z.string()).default([]).describe("Tags for categorization"),
204
+ importance: z.number().min(1).max(10).default(5).describe("Importance score 1-10"),
205
+ session_id: z.string().optional().describe("Optional session grouping"),
206
+ embedding: z.array(z.number()).optional().describe("Vector embedding"),
207
+ ttl_seconds: z.number().optional().describe("Time-to-live in seconds (auto-expires)"),
208
+ expires_at: z.number().optional().describe("Unix timestamp when memory expires")
209
+ });
210
+ var CreateMemorySchema = z.object({
211
+ content: z.string().min(1).describe("The memory content to store"),
212
+ context_type: ContextType.default("information"),
213
+ tags: z.array(z.string()).default([]).describe("Tags for categorization"),
214
+ importance: z.number().min(1).max(10).default(5).describe("Importance score 1-10"),
215
+ summary: z.string().optional().describe("Optional summary"),
216
+ session_id: z.string().optional().describe("Optional session ID"),
217
+ ttl_seconds: z.number().min(60).optional().describe("Time-to-live in seconds (minimum 60s)")
218
+ });
219
+ var BatchCreateMemoriesSchema = z.object({
220
+ memories: z.array(CreateMemorySchema).min(1).describe("Array of memories to store")
221
+ });
222
+ var UpdateMemorySchema = z.object({
223
+ memory_id: z.string().describe("ULID of memory to update"),
224
+ content: z.string().optional(),
225
+ context_type: ContextType.optional(),
226
+ tags: z.array(z.string()).optional(),
227
+ importance: z.number().min(1).max(10).optional(),
228
+ summary: z.string().optional(),
229
+ session_id: z.string().optional()
230
+ });
231
+ var DeleteMemorySchema = z.object({
232
+ memory_id: z.string().describe("ULID of memory to delete")
233
+ });
234
+ var SearchMemorySchema = z.object({
235
+ query: z.string().describe("Search query"),
236
+ limit: z.number().min(1).max(100).default(10).describe("Number of results"),
237
+ min_importance: z.number().min(1).max(10).optional().describe("Filter by minimum importance"),
238
+ context_types: z.array(ContextType).optional().describe("Filter by context types")
239
+ });
240
+ var OrganizeSessionSchema = z.object({
241
+ session_name: z.string().describe("Name for the session"),
242
+ memory_ids: z.array(z.string()).min(1).describe("Array of memory IDs to include"),
243
+ summary: z.string().optional().describe("Optional session summary")
244
+ });
245
+ function createWorkspaceId(path) {
246
+ let hash = 0;
247
+ for (let i = 0; i < path.length; i++) {
248
+ const char = path.charCodeAt(i);
249
+ hash = (hash << 5) - hash + char;
250
+ hash = hash & hash;
251
+ }
252
+ return Math.abs(hash).toString(36);
253
+ }
254
+ var RecallContextSchema = z.object({
255
+ current_task: z.string().describe("Description of what I'm currently working on"),
256
+ query: z.string().optional().describe("Optional specific search query"),
257
+ limit: z.number().min(1).max(20).default(5).describe("Number of results to return"),
258
+ min_importance: z.number().min(1).max(10).default(6).describe("Minimum importance threshold")
259
+ });
260
+ var AnalyzeConversationSchema = z.object({
261
+ conversation_text: z.string().min(1).describe("Conversation text to analyze and extract memories from"),
262
+ auto_categorize: z.boolean().default(true).describe("Automatically categorize extracted memories"),
263
+ auto_store: z.boolean().default(true).describe("Automatically store extracted memories")
264
+ });
265
+ var SummarizeSessionSchema = z.object({
266
+ session_name: z.string().optional().describe("Optional name for the session"),
267
+ auto_create_snapshot: z.boolean().default(true).describe("Automatically create session snapshot"),
268
+ lookback_minutes: z.number().default(60).describe("How many minutes back to look for memories")
269
+ });
270
+ var ExportMemoriesSchema = z.object({
271
+ format: z.enum(["json"]).default("json").describe("Export format"),
272
+ include_embeddings: z.boolean().default(false).describe("Include vector embeddings in export"),
273
+ filter_by_type: z.array(ContextType).optional().describe("Only export specific types"),
274
+ min_importance: z.number().min(1).max(10).optional().describe("Only export above this importance")
275
+ });
276
+ var ImportMemoriesSchema = z.object({
277
+ data: z.string().describe("JSON string of exported memories"),
278
+ overwrite_existing: z.boolean().default(false).describe("Overwrite if memory ID already exists"),
279
+ regenerate_embeddings: z.boolean().default(true).describe("Regenerate embeddings on import")
280
+ });
281
+ var FindDuplicatesSchema = z.object({
282
+ similarity_threshold: z.number().min(0).max(1).default(0.85).describe("Similarity threshold (0-1)"),
283
+ auto_merge: z.boolean().default(false).describe("Automatically merge duplicates"),
284
+ keep_highest_importance: z.boolean().default(true).describe("When merging, keep highest importance")
285
+ });
286
+ var ConsolidateMemoriesSchema = z.object({
287
+ memory_ids: z.array(z.string()).min(2).describe("Array of memory IDs to consolidate"),
288
+ keep_id: z.string().optional().describe("Optional ID of memory to keep (default: highest importance)")
289
+ });
290
+ var RedisKeys = {
291
+ memory: (workspace, id) => `ws:${workspace}:memory:${id}`,
292
+ memories: (workspace) => `ws:${workspace}:memories:all`,
293
+ byType: (workspace, type) => `ws:${workspace}:memories:type:${type}`,
294
+ byTag: (workspace, tag) => `ws:${workspace}:memories:tag:${tag}`,
295
+ timeline: (workspace) => `ws:${workspace}:memories:timeline`,
296
+ session: (workspace, id) => `ws:${workspace}:session:${id}`,
297
+ sessions: (workspace) => `ws:${workspace}:sessions:all`,
298
+ important: (workspace) => `ws:${workspace}:memories:important`
299
+ };
300
+
301
+ // src/redis/memory-store.ts
302
+ var MemoryStore = class {
303
+ redis;
304
+ workspaceId;
305
+ workspacePath;
306
+ constructor(workspacePath) {
307
+ this.redis = getRedisClient();
308
+ this.workspacePath = workspacePath || process.cwd();
309
+ this.workspaceId = createWorkspaceId(this.workspacePath);
310
+ console.error(`[MemoryStore] Workspace: ${this.workspacePath}`);
311
+ console.error(`[MemoryStore] Workspace ID: ${this.workspaceId}`);
312
+ }
313
+ // Store a new memory
314
+ async createMemory(data) {
315
+ const id = ulid();
316
+ const timestamp = Date.now();
317
+ const embedding = await generateEmbedding(data.content);
318
+ const summary = data.summary || this.generateSummary(data.content);
319
+ let expiresAt;
320
+ if (data.ttl_seconds) {
321
+ expiresAt = timestamp + data.ttl_seconds * 1e3;
322
+ }
323
+ const memory = {
324
+ id,
325
+ timestamp,
326
+ context_type: data.context_type,
327
+ content: data.content,
328
+ summary,
329
+ tags: data.tags,
330
+ importance: data.importance,
331
+ session_id: data.session_id,
332
+ embedding,
333
+ ttl_seconds: data.ttl_seconds,
334
+ expires_at: expiresAt
335
+ };
336
+ const pipeline = this.redis.pipeline();
337
+ pipeline.hset(RedisKeys.memory(this.workspaceId, id), this.serializeMemory(memory));
338
+ if (data.ttl_seconds) {
339
+ pipeline.expire(RedisKeys.memory(this.workspaceId, id), data.ttl_seconds);
340
+ }
341
+ pipeline.sadd(RedisKeys.memories(this.workspaceId), id);
342
+ pipeline.zadd(RedisKeys.timeline(this.workspaceId), timestamp, id);
343
+ pipeline.sadd(RedisKeys.byType(this.workspaceId, data.context_type), id);
344
+ for (const tag of data.tags) {
345
+ pipeline.sadd(RedisKeys.byTag(this.workspaceId, tag), id);
346
+ }
347
+ if (data.importance >= 8) {
348
+ pipeline.zadd(RedisKeys.important(this.workspaceId), data.importance, id);
349
+ }
350
+ await pipeline.exec();
351
+ return memory;
352
+ }
353
+ // Batch create memories
354
+ async createMemories(memories) {
355
+ const results = [];
356
+ for (const memoryData of memories) {
357
+ const memory = await this.createMemory(memoryData);
358
+ results.push(memory);
359
+ }
360
+ return results;
361
+ }
362
+ // Get memory by ID
363
+ async getMemory(id) {
364
+ const data = await this.redis.hgetall(RedisKeys.memory(this.workspaceId, id));
365
+ if (!data || Object.keys(data).length === 0) {
366
+ return null;
367
+ }
368
+ return this.deserializeMemory(data);
369
+ }
370
+ // Get multiple memories by IDs
371
+ async getMemories(ids) {
372
+ const memories = [];
373
+ for (const id of ids) {
374
+ const memory = await this.getMemory(id);
375
+ if (memory) {
376
+ memories.push(memory);
377
+ }
378
+ }
379
+ return memories;
380
+ }
381
+ // Get recent memories
382
+ async getRecentMemories(limit = 50) {
383
+ const ids = await this.redis.zrevrange(RedisKeys.timeline(this.workspaceId), 0, limit - 1);
384
+ return this.getMemories(ids);
385
+ }
386
+ // Get memories by type
387
+ async getMemoriesByType(type, limit) {
388
+ const ids = await this.redis.smembers(RedisKeys.byType(this.workspaceId, type));
389
+ const memories = await this.getMemories(ids);
390
+ memories.sort((a, b) => b.timestamp - a.timestamp);
391
+ return limit ? memories.slice(0, limit) : memories;
392
+ }
393
+ // Get memories by tag
394
+ async getMemoriesByTag(tag, limit) {
395
+ const ids = await this.redis.smembers(RedisKeys.byTag(this.workspaceId, tag));
396
+ const memories = await this.getMemories(ids);
397
+ memories.sort((a, b) => b.timestamp - a.timestamp);
398
+ return limit ? memories.slice(0, limit) : memories;
399
+ }
400
+ // Get important memories
401
+ async getImportantMemories(minImportance = 8, limit) {
402
+ const results = await this.redis.zrevrangebyscore(
403
+ RedisKeys.important(this.workspaceId),
404
+ 10,
405
+ minImportance,
406
+ "LIMIT",
407
+ 0,
408
+ limit || 100
409
+ );
410
+ return this.getMemories(results);
411
+ }
412
+ // Update memory
413
+ async updateMemory(id, updates) {
414
+ const existing = await this.getMemory(id);
415
+ if (!existing) {
416
+ return null;
417
+ }
418
+ const pipeline = this.redis.pipeline();
419
+ let embedding = existing.embedding;
420
+ if (updates.content && updates.content !== existing.content) {
421
+ embedding = await generateEmbedding(updates.content);
422
+ }
423
+ const updated = {
424
+ ...existing,
425
+ ...updates,
426
+ embedding,
427
+ summary: updates.summary || (updates.content ? this.generateSummary(updates.content) : existing.summary)
428
+ };
429
+ pipeline.hset(RedisKeys.memory(this.workspaceId, id), this.serializeMemory(updated));
430
+ if (updates.context_type && updates.context_type !== existing.context_type) {
431
+ pipeline.srem(RedisKeys.byType(this.workspaceId, existing.context_type), id);
432
+ pipeline.sadd(RedisKeys.byType(this.workspaceId, updates.context_type), id);
433
+ }
434
+ if (updates.tags) {
435
+ for (const tag of existing.tags) {
436
+ if (!updates.tags.includes(tag)) {
437
+ pipeline.srem(RedisKeys.byTag(this.workspaceId, tag), id);
438
+ }
439
+ }
440
+ for (const tag of updates.tags) {
441
+ if (!existing.tags.includes(tag)) {
442
+ pipeline.sadd(RedisKeys.byTag(this.workspaceId, tag), id);
443
+ }
444
+ }
445
+ }
446
+ if (updates.importance !== void 0) {
447
+ if (existing.importance >= 8) {
448
+ pipeline.zrem(RedisKeys.important(this.workspaceId), id);
449
+ }
450
+ if (updates.importance >= 8) {
451
+ pipeline.zadd(RedisKeys.important(this.workspaceId), updates.importance, id);
452
+ }
453
+ }
454
+ await pipeline.exec();
455
+ return updated;
456
+ }
457
+ // Delete memory
458
+ async deleteMemory(id) {
459
+ const memory = await this.getMemory(id);
460
+ if (!memory) {
461
+ return false;
462
+ }
463
+ const pipeline = this.redis.pipeline();
464
+ pipeline.del(RedisKeys.memory(this.workspaceId, id));
465
+ pipeline.srem(RedisKeys.memories(this.workspaceId), id);
466
+ pipeline.zrem(RedisKeys.timeline(this.workspaceId), id);
467
+ pipeline.srem(RedisKeys.byType(this.workspaceId, memory.context_type), id);
468
+ for (const tag of memory.tags) {
469
+ pipeline.srem(RedisKeys.byTag(this.workspaceId, tag), id);
470
+ }
471
+ if (memory.importance >= 8) {
472
+ pipeline.zrem(RedisKeys.important(this.workspaceId), id);
473
+ }
474
+ await pipeline.exec();
475
+ return true;
476
+ }
477
+ // Semantic search
478
+ async searchMemories(query, limit = 10, minImportance, contextTypes) {
479
+ const queryEmbedding = await generateEmbedding(query);
480
+ let ids;
481
+ if (contextTypes && contextTypes.length > 0) {
482
+ const sets = contextTypes.map((type) => RedisKeys.byType(this.workspaceId, type));
483
+ ids = await this.redis.sunion(...sets);
484
+ } else {
485
+ ids = await this.redis.smembers(RedisKeys.memories(this.workspaceId));
486
+ }
487
+ const memories = await this.getMemories(ids);
488
+ let filtered = memories;
489
+ if (minImportance !== void 0) {
490
+ filtered = memories.filter((m) => m.importance >= minImportance);
491
+ }
492
+ const withSimilarity = filtered.map((memory) => ({
493
+ ...memory,
494
+ similarity: memory.embedding ? cosineSimilarity(queryEmbedding, memory.embedding) : 0
495
+ }));
496
+ withSimilarity.sort((a, b) => b.similarity - a.similarity);
497
+ return withSimilarity.slice(0, limit);
498
+ }
499
+ // Create session
500
+ async createSession(name, memoryIds, summary) {
501
+ const sessionId = ulid();
502
+ const timestamp = Date.now();
503
+ const validIds = [];
504
+ for (const id of memoryIds) {
505
+ const exists = await this.redis.exists(RedisKeys.memory(this.workspaceId, id));
506
+ if (exists) {
507
+ validIds.push(id);
508
+ }
509
+ }
510
+ const session = {
511
+ session_id: sessionId,
512
+ session_name: name,
513
+ created_at: timestamp,
514
+ memory_count: validIds.length,
515
+ summary,
516
+ memory_ids: validIds
517
+ };
518
+ await this.redis.hset(RedisKeys.session(this.workspaceId, sessionId), {
519
+ session_id: sessionId,
520
+ session_name: name,
521
+ created_at: timestamp.toString(),
522
+ memory_count: validIds.length.toString(),
523
+ summary: summary || "",
524
+ memory_ids: JSON.stringify(validIds)
525
+ });
526
+ await this.redis.sadd(RedisKeys.sessions(this.workspaceId), sessionId);
527
+ return session;
528
+ }
529
+ // Get session
530
+ async getSession(sessionId) {
531
+ const data = await this.redis.hgetall(RedisKeys.session(this.workspaceId, sessionId));
532
+ if (!data || Object.keys(data).length === 0) {
533
+ return null;
534
+ }
535
+ return {
536
+ session_id: data.session_id,
537
+ session_name: data.session_name,
538
+ created_at: parseInt(data.created_at, 10),
539
+ memory_count: parseInt(data.memory_count, 10),
540
+ summary: data.summary || void 0,
541
+ memory_ids: JSON.parse(data.memory_ids)
542
+ };
543
+ }
544
+ // Get all sessions
545
+ async getAllSessions() {
546
+ const ids = await this.redis.smembers(RedisKeys.sessions(this.workspaceId));
547
+ const sessions = [];
548
+ for (const id of ids) {
549
+ const session = await this.getSession(id);
550
+ if (session) {
551
+ sessions.push(session);
552
+ }
553
+ }
554
+ return sessions.sort((a, b) => b.created_at - a.created_at);
555
+ }
556
+ // Get memories in session
557
+ async getSessionMemories(sessionId) {
558
+ const session = await this.getSession(sessionId);
559
+ if (!session) {
560
+ return [];
561
+ }
562
+ return this.getMemories(session.memory_ids);
563
+ }
564
+ // Generate summary stats
565
+ async getSummaryStats() {
566
+ const totalMemories = await this.redis.scard(RedisKeys.memories(this.workspaceId));
567
+ const totalSessions = await this.redis.scard(RedisKeys.sessions(this.workspaceId));
568
+ const importantCount = await this.redis.zcard(RedisKeys.important(this.workspaceId));
569
+ const byType = {};
570
+ const types = ["directive", "information", "heading", "decision", "code_pattern", "requirement", "error", "todo", "insight", "preference"];
571
+ for (const type of types) {
572
+ byType[type] = await this.redis.scard(RedisKeys.byType(this.workspaceId, type));
573
+ }
574
+ return {
575
+ total_memories: totalMemories,
576
+ by_type: byType,
577
+ total_sessions: totalSessions,
578
+ important_count: importantCount,
579
+ workspace_path: this.workspacePath
580
+ };
581
+ }
582
+ // Merge multiple memories into one
583
+ async mergeMemories(memoryIds, keepId) {
584
+ const memories = await this.getMemories(memoryIds);
585
+ if (memories.length === 0) {
586
+ return null;
587
+ }
588
+ const toKeep = keepId ? memories.find((m) => m.id === keepId) : memories.reduce(
589
+ (prev, current) => current.importance > prev.importance ? current : prev
590
+ );
591
+ if (!toKeep) {
592
+ return null;
593
+ }
594
+ const allTags = /* @__PURE__ */ new Set();
595
+ const contentParts = [];
596
+ for (const memory of memories) {
597
+ if (memory.id !== toKeep.id) {
598
+ contentParts.push(memory.content);
599
+ }
600
+ memory.tags.forEach((tag) => allTags.add(tag));
601
+ }
602
+ const mergedContent = contentParts.length > 0 ? `${toKeep.content}
603
+
604
+ --- Merged content ---
605
+ ${contentParts.join("\n\n")}` : toKeep.content;
606
+ const updated = await this.updateMemory(toKeep.id, {
607
+ content: mergedContent,
608
+ tags: Array.from(allTags),
609
+ importance: Math.max(...memories.map((m) => m.importance))
610
+ });
611
+ for (const memory of memories) {
612
+ if (memory.id !== toKeep.id) {
613
+ await this.deleteMemory(memory.id);
614
+ }
615
+ }
616
+ return updated;
617
+ }
618
+ // Helper: Generate summary from content (first 100 chars)
619
+ generateSummary(content) {
620
+ return content.length > 100 ? content.substring(0, 100) + "..." : content;
621
+ }
622
+ // Helper: Serialize memory for Redis
623
+ serializeMemory(memory) {
624
+ return {
625
+ id: memory.id,
626
+ timestamp: memory.timestamp.toString(),
627
+ context_type: memory.context_type,
628
+ content: memory.content,
629
+ summary: memory.summary || "",
630
+ tags: JSON.stringify(memory.tags),
631
+ importance: memory.importance.toString(),
632
+ session_id: memory.session_id || "",
633
+ embedding: JSON.stringify(memory.embedding || []),
634
+ ttl_seconds: memory.ttl_seconds?.toString() || "",
635
+ expires_at: memory.expires_at?.toString() || ""
636
+ };
637
+ }
638
+ // Helper: Deserialize memory from Redis
639
+ deserializeMemory(data) {
640
+ return {
641
+ id: data.id,
642
+ timestamp: parseInt(data.timestamp, 10),
643
+ context_type: data.context_type,
644
+ content: data.content,
645
+ summary: data.summary || void 0,
646
+ tags: JSON.parse(data.tags || "[]"),
647
+ importance: parseInt(data.importance, 10),
648
+ session_id: data.session_id || void 0,
649
+ embedding: JSON.parse(data.embedding || "[]"),
650
+ ttl_seconds: data.ttl_seconds ? parseInt(data.ttl_seconds, 10) : void 0,
651
+ expires_at: data.expires_at ? parseInt(data.expires_at, 10) : void 0
652
+ };
653
+ }
654
+ };
655
+
656
+ // src/tools/context-tools.ts
657
+ import { z as z2 } from "zod";
658
+ import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
659
+
660
+ // src/analysis/conversation-analyzer.ts
661
+ import Anthropic2 from "@anthropic-ai/sdk";
662
+ var ConversationAnalyzer = class {
663
+ client;
664
+ constructor() {
665
+ const apiKey = process.env.ANTHROPIC_API_KEY;
666
+ if (!apiKey) {
667
+ throw new Error("ANTHROPIC_API_KEY environment variable is required");
668
+ }
669
+ this.client = new Anthropic2({ apiKey });
670
+ }
671
+ /**
672
+ * Analyze conversation and extract structured memories
673
+ */
674
+ async analyzeConversation(conversationText) {
675
+ try {
676
+ const response = await this.client.messages.create({
677
+ model: "claude-3-5-haiku-20241022",
678
+ max_tokens: 2e3,
679
+ messages: [{
680
+ role: "user",
681
+ content: `Analyze this conversation and extract important information that should be remembered long-term.
682
+
683
+ For each important piece of information, output EXACTLY in this JSON format (one per line):
684
+ {"content":"the information","type":"directive|information|decision|code_pattern|requirement|error|todo|insight|preference","importance":1-10,"tags":["tag1","tag2"],"summary":"brief summary"}
685
+
686
+ Guidelines:
687
+ - Extract directives (instructions to follow)
688
+ - Extract decisions (choices made)
689
+ - Extract code_patterns (coding conventions)
690
+ - Extract requirements (project specs)
691
+ - Extract errors (problems and solutions)
692
+ - Extract insights (key realizations)
693
+ - Extract preferences (user preferences)
694
+ - Importance: 10=critical, 8-9=very important, 6-7=important, 1-5=nice to have
695
+ - Tags: relevant keywords for categorization
696
+ - Summary: max 50 chars
697
+
698
+ Conversation:
699
+ ${conversationText}
700
+
701
+ Output ONLY the JSON objects, one per line, no other text:`
702
+ }]
703
+ });
704
+ const content = response.content[0];
705
+ if (content.type !== "text") {
706
+ return [];
707
+ }
708
+ const lines = content.text.split("\n").filter((line) => line.trim().startsWith("{"));
709
+ const extracted = [];
710
+ for (const line of lines) {
711
+ try {
712
+ const parsed = JSON.parse(line.trim());
713
+ if (parsed.content && parsed.type && parsed.importance) {
714
+ extracted.push({
715
+ content: parsed.content,
716
+ context_type: this.normalizeContextType(parsed.type),
717
+ importance: Math.min(10, Math.max(1, parseInt(parsed.importance))),
718
+ tags: Array.isArray(parsed.tags) ? parsed.tags : [],
719
+ summary: parsed.summary || void 0
720
+ });
721
+ }
722
+ } catch (e) {
723
+ console.error("Failed to parse line:", line, e);
724
+ }
725
+ }
726
+ return extracted;
727
+ } catch (error) {
728
+ console.error("Error analyzing conversation:", error);
729
+ throw error;
730
+ }
731
+ }
732
+ /**
733
+ * Generate a summary of a session
734
+ */
735
+ async summarizeSession(memories) {
736
+ try {
737
+ const memoriesText = memories.sort((a, b) => b.importance - a.importance).map((m) => `[${m.context_type}] ${m.content}`).join("\n");
738
+ const response = await this.client.messages.create({
739
+ model: "claude-3-5-haiku-20241022",
740
+ max_tokens: 500,
741
+ messages: [{
742
+ role: "user",
743
+ content: `Summarize this work session in 2-3 sentences. Focus on what was accomplished, decided, or learned.
744
+
745
+ Session memories:
746
+ ${memoriesText}
747
+
748
+ Summary (2-3 sentences):`
749
+ }]
750
+ });
751
+ const content = response.content[0];
752
+ if (content.type === "text") {
753
+ return content.text.trim();
754
+ }
755
+ return "Session completed with multiple activities";
756
+ } catch (error) {
757
+ console.error("Error summarizing session:", error);
758
+ return "Session summary unavailable";
759
+ }
760
+ }
761
+ /**
762
+ * Enhance a search query for better semantic matching
763
+ */
764
+ async enhanceQuery(currentTask, query) {
765
+ const combined = query ? `${currentTask} ${query}` : currentTask;
766
+ return combined;
767
+ }
768
+ /**
769
+ * Normalize context type strings from Claude
770
+ */
771
+ normalizeContextType(type) {
772
+ const normalized = type.toLowerCase().trim();
773
+ const mapping = {
774
+ "directive": "directive",
775
+ "instruction": "directive",
776
+ "command": "directive",
777
+ "information": "information",
778
+ "info": "information",
779
+ "fact": "information",
780
+ "heading": "heading",
781
+ "section": "heading",
782
+ "title": "heading",
783
+ "decision": "decision",
784
+ "choice": "decision",
785
+ "code_pattern": "code_pattern",
786
+ "pattern": "code_pattern",
787
+ "convention": "code_pattern",
788
+ "requirement": "requirement",
789
+ "req": "requirement",
790
+ "spec": "requirement",
791
+ "error": "error",
792
+ "bug": "error",
793
+ "issue": "error",
794
+ "todo": "todo",
795
+ "task": "todo",
796
+ "insight": "insight",
797
+ "realization": "insight",
798
+ "learning": "insight",
799
+ "preference": "preference",
800
+ "pref": "preference",
801
+ "setting": "preference"
802
+ };
803
+ return mapping[normalized] || "information";
804
+ }
805
+ };
806
+
807
+ // src/tools/context-tools.ts
808
+ var memoryStore = new MemoryStore();
809
+ var analyzer = new ConversationAnalyzer();
810
+ var recall_relevant_context = {
811
+ description: "Proactively search memory for context relevant to current task. Use this when you need to recall patterns, decisions, or conventions.",
812
+ inputSchema: zodToJsonSchema(RecallContextSchema),
813
+ handler: async (args) => {
814
+ try {
815
+ const enhancedQuery = await analyzer.enhanceQuery(args.current_task, args.query);
816
+ const results = await memoryStore.searchMemories(
817
+ enhancedQuery,
818
+ args.limit,
819
+ args.min_importance
820
+ );
821
+ const formattedResults = results.map((r) => ({
822
+ content: r.content,
823
+ summary: r.summary,
824
+ context_type: r.context_type,
825
+ importance: r.importance,
826
+ tags: r.tags,
827
+ similarity: Math.round(r.similarity * 100) / 100
828
+ }));
829
+ return {
830
+ content: [
831
+ {
832
+ type: "text",
833
+ text: JSON.stringify({
834
+ current_task: args.current_task,
835
+ found: results.length,
836
+ relevant_memories: formattedResults
837
+ }, null, 2)
838
+ }
839
+ ]
840
+ };
841
+ } catch (error) {
842
+ throw new McpError(
843
+ ErrorCode.InternalError,
844
+ `Failed to recall context: ${error instanceof Error ? error.message : String(error)}`
845
+ );
846
+ }
847
+ }
848
+ };
849
+ var analyze_and_remember = {
850
+ description: "Analyze conversation text and automatically extract and store important information (decisions, patterns, directives, etc.). Use this after important discussions.",
851
+ inputSchema: zodToJsonSchema(AnalyzeConversationSchema),
852
+ handler: async (args) => {
853
+ try {
854
+ const extracted = await analyzer.analyzeConversation(args.conversation_text);
855
+ const result = {
856
+ extracted_memories: extracted,
857
+ total_count: extracted.length
858
+ };
859
+ if (args.auto_store && extracted.length > 0) {
860
+ const memories = await memoryStore.createMemories(
861
+ extracted.map((e) => ({
862
+ content: e.content,
863
+ context_type: e.context_type,
864
+ importance: e.importance,
865
+ tags: e.tags,
866
+ summary: e.summary
867
+ }))
868
+ );
869
+ result.stored_ids = memories.map((m) => m.id);
870
+ }
871
+ const response = {
872
+ success: true,
873
+ analyzed: result.total_count,
874
+ stored: result.stored_ids?.length || 0,
875
+ breakdown: {
876
+ directives: extracted.filter((e) => e.context_type === "directive").length,
877
+ decisions: extracted.filter((e) => e.context_type === "decision").length,
878
+ patterns: extracted.filter((e) => e.context_type === "code_pattern").length,
879
+ requirements: extracted.filter((e) => e.context_type === "requirement").length,
880
+ errors: extracted.filter((e) => e.context_type === "error").length,
881
+ insights: extracted.filter((e) => e.context_type === "insight").length,
882
+ other: extracted.filter((e) => !["directive", "decision", "code_pattern", "requirement", "error", "insight"].includes(e.context_type)).length
883
+ },
884
+ memories: extracted.map((e) => ({
885
+ content: e.content.substring(0, 100) + (e.content.length > 100 ? "..." : ""),
886
+ type: e.context_type,
887
+ importance: e.importance
888
+ }))
889
+ };
890
+ return {
891
+ content: [
892
+ {
893
+ type: "text",
894
+ text: JSON.stringify(response, null, 2)
895
+ }
896
+ ]
897
+ };
898
+ } catch (error) {
899
+ throw new McpError(
900
+ ErrorCode.InternalError,
901
+ `Failed to analyze conversation: ${error instanceof Error ? error.message : String(error)}`
902
+ );
903
+ }
904
+ }
905
+ };
906
+ var summarize_session = {
907
+ description: "Summarize the current work session and create a snapshot. Use this at the end of a work session to preserve context.",
908
+ inputSchema: zodToJsonSchema(SummarizeSessionSchema),
909
+ handler: async (args) => {
910
+ try {
911
+ const lookbackMs = args.lookback_minutes * 60 * 1e3;
912
+ const cutoffTime = Date.now() - lookbackMs;
913
+ const allRecent = await memoryStore.getRecentMemories(100);
914
+ const sessionMemories = allRecent.filter((m) => m.timestamp >= cutoffTime);
915
+ if (sessionMemories.length === 0) {
916
+ return {
917
+ content: [
918
+ {
919
+ type: "text",
920
+ text: JSON.stringify({
921
+ success: false,
922
+ message: "No memories found in the specified lookback period",
923
+ lookback_minutes: args.lookback_minutes
924
+ }, null, 2)
925
+ }
926
+ ]
927
+ };
928
+ }
929
+ const summary = await analyzer.summarizeSession(
930
+ sessionMemories.map((m) => ({
931
+ content: m.content,
932
+ context_type: m.context_type,
933
+ importance: m.importance
934
+ }))
935
+ );
936
+ let sessionInfo = null;
937
+ if (args.auto_create_snapshot) {
938
+ const sessionName = args.session_name || `Session ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`;
939
+ sessionInfo = await memoryStore.createSession(
940
+ sessionName,
941
+ sessionMemories.map((m) => m.id),
942
+ summary
943
+ );
944
+ }
945
+ return {
946
+ content: [
947
+ {
948
+ type: "text",
949
+ text: JSON.stringify({
950
+ success: true,
951
+ summary,
952
+ session_id: sessionInfo?.session_id,
953
+ session_name: sessionInfo?.session_name,
954
+ memory_count: sessionMemories.length,
955
+ lookback_minutes: args.lookback_minutes,
956
+ breakdown: {
957
+ directives: sessionMemories.filter((m) => m.context_type === "directive").length,
958
+ decisions: sessionMemories.filter((m) => m.context_type === "decision").length,
959
+ patterns: sessionMemories.filter((m) => m.context_type === "code_pattern").length,
960
+ insights: sessionMemories.filter((m) => m.context_type === "insight").length
961
+ }
962
+ }, null, 2)
963
+ }
964
+ ]
965
+ };
966
+ } catch (error) {
967
+ throw new McpError(
968
+ ErrorCode.InternalError,
969
+ `Failed to summarize session: ${error instanceof Error ? error.message : String(error)}`
970
+ );
971
+ }
972
+ }
973
+ };
974
+ function zodToJsonSchema(schema) {
975
+ if (schema instanceof z2.ZodObject) {
976
+ const shape = schema._def.shape();
977
+ const properties = {};
978
+ const required = [];
979
+ for (const [key, value] of Object.entries(shape)) {
980
+ properties[key] = zodToJsonSchemaInner(value);
981
+ if (!value.isOptional()) {
982
+ required.push(key);
983
+ }
984
+ }
985
+ return {
986
+ type: "object",
987
+ properties,
988
+ required
989
+ };
990
+ }
991
+ return zodToJsonSchemaInner(schema);
992
+ }
993
+ function zodToJsonSchemaInner(schema) {
994
+ if (schema instanceof z2.ZodString) {
995
+ const result = { type: "string" };
996
+ if (schema.description) result.description = schema.description;
997
+ return result;
998
+ }
999
+ if (schema instanceof z2.ZodNumber) {
1000
+ const result = { type: "number" };
1001
+ if (schema.description) result.description = schema.description;
1002
+ return result;
1003
+ }
1004
+ if (schema instanceof z2.ZodBoolean) {
1005
+ const result = { type: "boolean" };
1006
+ if (schema.description) result.description = schema.description;
1007
+ return result;
1008
+ }
1009
+ if (schema instanceof z2.ZodArray) {
1010
+ const result = {
1011
+ type: "array",
1012
+ items: zodToJsonSchemaInner(schema.element)
1013
+ };
1014
+ if (schema.description) result.description = schema.description;
1015
+ return result;
1016
+ }
1017
+ if (schema instanceof z2.ZodEnum) {
1018
+ const result = {
1019
+ type: "string",
1020
+ enum: schema.options
1021
+ };
1022
+ if (schema.description) result.description = schema.description;
1023
+ return result;
1024
+ }
1025
+ if (schema instanceof z2.ZodOptional) {
1026
+ return zodToJsonSchemaInner(schema.unwrap());
1027
+ }
1028
+ if (schema instanceof z2.ZodDefault) {
1029
+ const inner = zodToJsonSchemaInner(schema._def.innerType);
1030
+ inner.default = schema._def.defaultValue();
1031
+ return inner;
1032
+ }
1033
+ if (schema instanceof z2.ZodObject) {
1034
+ return zodToJsonSchema(schema);
1035
+ }
1036
+ return { type: "string" };
1037
+ }
1038
+
1039
+ // src/tools/export-import-tools.ts
1040
+ async function exportMemories(args, workspacePath) {
1041
+ const store = new MemoryStore(workspacePath);
1042
+ let memories;
1043
+ if (args.filter_by_type && args.filter_by_type.length > 0) {
1044
+ const memoriesByType = [];
1045
+ for (const type of args.filter_by_type) {
1046
+ const typeMemories = await store.getMemoriesByType(type);
1047
+ memoriesByType.push(...typeMemories);
1048
+ }
1049
+ const uniqueMap = /* @__PURE__ */ new Map();
1050
+ for (const memory of memoriesByType) {
1051
+ uniqueMap.set(memory.id, memory);
1052
+ }
1053
+ memories = Array.from(uniqueMap.values());
1054
+ } else {
1055
+ memories = await store.getRecentMemories(1e4);
1056
+ }
1057
+ if (args.min_importance !== void 0) {
1058
+ memories = memories.filter((m) => m.importance >= args.min_importance);
1059
+ }
1060
+ const exportData = memories.map((memory) => {
1061
+ if (!args.include_embeddings) {
1062
+ const { embedding, ...rest } = memory;
1063
+ return rest;
1064
+ }
1065
+ return memory;
1066
+ });
1067
+ const exportObject = {
1068
+ version: "1.2.0",
1069
+ exported_at: Date.now(),
1070
+ memory_count: exportData.length,
1071
+ memories: exportData
1072
+ };
1073
+ const jsonString = JSON.stringify(exportObject, null, 2);
1074
+ return {
1075
+ content: [
1076
+ {
1077
+ type: "text",
1078
+ text: `Successfully exported ${exportData.length} memories
1079
+
1080
+ ${jsonString}`
1081
+ }
1082
+ ]
1083
+ };
1084
+ }
1085
+ async function importMemories(args, workspacePath) {
1086
+ const store = new MemoryStore(workspacePath);
1087
+ let importData;
1088
+ try {
1089
+ importData = JSON.parse(args.data);
1090
+ } catch (error) {
1091
+ throw new Error(`Invalid JSON data: ${error instanceof Error ? error.message : "Unknown error"}`);
1092
+ }
1093
+ if (!importData.memories || !Array.isArray(importData.memories)) {
1094
+ throw new Error("Invalid import format: missing memories array");
1095
+ }
1096
+ const results = {
1097
+ imported: 0,
1098
+ skipped: 0,
1099
+ overwritten: 0,
1100
+ errors: []
1101
+ };
1102
+ for (const memoryData of importData.memories) {
1103
+ try {
1104
+ const existing = await store.getMemory(memoryData.id);
1105
+ if (existing && !args.overwrite_existing) {
1106
+ results.skipped++;
1107
+ continue;
1108
+ }
1109
+ const createData = {
1110
+ content: memoryData.content,
1111
+ context_type: memoryData.context_type,
1112
+ tags: memoryData.tags || [],
1113
+ importance: memoryData.importance || 5,
1114
+ summary: memoryData.summary,
1115
+ session_id: memoryData.session_id,
1116
+ ttl_seconds: memoryData.ttl_seconds
1117
+ };
1118
+ if (existing && args.overwrite_existing) {
1119
+ await store.updateMemory(memoryData.id, createData);
1120
+ results.overwritten++;
1121
+ } else {
1122
+ const importedMemory = {
1123
+ id: memoryData.id,
1124
+ timestamp: memoryData.timestamp || Date.now(),
1125
+ context_type: memoryData.context_type,
1126
+ content: memoryData.content,
1127
+ summary: memoryData.summary,
1128
+ tags: memoryData.tags || [],
1129
+ importance: memoryData.importance || 5,
1130
+ session_id: memoryData.session_id,
1131
+ embedding: args.regenerate_embeddings ? void 0 : memoryData.embedding,
1132
+ ttl_seconds: memoryData.ttl_seconds,
1133
+ expires_at: memoryData.expires_at
1134
+ };
1135
+ if (args.regenerate_embeddings) {
1136
+ await store.createMemory(createData);
1137
+ } else {
1138
+ await store.createMemory(createData);
1139
+ }
1140
+ results.imported++;
1141
+ }
1142
+ } catch (error) {
1143
+ results.errors.push(`Failed to import memory ${memoryData.id}: ${error instanceof Error ? error.message : "Unknown error"}`);
1144
+ }
1145
+ }
1146
+ const summary = [
1147
+ `Import completed:`,
1148
+ `- Imported: ${results.imported}`,
1149
+ `- Overwritten: ${results.overwritten}`,
1150
+ `- Skipped: ${results.skipped}`,
1151
+ `- Errors: ${results.errors.length}`
1152
+ ];
1153
+ if (results.errors.length > 0) {
1154
+ summary.push("", "Errors:", ...results.errors.slice(0, 10));
1155
+ if (results.errors.length > 10) {
1156
+ summary.push(`... and ${results.errors.length - 10} more errors`);
1157
+ }
1158
+ }
1159
+ return {
1160
+ content: [
1161
+ {
1162
+ type: "text",
1163
+ text: summary.join("\n")
1164
+ }
1165
+ ]
1166
+ };
1167
+ }
1168
+ async function findDuplicates(args, workspacePath) {
1169
+ const store = new MemoryStore(workspacePath);
1170
+ const memories = await store.getRecentMemories(1e4);
1171
+ const duplicateGroups = [];
1172
+ const processed = /* @__PURE__ */ new Set();
1173
+ for (let i = 0; i < memories.length; i++) {
1174
+ const memory1 = memories[i];
1175
+ if (processed.has(memory1.id)) {
1176
+ continue;
1177
+ }
1178
+ const similarMemories = [memory1];
1179
+ let maxSimilarity = 0;
1180
+ for (let j = i + 1; j < memories.length; j++) {
1181
+ const memory2 = memories[j];
1182
+ if (processed.has(memory2.id)) {
1183
+ continue;
1184
+ }
1185
+ if (memory1.embedding && memory2.embedding) {
1186
+ const similarity = cosineSimilarity(memory1.embedding, memory2.embedding);
1187
+ if (similarity >= args.similarity_threshold) {
1188
+ similarMemories.push(memory2);
1189
+ maxSimilarity = Math.max(maxSimilarity, similarity);
1190
+ processed.add(memory2.id);
1191
+ }
1192
+ }
1193
+ }
1194
+ if (similarMemories.length > 1) {
1195
+ duplicateGroups.push({
1196
+ memories: similarMemories,
1197
+ similarity_score: maxSimilarity
1198
+ });
1199
+ processed.add(memory1.id);
1200
+ }
1201
+ }
1202
+ if (args.auto_merge && duplicateGroups.length > 0) {
1203
+ let mergedCount = 0;
1204
+ for (const group of duplicateGroups) {
1205
+ try {
1206
+ const toKeep = args.keep_highest_importance ? group.memories.reduce(
1207
+ (prev, current) => current.importance > prev.importance ? current : prev
1208
+ ) : group.memories[0];
1209
+ const allTags = /* @__PURE__ */ new Set();
1210
+ for (const memory of group.memories) {
1211
+ memory.tags.forEach((tag) => allTags.add(tag));
1212
+ }
1213
+ await store.updateMemory(toKeep.id, {
1214
+ tags: Array.from(allTags)
1215
+ });
1216
+ for (const memory of group.memories) {
1217
+ if (memory.id !== toKeep.id) {
1218
+ await store.deleteMemory(memory.id);
1219
+ mergedCount++;
1220
+ }
1221
+ }
1222
+ } catch (error) {
1223
+ console.error(`Failed to merge duplicate group: ${error}`);
1224
+ }
1225
+ }
1226
+ return {
1227
+ content: [
1228
+ {
1229
+ type: "text",
1230
+ text: `Found ${duplicateGroups.length} duplicate groups and merged ${mergedCount} duplicate memories.`
1231
+ }
1232
+ ]
1233
+ };
1234
+ }
1235
+ if (duplicateGroups.length === 0) {
1236
+ return {
1237
+ content: [
1238
+ {
1239
+ type: "text",
1240
+ text: "No duplicate memories found."
1241
+ }
1242
+ ]
1243
+ };
1244
+ }
1245
+ const report = [
1246
+ `Found ${duplicateGroups.length} duplicate groups:
1247
+ `
1248
+ ];
1249
+ for (let i = 0; i < duplicateGroups.length; i++) {
1250
+ const group = duplicateGroups[i];
1251
+ report.push(`Group ${i + 1} (similarity: ${group.similarity_score.toFixed(3)}):`);
1252
+ for (const memory of group.memories) {
1253
+ report.push(` - ID: ${memory.id} | Importance: ${memory.importance} | Summary: ${memory.summary || memory.content.substring(0, 50)}`);
1254
+ }
1255
+ report.push("");
1256
+ }
1257
+ return {
1258
+ content: [
1259
+ {
1260
+ type: "text",
1261
+ text: report.join("\n")
1262
+ }
1263
+ ]
1264
+ };
1265
+ }
1266
+ async function consolidateMemories(args, workspacePath) {
1267
+ const store = new MemoryStore(workspacePath);
1268
+ const result = await store.mergeMemories(args.memory_ids, args.keep_id);
1269
+ if (!result) {
1270
+ return {
1271
+ content: [
1272
+ {
1273
+ type: "text",
1274
+ text: "Failed to consolidate memories. Check that all memory IDs exist."
1275
+ }
1276
+ ]
1277
+ };
1278
+ }
1279
+ return {
1280
+ content: [
1281
+ {
1282
+ type: "text",
1283
+ text: `Successfully consolidated ${args.memory_ids.length} memories into ID: ${result.id}
1284
+
1285
+ Merged Memory:
1286
+ - Content: ${result.summary || result.content.substring(0, 100)}
1287
+ - Tags: ${result.tags.join(", ")}
1288
+ - Importance: ${result.importance}`
1289
+ }
1290
+ ]
1291
+ };
1292
+ }
1293
+
1294
+ // src/tools/index.ts
1295
+ var memoryStore2 = new MemoryStore();
1296
+ var tools = {
1297
+ // Context management tools
1298
+ recall_relevant_context,
1299
+ analyze_and_remember,
1300
+ summarize_session,
1301
+ // Export/Import tools
1302
+ export_memories: {
1303
+ description: "Export memories to JSON format with optional filtering",
1304
+ inputSchema: zodToJsonSchema2(ExportMemoriesSchema),
1305
+ handler: async (args) => {
1306
+ try {
1307
+ return await exportMemories(args);
1308
+ } catch (error) {
1309
+ throw new McpError2(
1310
+ ErrorCode2.InternalError,
1311
+ `Failed to export memories: ${error instanceof Error ? error.message : String(error)}`
1312
+ );
1313
+ }
1314
+ }
1315
+ },
1316
+ import_memories: {
1317
+ description: "Import memories from JSON export data",
1318
+ inputSchema: zodToJsonSchema2(ImportMemoriesSchema),
1319
+ handler: async (args) => {
1320
+ try {
1321
+ return await importMemories(args);
1322
+ } catch (error) {
1323
+ throw new McpError2(
1324
+ ErrorCode2.InternalError,
1325
+ `Failed to import memories: ${error instanceof Error ? error.message : String(error)}`
1326
+ );
1327
+ }
1328
+ }
1329
+ },
1330
+ find_duplicates: {
1331
+ description: "Find and optionally merge duplicate memories based on similarity",
1332
+ inputSchema: zodToJsonSchema2(FindDuplicatesSchema),
1333
+ handler: async (args) => {
1334
+ try {
1335
+ return await findDuplicates(args);
1336
+ } catch (error) {
1337
+ throw new McpError2(
1338
+ ErrorCode2.InternalError,
1339
+ `Failed to find duplicates: ${error instanceof Error ? error.message : String(error)}`
1340
+ );
1341
+ }
1342
+ }
1343
+ },
1344
+ consolidate_memories: {
1345
+ description: "Manually consolidate multiple memories into one",
1346
+ inputSchema: zodToJsonSchema2(ConsolidateMemoriesSchema),
1347
+ handler: async (args) => {
1348
+ try {
1349
+ return await consolidateMemories(args);
1350
+ } catch (error) {
1351
+ throw new McpError2(
1352
+ ErrorCode2.InternalError,
1353
+ `Failed to consolidate memories: ${error instanceof Error ? error.message : String(error)}`
1354
+ );
1355
+ }
1356
+ }
1357
+ },
1358
+ // Original memory tools
1359
+ store_memory: {
1360
+ description: "Store a new memory/context entry for long-term persistence",
1361
+ inputSchema: zodToJsonSchema2(CreateMemorySchema),
1362
+ handler: async (args) => {
1363
+ try {
1364
+ const memory = await memoryStore2.createMemory(args);
1365
+ return {
1366
+ content: [
1367
+ {
1368
+ type: "text",
1369
+ text: JSON.stringify({
1370
+ success: true,
1371
+ memory_id: memory.id,
1372
+ timestamp: memory.timestamp,
1373
+ summary: memory.summary
1374
+ }, null, 2)
1375
+ }
1376
+ ]
1377
+ };
1378
+ } catch (error) {
1379
+ throw new McpError2(
1380
+ ErrorCode2.InternalError,
1381
+ `Failed to store memory: ${error instanceof Error ? error.message : String(error)}`
1382
+ );
1383
+ }
1384
+ }
1385
+ },
1386
+ store_batch_memories: {
1387
+ description: "Store multiple memories in a batch operation",
1388
+ inputSchema: zodToJsonSchema2(BatchCreateMemoriesSchema),
1389
+ handler: async (args) => {
1390
+ try {
1391
+ const memories = await memoryStore2.createMemories(args.memories);
1392
+ return {
1393
+ content: [
1394
+ {
1395
+ type: "text",
1396
+ text: JSON.stringify({
1397
+ success: true,
1398
+ count: memories.length,
1399
+ memory_ids: memories.map((m) => m.id)
1400
+ }, null, 2)
1401
+ }
1402
+ ]
1403
+ };
1404
+ } catch (error) {
1405
+ throw new McpError2(
1406
+ ErrorCode2.InternalError,
1407
+ `Failed to store memories: ${error instanceof Error ? error.message : String(error)}`
1408
+ );
1409
+ }
1410
+ }
1411
+ },
1412
+ update_memory: {
1413
+ description: "Update an existing memory entry",
1414
+ inputSchema: zodToJsonSchema2(UpdateMemorySchema),
1415
+ handler: async (args) => {
1416
+ try {
1417
+ const { memory_id, ...updates } = args;
1418
+ const memory = await memoryStore2.updateMemory(memory_id, updates);
1419
+ if (!memory) {
1420
+ throw new McpError2(ErrorCode2.InvalidRequest, `Memory ${memory_id} not found`);
1421
+ }
1422
+ return {
1423
+ content: [
1424
+ {
1425
+ type: "text",
1426
+ text: JSON.stringify({
1427
+ success: true,
1428
+ memory_id: memory.id,
1429
+ updated_at: Date.now()
1430
+ }, null, 2)
1431
+ }
1432
+ ]
1433
+ };
1434
+ } catch (error) {
1435
+ if (error instanceof McpError2) throw error;
1436
+ throw new McpError2(
1437
+ ErrorCode2.InternalError,
1438
+ `Failed to update memory: ${error instanceof Error ? error.message : String(error)}`
1439
+ );
1440
+ }
1441
+ }
1442
+ },
1443
+ delete_memory: {
1444
+ description: "Delete a memory entry",
1445
+ inputSchema: zodToJsonSchema2(DeleteMemorySchema),
1446
+ handler: async (args) => {
1447
+ try {
1448
+ const success = await memoryStore2.deleteMemory(args.memory_id);
1449
+ if (!success) {
1450
+ throw new McpError2(ErrorCode2.InvalidRequest, `Memory ${args.memory_id} not found`);
1451
+ }
1452
+ return {
1453
+ content: [
1454
+ {
1455
+ type: "text",
1456
+ text: JSON.stringify({
1457
+ success: true,
1458
+ memory_id: args.memory_id,
1459
+ deleted_at: Date.now()
1460
+ }, null, 2)
1461
+ }
1462
+ ]
1463
+ };
1464
+ } catch (error) {
1465
+ if (error instanceof McpError2) throw error;
1466
+ throw new McpError2(
1467
+ ErrorCode2.InternalError,
1468
+ `Failed to delete memory: ${error instanceof Error ? error.message : String(error)}`
1469
+ );
1470
+ }
1471
+ }
1472
+ },
1473
+ search_memories: {
1474
+ description: "Search memories using semantic similarity",
1475
+ inputSchema: zodToJsonSchema2(SearchMemorySchema),
1476
+ handler: async (args) => {
1477
+ try {
1478
+ const results = await memoryStore2.searchMemories(
1479
+ args.query,
1480
+ args.limit,
1481
+ args.min_importance,
1482
+ args.context_types
1483
+ );
1484
+ return {
1485
+ content: [
1486
+ {
1487
+ type: "text",
1488
+ text: JSON.stringify({
1489
+ query: args.query,
1490
+ count: results.length,
1491
+ results: results.map((r) => ({
1492
+ memory_id: r.id,
1493
+ content: r.content,
1494
+ summary: r.summary,
1495
+ context_type: r.context_type,
1496
+ importance: r.importance,
1497
+ tags: r.tags,
1498
+ similarity: r.similarity,
1499
+ timestamp: r.timestamp
1500
+ }))
1501
+ }, null, 2)
1502
+ }
1503
+ ]
1504
+ };
1505
+ } catch (error) {
1506
+ throw new McpError2(
1507
+ ErrorCode2.InternalError,
1508
+ `Failed to search memories: ${error instanceof Error ? error.message : String(error)}`
1509
+ );
1510
+ }
1511
+ }
1512
+ },
1513
+ organize_session: {
1514
+ description: "Create a session snapshot grouping related memories",
1515
+ inputSchema: zodToJsonSchema2(OrganizeSessionSchema),
1516
+ handler: async (args) => {
1517
+ try {
1518
+ const session = await memoryStore2.createSession(
1519
+ args.session_name,
1520
+ args.memory_ids,
1521
+ args.summary
1522
+ );
1523
+ return {
1524
+ content: [
1525
+ {
1526
+ type: "text",
1527
+ text: JSON.stringify({
1528
+ success: true,
1529
+ session_id: session.session_id,
1530
+ session_name: session.session_name,
1531
+ memory_count: session.memory_count,
1532
+ created_at: session.created_at
1533
+ }, null, 2)
1534
+ }
1535
+ ]
1536
+ };
1537
+ } catch (error) {
1538
+ throw new McpError2(
1539
+ ErrorCode2.InternalError,
1540
+ `Failed to organize session: ${error instanceof Error ? error.message : String(error)}`
1541
+ );
1542
+ }
1543
+ }
1544
+ }
1545
+ };
1546
+ function zodToJsonSchema2(schema) {
1547
+ if (schema instanceof z3.ZodObject) {
1548
+ const shape = schema._def.shape();
1549
+ const properties = {};
1550
+ const required = [];
1551
+ for (const [key, value] of Object.entries(shape)) {
1552
+ properties[key] = zodToJsonSchemaInner2(value);
1553
+ if (!value.isOptional()) {
1554
+ required.push(key);
1555
+ }
1556
+ }
1557
+ return {
1558
+ type: "object",
1559
+ properties,
1560
+ required
1561
+ };
1562
+ }
1563
+ return zodToJsonSchemaInner2(schema);
1564
+ }
1565
+ function zodToJsonSchemaInner2(schema) {
1566
+ if (schema instanceof z3.ZodString) {
1567
+ const result = { type: "string" };
1568
+ if (schema.description) result.description = schema.description;
1569
+ return result;
1570
+ }
1571
+ if (schema instanceof z3.ZodNumber) {
1572
+ const result = { type: "number" };
1573
+ if (schema.description) result.description = schema.description;
1574
+ return result;
1575
+ }
1576
+ if (schema instanceof z3.ZodBoolean) {
1577
+ const result = { type: "boolean" };
1578
+ if (schema.description) result.description = schema.description;
1579
+ return result;
1580
+ }
1581
+ if (schema instanceof z3.ZodArray) {
1582
+ const result = {
1583
+ type: "array",
1584
+ items: zodToJsonSchemaInner2(schema.element)
1585
+ };
1586
+ if (schema.description) result.description = schema.description;
1587
+ return result;
1588
+ }
1589
+ if (schema instanceof z3.ZodEnum) {
1590
+ const result = {
1591
+ type: "string",
1592
+ enum: schema.options
1593
+ };
1594
+ if (schema.description) result.description = schema.description;
1595
+ return result;
1596
+ }
1597
+ if (schema instanceof z3.ZodOptional) {
1598
+ return zodToJsonSchemaInner2(schema.unwrap());
1599
+ }
1600
+ if (schema instanceof z3.ZodDefault) {
1601
+ const inner = zodToJsonSchemaInner2(schema._def.innerType);
1602
+ inner.default = schema._def.defaultValue();
1603
+ return inner;
1604
+ }
1605
+ if (schema instanceof z3.ZodObject) {
1606
+ return zodToJsonSchema2(schema);
1607
+ }
1608
+ return { type: "string" };
1609
+ }
1610
+
1611
+ // src/resources/index.ts
1612
+ import { McpError as McpError3, ErrorCode as ErrorCode3 } from "@modelcontextprotocol/sdk/types.js";
1613
+
1614
+ // src/resources/analytics.ts
1615
+ async function getAnalytics(workspacePath) {
1616
+ const store = new MemoryStore(workspacePath);
1617
+ const stats = await store.getSummaryStats();
1618
+ const recentMemories = await store.getRecentMemories(1e3);
1619
+ const now = Date.now();
1620
+ const day24h = now - 24 * 60 * 60 * 1e3;
1621
+ const day7d = now - 7 * 24 * 60 * 60 * 1e3;
1622
+ const day30d = now - 30 * 24 * 60 * 60 * 1e3;
1623
+ const memories24h = recentMemories.filter((m) => m.timestamp >= day24h);
1624
+ const memories7d = recentMemories.filter((m) => m.timestamp >= day7d);
1625
+ const memories30d = recentMemories.filter((m) => m.timestamp >= day30d);
1626
+ const typeCount24h = /* @__PURE__ */ new Map();
1627
+ for (const memory of memories24h) {
1628
+ typeCount24h.set(memory.context_type, (typeCount24h.get(memory.context_type) || 0) + 1);
1629
+ }
1630
+ const mostActiveTypes24h = Array.from(typeCount24h.entries()).map(([type, count]) => ({ type, count })).sort((a, b) => b.count - a.count).slice(0, 5);
1631
+ const tagCount = /* @__PURE__ */ new Map();
1632
+ for (const memory of recentMemories) {
1633
+ for (const tag of memory.tags) {
1634
+ tagCount.set(tag, (tagCount.get(tag) || 0) + 1);
1635
+ }
1636
+ }
1637
+ const topTags = Array.from(tagCount.entries()).map(([tag, count]) => ({ tag, count })).sort((a, b) => b.count - a.count).slice(0, 10);
1638
+ const importanceDist = {
1639
+ critical: recentMemories.filter((m) => m.importance >= 9).length,
1640
+ high: recentMemories.filter((m) => m.importance >= 7 && m.importance < 9).length,
1641
+ medium: recentMemories.filter((m) => m.importance >= 5 && m.importance < 7).length,
1642
+ low: recentMemories.filter((m) => m.importance < 5).length
1643
+ };
1644
+ const activityByDay = /* @__PURE__ */ new Map();
1645
+ const last7Days = [];
1646
+ for (let i = 6; i >= 0; i--) {
1647
+ const date = new Date(now - i * 24 * 60 * 60 * 1e3);
1648
+ const dateStr = date.toISOString().split("T")[0];
1649
+ last7Days.push(dateStr);
1650
+ activityByDay.set(dateStr, { count: 0, types: /* @__PURE__ */ new Map() });
1651
+ }
1652
+ for (const memory of memories7d) {
1653
+ const dateStr = new Date(memory.timestamp).toISOString().split("T")[0];
1654
+ const activity = activityByDay.get(dateStr);
1655
+ if (activity) {
1656
+ activity.count++;
1657
+ activity.types.set(memory.context_type, (activity.types.get(memory.context_type) || 0) + 1);
1658
+ }
1659
+ }
1660
+ const recentActivity = last7Days.map((date) => {
1661
+ const activity = activityByDay.get(date);
1662
+ return {
1663
+ date,
1664
+ count: activity.count,
1665
+ types: Object.fromEntries(activity.types)
1666
+ };
1667
+ });
1668
+ const analytics = {
1669
+ overview: stats,
1670
+ trends: {
1671
+ memories_last_24h: memories24h.length,
1672
+ memories_last_7d: memories7d.length,
1673
+ memories_last_30d: memories30d.length,
1674
+ most_active_types_24h: mostActiveTypes24h
1675
+ },
1676
+ top_tags: topTags,
1677
+ importance_distribution: importanceDist,
1678
+ recent_activity: recentActivity
1679
+ };
1680
+ return formatAnalytics(analytics);
1681
+ }
1682
+ function formatAnalytics(data) {
1683
+ const lines = [
1684
+ "# Memory Analytics Dashboard",
1685
+ "",
1686
+ `**Workspace**: ${data.overview.workspace_path}`,
1687
+ "",
1688
+ "## Overview",
1689
+ `- Total Memories: ${data.overview.total_memories}`,
1690
+ `- Sessions: ${data.overview.total_sessions}`,
1691
+ `- Important Memories (\u22658): ${data.overview.important_count}`,
1692
+ "",
1693
+ "### Memories by Type"
1694
+ ];
1695
+ for (const [type, count] of Object.entries(data.overview.by_type)) {
1696
+ if (count > 0) {
1697
+ lines.push(`- ${type}: ${count}`);
1698
+ }
1699
+ }
1700
+ lines.push("", "## Recent Activity Trends");
1701
+ lines.push(`- Last 24 hours: ${data.trends.memories_last_24h} memories`);
1702
+ lines.push(`- Last 7 days: ${data.trends.memories_last_7d} memories`);
1703
+ lines.push(`- Last 30 days: ${data.trends.memories_last_30d} memories`);
1704
+ if (data.trends.most_active_types_24h.length > 0) {
1705
+ lines.push("", "### Most Active Types (24h)");
1706
+ for (const { type, count } of data.trends.most_active_types_24h) {
1707
+ lines.push(`- ${type}: ${count}`);
1708
+ }
1709
+ }
1710
+ if (data.top_tags.length > 0) {
1711
+ lines.push("", "## Top Tags");
1712
+ for (const { tag, count } of data.top_tags) {
1713
+ lines.push(`- ${tag}: ${count}`);
1714
+ }
1715
+ }
1716
+ lines.push("", "## Importance Distribution");
1717
+ lines.push(`- Critical (9-10): ${data.importance_distribution.critical}`);
1718
+ lines.push(`- High (7-8): ${data.importance_distribution.high}`);
1719
+ lines.push(`- Medium (5-6): ${data.importance_distribution.medium}`);
1720
+ lines.push(`- Low (1-4): ${data.importance_distribution.low}`);
1721
+ lines.push("", "## Activity Last 7 Days");
1722
+ for (const activity of data.recent_activity) {
1723
+ const typeSummary = Object.entries(activity.types).map(([type, count]) => `${type}:${count}`).join(", ");
1724
+ lines.push(`- ${activity.date}: ${activity.count} memories ${typeSummary ? `(${typeSummary})` : ""}`);
1725
+ }
1726
+ return lines.join("\n");
1727
+ }
1728
+
1729
+ // src/resources/index.ts
1730
+ var memoryStore3 = new MemoryStore();
1731
+ var resources = {
1732
+ "memory://recent": {
1733
+ name: "Recent Memories",
1734
+ description: "Get the most recent memories (default: 50)",
1735
+ mimeType: "application/json",
1736
+ handler: async (uri) => {
1737
+ const limit = parseInt(uri.searchParams.get("limit") || "50", 10);
1738
+ const memories = await memoryStore3.getRecentMemories(limit);
1739
+ return {
1740
+ contents: [
1741
+ {
1742
+ uri: uri.toString(),
1743
+ mimeType: "application/json",
1744
+ text: JSON.stringify(
1745
+ {
1746
+ count: memories.length,
1747
+ memories: memories.map((m) => ({
1748
+ memory_id: m.id,
1749
+ content: m.content,
1750
+ summary: m.summary,
1751
+ context_type: m.context_type,
1752
+ importance: m.importance,
1753
+ tags: m.tags,
1754
+ timestamp: m.timestamp,
1755
+ session_id: m.session_id
1756
+ }))
1757
+ },
1758
+ null,
1759
+ 2
1760
+ )
1761
+ }
1762
+ ]
1763
+ };
1764
+ }
1765
+ },
1766
+ "memory://by-type/{type}": {
1767
+ name: "Memories by Type",
1768
+ description: "Get memories filtered by context type",
1769
+ mimeType: "application/json",
1770
+ handler: async (uri, params) => {
1771
+ const type = params.type;
1772
+ const limit = uri.searchParams.get("limit") ? parseInt(uri.searchParams.get("limit"), 10) : void 0;
1773
+ const memories = await memoryStore3.getMemoriesByType(type, limit);
1774
+ return {
1775
+ contents: [
1776
+ {
1777
+ uri: uri.toString(),
1778
+ mimeType: "application/json",
1779
+ text: JSON.stringify(
1780
+ {
1781
+ context_type: type,
1782
+ count: memories.length,
1783
+ memories: memories.map((m) => ({
1784
+ memory_id: m.id,
1785
+ content: m.content,
1786
+ summary: m.summary,
1787
+ importance: m.importance,
1788
+ tags: m.tags,
1789
+ timestamp: m.timestamp
1790
+ }))
1791
+ },
1792
+ null,
1793
+ 2
1794
+ )
1795
+ }
1796
+ ]
1797
+ };
1798
+ }
1799
+ },
1800
+ "memory://by-tag/{tag}": {
1801
+ name: "Memories by Tag",
1802
+ description: "Get memories filtered by tag",
1803
+ mimeType: "application/json",
1804
+ handler: async (uri, params) => {
1805
+ const { tag } = params;
1806
+ const limit = uri.searchParams.get("limit") ? parseInt(uri.searchParams.get("limit"), 10) : void 0;
1807
+ const memories = await memoryStore3.getMemoriesByTag(tag, limit);
1808
+ return {
1809
+ contents: [
1810
+ {
1811
+ uri: uri.toString(),
1812
+ mimeType: "application/json",
1813
+ text: JSON.stringify(
1814
+ {
1815
+ tag,
1816
+ count: memories.length,
1817
+ memories: memories.map((m) => ({
1818
+ memory_id: m.id,
1819
+ content: m.content,
1820
+ summary: m.summary,
1821
+ context_type: m.context_type,
1822
+ importance: m.importance,
1823
+ tags: m.tags,
1824
+ timestamp: m.timestamp
1825
+ }))
1826
+ },
1827
+ null,
1828
+ 2
1829
+ )
1830
+ }
1831
+ ]
1832
+ };
1833
+ }
1834
+ },
1835
+ "memory://important": {
1836
+ name: "Important Memories",
1837
+ description: "Get high-importance memories (importance >= 8)",
1838
+ mimeType: "application/json",
1839
+ handler: async (uri) => {
1840
+ const minImportance = parseInt(uri.searchParams.get("min") || "8", 10);
1841
+ const limit = uri.searchParams.get("limit") ? parseInt(uri.searchParams.get("limit"), 10) : void 0;
1842
+ const memories = await memoryStore3.getImportantMemories(minImportance, limit);
1843
+ return {
1844
+ contents: [
1845
+ {
1846
+ uri: uri.toString(),
1847
+ mimeType: "application/json",
1848
+ text: JSON.stringify(
1849
+ {
1850
+ min_importance: minImportance,
1851
+ count: memories.length,
1852
+ memories: memories.map((m) => ({
1853
+ memory_id: m.id,
1854
+ content: m.content,
1855
+ summary: m.summary,
1856
+ context_type: m.context_type,
1857
+ importance: m.importance,
1858
+ tags: m.tags,
1859
+ timestamp: m.timestamp
1860
+ }))
1861
+ },
1862
+ null,
1863
+ 2
1864
+ )
1865
+ }
1866
+ ]
1867
+ };
1868
+ }
1869
+ },
1870
+ "memory://session/{session_id}": {
1871
+ name: "Session Memories",
1872
+ description: "Get all memories in a specific session",
1873
+ mimeType: "application/json",
1874
+ handler: async (uri, params) => {
1875
+ const { session_id } = params;
1876
+ const session = await memoryStore3.getSession(session_id);
1877
+ if (!session) {
1878
+ throw new McpError3(ErrorCode3.InvalidRequest, `Session ${session_id} not found`);
1879
+ }
1880
+ const memories = await memoryStore3.getSessionMemories(session_id);
1881
+ return {
1882
+ contents: [
1883
+ {
1884
+ uri: uri.toString(),
1885
+ mimeType: "application/json",
1886
+ text: JSON.stringify(
1887
+ {
1888
+ session_id: session.session_id,
1889
+ session_name: session.session_name,
1890
+ created_at: session.created_at,
1891
+ summary: session.summary,
1892
+ count: memories.length,
1893
+ memories: memories.map((m) => ({
1894
+ memory_id: m.id,
1895
+ content: m.content,
1896
+ summary: m.summary,
1897
+ context_type: m.context_type,
1898
+ importance: m.importance,
1899
+ tags: m.tags,
1900
+ timestamp: m.timestamp
1901
+ }))
1902
+ },
1903
+ null,
1904
+ 2
1905
+ )
1906
+ }
1907
+ ]
1908
+ };
1909
+ }
1910
+ },
1911
+ "memory://sessions": {
1912
+ name: "All Sessions",
1913
+ description: "Get list of all sessions",
1914
+ mimeType: "application/json",
1915
+ handler: async (uri) => {
1916
+ const sessions = await memoryStore3.getAllSessions();
1917
+ return {
1918
+ contents: [
1919
+ {
1920
+ uri: uri.toString(),
1921
+ mimeType: "application/json",
1922
+ text: JSON.stringify(
1923
+ {
1924
+ count: sessions.length,
1925
+ sessions: sessions.map((s) => ({
1926
+ session_id: s.session_id,
1927
+ session_name: s.session_name,
1928
+ created_at: s.created_at,
1929
+ memory_count: s.memory_count,
1930
+ summary: s.summary
1931
+ }))
1932
+ },
1933
+ null,
1934
+ 2
1935
+ )
1936
+ }
1937
+ ]
1938
+ };
1939
+ }
1940
+ },
1941
+ "memory://summary": {
1942
+ name: "Memory Summary",
1943
+ description: "Get overall summary statistics of stored memories",
1944
+ mimeType: "application/json",
1945
+ handler: async (uri) => {
1946
+ const stats = await memoryStore3.getSummaryStats();
1947
+ return {
1948
+ contents: [
1949
+ {
1950
+ uri: uri.toString(),
1951
+ mimeType: "application/json",
1952
+ text: JSON.stringify(stats, null, 2)
1953
+ }
1954
+ ]
1955
+ };
1956
+ }
1957
+ },
1958
+ "memory://search": {
1959
+ name: "Search Memories",
1960
+ description: "Search memories using semantic similarity",
1961
+ mimeType: "application/json",
1962
+ handler: async (uri) => {
1963
+ const query = uri.searchParams.get("q");
1964
+ if (!query) {
1965
+ throw new McpError3(ErrorCode3.InvalidRequest, 'Query parameter "q" is required');
1966
+ }
1967
+ const limit = parseInt(uri.searchParams.get("limit") || "10", 10);
1968
+ const minImportance = uri.searchParams.get("min_importance") ? parseInt(uri.searchParams.get("min_importance"), 10) : void 0;
1969
+ const results = await memoryStore3.searchMemories(query, limit, minImportance);
1970
+ return {
1971
+ contents: [
1972
+ {
1973
+ uri: uri.toString(),
1974
+ mimeType: "application/json",
1975
+ text: JSON.stringify(
1976
+ {
1977
+ query,
1978
+ count: results.length,
1979
+ results: results.map((r) => ({
1980
+ memory_id: r.id,
1981
+ content: r.content,
1982
+ summary: r.summary,
1983
+ context_type: r.context_type,
1984
+ importance: r.importance,
1985
+ tags: r.tags,
1986
+ similarity: r.similarity,
1987
+ timestamp: r.timestamp
1988
+ }))
1989
+ },
1990
+ null,
1991
+ 2
1992
+ )
1993
+ }
1994
+ ]
1995
+ };
1996
+ }
1997
+ },
1998
+ "memory://analytics": {
1999
+ name: "Memory Analytics",
2000
+ description: "Get detailed analytics about memory usage and trends",
2001
+ mimeType: "text/markdown",
2002
+ handler: async (uri) => {
2003
+ const analytics = await getAnalytics();
2004
+ return {
2005
+ contents: [
2006
+ {
2007
+ uri: uri.toString(),
2008
+ mimeType: "text/markdown",
2009
+ text: analytics
2010
+ }
2011
+ ]
2012
+ };
2013
+ }
2014
+ }
2015
+ };
2016
+
2017
+ // src/prompts/formatters.ts
2018
+ function formatWorkspaceContext(workspacePath, directives, decisions, patterns) {
2019
+ const sections = [];
2020
+ sections.push(`# Workspace Context: ${workspacePath}`);
2021
+ sections.push("");
2022
+ sections.push("*Critical information to remember for this project*");
2023
+ sections.push("");
2024
+ if (directives.length > 0) {
2025
+ sections.push("## \u{1F3AF} Critical Directives");
2026
+ sections.push("");
2027
+ const sorted = directives.sort((a, b) => b.importance - a.importance);
2028
+ for (const dir of sorted.slice(0, 10)) {
2029
+ sections.push(`- **[Importance: ${dir.importance}/10]** ${dir.content}`);
2030
+ if (dir.tags.length > 0) {
2031
+ sections.push(` *Tags: ${dir.tags.join(", ")}*`);
2032
+ }
2033
+ }
2034
+ sections.push("");
2035
+ }
2036
+ if (decisions.length > 0) {
2037
+ sections.push("## \u{1F4A1} Key Decisions");
2038
+ sections.push("");
2039
+ const sorted = decisions.sort((a, b) => b.importance - a.importance);
2040
+ for (const dec of sorted.slice(0, 8)) {
2041
+ const age = getAgeString(dec.timestamp);
2042
+ sections.push(`- **[${age}]** ${dec.content}`);
2043
+ }
2044
+ sections.push("");
2045
+ }
2046
+ if (patterns.length > 0) {
2047
+ sections.push("## \u{1F527} Code Patterns & Conventions");
2048
+ sections.push("");
2049
+ const sorted = patterns.sort((a, b) => b.importance - a.importance);
2050
+ for (const pat of sorted.slice(0, 8)) {
2051
+ sections.push(`- ${pat.content}`);
2052
+ if (pat.tags.length > 0) {
2053
+ sections.push(` *Applies to: ${pat.tags.join(", ")}*`);
2054
+ }
2055
+ }
2056
+ sections.push("");
2057
+ }
2058
+ if (directives.length === 0 && decisions.length === 0 && patterns.length === 0) {
2059
+ sections.push("*No critical context stored yet. As we work, I'll remember important patterns and decisions.*");
2060
+ }
2061
+ sections.push("");
2062
+ sections.push("---");
2063
+ sections.push("*This context is automatically injected to help me remember important project conventions and decisions.*");
2064
+ return sections.join("\n");
2065
+ }
2066
+ function getAgeString(timestamp) {
2067
+ const ageMs = Date.now() - timestamp;
2068
+ const ageMinutes = Math.floor(ageMs / (1e3 * 60));
2069
+ const ageHours = Math.floor(ageMs / (1e3 * 60 * 60));
2070
+ const ageDays = Math.floor(ageMs / (1e3 * 60 * 60 * 24));
2071
+ if (ageDays > 0) {
2072
+ return `${ageDays}d ago`;
2073
+ } else if (ageHours > 0) {
2074
+ return `${ageHours}h ago`;
2075
+ } else if (ageMinutes > 0) {
2076
+ return `${ageMinutes}m ago`;
2077
+ } else {
2078
+ return "just now";
2079
+ }
2080
+ }
2081
+
2082
+ // src/prompts/index.ts
2083
+ var memoryStore4 = new MemoryStore();
2084
+ var prompts = {
2085
+ workspace_context: {
2086
+ name: "workspace_context",
2087
+ description: "Critical workspace context: directives, decisions, and code patterns",
2088
+ arguments: [],
2089
+ handler: async () => {
2090
+ const directives = await memoryStore4.getMemoriesByType("directive");
2091
+ const decisions = await memoryStore4.getMemoriesByType("decision");
2092
+ const patterns = await memoryStore4.getMemoriesByType("code_pattern");
2093
+ const importantDirectives = directives.filter((d) => d.importance >= 8);
2094
+ const importantDecisions = decisions.filter((d) => d.importance >= 7);
2095
+ const importantPatterns = patterns.filter((p) => p.importance >= 7);
2096
+ const stats = await memoryStore4.getSummaryStats();
2097
+ const workspacePath = stats.workspace_path;
2098
+ const contextText = formatWorkspaceContext(
2099
+ workspacePath,
2100
+ importantDirectives,
2101
+ importantDecisions,
2102
+ importantPatterns
2103
+ );
2104
+ return {
2105
+ description: "Workspace-specific context and conventions",
2106
+ messages: [
2107
+ {
2108
+ role: "user",
2109
+ content: {
2110
+ type: "text",
2111
+ text: contextText
2112
+ }
2113
+ }
2114
+ ]
2115
+ };
2116
+ }
2117
+ }
2118
+ };
2119
+ async function listPrompts() {
2120
+ return Object.values(prompts).map((p) => ({
2121
+ name: p.name,
2122
+ description: p.description,
2123
+ arguments: p.arguments
2124
+ }));
2125
+ }
2126
+ async function getPrompt(name) {
2127
+ const prompt = prompts[name];
2128
+ if (!prompt) {
2129
+ throw new Error(`Unknown prompt: ${name}`);
2130
+ }
2131
+ return await prompt.handler();
2132
+ }
2133
+
2134
+ // src/index.ts
2135
+ var server = new Server(
2136
+ {
2137
+ name: "@joseairosa/recall",
2138
+ version: "1.2.0"
2139
+ },
2140
+ {
2141
+ capabilities: {
2142
+ resources: {},
2143
+ tools: {},
2144
+ prompts: {}
2145
+ }
2146
+ }
2147
+ );
2148
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
2149
+ return {
2150
+ tools: Object.entries(tools).map(([name, tool]) => ({
2151
+ name,
2152
+ description: tool.description,
2153
+ inputSchema: tool.inputSchema
2154
+ }))
2155
+ };
2156
+ });
2157
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2158
+ const { name, arguments: args } = request.params;
2159
+ const tool = tools[name];
2160
+ if (!tool) {
2161
+ throw new Error(`Unknown tool: ${name}`);
2162
+ }
2163
+ return await tool.handler(args);
2164
+ });
2165
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
2166
+ return {
2167
+ resources: [
2168
+ {
2169
+ uri: "memory://recent",
2170
+ name: "Recent Memories",
2171
+ description: "Get the most recent memories (default: 50)",
2172
+ mimeType: "application/json"
2173
+ },
2174
+ {
2175
+ uri: "memory://by-type/{type}",
2176
+ name: "Memories by Type",
2177
+ description: "Get memories filtered by context type (directive, information, heading, decision, code_pattern, requirement, error, todo, insight, preference)",
2178
+ mimeType: "application/json"
2179
+ },
2180
+ {
2181
+ uri: "memory://by-tag/{tag}",
2182
+ name: "Memories by Tag",
2183
+ description: "Get memories filtered by tag",
2184
+ mimeType: "application/json"
2185
+ },
2186
+ {
2187
+ uri: "memory://important",
2188
+ name: "Important Memories",
2189
+ description: "Get high-importance memories (importance >= 8)",
2190
+ mimeType: "application/json"
2191
+ },
2192
+ {
2193
+ uri: "memory://session/{session_id}",
2194
+ name: "Session Memories",
2195
+ description: "Get all memories in a specific session",
2196
+ mimeType: "application/json"
2197
+ },
2198
+ {
2199
+ uri: "memory://sessions",
2200
+ name: "All Sessions",
2201
+ description: "Get list of all sessions",
2202
+ mimeType: "application/json"
2203
+ },
2204
+ {
2205
+ uri: "memory://summary",
2206
+ name: "Memory Summary",
2207
+ description: "Get overall summary statistics",
2208
+ mimeType: "application/json"
2209
+ },
2210
+ {
2211
+ uri: "memory://search",
2212
+ name: "Search Memories",
2213
+ description: 'Search memories using semantic similarity. Requires query parameter "q"',
2214
+ mimeType: "application/json"
2215
+ }
2216
+ ]
2217
+ };
2218
+ });
2219
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
2220
+ const uriString = request.params.uri;
2221
+ const uri = new URL(uriString);
2222
+ const resourcePath = uri.hostname + uri.pathname;
2223
+ if (resourcePath === "recent") {
2224
+ return await resources["memory://recent"].handler(uri);
2225
+ }
2226
+ if (resourcePath === "important") {
2227
+ return await resources["memory://important"].handler(uri);
2228
+ }
2229
+ if (resourcePath === "sessions") {
2230
+ return await resources["memory://sessions"].handler(uri);
2231
+ }
2232
+ if (resourcePath === "summary") {
2233
+ return await resources["memory://summary"].handler(uri);
2234
+ }
2235
+ if (resourcePath === "search") {
2236
+ return await resources["memory://search"].handler(uri);
2237
+ }
2238
+ if (resourcePath === "analytics") {
2239
+ return await resources["memory://analytics"].handler(uri);
2240
+ }
2241
+ const typeMatch = resourcePath.match(/^by-type\/(.+)$/);
2242
+ if (typeMatch) {
2243
+ return await resources["memory://by-type/{type}"].handler(uri, { type: typeMatch[1] });
2244
+ }
2245
+ const tagMatch = resourcePath.match(/^by-tag\/(.+)$/);
2246
+ if (tagMatch) {
2247
+ return await resources["memory://by-tag/{tag}"].handler(uri, { tag: tagMatch[1] });
2248
+ }
2249
+ const sessionMatch = resourcePath.match(/^session\/(.+)$/);
2250
+ if (sessionMatch) {
2251
+ return await resources["memory://session/{session_id}"].handler(uri, { session_id: sessionMatch[1] });
2252
+ }
2253
+ throw new Error(`Unknown resource: ${request.params.uri}`);
2254
+ });
2255
+ server.setRequestHandler(ListPromptsRequestSchema, async () => {
2256
+ const promptsList = await listPrompts();
2257
+ return {
2258
+ prompts: promptsList
2259
+ };
2260
+ });
2261
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
2262
+ const promptResult = await getPrompt(request.params.name);
2263
+ return promptResult;
2264
+ });
2265
+ async function main() {
2266
+ console.error("Checking Redis connection...");
2267
+ const isConnected = await checkRedisConnection();
2268
+ if (!isConnected) {
2269
+ console.error("ERROR: Failed to connect to Redis");
2270
+ console.error("Please ensure Redis is running and REDIS_URL is set correctly");
2271
+ process.exit(1);
2272
+ }
2273
+ console.error("Redis connection successful");
2274
+ const transport = new StdioServerTransport();
2275
+ await server.connect(transport);
2276
+ console.error("Recall MCP Server started successfully");
2277
+ console.error("Listening on stdio...");
2278
+ }
2279
+ process.on("SIGINT", async () => {
2280
+ console.error("\nShutting down...");
2281
+ await closeRedisClient();
2282
+ process.exit(0);
2283
+ });
2284
+ process.on("SIGTERM", async () => {
2285
+ console.error("\nShutting down...");
2286
+ await closeRedisClient();
2287
+ process.exit(0);
2288
+ });
2289
+ main().catch((error) => {
2290
+ console.error("Fatal error:", error);
2291
+ process.exit(1);
2292
+ });
2293
+ //# sourceMappingURL=index.js.map