@aeriondyseti/vector-memory-mcp 2.2.6 → 2.3.0-dev.1
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/package.json +5 -6
- package/server/core/embeddings.service.ts +95 -18
- package/server/core/migrations.ts +148 -0
- package/server/index.ts +4 -39
- package/server/transports/http/server.ts +1 -29
- package/server/transports/mcp/resources.ts +8 -149
- package/scripts/lancedb-extract.ts +0 -181
- package/scripts/migrate-from-lancedb.ts +0 -56
- package/scripts/smoke-test.ts +0 -699
- package/scripts/sync-version.ts +0 -35
- package/scripts/test-runner.ts +0 -76
- package/scripts/warmup.ts +0 -72
- package/server/migration.ts +0 -203
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
/**
|
|
3
|
-
* Standalone LanceDB data extractor — runs in a child process so that
|
|
4
|
-
* @lancedb/lancedb native bindings never coexist with bun:sqlite's
|
|
5
|
-
* extension loading in the same process.
|
|
6
|
-
*
|
|
7
|
-
* Usage: bun scripts/lancedb-extract.ts <lance-db-path>
|
|
8
|
-
* Output: JSON on stdout — { memories: Row[], conversations: Row[] }
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
const source = process.argv[2];
|
|
12
|
-
if (!source) {
|
|
13
|
-
console.error("Usage: bun scripts/lancedb-extract.ts <lance-db-path>");
|
|
14
|
-
process.exit(1);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// Arrow TimeUnit enum → divisor to convert to milliseconds.
|
|
18
|
-
// 0=SECOND, 1=MILLISECOND, 2=MICROSECOND, 3=NANOSECOND
|
|
19
|
-
// Negative divisor = multiply (seconds → ms needs ×1000).
|
|
20
|
-
const TIME_UNIT_TO_MS_DIVISOR: Record<number, bigint> = {
|
|
21
|
-
0: -1000n, // seconds → ms (multiply by 1000)
|
|
22
|
-
1: 1n, // ms → no conversion
|
|
23
|
-
2: 1000n, // μs → ms
|
|
24
|
-
3: 1000000n, // ns → ms
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
function buildTimestampDivisors(schema: any): Map<string, bigint> {
|
|
28
|
-
const map = new Map<string, bigint>();
|
|
29
|
-
for (const field of schema.fields) {
|
|
30
|
-
if (field.type.typeId === 10) {
|
|
31
|
-
map.set(field.name, TIME_UNIT_TO_MS_DIVISOR[field.type.unit] ?? 1n);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return map;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function columnValue(batch: any, colName: string, rowIdx: number): unknown {
|
|
38
|
-
const col = batch.getChild(colName);
|
|
39
|
-
if (!col) return undefined;
|
|
40
|
-
try {
|
|
41
|
-
return col.get(rowIdx);
|
|
42
|
-
} catch {
|
|
43
|
-
// Arrow's getter can throw on BigInt timestamps exceeding MAX_SAFE_INTEGER;
|
|
44
|
-
// fall back to the raw typed array.
|
|
45
|
-
let offset = rowIdx;
|
|
46
|
-
for (const data of col.data) {
|
|
47
|
-
if (offset < data.length) {
|
|
48
|
-
return (data.values instanceof BigInt64Array || data.values instanceof BigUint64Array)
|
|
49
|
-
? data.values[offset]
|
|
50
|
-
: null;
|
|
51
|
-
}
|
|
52
|
-
offset -= data.length;
|
|
53
|
-
}
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function toEpochMs(value: unknown, divisor: bigint = 1n): number {
|
|
59
|
-
if (value == null) return Date.now();
|
|
60
|
-
if (value instanceof Date) return value.getTime();
|
|
61
|
-
if (typeof value === "bigint") {
|
|
62
|
-
if (divisor < 0n) return Number(value * -divisor); // seconds → ms
|
|
63
|
-
if (divisor === 1n) return Number(value);
|
|
64
|
-
return Number(value / divisor);
|
|
65
|
-
}
|
|
66
|
-
if (typeof value === "number") {
|
|
67
|
-
if (divisor < 0n) return value * Number(-divisor);
|
|
68
|
-
if (divisor === 1n) return value;
|
|
69
|
-
return Math.floor(value / Number(divisor));
|
|
70
|
-
}
|
|
71
|
-
return Date.now();
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function toFloatArray(vec: unknown): number[] {
|
|
75
|
-
if (Array.isArray(vec)) return vec;
|
|
76
|
-
if (vec instanceof Float32Array) return Array.from(vec);
|
|
77
|
-
if (vec && typeof (vec as any).toArray === "function") {
|
|
78
|
-
return Array.from((vec as any).toArray());
|
|
79
|
-
}
|
|
80
|
-
if (ArrayBuffer.isView(vec)) {
|
|
81
|
-
const view = vec as DataView;
|
|
82
|
-
return Array.from(new Float32Array(view.buffer, view.byteOffset, view.byteLength / 4));
|
|
83
|
-
}
|
|
84
|
-
return [];
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const BATCH_SIZE = 100;
|
|
88
|
-
const lancedb = await import("@lancedb/lancedb");
|
|
89
|
-
const db = await lancedb.connect(source);
|
|
90
|
-
const tableNames = await db.tableNames();
|
|
91
|
-
console.error(`Found tables: ${tableNames.join(", ")}`);
|
|
92
|
-
|
|
93
|
-
const result: { memories: any[]; conversations: any[] } = {
|
|
94
|
-
memories: [],
|
|
95
|
-
conversations: [],
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
if (tableNames.includes("memories")) {
|
|
99
|
-
const table = await db.openTable("memories");
|
|
100
|
-
const total = await table.countRows();
|
|
101
|
-
console.error(`Reading ${total} memories...`);
|
|
102
|
-
|
|
103
|
-
// Paginated scan — query().toArrow() without offset/limit returns
|
|
104
|
-
// non-deterministic results that can duplicate some rows and skip others.
|
|
105
|
-
const schemaSample = await table.query().limit(1).toArrow();
|
|
106
|
-
const tsDivisors = buildTimestampDivisors(schemaSample.schema);
|
|
107
|
-
const seen = new Map<string, any>();
|
|
108
|
-
|
|
109
|
-
for (let offset = 0; offset < total; offset += BATCH_SIZE) {
|
|
110
|
-
const arrowTable = await table.query().offset(offset).limit(BATCH_SIZE).toArrow();
|
|
111
|
-
for (const batch of arrowTable.batches) {
|
|
112
|
-
for (let i = 0; i < batch.numRows; i++) {
|
|
113
|
-
const id = columnValue(batch, "id", i) as string;
|
|
114
|
-
const content = columnValue(batch, "content", i) as string;
|
|
115
|
-
const lastAccessed = columnValue(batch, "last_accessed", i);
|
|
116
|
-
const accessedMs = lastAccessed != null ? toEpochMs(lastAccessed, tsDivisors.get("last_accessed")) : null;
|
|
117
|
-
// Deduplicate by ID: prefer most recently accessed, then longest content.
|
|
118
|
-
const existing = seen.get(id);
|
|
119
|
-
if (existing) {
|
|
120
|
-
const existingAccess = existing.last_accessed ?? 0;
|
|
121
|
-
const newAccess = accessedMs ?? 0;
|
|
122
|
-
if (newAccess < existingAccess) continue;
|
|
123
|
-
if (newAccess === existingAccess && content.length <= existing.content.length) continue;
|
|
124
|
-
}
|
|
125
|
-
seen.set(id, {
|
|
126
|
-
id,
|
|
127
|
-
content,
|
|
128
|
-
metadata: columnValue(batch, "metadata", i) ?? "{}",
|
|
129
|
-
vector: toFloatArray(columnValue(batch, "vector", i)),
|
|
130
|
-
created_at: toEpochMs(columnValue(batch, "created_at", i), tsDivisors.get("created_at")),
|
|
131
|
-
updated_at: toEpochMs(columnValue(batch, "updated_at", i), tsDivisors.get("updated_at")),
|
|
132
|
-
last_accessed: accessedMs,
|
|
133
|
-
superseded_by: columnValue(batch, "superseded_by", i) ?? null,
|
|
134
|
-
usefulness: columnValue(batch, "usefulness", i) ?? 0,
|
|
135
|
-
access_count: columnValue(batch, "access_count", i) ?? 0,
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
result.memories = [...seen.values()];
|
|
141
|
-
console.error(` ${result.memories.length} unique memories read (${total} rows scanned)`);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (tableNames.includes("conversation_history")) {
|
|
145
|
-
const table = await db.openTable("conversation_history");
|
|
146
|
-
const total = await table.countRows();
|
|
147
|
-
console.error(`Reading ${total} conversation chunks...`);
|
|
148
|
-
|
|
149
|
-
const schemaSample = await table.query().limit(1).toArrow();
|
|
150
|
-
const tsDivisors = buildTimestampDivisors(schemaSample.schema);
|
|
151
|
-
const seen = new Map<string, any>();
|
|
152
|
-
|
|
153
|
-
for (let offset = 0; offset < total; offset += BATCH_SIZE) {
|
|
154
|
-
const arrowTable = await table.query().offset(offset).limit(BATCH_SIZE).toArrow();
|
|
155
|
-
for (const batch of arrowTable.batches) {
|
|
156
|
-
for (let i = 0; i < batch.numRows; i++) {
|
|
157
|
-
const id = columnValue(batch, "id", i) as string;
|
|
158
|
-
const content = columnValue(batch, "content", i) as string;
|
|
159
|
-
const existing = seen.get(id);
|
|
160
|
-
if (existing && existing.content.length >= content.length) continue;
|
|
161
|
-
seen.set(id, {
|
|
162
|
-
id,
|
|
163
|
-
content,
|
|
164
|
-
metadata: columnValue(batch, "metadata", i) ?? "{}",
|
|
165
|
-
vector: toFloatArray(columnValue(batch, "vector", i)),
|
|
166
|
-
created_at: toEpochMs(columnValue(batch, "created_at", i), tsDivisors.get("created_at")),
|
|
167
|
-
session_id: columnValue(batch, "session_id", i),
|
|
168
|
-
role: columnValue(batch, "role", i),
|
|
169
|
-
message_index_start: columnValue(batch, "message_index_start", i) ?? 0,
|
|
170
|
-
message_index_end: columnValue(batch, "message_index_end", i) ?? 0,
|
|
171
|
-
project: columnValue(batch, "project", i) ?? "",
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
result.conversations = [...seen.values()];
|
|
177
|
-
console.error(` ${result.conversations.length} unique conversation chunks read (${total} rows scanned)`);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
await db.close?.();
|
|
181
|
-
process.stdout.write(JSON.stringify(result));
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
/**
|
|
3
|
-
* Standalone migration script: LanceDB → SQLite (sqlite-vec)
|
|
4
|
-
*
|
|
5
|
-
* This is a thin wrapper around server/migration.ts for direct invocation.
|
|
6
|
-
* The preferred way to migrate is `vector-memory-mcp migrate`.
|
|
7
|
-
*
|
|
8
|
-
* Usage:
|
|
9
|
-
* bun scripts/migrate-from-lancedb.ts [--source <lancedb-dir>] [--target <sqlite-file>]
|
|
10
|
-
*
|
|
11
|
-
* Defaults:
|
|
12
|
-
* --source .vector-memory/memories.db (the old LanceDB directory)
|
|
13
|
-
* --target .vector-memory/memories.db.sqlite (new SQLite file)
|
|
14
|
-
*
|
|
15
|
-
* @deprecated Use `vector-memory-mcp migrate` instead. This script will be
|
|
16
|
-
* removed in the next major version.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import { migrate, formatMigrationSummary } from "../server/migration.js";
|
|
20
|
-
|
|
21
|
-
function parseArgs(): { source: string; target: string } {
|
|
22
|
-
const args = process.argv.slice(2);
|
|
23
|
-
let source = ".vector-memory/memories.db";
|
|
24
|
-
let target = ".vector-memory/memories.db.sqlite";
|
|
25
|
-
|
|
26
|
-
for (let i = 0; i < args.length; i++) {
|
|
27
|
-
if (args[i] === "--source" && args[i + 1]) source = args[++i];
|
|
28
|
-
else if (args[i] === "--target" && args[i + 1]) target = args[++i];
|
|
29
|
-
else if (args[i] === "--help" || args[i] === "-h") {
|
|
30
|
-
console.log(`
|
|
31
|
-
Usage: bun scripts/migrate-from-lancedb.ts [options]
|
|
32
|
-
|
|
33
|
-
Prefer: vector-memory-mcp migrate
|
|
34
|
-
|
|
35
|
-
Options:
|
|
36
|
-
--source <path> LanceDB directory (default: .vector-memory/memories.db)
|
|
37
|
-
--target <path> SQLite output file (default: .vector-memory/memories.db.sqlite)
|
|
38
|
-
--help Show this help
|
|
39
|
-
`);
|
|
40
|
-
process.exit(0);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return { source, target };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async function main() {
|
|
48
|
-
const { source, target } = parseArgs();
|
|
49
|
-
const result = await migrate({ source, target });
|
|
50
|
-
console.error(formatMigrationSummary(source, target, result));
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
main().catch((err) => {
|
|
54
|
-
console.error("❌ Migration failed:", err.message ?? err);
|
|
55
|
-
process.exit(1);
|
|
56
|
-
});
|