@aeriondyseti/vector-memory-mcp 1.1.0-dev.2 → 1.1.0-dev.3

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.
Files changed (92) hide show
  1. package/dist/package.json +1 -1
  2. package/dist/src/config/index.d.ts +17 -10
  3. package/dist/src/config/index.d.ts.map +1 -1
  4. package/dist/src/config/index.js +25 -11
  5. package/dist/src/config/index.js.map +1 -1
  6. package/dist/src/db/conversation.repository.d.ts +26 -0
  7. package/dist/src/db/conversation.repository.d.ts.map +1 -0
  8. package/dist/src/db/conversation.repository.js +72 -0
  9. package/dist/src/db/conversation.repository.js.map +1 -0
  10. package/dist/src/db/conversation.schema.d.ts +4 -0
  11. package/dist/src/db/conversation.schema.d.ts.map +1 -0
  12. package/dist/src/db/conversation.schema.js +15 -0
  13. package/dist/src/db/conversation.schema.js.map +1 -0
  14. package/dist/src/db/lancedb-utils.d.ts +13 -3
  15. package/dist/src/db/lancedb-utils.d.ts.map +1 -1
  16. package/dist/src/db/lancedb-utils.js +36 -7
  17. package/dist/src/db/lancedb-utils.js.map +1 -1
  18. package/dist/src/db/memory.repository.js +7 -7
  19. package/dist/src/db/memory.repository.js.map +1 -1
  20. package/dist/src/http/server.d.ts.map +1 -1
  21. package/dist/src/http/server.js +26 -7
  22. package/dist/src/http/server.js.map +1 -1
  23. package/dist/src/index.js +7 -6
  24. package/dist/src/index.js.map +1 -1
  25. package/dist/src/mcp/handlers.d.ts +1 -1
  26. package/dist/src/mcp/handlers.d.ts.map +1 -1
  27. package/dist/src/mcp/handlers.js +106 -117
  28. package/dist/src/mcp/handlers.js.map +1 -1
  29. package/dist/src/mcp/tools.d.ts.map +1 -1
  30. package/dist/src/mcp/tools.js +43 -14
  31. package/dist/src/mcp/tools.js.map +1 -1
  32. package/dist/src/services/conversation.service.d.ts +38 -0
  33. package/dist/src/services/conversation.service.d.ts.map +1 -0
  34. package/dist/src/services/conversation.service.js +252 -0
  35. package/dist/src/services/conversation.service.js.map +1 -0
  36. package/dist/src/services/memory.service.d.ts +7 -25
  37. package/dist/src/services/memory.service.d.ts.map +1 -1
  38. package/dist/src/services/memory.service.js +66 -80
  39. package/dist/src/services/memory.service.js.map +1 -1
  40. package/dist/src/services/parsers/claude-code.parser.d.ts +8 -0
  41. package/dist/src/services/parsers/claude-code.parser.d.ts.map +1 -0
  42. package/dist/src/services/parsers/claude-code.parser.js +191 -0
  43. package/dist/src/services/parsers/claude-code.parser.js.map +1 -0
  44. package/dist/src/services/parsers/types.d.ts +9 -0
  45. package/dist/src/services/parsers/types.d.ts.map +1 -0
  46. package/dist/src/services/parsers/types.js +2 -0
  47. package/dist/src/services/parsers/types.js.map +1 -0
  48. package/dist/src/types/conversation.d.ts +99 -0
  49. package/dist/src/types/conversation.d.ts.map +1 -0
  50. package/dist/src/types/conversation.js +2 -0
  51. package/dist/src/types/conversation.js.map +1 -0
  52. package/hooks/session-start.ts +60 -42
  53. package/package.json +1 -1
  54. package/src/config/index.ts +39 -21
  55. package/src/db/conversation.repository.ts +120 -0
  56. package/src/db/conversation.schema.ts +33 -0
  57. package/src/db/lancedb-utils.ts +35 -7
  58. package/src/db/memory.repository.ts +7 -7
  59. package/src/http/server.ts +31 -7
  60. package/src/index.ts +10 -11
  61. package/src/mcp/handlers.ts +121 -123
  62. package/src/mcp/tools.ts +44 -15
  63. package/src/services/conversation.service.ts +354 -0
  64. package/src/services/memory.service.ts +101 -105
  65. package/src/services/parsers/claude-code.parser.ts +242 -0
  66. package/src/services/parsers/types.ts +14 -0
  67. package/src/types/conversation.ts +108 -0
  68. package/dist/src/db/conversation-history.repository.d.ts +0 -24
  69. package/dist/src/db/conversation-history.repository.d.ts.map +0 -1
  70. package/dist/src/db/conversation-history.repository.js +0 -184
  71. package/dist/src/db/conversation-history.repository.js.map +0 -1
  72. package/dist/src/db/conversation-history.schema.d.ts +0 -10
  73. package/dist/src/db/conversation-history.schema.d.ts.map +0 -1
  74. package/dist/src/db/conversation-history.schema.js +0 -31
  75. package/dist/src/db/conversation-history.schema.js.map +0 -1
  76. package/dist/src/services/conversation-history.service.d.ts +0 -64
  77. package/dist/src/services/conversation-history.service.d.ts.map +0 -1
  78. package/dist/src/services/conversation-history.service.js +0 -244
  79. package/dist/src/services/conversation-history.service.js.map +0 -1
  80. package/dist/src/services/session-parser.d.ts +0 -59
  81. package/dist/src/services/session-parser.d.ts.map +0 -1
  82. package/dist/src/services/session-parser.js +0 -147
  83. package/dist/src/services/session-parser.js.map +0 -1
  84. package/dist/src/types/conversation-history.d.ts +0 -74
  85. package/dist/src/types/conversation-history.d.ts.map +0 -1
  86. package/dist/src/types/conversation-history.js +0 -2
  87. package/dist/src/types/conversation-history.js.map +0 -1
  88. package/src/db/conversation-history.repository.ts +0 -255
  89. package/src/db/conversation-history.schema.ts +0 -40
  90. package/src/services/conversation-history.service.ts +0 -320
  91. package/src/services/session-parser.ts +0 -232
  92. package/src/types/conversation-history.ts +0 -82
@@ -0,0 +1,120 @@
1
+ import * as lancedb from "@lancedb/lancedb";
2
+ import { type Table } from "@lancedb/lancedb";
3
+ import {
4
+ CONVERSATION_TABLE_NAME,
5
+ conversationSchema,
6
+ } from "./conversation.schema.js";
7
+ import type {
8
+ ConversationHybridRow,
9
+ HistoryFilters,
10
+ } from "../types/conversation.js";
11
+ import {
12
+ getOrCreateTable,
13
+ createFtsMutex,
14
+ createRerankerMutex,
15
+ escapeSql,
16
+ safeParseJsonObject,
17
+ } from "./lancedb-utils.js";
18
+
19
+ export class ConversationRepository {
20
+ private tablePromise: Promise<Table> | null = null;
21
+
22
+ // FTS index mutex — recreated after data mutations to force re-check
23
+ private ensureFtsIndex = createFtsMutex(() => this.getTable());
24
+
25
+ // Cached reranker — k=60 is constant, no need to recreate per search
26
+ private getReranker = createRerankerMutex();
27
+
28
+ constructor(private db: lancedb.Connection) {}
29
+
30
+ private getTable(): Promise<Table> {
31
+ if (!this.tablePromise) {
32
+ this.tablePromise = getOrCreateTable(
33
+ this.db,
34
+ CONVERSATION_TABLE_NAME,
35
+ conversationSchema,
36
+ ).catch((err) => {
37
+ this.tablePromise = null;
38
+ throw err;
39
+ });
40
+ }
41
+ return this.tablePromise;
42
+ }
43
+
44
+ private rowToConversationHybridRow(
45
+ row: Record<string, unknown>
46
+ ): ConversationHybridRow {
47
+ const metadata = safeParseJsonObject(row.metadata as string);
48
+ return {
49
+ id: row.id as string,
50
+ content: row.content as string,
51
+ metadata,
52
+ createdAt: new Date(row.created_at as number),
53
+ rrfScore: (row._relevance_score as number) ?? 0,
54
+ };
55
+ }
56
+
57
+ async insertBatch(
58
+ rows: Array<{
59
+ id: string;
60
+ vector: number[];
61
+ content: string;
62
+ metadata: string;
63
+ created_at: number;
64
+ session_id: string;
65
+ role: string;
66
+ message_index_start: number;
67
+ message_index_end: number;
68
+ project: string;
69
+ }>
70
+ ): Promise<void> {
71
+ if (rows.length === 0) return;
72
+ const table = await this.getTable();
73
+ await table.add(rows);
74
+ // Reset FTS mutex so index existence is re-verified after new data
75
+ this.ensureFtsIndex = createFtsMutex(() => this.getTable());
76
+ }
77
+
78
+ async deleteBySessionId(sessionId: string): Promise<void> {
79
+ const table = await this.getTable();
80
+ await table.delete(`session_id = '${escapeSql(sessionId)}'`);
81
+ this.ensureFtsIndex = createFtsMutex(() => this.getTable());
82
+ }
83
+
84
+ async findHybrid(
85
+ embedding: number[],
86
+ query: string,
87
+ limit: number,
88
+ filters?: HistoryFilters
89
+ ): Promise<ConversationHybridRow[]> {
90
+ await this.ensureFtsIndex();
91
+ const table = await this.getTable();
92
+ const reranker = await this.getReranker();
93
+
94
+ let queryBuilder = table
95
+ .query()
96
+ .nearestTo(embedding)
97
+ .fullTextSearch(query)
98
+ .rerank(reranker);
99
+
100
+ const conditions: string[] = [];
101
+ if (filters?.sessionId)
102
+ conditions.push(`session_id = '${escapeSql(filters.sessionId)}'`);
103
+ if (filters?.role) conditions.push(`role = '${escapeSql(filters.role)}'`);
104
+ if (filters?.project)
105
+ conditions.push(`project = '${escapeSql(filters.project)}'`);
106
+ if (filters?.after)
107
+ conditions.push(`created_at > ${filters.after.getTime()}`);
108
+ if (filters?.before)
109
+ conditions.push(`created_at < ${filters.before.getTime()}`);
110
+
111
+ if (conditions.length > 0) {
112
+ queryBuilder = queryBuilder.where(conditions.join(" AND "));
113
+ }
114
+
115
+ const results = await queryBuilder.limit(limit).toArray();
116
+ return results.map((row) =>
117
+ this.rowToConversationHybridRow(row as Record<string, unknown>)
118
+ );
119
+ }
120
+ }
@@ -0,0 +1,33 @@
1
+ import {
2
+ Schema,
3
+ Field,
4
+ FixedSizeList,
5
+ Float32,
6
+ Utf8,
7
+ Timestamp,
8
+ TimeUnit,
9
+ Int32,
10
+ } from "apache-arrow";
11
+
12
+ export const CONVERSATION_TABLE_NAME = "conversation_history";
13
+
14
+ export const conversationSchema = new Schema([
15
+ new Field("id", new Utf8(), false),
16
+ new Field(
17
+ "vector",
18
+ new FixedSizeList(384, new Field("item", new Float32(), false)),
19
+ false
20
+ ),
21
+ new Field("content", new Utf8(), false),
22
+ new Field("metadata", new Utf8(), false), // JSON string
23
+ new Field(
24
+ "created_at",
25
+ new Timestamp(TimeUnit.MILLISECOND),
26
+ false
27
+ ),
28
+ new Field("session_id", new Utf8(), false),
29
+ new Field("role", new Utf8(), false),
30
+ new Field("message_index_start", new Int32(), false),
31
+ new Field("message_index_end", new Int32(), false),
32
+ new Field("project", new Utf8(), false),
33
+ ]);
@@ -3,13 +3,20 @@ import { Index, rerankers, type Table } from "@lancedb/lancedb";
3
3
  import type { Schema } from "apache-arrow";
4
4
 
5
5
  /**
6
- * Escapes a string for use in LanceDB/DataFusion SQL WHERE clauses.
7
- * Doubles single quotes to prevent SQL injection (standard SQL escaping).
6
+ * Escape a string value for safe interpolation into LanceDB/DataFusion SQL WHERE clauses.
7
+ *
8
+ * DataFusion uses ANSI SQL string literal rules:
9
+ * - String literals are delimited by single quotes
10
+ * - Single quotes within strings are escaped by doubling: ' -> ''
11
+ * - Backslashes are NOT escape characters (treated literally)
8
12
  */
9
- export function escapeLanceDbString(value: string): string {
13
+ export function escapeSql(value: string): string {
10
14
  return value.replace(/'/g, "''");
11
15
  }
12
16
 
17
+ /** Default k parameter for Reciprocal Rank Fusion reranking. */
18
+ export const RRF_K = 60;
19
+
13
20
  /**
14
21
  * Converts LanceDB's Arrow Vector type to a plain number[].
15
22
  * LanceDB returns an Arrow Vector object which is iterable but not an array.
@@ -20,6 +27,17 @@ export function arrowVectorToArray(value: unknown): number[] {
20
27
  : (Array.from(value as Iterable<number>) as number[]);
21
28
  }
22
29
 
30
+ /**
31
+ * Safely parse a JSON string into an object, returning an empty object on failure.
32
+ */
33
+ export function safeParseJsonObject(raw: string): Record<string, unknown> {
34
+ try {
35
+ return JSON.parse(raw);
36
+ } catch {
37
+ return {};
38
+ }
39
+ }
40
+
23
41
  /**
24
42
  * Opens an existing table or creates it with the given schema.
25
43
  * Does NOT cache — callers should cache the returned Table if desired.
@@ -29,11 +47,21 @@ export async function getOrCreateTable(
29
47
  name: string,
30
48
  schema: Schema
31
49
  ): Promise<Table> {
32
- const names = await db.tableNames();
33
- if (names.includes(name)) {
50
+ try {
51
+ return await db.openTable(name);
52
+ } catch (err: unknown) {
53
+ // Only proceed to create if the table was not found
54
+ const message = err instanceof Error ? err.message : String(err);
55
+ if (!message.includes("was not found") && !message.includes("does not exist")) {
56
+ throw err;
57
+ }
58
+ }
59
+ try {
60
+ return await db.createTable(name, [], { schema });
61
+ } catch {
62
+ // Another caller may have created it concurrently
34
63
  return await db.openTable(name);
35
64
  }
36
- return await db.createTable(name, [], { schema });
37
65
  }
38
66
 
39
67
  /**
@@ -81,7 +109,7 @@ export function createFtsMutex(
81
109
  * Same pattern as createFtsMutex: create once, cache forever, reset on error.
82
110
  */
83
111
  export function createRerankerMutex(
84
- k: number = 60
112
+ k: number = RRF_K
85
113
  ): () => Promise<rerankers.RRFReranker> {
86
114
  let promise: Promise<rerankers.RRFReranker> | null = null;
87
115
 
@@ -1,7 +1,7 @@
1
1
  import * as lancedb from "@lancedb/lancedb";
2
2
  import { type Table } from "@lancedb/lancedb";
3
3
  import { TABLE_NAME, memorySchema } from "./schema.js";
4
- import { arrowVectorToArray, createFtsMutex, createRerankerMutex, escapeLanceDbString } from "./lancedb-utils.js";
4
+ import { arrowVectorToArray, createFtsMutex, createRerankerMutex, escapeSql, safeParseJsonObject } from "./lancedb-utils.js";
5
5
  import {
6
6
  type Memory,
7
7
  type HybridRow,
@@ -83,7 +83,7 @@ export class MemoryRepository {
83
83
  id: row.id as string,
84
84
  content: row.content as string,
85
85
  embedding: arrowVectorToArray(row.vector),
86
- metadata: JSON.parse(row.metadata as string),
86
+ metadata: safeParseJsonObject(row.metadata as string),
87
87
  createdAt: new Date(row.created_at as number),
88
88
  updatedAt: new Date(row.updated_at as number),
89
89
  supersededBy: row.superseded_by as string | null,
@@ -115,14 +115,14 @@ export class MemoryRepository {
115
115
 
116
116
  async upsert(memory: Memory): Promise<void> {
117
117
  const table = await this.getTable();
118
- const existing = await table.query().where(`id = '${escapeLanceDbString(memory.id)}'`).limit(1).toArray();
118
+ const existing = await table.query().where(`id = '${escapeSql(memory.id)}'`).limit(1).toArray();
119
119
 
120
120
  if (existing.length === 0) {
121
121
  return await this.insert(memory);
122
122
  }
123
123
 
124
124
  await table.update({
125
- where: `id = '${escapeLanceDbString(memory.id)}'`,
125
+ where: `id = '${escapeSql(memory.id)}'`,
126
126
  values: {
127
127
  vector: memory.embedding,
128
128
  content: memory.content,
@@ -139,7 +139,7 @@ export class MemoryRepository {
139
139
 
140
140
  async findById(id: string): Promise<Memory | null> {
141
141
  const table = await this.getTable();
142
- const results = await table.query().where(`id = '${escapeLanceDbString(id)}'`).limit(1).toArray();
142
+ const results = await table.query().where(`id = '${escapeSql(id)}'`).limit(1).toArray();
143
143
 
144
144
  if (results.length === 0) {
145
145
  return null;
@@ -152,14 +152,14 @@ export class MemoryRepository {
152
152
  const table = await this.getTable();
153
153
 
154
154
  // Verify existence first to match previous behavior (return false if not found)
155
- const existing = await table.query().where(`id = '${escapeLanceDbString(id)}'`).limit(1).toArray();
155
+ const existing = await table.query().where(`id = '${escapeSql(id)}'`).limit(1).toArray();
156
156
  if (existing.length === 0) {
157
157
  return false;
158
158
  }
159
159
 
160
160
  const now = Date.now();
161
161
  await table.update({
162
- where: `id = '${escapeLanceDbString(id)}'`,
162
+ where: `id = '${escapeSql(id)}'`,
163
163
  values: {
164
164
  superseded_by: DELETED_TOMBSTONE,
165
165
  updated_at: now,
@@ -85,6 +85,7 @@ export function createHttpApp(memoryService: MemoryService, config: Config): Hon
85
85
  dbPath: config.dbPath,
86
86
  embeddingModel: config.embeddingModel,
87
87
  embeddingDimension: config.embeddingDimension,
88
+ historyEnabled: config.conversationHistory.enabled,
88
89
  },
89
90
  });
90
91
  });
@@ -101,16 +102,17 @@ export function createHttpApp(memoryService: MemoryService, config: Config): Hon
101
102
  return c.json({ error: "Missing or invalid 'query' field" }, 400);
102
103
  }
103
104
 
104
- const memories = await memoryService.search(query, intent, limit);
105
+ const results = await memoryService.search(query, intent, limit);
105
106
 
106
107
  return c.json({
107
- memories: memories.map((m) => ({
108
- id: m.id,
109
- content: m.content,
110
- metadata: m.metadata,
111
- createdAt: m.createdAt.toISOString(),
108
+ results: results.map((r) => ({
109
+ id: r.id,
110
+ content: r.content,
111
+ metadata: r.metadata,
112
+ source: r.source,
113
+ createdAt: r.createdAt.toISOString(),
112
114
  })),
113
- count: memories.length,
115
+ count: results.length,
114
116
  });
115
117
  } catch (error) {
116
118
  const message = error instanceof Error ? error.message : "Unknown error";
@@ -193,6 +195,28 @@ export function createHttpApp(memoryService: MemoryService, config: Config): Hon
193
195
  }
194
196
  });
195
197
 
198
+ // Index conversations (trigger incremental indexing)
199
+ app.post("/index-conversations", async (c) => {
200
+ try {
201
+ const conversationService = memoryService.getConversationService();
202
+ if (!conversationService) {
203
+ return c.json({ error: "Conversation history indexing is not enabled" }, 400);
204
+ }
205
+
206
+ const body = await c.req.json().catch(() => ({}));
207
+ const since = body.since ? new Date(body.since as string) : undefined;
208
+ const result = await conversationService.indexConversations(
209
+ body.path as string | undefined,
210
+ since
211
+ );
212
+
213
+ return c.json(result);
214
+ } catch (error) {
215
+ const message = error instanceof Error ? error.message : "Unknown error";
216
+ return c.json({ error: message }, 500);
217
+ }
218
+ });
219
+
196
220
  // Get single memory
197
221
  app.get("/memories/:id", async (c) => {
198
222
  try {
package/src/index.ts CHANGED
@@ -3,10 +3,10 @@
3
3
  import { loadConfig, parseCliArgs } from "./config/index.js";
4
4
  import { connectToDatabase } from "./db/connection.js";
5
5
  import { MemoryRepository } from "./db/memory.repository.js";
6
+ import { ConversationRepository } from "./db/conversation.repository.js";
6
7
  import { EmbeddingsService } from "./services/embeddings.service.js";
7
8
  import { MemoryService } from "./services/memory.service.js";
8
- import { ConversationHistoryRepository } from "./db/conversation-history.repository.js";
9
- import { ConversationHistoryService } from "./services/conversation-history.service.js";
9
+ import { ConversationHistoryService } from "./services/conversation.service.js";
10
10
  import { startServer } from "./mcp/server.js";
11
11
  import { startHttpServer } from "./http/server.js";
12
12
 
@@ -32,18 +32,17 @@ async function main(): Promise<void> {
32
32
  const embeddings = new EmbeddingsService(config.embeddingModel, config.embeddingDimension);
33
33
  const memoryService = new MemoryService(repository, embeddings);
34
34
 
35
- // Wire conversation history if enabled
35
+ // Conditionally initialize conversation history indexing
36
36
  if (config.conversationHistory.enabled) {
37
- const historyRepo = new ConversationHistoryRepository(db);
38
- const historyService = new ConversationHistoryService(
39
- historyRepo,
37
+ const conversationRepository = new ConversationRepository(db);
38
+ const conversationService = new ConversationHistoryService(
39
+ conversationRepository,
40
40
  embeddings,
41
- config.conversationHistory.sessionPath,
42
- );
43
- memoryService.setConversationHistory(
44
- historyService,
45
- config.conversationHistory.historyWeight,
41
+ config.conversationHistory,
42
+ config.dbPath
46
43
  );
44
+ memoryService.setConversationService(conversationService);
45
+ console.error("[vector-memory-mcp] Conversation history indexing enabled");
47
46
  }
48
47
 
49
48
  // Track cleanup functions