@waynesutton/agent-memory 0.0.1-alpha.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.
Files changed (200) hide show
  1. package/.claude/settings.json +9 -0
  2. package/.claude/settings.local.json +7 -0
  3. package/AGENTS.md +113 -0
  4. package/CLAUDE.md +79 -0
  5. package/README.md +1003 -0
  6. package/dist/cli/index.d.ts +3 -0
  7. package/dist/cli/index.d.ts.map +1 -0
  8. package/dist/cli/index.js +192 -0
  9. package/dist/cli/index.js.map +1 -0
  10. package/dist/cli/parsers/claude-code.d.ts +3 -0
  11. package/dist/cli/parsers/claude-code.d.ts.map +1 -0
  12. package/dist/cli/parsers/claude-code.js +75 -0
  13. package/dist/cli/parsers/claude-code.js.map +1 -0
  14. package/dist/cli/parsers/codex.d.ts +3 -0
  15. package/dist/cli/parsers/codex.d.ts.map +1 -0
  16. package/dist/cli/parsers/codex.js +42 -0
  17. package/dist/cli/parsers/codex.js.map +1 -0
  18. package/dist/cli/parsers/conductor.d.ts +3 -0
  19. package/dist/cli/parsers/conductor.d.ts.map +1 -0
  20. package/dist/cli/parsers/conductor.js +43 -0
  21. package/dist/cli/parsers/conductor.js.map +1 -0
  22. package/dist/cli/parsers/cursor.d.ts +3 -0
  23. package/dist/cli/parsers/cursor.d.ts.map +1 -0
  24. package/dist/cli/parsers/cursor.js +50 -0
  25. package/dist/cli/parsers/cursor.js.map +1 -0
  26. package/dist/cli/parsers/index.d.ts +12 -0
  27. package/dist/cli/parsers/index.d.ts.map +1 -0
  28. package/dist/cli/parsers/index.js +27 -0
  29. package/dist/cli/parsers/index.js.map +1 -0
  30. package/dist/cli/parsers/opencode.d.ts +3 -0
  31. package/dist/cli/parsers/opencode.d.ts.map +1 -0
  32. package/dist/cli/parsers/opencode.js +72 -0
  33. package/dist/cli/parsers/opencode.js.map +1 -0
  34. package/dist/cli/parsers/parsers.test.d.ts +2 -0
  35. package/dist/cli/parsers/parsers.test.d.ts.map +1 -0
  36. package/dist/cli/parsers/parsers.test.js +151 -0
  37. package/dist/cli/parsers/parsers.test.js.map +1 -0
  38. package/dist/cli/parsers/pi.d.ts +3 -0
  39. package/dist/cli/parsers/pi.d.ts.map +1 -0
  40. package/dist/cli/parsers/pi.js +43 -0
  41. package/dist/cli/parsers/pi.js.map +1 -0
  42. package/dist/cli/parsers/types.d.ts +25 -0
  43. package/dist/cli/parsers/types.d.ts.map +1 -0
  44. package/dist/cli/parsers/types.js +2 -0
  45. package/dist/cli/parsers/types.js.map +1 -0
  46. package/dist/cli/parsers/vscode-copilot.d.ts +3 -0
  47. package/dist/cli/parsers/vscode-copilot.d.ts.map +1 -0
  48. package/dist/cli/parsers/vscode-copilot.js +69 -0
  49. package/dist/cli/parsers/vscode-copilot.js.map +1 -0
  50. package/dist/cli/parsers/zed.d.ts +3 -0
  51. package/dist/cli/parsers/zed.d.ts.map +1 -0
  52. package/dist/cli/parsers/zed.js +43 -0
  53. package/dist/cli/parsers/zed.js.map +1 -0
  54. package/dist/cli/sync.d.ts +21 -0
  55. package/dist/cli/sync.d.ts.map +1 -0
  56. package/dist/cli/sync.js +78 -0
  57. package/dist/cli/sync.js.map +1 -0
  58. package/dist/cli/type-extractor.d.ts +25 -0
  59. package/dist/cli/type-extractor.d.ts.map +1 -0
  60. package/dist/cli/type-extractor.js +254 -0
  61. package/dist/cli/type-extractor.js.map +1 -0
  62. package/dist/cli/type-extractor.test.d.ts +2 -0
  63. package/dist/cli/type-extractor.test.d.ts.map +1 -0
  64. package/dist/cli/type-extractor.test.js +173 -0
  65. package/dist/cli/type-extractor.test.js.map +1 -0
  66. package/dist/client/http.d.ts +44 -0
  67. package/dist/client/http.d.ts.map +1 -0
  68. package/dist/client/http.js +311 -0
  69. package/dist/client/http.js.map +1 -0
  70. package/dist/client/index.d.ts +158 -0
  71. package/dist/client/index.d.ts.map +1 -0
  72. package/dist/client/index.js +256 -0
  73. package/dist/client/index.js.map +1 -0
  74. package/dist/component/_generated/api.d.ts +12 -0
  75. package/dist/component/_generated/api.d.ts.map +1 -0
  76. package/dist/component/_generated/api.js +13 -0
  77. package/dist/component/_generated/api.js.map +1 -0
  78. package/dist/component/_generated/dataModel.d.ts +18 -0
  79. package/dist/component/_generated/dataModel.d.ts.map +1 -0
  80. package/dist/component/_generated/dataModel.js +11 -0
  81. package/dist/component/_generated/dataModel.js.map +1 -0
  82. package/dist/component/_generated/server.d.ts +42 -0
  83. package/dist/component/_generated/server.d.ts.map +1 -0
  84. package/dist/component/_generated/server.js +39 -0
  85. package/dist/component/_generated/server.js.map +1 -0
  86. package/dist/component/actions.d.ts +42 -0
  87. package/dist/component/actions.d.ts.map +1 -0
  88. package/dist/component/actions.js +405 -0
  89. package/dist/component/actions.js.map +1 -0
  90. package/dist/component/apiKeyMutations.d.ts +29 -0
  91. package/dist/component/apiKeyMutations.d.ts.map +1 -0
  92. package/dist/component/apiKeyMutations.js +149 -0
  93. package/dist/component/apiKeyMutations.js.map +1 -0
  94. package/dist/component/apiKeyQueries.d.ts +37 -0
  95. package/dist/component/apiKeyQueries.d.ts.map +1 -0
  96. package/dist/component/apiKeyQueries.js +127 -0
  97. package/dist/component/apiKeyQueries.js.map +1 -0
  98. package/dist/component/checksum.d.ts +6 -0
  99. package/dist/component/checksum.d.ts.map +1 -0
  100. package/dist/component/checksum.js +14 -0
  101. package/dist/component/checksum.js.map +1 -0
  102. package/dist/component/checksum.test.d.ts +2 -0
  103. package/dist/component/checksum.test.d.ts.map +1 -0
  104. package/dist/component/checksum.test.js +27 -0
  105. package/dist/component/checksum.test.js.map +1 -0
  106. package/dist/component/convex.config.d.ts +3 -0
  107. package/dist/component/convex.config.d.ts.map +1 -0
  108. package/dist/component/convex.config.js +4 -0
  109. package/dist/component/convex.config.js.map +1 -0
  110. package/dist/component/cronActions.d.ts +3 -0
  111. package/dist/component/cronActions.d.ts.map +1 -0
  112. package/dist/component/cronActions.js +38 -0
  113. package/dist/component/cronActions.js.map +1 -0
  114. package/dist/component/cronQueries.d.ts +6 -0
  115. package/dist/component/cronQueries.d.ts.map +1 -0
  116. package/dist/component/cronQueries.js +38 -0
  117. package/dist/component/cronQueries.js.map +1 -0
  118. package/dist/component/crons.d.ts +3 -0
  119. package/dist/component/crons.d.ts.map +1 -0
  120. package/dist/component/crons.js +18 -0
  121. package/dist/component/crons.js.map +1 -0
  122. package/dist/component/format.d.ts +11 -0
  123. package/dist/component/format.d.ts.map +1 -0
  124. package/dist/component/format.js +175 -0
  125. package/dist/component/format.js.map +1 -0
  126. package/dist/component/format.test.d.ts +2 -0
  127. package/dist/component/format.test.d.ts.map +1 -0
  128. package/dist/component/format.test.js +118 -0
  129. package/dist/component/format.test.js.map +1 -0
  130. package/dist/component/mutations.d.ts +158 -0
  131. package/dist/component/mutations.d.ts.map +1 -0
  132. package/dist/component/mutations.js +745 -0
  133. package/dist/component/mutations.js.map +1 -0
  134. package/dist/component/queries.d.ts +94 -0
  135. package/dist/component/queries.d.ts.map +1 -0
  136. package/dist/component/queries.js +574 -0
  137. package/dist/component/queries.js.map +1 -0
  138. package/dist/component/schema.d.ts +278 -0
  139. package/dist/component/schema.d.ts.map +1 -0
  140. package/dist/component/schema.js +161 -0
  141. package/dist/component/schema.js.map +1 -0
  142. package/dist/mcp/server.d.ts +11 -0
  143. package/dist/mcp/server.d.ts.map +1 -0
  144. package/dist/mcp/server.js +571 -0
  145. package/dist/mcp/server.js.map +1 -0
  146. package/dist/shared.d.ts +126 -0
  147. package/dist/shared.d.ts.map +1 -0
  148. package/dist/shared.js +67 -0
  149. package/dist/shared.js.map +1 -0
  150. package/dist/test.d.ts +23 -0
  151. package/dist/test.d.ts.map +1 -0
  152. package/dist/test.js +21 -0
  153. package/dist/test.js.map +1 -0
  154. package/eslint.config.js +15 -0
  155. package/example/convex/convex.config.ts +7 -0
  156. package/example/convex/memory.ts +129 -0
  157. package/llms.md +175 -0
  158. package/llms.txt +126 -0
  159. package/package.json +80 -0
  160. package/prds/API-REFERENCE.md +935 -0
  161. package/prds/SETUP.md +682 -0
  162. package/src/cli/index.ts +254 -0
  163. package/src/cli/parsers/claude-code.ts +80 -0
  164. package/src/cli/parsers/codex.ts +45 -0
  165. package/src/cli/parsers/conductor.ts +47 -0
  166. package/src/cli/parsers/cursor.ts +55 -0
  167. package/src/cli/parsers/index.ts +30 -0
  168. package/src/cli/parsers/opencode.ts +84 -0
  169. package/src/cli/parsers/parsers.test.ts +201 -0
  170. package/src/cli/parsers/pi.ts +47 -0
  171. package/src/cli/parsers/types.ts +26 -0
  172. package/src/cli/parsers/vscode-copilot.ts +78 -0
  173. package/src/cli/parsers/zed.ts +47 -0
  174. package/src/cli/sync.ts +110 -0
  175. package/src/cli/type-extractor.test.ts +241 -0
  176. package/src/cli/type-extractor.ts +331 -0
  177. package/src/client/http.ts +415 -0
  178. package/src/client/index.ts +519 -0
  179. package/src/component/_generated/api.ts +14 -0
  180. package/src/component/_generated/dataModel.ts +20 -0
  181. package/src/component/_generated/server.ts +64 -0
  182. package/src/component/actions.ts +558 -0
  183. package/src/component/apiKeyMutations.ts +175 -0
  184. package/src/component/apiKeyQueries.ts +156 -0
  185. package/src/component/checksum.test.ts +31 -0
  186. package/src/component/checksum.ts +13 -0
  187. package/src/component/convex.config.ts +5 -0
  188. package/src/component/cronActions.ts +52 -0
  189. package/src/component/cronQueries.ts +42 -0
  190. package/src/component/crons.ts +34 -0
  191. package/src/component/format.test.ts +133 -0
  192. package/src/component/format.ts +232 -0
  193. package/src/component/mutations.ts +824 -0
  194. package/src/component/queries.ts +684 -0
  195. package/src/component/schema.ts +207 -0
  196. package/src/mcp/server.ts +695 -0
  197. package/src/shared.ts +251 -0
  198. package/src/test.ts +32 -0
  199. package/tsconfig.json +21 -0
  200. package/vitest.config.ts +8 -0
@@ -0,0 +1,695 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import {
4
+ CallToolRequestSchema,
5
+ ListToolsRequestSchema,
6
+ ListResourcesRequestSchema,
7
+ ReadResourceRequestSchema,
8
+ } from "@modelcontextprotocol/sdk/types.js";
9
+ import { ConvexHttpClient } from "convex/browser";
10
+
11
+ export interface McpServerConfig {
12
+ convexUrl: string;
13
+ projectId: string;
14
+ readOnly: boolean;
15
+ disabledTools: string[];
16
+ embeddingApiKey?: string;
17
+ llmApiKey?: string;
18
+ llmModel?: string;
19
+ }
20
+
21
+ export async function startMcpServer(config: McpServerConfig): Promise<void> {
22
+ const client = new ConvexHttpClient(config.convexUrl);
23
+ const server = new Server(
24
+ {
25
+ name: "agent-memory",
26
+ version: "0.1.0",
27
+ },
28
+ {
29
+ capabilities: {
30
+ tools: {},
31
+ resources: {},
32
+ },
33
+ },
34
+ );
35
+
36
+ const WRITE_TOOLS = [
37
+ "memory_remember",
38
+ "memory_forget",
39
+ "memory_update",
40
+ "memory_restore",
41
+ "memory_feedback",
42
+ "memory_relate",
43
+ "memory_batch_archive",
44
+ "memory_ingest",
45
+ ];
46
+
47
+ const isDisabled = (name: string) =>
48
+ config.disabledTools.includes(name) ||
49
+ (config.readOnly && WRITE_TOOLS.includes(name));
50
+
51
+ // ── List Tools ──────────────────────────────────────────────────
52
+
53
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
54
+ const allTools = [
55
+ {
56
+ name: "memory_remember",
57
+ description: "Save a new memory for future sessions",
58
+ inputSchema: {
59
+ type: "object" as const,
60
+ properties: {
61
+ title: { type: "string", description: "Short title/slug for the memory" },
62
+ content: { type: "string", description: "Markdown content of the memory" },
63
+ memoryType: {
64
+ type: "string",
65
+ enum: ["instruction", "learning", "reference", "feedback", "journal"],
66
+ description: "Type of memory",
67
+ },
68
+ tags: { type: "array", items: { type: "string" }, description: "Tags" },
69
+ priority: { type: "number", minimum: 0, maximum: 1, description: "Priority (0-1, >= 0.8 = pinned)" },
70
+ agentId: { type: "string", description: "Agent that created this memory" },
71
+ sessionId: { type: "string", description: "Session/conversation ID" },
72
+ },
73
+ required: ["title", "content", "memoryType"],
74
+ },
75
+ },
76
+ {
77
+ name: "memory_recall",
78
+ description: "Search memories by keyword (full-text)",
79
+ inputSchema: {
80
+ type: "object" as const,
81
+ properties: {
82
+ query: { type: "string", description: "Search query" },
83
+ memoryType: { type: "string", description: "Filter by type" },
84
+ limit: { type: "number", description: "Max results" },
85
+ },
86
+ required: ["query"],
87
+ },
88
+ },
89
+ {
90
+ name: "memory_semantic_recall",
91
+ description: "Search memories by meaning (vector similarity)",
92
+ inputSchema: {
93
+ type: "object" as const,
94
+ properties: {
95
+ query: { type: "string", description: "Search query" },
96
+ limit: { type: "number", description: "Max results" },
97
+ },
98
+ required: ["query"],
99
+ },
100
+ },
101
+ {
102
+ name: "memory_list",
103
+ description: "List all memories with optional filters",
104
+ inputSchema: {
105
+ type: "object" as const,
106
+ properties: {
107
+ memoryType: { type: "string", description: "Filter by type" },
108
+ minPriority: { type: "number", description: "Minimum priority" },
109
+ agentId: { type: "string", description: "Filter by agent" },
110
+ sessionId: { type: "string", description: "Filter by session" },
111
+ source: { type: "string", description: "Filter by source tool" },
112
+ tags: { type: "array", items: { type: "string" }, description: "Filter by tags (any match)" },
113
+ limit: { type: "number", description: "Max results" },
114
+ },
115
+ },
116
+ },
117
+ {
118
+ name: "memory_context",
119
+ description: "Get context bundle (pinned + relevant memories) for the session",
120
+ inputSchema: {
121
+ type: "object" as const,
122
+ properties: {
123
+ activePaths: {
124
+ type: "array",
125
+ items: { type: "string" },
126
+ description: "Currently open file paths",
127
+ },
128
+ maxTokens: { type: "number", description: "Token budget" },
129
+ },
130
+ },
131
+ },
132
+ {
133
+ name: "memory_forget",
134
+ description: "Archive a memory so it is no longer loaded",
135
+ inputSchema: {
136
+ type: "object" as const,
137
+ properties: {
138
+ memoryId: { type: "string", description: "Memory ID to archive" },
139
+ },
140
+ required: ["memoryId"],
141
+ },
142
+ },
143
+ {
144
+ name: "memory_restore",
145
+ description: "Restore a previously archived memory",
146
+ inputSchema: {
147
+ type: "object" as const,
148
+ properties: {
149
+ memoryId: { type: "string", description: "Memory ID to restore" },
150
+ },
151
+ required: ["memoryId"],
152
+ },
153
+ },
154
+ {
155
+ name: "memory_update",
156
+ description: "Update an existing memory",
157
+ inputSchema: {
158
+ type: "object" as const,
159
+ properties: {
160
+ memoryId: { type: "string", description: "Memory ID to update" },
161
+ content: { type: "string", description: "New content" },
162
+ title: { type: "string", description: "New title" },
163
+ tags: { type: "array", items: { type: "string" } },
164
+ priority: { type: "number", minimum: 0, maximum: 1 },
165
+ },
166
+ required: ["memoryId"],
167
+ },
168
+ },
169
+ {
170
+ name: "memory_history",
171
+ description: "View the change history of a memory (audit trail)",
172
+ inputSchema: {
173
+ type: "object" as const,
174
+ properties: {
175
+ memoryId: { type: "string", description: "Memory ID" },
176
+ limit: { type: "number", description: "Max entries" },
177
+ },
178
+ required: ["memoryId"],
179
+ },
180
+ },
181
+ {
182
+ name: "memory_feedback",
183
+ description: "Rate a memory as helpful or unhelpful",
184
+ inputSchema: {
185
+ type: "object" as const,
186
+ properties: {
187
+ memoryId: { type: "string", description: "Memory ID to rate" },
188
+ sentiment: {
189
+ type: "string",
190
+ enum: ["positive", "negative", "very_negative"],
191
+ description: "Feedback sentiment",
192
+ },
193
+ comment: { type: "string", description: "Optional comment explaining the rating" },
194
+ },
195
+ required: ["memoryId", "sentiment"],
196
+ },
197
+ },
198
+ {
199
+ name: "memory_relate",
200
+ description: "Create a relationship between two memories",
201
+ inputSchema: {
202
+ type: "object" as const,
203
+ properties: {
204
+ fromMemoryId: { type: "string", description: "Source memory ID" },
205
+ toMemoryId: { type: "string", description: "Target memory ID" },
206
+ relationship: {
207
+ type: "string",
208
+ description: "Type of relationship (e.g. 'contradicts', 'extends', 'replaces', 'related_to')",
209
+ },
210
+ },
211
+ required: ["fromMemoryId", "toMemoryId", "relationship"],
212
+ },
213
+ },
214
+ {
215
+ name: "memory_relations",
216
+ description: "View relationships of a memory (graph connections)",
217
+ inputSchema: {
218
+ type: "object" as const,
219
+ properties: {
220
+ memoryId: { type: "string", description: "Memory ID" },
221
+ relationship: { type: "string", description: "Filter by relationship type" },
222
+ direction: { type: "string", enum: ["from", "to", "both"], description: "Direction filter" },
223
+ },
224
+ required: ["memoryId"],
225
+ },
226
+ },
227
+ {
228
+ name: "memory_batch_archive",
229
+ description: "Archive multiple memories at once",
230
+ inputSchema: {
231
+ type: "object" as const,
232
+ properties: {
233
+ memoryIds: {
234
+ type: "array",
235
+ items: { type: "string" },
236
+ description: "Memory IDs to archive",
237
+ },
238
+ },
239
+ required: ["memoryIds"],
240
+ },
241
+ },
242
+ {
243
+ name: "memory_ingest",
244
+ description:
245
+ "Intelligently extract memories from raw text. Uses LLM to extract facts, deduplicates against existing memories, and decides whether to add, update, or skip each fact.",
246
+ inputSchema: {
247
+ type: "object" as const,
248
+ properties: {
249
+ content: { type: "string", description: "Raw text to extract memories from (conversation, notes, etc.)" },
250
+ agentId: { type: "string", description: "Agent performing the ingest" },
251
+ sessionId: { type: "string", description: "Session/conversation ID" },
252
+ },
253
+ required: ["content"],
254
+ },
255
+ },
256
+ ];
257
+
258
+ return {
259
+ tools: allTools.filter((t) => !isDisabled(t.name)),
260
+ };
261
+ });
262
+
263
+ // ── Call Tool ───────────────────────────────────────────────────
264
+
265
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
266
+ const { name, arguments: args } = request.params;
267
+
268
+ if (isDisabled(name)) {
269
+ return {
270
+ content: [{ type: "text", text: `Tool "${name}" is disabled.` }],
271
+ isError: true,
272
+ };
273
+ }
274
+
275
+ try {
276
+ switch (name) {
277
+ case "memory_remember": {
278
+ const id = await client.mutation(
279
+ "agentMemory/mutations:create" as any,
280
+ {
281
+ projectId: config.projectId,
282
+ scope: "project",
283
+ title: args!.title as string,
284
+ content: args!.content as string,
285
+ memoryType: args!.memoryType as string,
286
+ tags: (args!.tags as string[]) ?? [],
287
+ priority: args!.priority as number | undefined,
288
+ agentId: args!.agentId as string | undefined,
289
+ sessionId: args!.sessionId as string | undefined,
290
+ source: "mcp",
291
+ },
292
+ );
293
+ return {
294
+ content: [
295
+ {
296
+ type: "text",
297
+ text: `Memory saved: "${args!.title}" (${id})`,
298
+ },
299
+ ],
300
+ };
301
+ }
302
+
303
+ case "memory_recall": {
304
+ const results = await client.query(
305
+ "agentMemory/queries:search" as any,
306
+ {
307
+ projectId: config.projectId,
308
+ query: args!.query as string,
309
+ memoryType: args!.memoryType as string | undefined,
310
+ limit: (args!.limit as number) ?? 10,
311
+ },
312
+ );
313
+ return {
314
+ content: [{ type: "text", text: formatMemories(results) }],
315
+ };
316
+ }
317
+
318
+ case "memory_semantic_recall": {
319
+ if (!config.embeddingApiKey) {
320
+ const results = await client.query(
321
+ "agentMemory/queries:search" as any,
322
+ {
323
+ projectId: config.projectId,
324
+ query: args!.query as string,
325
+ limit: (args!.limit as number) ?? 10,
326
+ },
327
+ );
328
+ return {
329
+ content: [{ type: "text", text: formatMemories(results) }],
330
+ };
331
+ }
332
+ // TODO: call semanticSearch action when action client support is available
333
+ const results = await client.query(
334
+ "agentMemory/queries:search" as any,
335
+ {
336
+ projectId: config.projectId,
337
+ query: args!.query as string,
338
+ limit: (args!.limit as number) ?? 10,
339
+ },
340
+ );
341
+ return {
342
+ content: [{ type: "text", text: formatMemories(results) }],
343
+ };
344
+ }
345
+
346
+ case "memory_list": {
347
+ const list = await client.query(
348
+ "agentMemory/queries:list" as any,
349
+ {
350
+ projectId: config.projectId,
351
+ memoryType: args?.memoryType as string | undefined,
352
+ minPriority: args?.minPriority as number | undefined,
353
+ agentId: args?.agentId as string | undefined,
354
+ sessionId: args?.sessionId as string | undefined,
355
+ source: args?.source as string | undefined,
356
+ tags: args?.tags as string[] | undefined,
357
+ limit: (args?.limit as number) ?? 50,
358
+ archived: false,
359
+ },
360
+ );
361
+ return {
362
+ content: [{ type: "text", text: formatMemories(list) }],
363
+ };
364
+ }
365
+
366
+ case "memory_context": {
367
+ const bundle = await client.query(
368
+ "agentMemory/queries:getContextBundle" as any,
369
+ {
370
+ projectId: config.projectId,
371
+ scope: "project",
372
+ activePaths: args?.activePaths as string[] | undefined,
373
+ maxTokens: args?.maxTokens as number | undefined,
374
+ },
375
+ );
376
+ let text = `## Pinned (${bundle.pinned.length})\n\n`;
377
+ for (const m of bundle.pinned) {
378
+ text += `### ${m.title}\n${m.content}\n\n`;
379
+ }
380
+ text += `## Relevant (${bundle.relevant.length})\n\n`;
381
+ for (const m of bundle.relevant) {
382
+ text += `### ${m.title}\n${m.content}\n\n`;
383
+ }
384
+ text += `## Available (${bundle.available.length})\n`;
385
+ for (const m of bundle.available) {
386
+ text += `- ${m.title} (${m.memoryType}, p=${m.priority})\n`;
387
+ }
388
+ return { content: [{ type: "text", text }] };
389
+ }
390
+
391
+ case "memory_forget": {
392
+ await client.mutation(
393
+ "agentMemory/mutations:archive" as any,
394
+ {
395
+ memoryId: args!.memoryId as string,
396
+ actor: "mcp",
397
+ },
398
+ );
399
+ return {
400
+ content: [{ type: "text", text: `Memory archived: ${args!.memoryId}` }],
401
+ };
402
+ }
403
+
404
+ case "memory_restore": {
405
+ await client.mutation(
406
+ "agentMemory/mutations:restore" as any,
407
+ {
408
+ memoryId: args!.memoryId as string,
409
+ actor: "mcp",
410
+ },
411
+ );
412
+ return {
413
+ content: [{ type: "text", text: `Memory restored: ${args!.memoryId}` }],
414
+ };
415
+ }
416
+
417
+ case "memory_update": {
418
+ await client.mutation(
419
+ "agentMemory/mutations:update" as any,
420
+ {
421
+ memoryId: args!.memoryId as string,
422
+ content: args?.content as string | undefined,
423
+ title: args?.title as string | undefined,
424
+ tags: args?.tags as string[] | undefined,
425
+ priority: args?.priority as number | undefined,
426
+ actor: "mcp",
427
+ },
428
+ );
429
+ return {
430
+ content: [{ type: "text", text: `Memory updated: ${args!.memoryId}` }],
431
+ };
432
+ }
433
+
434
+ case "memory_history": {
435
+ const entries = await client.query(
436
+ "agentMemory/queries:history" as any,
437
+ {
438
+ memoryId: args!.memoryId as string,
439
+ limit: (args?.limit as number) ?? 20,
440
+ },
441
+ );
442
+ if (entries.length === 0) {
443
+ return { content: [{ type: "text", text: "No history found." }] };
444
+ }
445
+ const text = entries
446
+ .map(
447
+ (e: any) =>
448
+ `**${e.event}** by ${e.actor} at ${new Date(e.timestamp).toISOString()}\n` +
449
+ (e.previousContent
450
+ ? ` Previous: ${e.previousContent.slice(0, 100)}...\n`
451
+ : "") +
452
+ (e.newContent
453
+ ? ` New: ${e.newContent.slice(0, 100)}...\n`
454
+ : ""),
455
+ )
456
+ .join("\n");
457
+ return { content: [{ type: "text", text }] };
458
+ }
459
+
460
+ case "memory_feedback": {
461
+ await client.mutation(
462
+ "agentMemory/mutations:addFeedback" as any,
463
+ {
464
+ memoryId: args!.memoryId as string,
465
+ sentiment: args!.sentiment as string,
466
+ comment: args?.comment as string | undefined,
467
+ actor: "mcp",
468
+ },
469
+ );
470
+ return {
471
+ content: [
472
+ {
473
+ type: "text",
474
+ text: `Feedback recorded: ${args!.sentiment} for ${args!.memoryId}`,
475
+ },
476
+ ],
477
+ };
478
+ }
479
+
480
+ case "memory_relate": {
481
+ const relationId = await client.mutation(
482
+ "agentMemory/mutations:addRelation" as any,
483
+ {
484
+ projectId: config.projectId,
485
+ fromMemoryId: args!.fromMemoryId as string,
486
+ toMemoryId: args!.toMemoryId as string,
487
+ relationship: args!.relationship as string,
488
+ metadata: { createdBy: "mcp" },
489
+ },
490
+ );
491
+ return {
492
+ content: [
493
+ {
494
+ type: "text",
495
+ text: `Relation created: ${args!.fromMemoryId} --[${args!.relationship}]--> ${args!.toMemoryId} (${relationId})`,
496
+ },
497
+ ],
498
+ };
499
+ }
500
+
501
+ case "memory_relations": {
502
+ const relations = await client.query(
503
+ "agentMemory/queries:getRelations" as any,
504
+ {
505
+ memoryId: args!.memoryId as string,
506
+ relationship: args?.relationship as string | undefined,
507
+ direction: args?.direction as string | undefined,
508
+ },
509
+ );
510
+ if (relations.length === 0) {
511
+ return { content: [{ type: "text", text: "No relations found." }] };
512
+ }
513
+ const text = relations
514
+ .map(
515
+ (r: any) =>
516
+ `${r.fromMemoryId} --[${r.relationship}]--> ${r.toMemoryId}`,
517
+ )
518
+ .join("\n");
519
+ return { content: [{ type: "text", text }] };
520
+ }
521
+
522
+ case "memory_batch_archive": {
523
+ const result = await client.mutation(
524
+ "agentMemory/mutations:batchArchive" as any,
525
+ {
526
+ memoryIds: args!.memoryIds as string[],
527
+ actor: "mcp",
528
+ },
529
+ );
530
+ return {
531
+ content: [
532
+ {
533
+ type: "text",
534
+ text: `Batch archive: ${result.archived} archived, ${result.failed} failed`,
535
+ },
536
+ ],
537
+ };
538
+ }
539
+
540
+ case "memory_ingest": {
541
+ if (!config.llmApiKey) {
542
+ return {
543
+ content: [
544
+ {
545
+ type: "text",
546
+ text: "Ingest requires --llm-api-key flag. Pass an OpenAI-compatible API key to enable intelligent memory extraction.",
547
+ },
548
+ ],
549
+ isError: true,
550
+ };
551
+ }
552
+ const result = await client.action(
553
+ "agentMemory/actions:ingest" as any,
554
+ {
555
+ projectId: config.projectId,
556
+ content: args!.content as string,
557
+ scope: "project",
558
+ agentId: args?.agentId as string | undefined,
559
+ sessionId: args?.sessionId as string | undefined,
560
+ llmApiKey: config.llmApiKey,
561
+ embeddingApiKey: config.embeddingApiKey,
562
+ },
563
+ );
564
+ const summary = result.results
565
+ .map(
566
+ (r: any) =>
567
+ `- [${r.event}] ${r.content.slice(0, 80)}...${r.previousContent ? ` (was: ${r.previousContent.slice(0, 40)}...)` : ""}`,
568
+ )
569
+ .join("\n");
570
+ return {
571
+ content: [
572
+ {
573
+ type: "text",
574
+ text: `Ingested ${result.totalProcessed} facts:\n${summary}`,
575
+ },
576
+ ],
577
+ };
578
+ }
579
+
580
+ default:
581
+ return {
582
+ content: [{ type: "text", text: `Unknown tool: ${name}` }],
583
+ isError: true,
584
+ };
585
+ }
586
+ } catch (error) {
587
+ return {
588
+ content: [
589
+ {
590
+ type: "text",
591
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
592
+ },
593
+ ],
594
+ isError: true,
595
+ };
596
+ }
597
+ });
598
+
599
+ // ── List Resources ──────────────────────────────────────────────
600
+
601
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
602
+ return {
603
+ resources: [
604
+ {
605
+ uri: `memory://project/${config.projectId}/pinned`,
606
+ name: "Pinned Memories",
607
+ description: "High-priority memories always loaded into context",
608
+ mimeType: "text/markdown",
609
+ },
610
+ ],
611
+ };
612
+ });
613
+
614
+ // ── Read Resource ───────────────────────────────────────────────
615
+
616
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
617
+ const { uri } = request.params;
618
+
619
+ if (uri === `memory://project/${config.projectId}/pinned`) {
620
+ const bundle = await client.query(
621
+ "agentMemory/queries:getContextBundle" as any,
622
+ {
623
+ projectId: config.projectId,
624
+ scope: "project",
625
+ },
626
+ );
627
+
628
+ const text = bundle.pinned
629
+ .map(
630
+ (m: { title: string; content: string }) =>
631
+ `## ${m.title}\n\n${m.content}`,
632
+ )
633
+ .join("\n\n---\n\n");
634
+
635
+ return {
636
+ contents: [
637
+ {
638
+ uri,
639
+ mimeType: "text/markdown",
640
+ text: text || "No pinned memories.",
641
+ },
642
+ ],
643
+ };
644
+ }
645
+
646
+ return {
647
+ contents: [
648
+ {
649
+ uri,
650
+ mimeType: "text/plain",
651
+ text: "Resource not found.",
652
+ },
653
+ ],
654
+ };
655
+ });
656
+
657
+ // ── Start ───────────────────────────────────────────────────────
658
+
659
+ const transport = new StdioServerTransport();
660
+ await server.connect(transport);
661
+ console.error(`agent-memory MCP server running for project "${config.projectId}"`);
662
+ }
663
+
664
+ // ── Helpers ─────────────────────────────────────────────────────────
665
+
666
+ function formatMemories(
667
+ memories: Array<{
668
+ _id: string;
669
+ title: string;
670
+ content: string;
671
+ memoryType: string;
672
+ priority?: number;
673
+ agentId?: string;
674
+ sessionId?: string;
675
+ accessCount?: number;
676
+ positiveCount?: number;
677
+ negativeCount?: number;
678
+ }>,
679
+ ): string {
680
+ if (memories.length === 0) return "No memories found.";
681
+
682
+ return memories
683
+ .map((m) => {
684
+ const meta: string[] = [];
685
+ if (m.priority !== undefined) meta.push(`p=${m.priority}`);
686
+ if (m.agentId) meta.push(`agent=${m.agentId}`);
687
+ if (m.accessCount) meta.push(`accessed=${m.accessCount}x`);
688
+ if (m.positiveCount || m.negativeCount) {
689
+ meta.push(`feedback=+${m.positiveCount ?? 0}/-${m.negativeCount ?? 0}`);
690
+ }
691
+ const metaStr = meta.length > 0 ? ` (${meta.join(", ")})` : "";
692
+ return `## ${m.title} [${m.memoryType}]${metaStr}\n\n${m.content}`;
693
+ })
694
+ .join("\n\n---\n\n");
695
+ }