@aeriondyseti/vector-memory-mcp 2.1.1-dev.0 → 2.2.0-dev.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aeriondyseti/vector-memory-mcp",
3
- "version": "2.1.1-dev.0",
3
+ "version": "2.2.0-dev.0",
4
4
  "description": "A zero-configuration RAG memory server for MCP clients",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -189,7 +189,8 @@ export function createHttpApp(memoryService: MemoryService, config: Config): Hon
189
189
  // Get latest waypoint
190
190
  app.get("/waypoint", async (c) => {
191
191
  try {
192
- const waypoint = await memoryService.getLatestWaypoint();
192
+ const project = c.req.query("project");
193
+ const waypoint = await memoryService.getLatestWaypoint(project);
193
194
 
194
195
  if (!waypoint) {
195
196
  return c.json({ error: "No waypoint found" }, 404);
@@ -38,6 +38,10 @@ function errorText(e: unknown): string {
38
38
  return e instanceof Error ? e.message : String(e);
39
39
  }
40
40
 
41
+ function errorResult(text: string): CallToolResult {
42
+ return { isError: true, content: [{ type: "text", text }] };
43
+ }
44
+
41
45
  function parseDate(value: unknown, fieldName: string): Date | undefined {
42
46
  if (value === undefined) return undefined;
43
47
  const date = new Date(value as string);
@@ -59,7 +63,7 @@ export async function handleStoreMemories(
59
63
  try {
60
64
  memories = asArray(args?.memories, "memories");
61
65
  } catch (e) {
62
- return { isError: true, content: [{ type: "text", text: errorText(e) }] };
66
+ return errorResult(errorText(e));
63
67
  }
64
68
 
65
69
  const ids: string[] = [];
@@ -93,7 +97,7 @@ export async function handleDeleteMemories(
93
97
  try {
94
98
  ids = asArray(args?.ids, "ids");
95
99
  } catch (e) {
96
- return { isError: true, content: [{ type: "text", text: errorText(e) }] };
100
+ return errorResult(errorText(e));
97
101
  }
98
102
  const results: string[] = [];
99
103
 
@@ -128,7 +132,7 @@ export async function handleUpdateMemories(
128
132
  try {
129
133
  updates = asArray(args?.updates, "updates");
130
134
  } catch (e) {
131
- return { isError: true, content: [{ type: "text", text: errorText(e) }] };
135
+ return errorResult(errorText(e));
132
136
  }
133
137
 
134
138
  const results: string[] = [];
@@ -168,10 +172,11 @@ export async function handleSearchMemories(
168
172
  ): Promise<CallToolResult> {
169
173
  const query = args?.query;
170
174
  if (typeof query !== "string" || query.trim() === "") {
171
- return { isError: true, content: [{ type: "text", text: "query is required and must be a non-empty string" }] };
175
+ return errorResult("query is required and must be a non-empty string");
172
176
  }
173
177
  const intent = (args?.intent as SearchIntent) ?? "fact_check";
174
178
  const limit = (args?.limit as number) ?? 10;
179
+ const offset = (args?.offset as number) ?? 0;
175
180
  const includeDeleted = (args?.include_deleted as boolean) ?? false;
176
181
  const historyOnly = (args?.history_only as boolean) ?? false;
177
182
  // history_only implies include_history
@@ -181,13 +186,14 @@ export async function handleSearchMemories(
181
186
  try {
182
187
  historyFilters = parseHistoryFilters(args);
183
188
  } catch (e) {
184
- return { isError: true, content: [{ type: "text", text: errorText(e) }] };
189
+ return errorResult(errorText(e));
185
190
  }
186
191
 
187
192
  const results = await service.search(query, intent, limit, includeDeleted, {
188
193
  includeHistory,
189
194
  historyOnly,
190
195
  historyFilters,
196
+ offset,
191
197
  });
192
198
 
193
199
  if (results.length === 0) {
@@ -243,7 +249,7 @@ export async function handleGetMemories(
243
249
  try {
244
250
  ids = asArray(args?.ids, "ids");
245
251
  } catch (e) {
246
- return { isError: true, content: [{ type: "text", text: errorText(e) }] };
252
+ return errorResult(errorText(e));
247
253
  }
248
254
 
249
255
  const memories = await service.getMultiple(ids);
@@ -267,10 +273,7 @@ export async function handleReportMemoryUsefulness(
267
273
  const memory = await service.vote(memoryId, useful ? 1 : -1);
268
274
 
269
275
  if (!memory) {
270
- return {
271
- content: [{ type: "text", text: `Memory ${memoryId} not found` }],
272
- isError: true,
273
- };
276
+ return errorResult(`Memory ${memoryId} not found`);
274
277
  }
275
278
 
276
279
  return {
@@ -305,10 +308,11 @@ export async function handleSetWaypoint(
305
308
  }
306
309
 
307
310
  export async function handleGetWaypoint(
308
- _args: Record<string, unknown> | undefined,
311
+ args: Record<string, unknown> | undefined,
309
312
  service: MemoryService
310
313
  ): Promise<CallToolResult> {
311
- const waypoint = await service.getLatestWaypoint();
314
+ const project = args?.project as string | undefined;
315
+ const waypoint = await service.getLatestWaypoint(project);
312
316
 
313
317
  if (!waypoint) {
314
318
  return {
@@ -350,15 +354,9 @@ function requireConversationService(
350
354
  const conversationService = service.getConversationService();
351
355
  if (!conversationService) {
352
356
  return {
353
- error: {
354
- content: [
355
- {
356
- type: "text",
357
- text: "Conversation history indexing is not enabled. Enable it with --enable-history.",
358
- },
359
- ],
360
- isError: true,
361
- },
357
+ error: errorResult(
358
+ "Conversation history indexing is not enabled. Enable it with --enable-history."
359
+ ),
362
360
  };
363
361
  }
364
362
  return { service: conversationService };
@@ -376,7 +374,7 @@ export async function handleIndexConversations(
376
374
  const sinceStr = args?.since as string | undefined;
377
375
  const since = sinceStr ? new Date(sinceStr) : undefined;
378
376
  if (since && isNaN(since.getTime())) {
379
- return { isError: true, content: [{ type: "text", text: "Invalid 'since' date format" }] };
377
+ return errorResult("Invalid 'since' date format");
380
378
  }
381
379
 
382
380
  const result = await conversationService.indexConversations(path, since);
@@ -444,18 +442,12 @@ export async function handleReindexSession(
444
442
 
445
443
  const sessionId = args?.session_id as string | undefined;
446
444
  if (!sessionId) {
447
- return {
448
- content: [{ type: "text", text: "session_id is required" }],
449
- isError: true,
450
- };
445
+ return errorResult("session_id is required");
451
446
  }
452
447
  const result = await conversationService.reindexSession(sessionId);
453
448
 
454
449
  if (!result.success) {
455
- return {
456
- content: [{ type: "text", text: `Reindex failed: ${result.error}` }],
457
- isError: true,
458
- };
450
+ return errorResult(`Reindex failed: ${result.error}`);
459
451
  }
460
452
 
461
453
  return {
@@ -497,9 +489,6 @@ export async function handleToolCall(
497
489
  case "reindex_session":
498
490
  return handleReindexSession(args, service);
499
491
  default:
500
- return {
501
- content: [{ type: "text", text: `Unknown tool: ${name}` }],
502
- isError: true,
503
- };
492
+ return errorResult(`Unknown tool: ${name}`);
504
493
  }
505
494
  }
package/src/mcp/tools.ts CHANGED
@@ -162,6 +162,11 @@ When in doubt, search. Missing context is costlier than an extra query.`,
162
162
  description: "Maximum results to return (default: 10).",
163
163
  default: 10,
164
164
  },
165
+ offset: {
166
+ type: "integer",
167
+ description: "Number of results to skip for pagination (default: 0).",
168
+ default: 0,
169
+ },
165
170
  include_deleted: {
166
171
  type: "boolean",
167
172
  description: "Include soft-deleted memories in results (default: false). Useful for recovering prior information.",
@@ -297,7 +302,13 @@ export const getWaypointTool: Tool = {
297
302
  "Load the current project waypoint snapshot. Call at conversation start or when resuming a project.",
298
303
  inputSchema: {
299
304
  type: "object",
300
- properties: {},
305
+ properties: {
306
+ project: {
307
+ type: "string",
308
+ description:
309
+ "Project name to retrieve waypoint for. If omitted, retrieves the default (legacy) waypoint.",
310
+ },
311
+ },
301
312
  },
302
313
  };
303
314
 
@@ -1,4 +1,4 @@
1
- import { randomUUID } from "crypto";
1
+ import { randomUUID, createHash } from "crypto";
2
2
  import type { Memory, SearchIntent, IntentProfile, HybridRow } from "../types/memory.js";
3
3
  import { isDeleted } from "../types/memory.js";
4
4
  import type { SearchResult, SearchOptions } from "../types/conversation.js";
@@ -185,6 +185,7 @@ export class MemoryService {
185
185
  const queryEmbedding = await this.embeddings.embed(query);
186
186
  const profile = INTENT_PROFILES[intent];
187
187
  const now = new Date();
188
+ const offset = Math.min(options?.offset ?? 0, 500);
188
189
 
189
190
  const hasConversationService = this.conversationService !== null;
190
191
  const historyOnly = (options?.historyOnly ?? false) && hasConversationService;
@@ -195,11 +196,14 @@ export class MemoryService {
195
196
  this.conversationService?.config.historyWeight ??
196
197
  0.75;
197
198
 
199
+ // Widen the candidate pool to account for offset
200
+ const effectiveLimit = offset + limit;
201
+
198
202
  // Run memory + history queries in parallel
199
203
  const memoryPromise =
200
204
  !historyOnly
201
205
  ? this.repository
202
- .findHybrid(queryEmbedding, query, limit * 5)
206
+ .findHybrid(queryEmbedding, query, effectiveLimit * 5)
203
207
  .then((candidates) =>
204
208
  candidates
205
209
  .filter((m) => includeDeleted || !isDeleted(m))
@@ -225,7 +229,7 @@ export class MemoryService {
225
229
  .searchHistory(
226
230
  query,
227
231
  queryEmbedding,
228
- historyOnly ? limit * 5 : limit * 3,
232
+ historyOnly ? effectiveLimit * 5 : effectiveLimit * 3,
229
233
  options?.historyFilters
230
234
  )
231
235
  .then((historyRows) =>
@@ -255,7 +259,7 @@ export class MemoryService {
255
259
  const merged = [...memoryResults, ...historyResults];
256
260
  merged.sort((a, b) => b.score - a.score);
257
261
 
258
- return merged.slice(0, limit);
262
+ return merged.slice(offset, offset + limit);
259
263
  }
260
264
 
261
265
  async trackAccess(ids: string[]): Promise<void> {
@@ -278,6 +282,19 @@ export class MemoryService {
278
282
  private static readonly UUID_ZERO =
279
283
  "00000000-0000-0000-0000-000000000000";
280
284
 
285
+ private static waypointId(project?: string): string {
286
+ if (!project?.length) return MemoryService.UUID_ZERO;
287
+ const hex = createHash("sha256").update(`waypoint:${project}`).digest("hex");
288
+ // Format as UUID: 8-4-4-4-12
289
+ return [
290
+ hex.slice(0, 8),
291
+ hex.slice(8, 12),
292
+ hex.slice(12, 16),
293
+ hex.slice(16, 20),
294
+ hex.slice(20, 32),
295
+ ].join("-");
296
+ }
297
+
281
298
  async setWaypoint(args: {
282
299
  project: string;
283
300
  branch?: string;
@@ -336,7 +353,7 @@ ${list(args.memory_ids)}`;
336
353
  };
337
354
 
338
355
  const memory: Memory = {
339
- id: MemoryService.UUID_ZERO,
356
+ id: MemoryService.waypointId(args.project),
340
357
  content,
341
358
  embedding: new Array(this.embeddings.dimension).fill(0),
342
359
  metadata,
@@ -352,7 +369,7 @@ ${list(args.memory_ids)}`;
352
369
  return memory;
353
370
  }
354
371
 
355
- async getLatestWaypoint(): Promise<Memory | null> {
356
- return await this.get(MemoryService.UUID_ZERO);
372
+ async getLatestWaypoint(project?: string): Promise<Memory | null> {
373
+ return await this.get(MemoryService.waypointId(project));
357
374
  }
358
375
  }
@@ -118,4 +118,5 @@ export interface SearchOptions {
118
118
  historyOnly?: boolean;
119
119
  historyWeight?: number;
120
120
  historyFilters?: HistoryFilters;
121
+ offset?: number;
121
122
  }