@aeriondyseti/vector-memory-mcp 2.4.4 → 2.5.0-dev.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -1
- package/package.json +3 -1
- package/scripts/lancedb-extract.ts +181 -0
- package/scripts/warmup.ts +63 -0
- package/server/config/index.ts +11 -2
- package/server/core/connection.ts +160 -4
- package/server/core/consolidation.service.ts +815 -0
- package/server/core/conversation.repository.ts +137 -30
- package/server/core/conversation.service.ts +51 -51
- package/server/core/conversation.ts +17 -0
- package/server/core/memory.repository.ts +80 -22
- package/server/core/memory.service.ts +171 -49
- package/server/core/memory.ts +43 -1
- package/server/core/migrations.ts +197 -16
- package/server/core/parsers/claude-code.parser.ts +18 -4
- package/server/core/project.ts +25 -0
- package/server/core/sqlite-utils.ts +56 -5
- package/server/core/time-expr.ts +77 -0
- package/server/index.ts +92 -2
- package/server/transports/http/server.ts +82 -32
- package/server/transports/mcp/handlers.ts +71 -26
- package/server/transports/mcp/tools.ts +40 -4
|
@@ -3,6 +3,7 @@ import type { MemoryService } from "../../core/memory.service";
|
|
|
3
3
|
import type { ConversationHistoryService } from "../../core/conversation.service";
|
|
4
4
|
import type { SearchIntent } from "../../core/memory";
|
|
5
5
|
import type { HistoryFilters, SearchResult } from "../../core/conversation";
|
|
6
|
+
import { resolveDateFilters } from "../../core/time-expr";
|
|
6
7
|
import { DEBUG } from "../../config/index";
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -59,6 +60,32 @@ function requireString(args: Record<string, unknown> | undefined, field: string)
|
|
|
59
60
|
return value;
|
|
60
61
|
}
|
|
61
62
|
|
|
63
|
+
const VALID_INTENTS = new Set(["continuity", "fact_check", "frequent", "associative", "explore"]);
|
|
64
|
+
|
|
65
|
+
function asIntent(value: unknown): SearchIntent {
|
|
66
|
+
if (typeof value === "string" && VALID_INTENTS.has(value)) return value as SearchIntent;
|
|
67
|
+
return "fact_check";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function asInt(value: unknown, fallback: number, min: number, max: number): number {
|
|
71
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
|
|
72
|
+
return Math.max(min, Math.min(max, Math.floor(value)));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function asBool(value: unknown, fallback: boolean): boolean {
|
|
76
|
+
return typeof value === "boolean" ? value : fallback;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function asOptionalString(value: unknown): string | undefined {
|
|
80
|
+
return typeof value === "string" ? value : undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function asObject(value: unknown): Record<string, unknown> {
|
|
84
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
85
|
+
? value as Record<string, unknown>
|
|
86
|
+
: {};
|
|
87
|
+
}
|
|
88
|
+
|
|
62
89
|
export async function handleStoreMemories(
|
|
63
90
|
args: Record<string, unknown> | undefined,
|
|
64
91
|
service: MemoryService
|
|
@@ -67,6 +94,7 @@ export async function handleStoreMemories(
|
|
|
67
94
|
content: string;
|
|
68
95
|
embedding_text?: string;
|
|
69
96
|
metadata?: Record<string, unknown>;
|
|
97
|
+
project?: string;
|
|
70
98
|
}>;
|
|
71
99
|
try {
|
|
72
100
|
memories = asArray(args?.memories, "memories");
|
|
@@ -79,7 +107,8 @@ export async function handleStoreMemories(
|
|
|
79
107
|
const memory = await service.store(
|
|
80
108
|
item.content,
|
|
81
109
|
item.metadata ?? {},
|
|
82
|
-
item.embedding_text
|
|
110
|
+
item.embedding_text,
|
|
111
|
+
typeof item.project === "string" ? item.project : undefined
|
|
83
112
|
);
|
|
84
113
|
ids.push(memory.id);
|
|
85
114
|
}
|
|
@@ -182,13 +211,13 @@ export async function handleSearchMemories(
|
|
|
182
211
|
if (typeof query !== "string" || query.trim() === "") {
|
|
183
212
|
return errorResult("query is required and must be a non-empty string");
|
|
184
213
|
}
|
|
185
|
-
const intent = (args?.intent
|
|
186
|
-
const limit = (args?.limit
|
|
187
|
-
const offset = (args?.offset
|
|
188
|
-
const includeDeleted = (args?.include_deleted
|
|
189
|
-
const historyOnly = (args?.history_only
|
|
214
|
+
const intent = asIntent(args?.intent);
|
|
215
|
+
const limit = asInt(args?.limit, 10, 1, 1000);
|
|
216
|
+
const offset = asInt(args?.offset, 0, 0, 10000);
|
|
217
|
+
const includeDeleted = asBool(args?.include_deleted, false);
|
|
218
|
+
const historyOnly = asBool(args?.history_only, false);
|
|
190
219
|
// history_only implies include_history
|
|
191
|
-
const includeHistory = historyOnly ? true : (args?.include_history
|
|
220
|
+
const includeHistory = historyOnly ? true : (typeof args?.include_history === "boolean" ? args.include_history : undefined);
|
|
192
221
|
|
|
193
222
|
let historyFilters: HistoryFilters;
|
|
194
223
|
try {
|
|
@@ -197,13 +226,27 @@ export async function handleSearchMemories(
|
|
|
197
226
|
return errorResult(errorText(e));
|
|
198
227
|
}
|
|
199
228
|
|
|
229
|
+
let dateFilters: { after?: Date; before?: Date };
|
|
230
|
+
try {
|
|
231
|
+
dateFilters = resolveDateFilters({
|
|
232
|
+
after: args?.after,
|
|
233
|
+
before: args?.before,
|
|
234
|
+
time_expr: args?.time_expr,
|
|
235
|
+
});
|
|
236
|
+
} catch (e) {
|
|
237
|
+
return errorResult(errorText(e));
|
|
238
|
+
}
|
|
239
|
+
|
|
200
240
|
const results = await service.search(query, intent, {
|
|
201
241
|
limit,
|
|
242
|
+
scope: asOptionalString(args?.scope),
|
|
202
243
|
includeDeleted,
|
|
203
244
|
includeHistory,
|
|
204
245
|
historyOnly,
|
|
205
246
|
historyFilters,
|
|
206
247
|
offset,
|
|
248
|
+
after: dateFilters.after,
|
|
249
|
+
before: dateFilters.before,
|
|
207
250
|
});
|
|
208
251
|
|
|
209
252
|
if (results.length === 0) {
|
|
@@ -240,7 +283,11 @@ function formatMemoryDetail(
|
|
|
240
283
|
}
|
|
241
284
|
|
|
242
285
|
function formatSearchResult(r: SearchResult, includeDeleted: boolean): string {
|
|
243
|
-
let result = `[${r.source}] ID: ${r.id}\
|
|
286
|
+
let result = `[${r.source}] ID: ${r.id}\nConfidence: ${r.confidence.toFixed(2)}`;
|
|
287
|
+
if (r.project) {
|
|
288
|
+
result += `\nProject: ${r.project}`;
|
|
289
|
+
}
|
|
290
|
+
result += `\nContent: ${r.content}`;
|
|
244
291
|
if (r.metadata && Object.keys(r.metadata).length > 0) {
|
|
245
292
|
result += `\nMetadata: ${JSON.stringify(r.metadata)}`;
|
|
246
293
|
}
|
|
@@ -305,25 +352,23 @@ export async function handleSetWaypoint(
|
|
|
305
352
|
args: Record<string, unknown> | undefined,
|
|
306
353
|
service: MemoryService
|
|
307
354
|
): Promise<CallToolResult> {
|
|
308
|
-
let project: string;
|
|
309
355
|
let summary: string;
|
|
310
356
|
try {
|
|
311
|
-
project = requireString(args, "project");
|
|
312
357
|
summary = requireString(args, "summary");
|
|
313
358
|
} catch (e) {
|
|
314
359
|
return errorResult(errorText(e));
|
|
315
360
|
}
|
|
316
361
|
|
|
317
362
|
const memory = await service.setWaypoint({
|
|
318
|
-
project,
|
|
319
|
-
branch: args?.branch
|
|
363
|
+
project: asOptionalString(args?.project),
|
|
364
|
+
branch: asOptionalString(args?.branch),
|
|
320
365
|
summary,
|
|
321
|
-
completed:
|
|
322
|
-
in_progress_blocked:
|
|
323
|
-
key_decisions:
|
|
324
|
-
next_steps:
|
|
325
|
-
memory_ids:
|
|
326
|
-
metadata: (args?.metadata
|
|
366
|
+
completed: args?.completed ? asArray(args.completed, "completed") : [],
|
|
367
|
+
in_progress_blocked: args?.in_progress_blocked ? asArray(args.in_progress_blocked, "in_progress_blocked") : [],
|
|
368
|
+
key_decisions: args?.key_decisions ? asArray(args.key_decisions, "key_decisions") : [],
|
|
369
|
+
next_steps: args?.next_steps ? asArray(args.next_steps, "next_steps") : [],
|
|
370
|
+
memory_ids: args?.memory_ids ? asArray(args.memory_ids, "memory_ids") : [],
|
|
371
|
+
metadata: asObject(args?.metadata),
|
|
327
372
|
});
|
|
328
373
|
|
|
329
374
|
return {
|
|
@@ -335,7 +380,7 @@ export async function handleGetWaypoint(
|
|
|
335
380
|
args: Record<string, unknown> | undefined,
|
|
336
381
|
service: MemoryService
|
|
337
382
|
): Promise<CallToolResult> {
|
|
338
|
-
const project = args?.project
|
|
383
|
+
const project = asOptionalString(args?.project);
|
|
339
384
|
const waypoint = await service.getLatestWaypoint(project);
|
|
340
385
|
|
|
341
386
|
if (!waypoint) {
|
|
@@ -365,8 +410,8 @@ function parseHistoryFilters(
|
|
|
365
410
|
args: Record<string, unknown> | undefined
|
|
366
411
|
): HistoryFilters {
|
|
367
412
|
return {
|
|
368
|
-
sessionId: args?.session_id
|
|
369
|
-
role: args?.role_filter
|
|
413
|
+
sessionId: asOptionalString(args?.session_id),
|
|
414
|
+
role: asOptionalString(args?.role_filter),
|
|
370
415
|
after: parseDate(args?.history_after, "history_after"),
|
|
371
416
|
before: parseDate(args?.history_before, "history_before"),
|
|
372
417
|
};
|
|
@@ -394,8 +439,8 @@ export async function handleIndexConversations(
|
|
|
394
439
|
if ("error" in conv) return conv.error;
|
|
395
440
|
const conversationService = conv.service;
|
|
396
441
|
|
|
397
|
-
const path = args?.path
|
|
398
|
-
const sinceStr = args?.since
|
|
442
|
+
const path = asOptionalString(args?.path);
|
|
443
|
+
const sinceStr = asOptionalString(args?.since);
|
|
399
444
|
const since = sinceStr ? new Date(sinceStr) : undefined;
|
|
400
445
|
if (since && isNaN(since.getTime())) {
|
|
401
446
|
return errorResult("Invalid 'since' date format");
|
|
@@ -425,8 +470,8 @@ export async function handleListIndexedSessions(
|
|
|
425
470
|
if ("error" in conv) return conv.error;
|
|
426
471
|
const conversationService = conv.service;
|
|
427
472
|
|
|
428
|
-
const limit = (args?.limit
|
|
429
|
-
const offset = (args?.offset
|
|
473
|
+
const limit = asInt(args?.limit, 20, 1, 1000);
|
|
474
|
+
const offset = asInt(args?.offset, 0, 0, 10000);
|
|
430
475
|
const { sessions, total } =
|
|
431
476
|
await conversationService.listIndexedSessions(limit, offset);
|
|
432
477
|
|
|
@@ -464,7 +509,7 @@ export async function handleReindexSession(
|
|
|
464
509
|
if ("error" in conv) return conv.error;
|
|
465
510
|
const conversationService = conv.service;
|
|
466
511
|
|
|
467
|
-
const sessionId = args?.session_id
|
|
512
|
+
const sessionId = asOptionalString(args?.session_id);
|
|
468
513
|
if (!sessionId) {
|
|
469
514
|
return errorResult("session_id is required");
|
|
470
515
|
}
|
|
@@ -47,6 +47,12 @@ For long content (>1000 chars), provide embedding_text with a searchable summary
|
|
|
47
47
|
description: "Optional key-value metadata.",
|
|
48
48
|
additionalProperties: true,
|
|
49
49
|
},
|
|
50
|
+
project: {
|
|
51
|
+
type: "string",
|
|
52
|
+
description:
|
|
53
|
+
"Project to tag this memory with (canonical absolute path). " +
|
|
54
|
+
"Defaults to the current project — only pass this to file a memory under a different project.",
|
|
55
|
+
},
|
|
50
56
|
},
|
|
51
57
|
required: ["content"],
|
|
52
58
|
},
|
|
@@ -139,7 +145,9 @@ INTENTS:
|
|
|
139
145
|
- associative: Brainstorm, find connections (high relevance + mild jitter)
|
|
140
146
|
- explore: Stuck/creative mode (balanced + high jitter)
|
|
141
147
|
|
|
142
|
-
When in doubt, search. Missing context is costlier than an extra query
|
|
148
|
+
When in doubt, search. Missing context is costlier than an extra query.
|
|
149
|
+
|
|
150
|
+
SCOPE: Memories are stored globally across all projects. By default, search covers every project (results from the current project rank slightly higher and each result carries its project path). Pass scope: "project" when the query is clearly specific to the current repo — it cuts cross-project noise and scan cost.`,
|
|
143
151
|
inputSchema: {
|
|
144
152
|
type: "object",
|
|
145
153
|
properties: {
|
|
@@ -148,6 +156,14 @@ When in doubt, search. Missing context is costlier than an extra query.`,
|
|
|
148
156
|
description:
|
|
149
157
|
"Natural language search query. Include relevant keywords, project names, or technical terms.",
|
|
150
158
|
},
|
|
159
|
+
scope: {
|
|
160
|
+
type: "string",
|
|
161
|
+
description:
|
|
162
|
+
'Project scope: "all" (default) searches every project with a ranking boost for the current one; ' +
|
|
163
|
+
'"project" restricts to the current project; or pass an explicit canonical project path ' +
|
|
164
|
+
'(e.g. "/home/user/Development/other-repo") to search that project only.',
|
|
165
|
+
default: "all",
|
|
166
|
+
},
|
|
151
167
|
intent: {
|
|
152
168
|
type: "string",
|
|
153
169
|
enum: ["continuity", "fact_check", "frequent", "associative", "explore"],
|
|
@@ -201,6 +217,21 @@ When in doubt, search. Missing context is costlier than an extra query.`,
|
|
|
201
217
|
type: "string",
|
|
202
218
|
description: "Filter conversation history results before this ISO date.",
|
|
203
219
|
},
|
|
220
|
+
after: {
|
|
221
|
+
type: "string",
|
|
222
|
+
description:
|
|
223
|
+
"Filter memories created after this ISO date (e.g. '2025-06-01'). Applies to both memories and conversation history.",
|
|
224
|
+
},
|
|
225
|
+
before: {
|
|
226
|
+
type: "string",
|
|
227
|
+
description:
|
|
228
|
+
"Filter memories created before this ISO date (e.g. '2026-01-01'). Applies to both memories and conversation history.",
|
|
229
|
+
},
|
|
230
|
+
time_expr: {
|
|
231
|
+
type: "string",
|
|
232
|
+
description:
|
|
233
|
+
"Natural relative time filter, resolved to 'after' date. Examples: 'past 7 days', 'last 2 weeks', 'past 3 hours'. Ignored if explicit 'after' is provided.",
|
|
234
|
+
},
|
|
204
235
|
},
|
|
205
236
|
required: ["query", "intent", "reason_for_search"],
|
|
206
237
|
},
|
|
@@ -258,7 +289,11 @@ Retrievable via get_waypoint. Only one waypoint per project—new waypoints over
|
|
|
258
289
|
inputSchema: {
|
|
259
290
|
type: "object",
|
|
260
291
|
properties: {
|
|
261
|
-
project: {
|
|
292
|
+
project: {
|
|
293
|
+
type: "string",
|
|
294
|
+
description:
|
|
295
|
+
"Project to save the waypoint under. Defaults to the current project (detected from cwd) — usually omit this.",
|
|
296
|
+
},
|
|
262
297
|
branch: { type: "string", description: "Branch name (optional)." },
|
|
263
298
|
summary: { type: "string", description: "2-3 sentences: primary goal, current status." },
|
|
264
299
|
completed: {
|
|
@@ -292,7 +327,7 @@ Retrievable via get_waypoint. Only one waypoint per project—new waypoints over
|
|
|
292
327
|
additionalProperties: true,
|
|
293
328
|
},
|
|
294
329
|
},
|
|
295
|
-
required: ["
|
|
330
|
+
required: ["summary"],
|
|
296
331
|
},
|
|
297
332
|
};
|
|
298
333
|
|
|
@@ -306,7 +341,8 @@ export const getWaypointTool: Tool = {
|
|
|
306
341
|
project: {
|
|
307
342
|
type: "string",
|
|
308
343
|
description:
|
|
309
|
-
"Project
|
|
344
|
+
"Project to retrieve the waypoint for (canonical absolute path). " +
|
|
345
|
+
"Defaults to the current project — only pass this to read another project's waypoint.",
|
|
310
346
|
},
|
|
311
347
|
},
|
|
312
348
|
},
|