@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.
@@ -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 as SearchIntent) ?? "fact_check";
186
- const limit = (args?.limit as number) ?? 10;
187
- const offset = (args?.offset as number) ?? 0;
188
- const includeDeleted = (args?.include_deleted as boolean) ?? false;
189
- const historyOnly = (args?.history_only as boolean) ?? false;
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 as boolean | undefined);
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}\nContent: ${r.content}`;
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 as string | undefined,
363
+ project: asOptionalString(args?.project),
364
+ branch: asOptionalString(args?.branch),
320
365
  summary,
321
- completed: (args?.completed as string[] | undefined) ?? [],
322
- in_progress_blocked: (args?.in_progress_blocked as string[] | undefined) ?? [],
323
- key_decisions: (args?.key_decisions as string[] | undefined) ?? [],
324
- next_steps: (args?.next_steps as string[] | undefined) ?? [],
325
- memory_ids: (args?.memory_ids as string[] | undefined) ?? [],
326
- metadata: (args?.metadata as Record<string, unknown>) ?? {},
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 as string | undefined;
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 as string | undefined,
369
- role: args?.role_filter as string | undefined,
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 as string | undefined;
398
- const sinceStr = args?.since as string | undefined;
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 as number) ?? 20;
429
- const offset = (args?.offset as number) ?? 0;
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 as string | undefined;
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: { type: "string", description: "Project name." },
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: ["project", "summary"],
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 name to retrieve waypoint for. If omitted, retrieves the default (legacy) waypoint.",
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
  },