@aeriondyseti/vector-memory-mcp 0.5.0 → 0.8.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.
@@ -1,34 +1,96 @@
1
1
  import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
2
  import type { MemoryService } from "../services/memory.service.js";
3
3
 
4
- export async function handleStoreMemory(
4
+ export async function handleStoreMemories(
5
5
  args: Record<string, unknown> | undefined,
6
6
  service: MemoryService
7
7
  ): Promise<CallToolResult> {
8
- const content = args?.content as string;
9
- const embeddingText = args?.embedding_text as string | undefined;
10
- const metadata = (args?.metadata as Record<string, unknown>) ?? {};
11
- const memory = await service.store(content, metadata, embeddingText);
8
+ const memories = args?.memories as Array<{
9
+ content: string;
10
+ embedding_text?: string;
11
+ metadata?: Record<string, unknown>;
12
+ }>;
13
+
14
+ const ids: string[] = [];
15
+ for (const item of memories) {
16
+ const memory = await service.store(
17
+ item.content,
18
+ item.metadata ?? {},
19
+ item.embedding_text
20
+ );
21
+ ids.push(memory.id);
22
+ }
12
23
 
13
24
  return {
14
- content: [{ type: "text", text: `Memory stored with ID: ${memory.id}` }],
25
+ content: [
26
+ {
27
+ type: "text",
28
+ text:
29
+ ids.length === 1
30
+ ? `Memory stored with ID: ${ids[0]}`
31
+ : `Stored ${ids.length} memories:\n${ids.map((id) => `- ${id}`).join("\n")}`,
32
+ },
33
+ ],
15
34
  };
16
35
  }
17
36
 
18
- export async function handleDeleteMemory(
37
+ export async function handleDeleteMemories(
19
38
  args: Record<string, unknown> | undefined,
20
39
  service: MemoryService
21
40
  ): Promise<CallToolResult> {
22
- const id = args?.id as string;
23
- const success = await service.delete(id);
41
+ const ids = args?.ids as string[];
42
+ const results: string[] = [];
43
+
44
+ for (const id of ids) {
45
+ const success = await service.delete(id);
46
+ results.push(
47
+ success ? `Memory ${id} deleted successfully` : `Memory ${id} not found`
48
+ );
49
+ }
24
50
 
25
51
  return {
26
52
  content: [
27
53
  {
28
54
  type: "text",
29
- text: success
30
- ? `Memory ${id} deleted successfully`
31
- : `Memory ${id} not found`,
55
+ text: results.join("\n"),
56
+ },
57
+ ],
58
+ };
59
+ }
60
+
61
+
62
+ export async function handleUpdateMemories(
63
+ args: Record<string, unknown> | undefined,
64
+ service: MemoryService
65
+ ): Promise<CallToolResult> {
66
+ const updates = args?.updates as Array<{
67
+ id: string;
68
+ content?: string;
69
+ embedding_text?: string;
70
+ metadata?: Record<string, unknown>;
71
+ }>;
72
+
73
+ const results: string[] = [];
74
+
75
+ for (const update of updates) {
76
+ const memory = await service.update(update.id, {
77
+ content: update.content,
78
+ embeddingText: update.embedding_text,
79
+ metadata: update.metadata,
80
+ });
81
+
82
+ if (memory) {
83
+ results.push(`Memory ${update.id} updated successfully`);
84
+ } else {
85
+ results.push(`Memory ${update.id} not found`);
86
+ }
87
+ }
88
+
89
+ return {
90
+ content: [
91
+ {
92
+ type: "text",
93
+ text: results.join("\n"),
32
94
  },
33
95
  ],
34
96
  };
@@ -40,7 +102,8 @@ export async function handleSearchMemories(
40
102
  ): Promise<CallToolResult> {
41
103
  const query = args?.query as string;
42
104
  const limit = (args?.limit as number) ?? 10;
43
- const memories = await service.search(query, limit);
105
+ const includeDeleted = (args?.include_deleted as boolean) ?? false;
106
+ const memories = await service.search(query, limit, includeDeleted);
44
107
 
45
108
  if (memories.length === 0) {
46
109
  return {
@@ -53,6 +116,9 @@ export async function handleSearchMemories(
53
116
  if (Object.keys(mem.metadata).length > 0) {
54
117
  result += `\nMetadata: ${JSON.stringify(mem.metadata)}`;
55
118
  }
119
+ if (includeDeleted && mem.supersededBy) {
120
+ result += `\n[DELETED]`;
121
+ }
56
122
  return result;
57
123
  });
58
124
 
@@ -61,31 +127,95 @@ export async function handleSearchMemories(
61
127
  };
62
128
  }
63
129
 
64
- export async function handleGetMemory(
130
+ export async function handleGetMemories(
65
131
  args: Record<string, unknown> | undefined,
66
132
  service: MemoryService
67
133
  ): Promise<CallToolResult> {
68
- const id = args?.id as string;
69
- const memory = await service.get(id);
134
+ const ids = args?.ids as string[];
70
135
 
71
- if (!memory) {
136
+ const format = (
137
+ memoryId: string,
138
+ memory: Awaited<ReturnType<MemoryService["get"]>>
139
+ ) => {
140
+ if (!memory) {
141
+ return `Memory ${memoryId} not found`;
142
+ }
143
+
144
+ let result = `ID: ${memory.id}\nContent: ${memory.content}`;
145
+ if (Object.keys(memory.metadata).length > 0) {
146
+ result += `\nMetadata: ${JSON.stringify(memory.metadata)}`;
147
+ }
148
+ result += `\nCreated: ${memory.createdAt.toISOString()}`;
149
+ result += `\nUpdated: ${memory.updatedAt.toISOString()}`;
150
+ if (memory.supersededBy) {
151
+ result += `\nSuperseded by: ${memory.supersededBy}`;
152
+ }
153
+ return result;
154
+ };
155
+
156
+ const blocks: string[] = [];
157
+ for (const id of ids) {
158
+ const memory = await service.get(id);
159
+ blocks.push(format(id, memory));
160
+ }
161
+
162
+ return {
163
+ content: [{ type: "text", text: blocks.join("\n\n---\n\n") }],
164
+ };
165
+ }
166
+
167
+ export async function handleStoreHandoff(
168
+ args: Record<string, unknown> | undefined,
169
+ service: MemoryService
170
+ ): Promise<CallToolResult> {
171
+ const memory = await service.storeHandoff({
172
+ project: args?.project as string,
173
+ branch: args?.branch as string | undefined,
174
+ summary: args?.summary as string,
175
+ completed: (args?.completed as string[] | undefined) ?? [],
176
+ in_progress_blocked: (args?.in_progress_blocked as string[] | undefined) ?? [],
177
+ key_decisions: (args?.key_decisions as string[] | undefined) ?? [],
178
+ next_steps: (args?.next_steps as string[] | undefined) ?? [],
179
+ memory_ids: (args?.memory_ids as string[] | undefined) ?? [],
180
+ metadata: (args?.metadata as Record<string, unknown>) ?? {},
181
+ });
182
+
183
+ return {
184
+ content: [{ type: "text", text: `Handoff stored with memory ID: ${memory.id}` }],
185
+ };
186
+ }
187
+
188
+ export async function handleGetHandoff(
189
+ _args: Record<string, unknown> | undefined,
190
+ service: MemoryService
191
+ ): Promise<CallToolResult> {
192
+ const handoff = await service.getLatestHandoff();
193
+
194
+ if (!handoff) {
72
195
  return {
73
- content: [{ type: "text", text: `Memory ${id} not found` }],
196
+ content: [{ type: "text", text: "No stored handoff found." }],
74
197
  };
75
198
  }
76
199
 
77
- let result = `ID: ${memory.id}\nContent: ${memory.content}`;
78
- if (Object.keys(memory.metadata).length > 0) {
79
- result += `\nMetadata: ${JSON.stringify(memory.metadata)}`;
80
- }
81
- result += `\nCreated: ${memory.createdAt.toISOString()}`;
82
- result += `\nUpdated: ${memory.updatedAt.toISOString()}`;
83
- if (memory.supersededBy) {
84
- result += `\nSuperseded by: ${memory.supersededBy}`;
200
+ // Fetch referenced memories if any
201
+ const memoryIds = (handoff.metadata.memory_ids as string[] | undefined) ?? [];
202
+ let memoriesSection = "";
203
+
204
+ if (memoryIds.length > 0) {
205
+ const memories: string[] = [];
206
+ for (const id of memoryIds) {
207
+ const memory = await service.get(id);
208
+ if (memory) {
209
+ memories.push(`### Memory: ${id}\n${memory.content}`);
210
+ }
211
+ }
212
+ if (memories.length > 0) {
213
+ memoriesSection = `\n\n## Referenced Memories\n\n${memories.join("\n\n")}`;
214
+ }
85
215
  }
86
216
 
87
217
  return {
88
- content: [{ type: "text", text: result }],
218
+ content: [{ type: "text", text: handoff.content + memoriesSection }],
89
219
  };
90
220
  }
91
221
 
@@ -95,14 +225,20 @@ export async function handleToolCall(
95
225
  service: MemoryService
96
226
  ): Promise<CallToolResult> {
97
227
  switch (name) {
98
- case "store_memory":
99
- return handleStoreMemory(args, service);
100
- case "delete_memory":
101
- return handleDeleteMemory(args, service);
228
+ case "store_memories":
229
+ return handleStoreMemories(args, service);
230
+ case "update_memories":
231
+ return handleUpdateMemories(args, service);
232
+ case "delete_memories":
233
+ return handleDeleteMemories(args, service);
102
234
  case "search_memories":
103
235
  return handleSearchMemories(args, service);
104
- case "get_memory":
105
- return handleGetMemory(args, service);
236
+ case "get_memories":
237
+ return handleGetMemories(args, service);
238
+ case "store_handoff":
239
+ return handleStoreHandoff(args, service);
240
+ case "get_handoff":
241
+ return handleGetHandoff(args, service);
106
242
  default:
107
243
  return {
108
244
  content: [{ type: "text", text: `Unknown tool: ${name}` }],
package/src/mcp/server.ts CHANGED
@@ -11,7 +11,7 @@ import type { MemoryService } from "../services/memory.service.js";
11
11
 
12
12
  export function createServer(memoryService: MemoryService): Server {
13
13
  const server = new Server(
14
- { name: "vector-memory-mcp", version: "0.2.0" },
14
+ { name: "vector-memory-mcp", version: "0.6.0" },
15
15
  { capabilities: { tools: {} } }
16
16
  );
17
17
 
package/src/mcp/tools.ts CHANGED
@@ -1,109 +1,214 @@
1
1
  import type { Tool } from "@modelcontextprotocol/sdk/types.js";
2
2
 
3
- export const storeMemoryTool: Tool = {
4
- name: "store_memory",
3
+ export const storeMemoriesTool: Tool = {
4
+ name: "store_memories",
5
5
  description:
6
- "Store a new memory for later recall. " +
7
- "PROACTIVELY store memories when: " +
8
- "(1) Important decisions are made (technical choices, architecture decisions, tradeoffs); " +
9
- "(2) Problems are solved (bugs fixed, errors resolved - include the solution); " +
10
- "(3) User preferences or conventions are established; " +
11
- "(4) Project-specific patterns or configurations are discussed; " +
12
- "(5) Key information would be valuable in future sessions. " +
13
- "IMPORTANT: If the content exceeds 1000 characters, you MUST provide an embedding_text " +
14
- "parameter with a concise summary (under 1000 characters) that captures the key semantic " +
15
- "meaning for search purposes. The full content will still be stored and returned in search results.",
6
+ "Build persistent memory across conversations. Store decisions, solutions, user preferences, " +
7
+ "patterns, or anything worth remembering. Batch related items in one call. " +
8
+ "For long content (>1000 chars), provide embedding_text with a searchable summary.",
16
9
  inputSchema: {
17
10
  type: "object",
18
11
  properties: {
19
- content: {
20
- type: "string",
21
- description:
22
- "The text content to store. Write in a way that will be useful when retrieved later - " +
23
- "include context, reasoning, and outcomes, not just bare facts.",
24
- },
25
- embedding_text: {
26
- type: "string",
27
- description:
28
- "A concise summary (under 1000 characters) used for generating the search embedding. " +
29
- "REQUIRED when content exceeds 1000 characters. Should capture the key topics and terms " +
30
- "someone might search for to find this memory.",
12
+ memories: {
13
+ type: "array",
14
+ description: "Memories to store.",
15
+ items: {
16
+ type: "object",
17
+ properties: {
18
+ content: {
19
+ type: "string",
20
+ description: "The content to store.",
21
+ },
22
+ embedding_text: {
23
+ type: "string",
24
+ description:
25
+ "Summary for search embedding (required if content >1000 chars).",
26
+ },
27
+ metadata: {
28
+ type: "object",
29
+ description: "Optional key-value metadata.",
30
+ additionalProperties: true,
31
+ },
32
+ },
33
+ required: ["content"],
34
+ },
31
35
  },
32
- metadata: {
33
- type: "object",
34
- description:
35
- "Optional key-value metadata. Recommended keys: 'project' (project name), 'type' (decision/solution/preference/pattern), 'tags' (array of keywords).",
36
- additionalProperties: true,
36
+ },
37
+ required: ["memories"],
38
+ },
39
+ };;
40
+
41
+ export const deleteMemoriesTool: Tool = {
42
+ name: "delete_memories",
43
+ description:
44
+ "Remove memories that are no longer needed—outdated info, superseded decisions, or incorrect content. " +
45
+ "Deleted memories can be recovered via search_memories with include_deleted: true.",
46
+ inputSchema: {
47
+ type: "object",
48
+ properties: {
49
+ ids: {
50
+ type: "array",
51
+ description: "IDs of memories to delete.",
52
+ items: {
53
+ type: "string",
54
+ },
37
55
  },
38
56
  },
39
- required: ["content"],
57
+ required: ["ids"],
40
58
  },
41
- };
59
+ };;
42
60
 
43
- export const deleteMemoryTool: Tool = {
44
- name: "delete_memory",
61
+
62
+ const updateMemoriesTool: Tool = {
63
+ name: "update_memories",
45
64
  description:
46
- "Delete a memory by its ID. The memory will be soft-deleted and won't appear in search results.",
65
+ "Update existing memories in place. Use to correct content, refine embedding text, or replace metadata " +
66
+ "without changing the memory ID. Prefer over delete+create when updating the same conceptual item.",
47
67
  inputSchema: {
48
68
  type: "object",
49
69
  properties: {
50
- id: {
51
- type: "string",
52
- description: "The ID of the memory to delete",
70
+ updates: {
71
+ type: "array",
72
+ description: "Updates to apply. Each must include id and at least one field to change.",
73
+ items: {
74
+ type: "object",
75
+ properties: {
76
+ id: {
77
+ type: "string",
78
+ description: "ID of memory to update.",
79
+ },
80
+ content: {
81
+ type: "string",
82
+ description: "New content (triggers embedding regeneration).",
83
+ },
84
+ embedding_text: {
85
+ type: "string",
86
+ description: "New embedding summary (triggers embedding regeneration).",
87
+ },
88
+ metadata: {
89
+ type: "object",
90
+ description: "New metadata (replaces existing entirely).",
91
+ additionalProperties: true,
92
+ },
93
+ },
94
+ required: ["id"],
95
+ },
53
96
  },
54
97
  },
55
- required: ["id"],
98
+ required: ["updates"],
56
99
  },
57
100
  };
58
101
 
59
102
  export const searchMemoriesTool: Tool = {
60
103
  name: "search_memories",
61
104
  description:
62
- "Search for memories using semantic similarity. Returns the most relevant memories for the given query. " +
63
- "IMPORTANT: Use this tool PROACTIVELY without being asked when you detect: " +
64
- "(1) References to past decisions or discussions ('what did we decide', 'as we discussed', 'previously'); " +
65
- "(2) Questions about prior work ('how did we', 'similar to before', 'like last time'); " +
66
- "(3) Project continuations or returning to earlier topics; " +
67
- "(4) Debugging issues that may have been solved before; " +
68
- "(5) Questions about established patterns, conventions, or preferences. " +
69
- "When in doubt, search - it's better to check for relevant context than to miss important prior decisions.",
105
+ "Search stored memories semantically. Use PROACTIVELY—don't wait to be asked. " +
106
+ "Triggers: references to past decisions ('what did we decide', 'as discussed'), " +
107
+ "questions about prior work, returning to a project, debugging something potentially solved before, " +
108
+ "or questions about established patterns/preferences. " +
109
+ "When uncertain, search—missing relevant memories is costlier than an extra query.",
70
110
  inputSchema: {
71
111
  type: "object",
72
112
  properties: {
73
113
  query: {
74
114
  type: "string",
75
115
  description:
76
- "The search query to find relevant memories. Use natural language describing what context you need. " +
77
- "Include relevant keywords, project names, or technical terms for better results.",
116
+ "Natural language search query. Include relevant keywords, project names, or technical terms.",
78
117
  },
79
118
  limit: {
80
119
  type: "integer",
81
- description: "Maximum number of results to return (default: 10)",
120
+ description: "Maximum results to return (default: 10).",
82
121
  default: 10,
83
122
  },
123
+ include_deleted: {
124
+ type: "boolean",
125
+ description: "Include soft-deleted memories in results (default: false). Useful for recovering prior information.",
126
+ default: false,
127
+ },
84
128
  },
85
129
  required: ["query"],
86
130
  },
87
131
  };
88
132
 
89
- export const getMemoryTool: Tool = {
90
- name: "get_memory",
91
- description: "Retrieve a specific memory by its ID.",
133
+ export const getMemoriesTool: Tool = {
134
+ name: "get_memories",
135
+ description:
136
+ "Retrieve full memory details by ID. Use when you have specific IDs from search results or prior references—otherwise use search_memories.",
92
137
  inputSchema: {
93
138
  type: "object",
94
139
  properties: {
95
- id: {
96
- type: "string",
97
- description: "The ID of the memory to retrieve",
140
+ ids: {
141
+ type: "array",
142
+ description: "Memory IDs to retrieve.",
143
+ items: { type: "string" },
98
144
  },
99
145
  },
100
- required: ["id"],
146
+ required: ["ids"],
147
+ },
148
+ };;
149
+
150
+ export const storeHandoffTool: Tool = {
151
+ name: "store_handoff",
152
+ description:
153
+ "Capture structured project state for handoff: summary, completed work, blockers, key decisions, next steps. " +
154
+ "Retrievable via get_handoff.",
155
+ inputSchema: {
156
+ type: "object",
157
+ properties: {
158
+ project: { type: "string", description: "Project name." },
159
+ branch: { type: "string", description: "Branch name (optional)." },
160
+ summary: { type: "string", description: "2-3 sentences: primary goal, current status." },
161
+ completed: {
162
+ type: "array",
163
+ items: { type: "string" },
164
+ description: "Completed items (include file paths where relevant).",
165
+ },
166
+ in_progress_blocked: {
167
+ type: "array",
168
+ items: { type: "string" },
169
+ description: "In progress or blocked items.",
170
+ },
171
+ key_decisions: {
172
+ type: "array",
173
+ items: { type: "string" },
174
+ description: "Decisions made and why.",
175
+ },
176
+ next_steps: {
177
+ type: "array",
178
+ items: { type: "string" },
179
+ description: "Concrete next steps.",
180
+ },
181
+ memory_ids: {
182
+ type: "array",
183
+ items: { type: "string" },
184
+ description: "Memory IDs referenced by this handoff.",
185
+ },
186
+ metadata: {
187
+ type: "object",
188
+ description: "Additional metadata.",
189
+ additionalProperties: true,
190
+ },
191
+ },
192
+ required: ["project", "summary"],
193
+ },
194
+ };
195
+
196
+ export const getHandoffTool: Tool = {
197
+ name: "get_handoff",
198
+ description:
199
+ "Load the current project handoff snapshot. Call at conversation start or when resuming a project.",
200
+ inputSchema: {
201
+ type: "object",
202
+ properties: {},
101
203
  },
102
204
  };
103
205
 
104
206
  export const tools: Tool[] = [
105
- storeMemoryTool,
106
- deleteMemoryTool,
207
+ storeMemoriesTool,
208
+ updateMemoriesTool,
209
+ deleteMemoriesTool,
107
210
  searchMemoriesTool,
108
- getMemoryTool,
211
+ getMemoriesTool,
212
+ storeHandoffTool,
213
+ getHandoffTool,
109
214
  ];
@@ -21,9 +21,11 @@ export class EmbeddingsService {
21
21
  }
22
22
 
23
23
  if (!this.initPromise) {
24
- this.initPromise = pipeline("feature-extraction", this.modelName, {
25
- quantized: true,
26
- }) as Promise<FeatureExtractionPipeline>;
24
+ this.initPromise = pipeline(
25
+ "feature-extraction",
26
+ this.modelName,
27
+ { dtype: "fp32" } as any
28
+ ) as Promise<FeatureExtractionPipeline>;
27
29
  }
28
30
 
29
31
  this.extractor = await this.initPromise;