@agentmemory/agentmemory 0.7.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.
Files changed (259) hide show
  1. package/.claude-plugin/marketplace.json +14 -0
  2. package/.github/workflows/ci.yml +22 -0
  3. package/.github/workflows/publish.yml +28 -0
  4. package/AGENTS.md +113 -0
  5. package/LICENSE +190 -0
  6. package/README.md +828 -0
  7. package/assets/banner.png +0 -0
  8. package/assets/demo.gif +0 -0
  9. package/assets/demo.mp4 +0 -0
  10. package/benchmark/QUALITY.md +73 -0
  11. package/benchmark/REAL-EMBEDDINGS.md +67 -0
  12. package/benchmark/SCALE.md +110 -0
  13. package/benchmark/dataset.ts +293 -0
  14. package/benchmark/quality-eval.ts +643 -0
  15. package/benchmark/real-embeddings-eval.ts +405 -0
  16. package/benchmark/scale-eval.ts +398 -0
  17. package/dist/cli.d.mts +1 -0
  18. package/dist/cli.mjs +137 -0
  19. package/dist/cli.mjs.map +1 -0
  20. package/dist/docker-compose.yml +14 -0
  21. package/dist/hooks/notification.d.mts +1 -0
  22. package/dist/hooks/notification.mjs +45 -0
  23. package/dist/hooks/notification.mjs.map +1 -0
  24. package/dist/hooks/post-tool-failure.d.mts +1 -0
  25. package/dist/hooks/post-tool-failure.mjs +45 -0
  26. package/dist/hooks/post-tool-failure.mjs.map +1 -0
  27. package/dist/hooks/post-tool-use.d.mts +1 -0
  28. package/dist/hooks/post-tool-use.mjs +53 -0
  29. package/dist/hooks/post-tool-use.mjs.map +1 -0
  30. package/dist/hooks/pre-compact.d.mts +1 -0
  31. package/dist/hooks/pre-compact.mjs +50 -0
  32. package/dist/hooks/pre-compact.mjs.map +1 -0
  33. package/dist/hooks/pre-tool-use.d.mts +1 -0
  34. package/dist/hooks/pre-tool-use.mjs +69 -0
  35. package/dist/hooks/pre-tool-use.mjs.map +1 -0
  36. package/dist/hooks/prompt-submit.d.mts +1 -0
  37. package/dist/hooks/prompt-submit.mjs +40 -0
  38. package/dist/hooks/prompt-submit.mjs.map +1 -0
  39. package/dist/hooks/session-end.d.mts +1 -0
  40. package/dist/hooks/session-end.mjs +61 -0
  41. package/dist/hooks/session-end.mjs.map +1 -0
  42. package/dist/hooks/session-start.d.mts +1 -0
  43. package/dist/hooks/session-start.mjs +42 -0
  44. package/dist/hooks/session-start.mjs.map +1 -0
  45. package/dist/hooks/stop.d.mts +1 -0
  46. package/dist/hooks/stop.mjs +33 -0
  47. package/dist/hooks/stop.mjs.map +1 -0
  48. package/dist/hooks/subagent-start.d.mts +1 -0
  49. package/dist/hooks/subagent-start.mjs +43 -0
  50. package/dist/hooks/subagent-start.mjs.map +1 -0
  51. package/dist/hooks/subagent-stop.d.mts +1 -0
  52. package/dist/hooks/subagent-stop.mjs +45 -0
  53. package/dist/hooks/subagent-stop.mjs.map +1 -0
  54. package/dist/hooks/task-completed.d.mts +1 -0
  55. package/dist/hooks/task-completed.mjs +46 -0
  56. package/dist/hooks/task-completed.mjs.map +1 -0
  57. package/dist/iii-config.yaml +51 -0
  58. package/dist/index.d.mts +2 -0
  59. package/dist/index.mjs +13776 -0
  60. package/dist/index.mjs.map +1 -0
  61. package/dist/src-QxitMPfJ.mjs +13775 -0
  62. package/dist/src-QxitMPfJ.mjs.map +1 -0
  63. package/dist/standalone.d.mts +1 -0
  64. package/dist/standalone.mjs +1155 -0
  65. package/dist/standalone.mjs.map +1 -0
  66. package/dist/transformers-BX_tgxdO.mjs +38684 -0
  67. package/dist/transformers-BX_tgxdO.mjs.map +1 -0
  68. package/dist/transformers-KMm1i9no.mjs +38683 -0
  69. package/dist/transformers-KMm1i9no.mjs.map +1 -0
  70. package/docker-compose.yml +14 -0
  71. package/iii-config.yaml +51 -0
  72. package/package.json +59 -0
  73. package/plugin/.claude-plugin/plugin.json +10 -0
  74. package/plugin/hooks/hooks.json +77 -0
  75. package/plugin/scripts/diagnostics.mjs +551 -0
  76. package/plugin/scripts/notification.mjs +45 -0
  77. package/plugin/scripts/post-tool-failure.mjs +45 -0
  78. package/plugin/scripts/post-tool-use.mjs +53 -0
  79. package/plugin/scripts/pre-compact.mjs +50 -0
  80. package/plugin/scripts/pre-tool-use.mjs +69 -0
  81. package/plugin/scripts/prompt-submit.mjs +40 -0
  82. package/plugin/scripts/session-end.mjs +61 -0
  83. package/plugin/scripts/session-start.mjs +42 -0
  84. package/plugin/scripts/stop.mjs +33 -0
  85. package/plugin/scripts/subagent-start.mjs +43 -0
  86. package/plugin/scripts/subagent-stop.mjs +45 -0
  87. package/plugin/scripts/task-completed.mjs +46 -0
  88. package/plugin/skills/forget/SKILL.md +32 -0
  89. package/plugin/skills/recall/SKILL.md +18 -0
  90. package/plugin/skills/remember/SKILL.md +25 -0
  91. package/plugin/skills/session-history/SKILL.md +17 -0
  92. package/src/auth.ts +12 -0
  93. package/src/cli.ts +159 -0
  94. package/src/config.ts +221 -0
  95. package/src/eval/metrics-store.ts +65 -0
  96. package/src/eval/quality.ts +51 -0
  97. package/src/eval/schemas.ts +124 -0
  98. package/src/eval/self-correct.ts +28 -0
  99. package/src/eval/validator.ts +31 -0
  100. package/src/functions/actions.ts +288 -0
  101. package/src/functions/audit.ts +61 -0
  102. package/src/functions/auto-forget.ts +169 -0
  103. package/src/functions/branch-aware.ts +169 -0
  104. package/src/functions/cascade.ts +80 -0
  105. package/src/functions/checkpoints.ts +209 -0
  106. package/src/functions/claude-bridge.ts +161 -0
  107. package/src/functions/compress.ts +194 -0
  108. package/src/functions/consolidate.ts +212 -0
  109. package/src/functions/consolidation-pipeline.ts +258 -0
  110. package/src/functions/context.ts +169 -0
  111. package/src/functions/crystallize.ts +293 -0
  112. package/src/functions/dedup.ts +57 -0
  113. package/src/functions/diagnostics.ts +785 -0
  114. package/src/functions/enrich.ts +132 -0
  115. package/src/functions/evict.ts +163 -0
  116. package/src/functions/export-import.ts +508 -0
  117. package/src/functions/facets.ts +248 -0
  118. package/src/functions/file-index.ts +106 -0
  119. package/src/functions/flow-compress.ts +214 -0
  120. package/src/functions/frontier.ts +196 -0
  121. package/src/functions/governance.ts +131 -0
  122. package/src/functions/graph-retrieval.ts +277 -0
  123. package/src/functions/graph.ts +275 -0
  124. package/src/functions/leases.ts +216 -0
  125. package/src/functions/lessons.ts +253 -0
  126. package/src/functions/mesh.ts +434 -0
  127. package/src/functions/migrate.ts +165 -0
  128. package/src/functions/observe.ts +144 -0
  129. package/src/functions/obsidian-export.ts +310 -0
  130. package/src/functions/patterns.ts +138 -0
  131. package/src/functions/privacy.ts +39 -0
  132. package/src/functions/profile.ts +155 -0
  133. package/src/functions/query-expansion.ts +186 -0
  134. package/src/functions/relations.ts +237 -0
  135. package/src/functions/remember.ts +162 -0
  136. package/src/functions/retention.ts +235 -0
  137. package/src/functions/routines.ts +289 -0
  138. package/src/functions/search.ts +80 -0
  139. package/src/functions/sentinels.ts +417 -0
  140. package/src/functions/signals.ts +186 -0
  141. package/src/functions/sketches.ts +274 -0
  142. package/src/functions/sliding-window.ts +257 -0
  143. package/src/functions/smart-search.ts +115 -0
  144. package/src/functions/snapshot.ts +219 -0
  145. package/src/functions/summarize.ts +155 -0
  146. package/src/functions/team.ts +147 -0
  147. package/src/functions/temporal-graph.ts +476 -0
  148. package/src/functions/timeline.ts +138 -0
  149. package/src/functions/verify.ts +117 -0
  150. package/src/health/monitor.ts +110 -0
  151. package/src/health/thresholds.ts +73 -0
  152. package/src/hooks/notification.ts +52 -0
  153. package/src/hooks/post-tool-failure.ts +58 -0
  154. package/src/hooks/post-tool-use.ts +62 -0
  155. package/src/hooks/pre-compact.ts +60 -0
  156. package/src/hooks/pre-tool-use.ts +72 -0
  157. package/src/hooks/prompt-submit.ts +46 -0
  158. package/src/hooks/session-end.ts +71 -0
  159. package/src/hooks/session-start.ts +48 -0
  160. package/src/hooks/stop.ts +39 -0
  161. package/src/hooks/subagent-start.ts +49 -0
  162. package/src/hooks/subagent-stop.ts +54 -0
  163. package/src/hooks/task-completed.ts +54 -0
  164. package/src/index.ts +342 -0
  165. package/src/mcp/in-memory-kv.ts +61 -0
  166. package/src/mcp/server.ts +1455 -0
  167. package/src/mcp/standalone.ts +177 -0
  168. package/src/mcp/tools-registry.ts +769 -0
  169. package/src/mcp/transport.ts +91 -0
  170. package/src/prompts/compression.ts +67 -0
  171. package/src/prompts/consolidation.ts +48 -0
  172. package/src/prompts/graph-extraction.ts +35 -0
  173. package/src/prompts/summary.ts +38 -0
  174. package/src/prompts/xml.ts +26 -0
  175. package/src/providers/agent-sdk.ts +34 -0
  176. package/src/providers/anthropic.ts +35 -0
  177. package/src/providers/circuit-breaker.ts +82 -0
  178. package/src/providers/embedding/cohere.ts +46 -0
  179. package/src/providers/embedding/gemini.ts +54 -0
  180. package/src/providers/embedding/index.ts +39 -0
  181. package/src/providers/embedding/local.ts +52 -0
  182. package/src/providers/embedding/openai.ts +45 -0
  183. package/src/providers/embedding/openrouter.ts +51 -0
  184. package/src/providers/embedding/voyage.ts +46 -0
  185. package/src/providers/fallback-chain.ts +31 -0
  186. package/src/providers/index.ts +84 -0
  187. package/src/providers/openrouter.ts +71 -0
  188. package/src/providers/resilient.ts +37 -0
  189. package/src/state/hybrid-search.ts +295 -0
  190. package/src/state/index-persistence.ts +63 -0
  191. package/src/state/keyed-mutex.ts +18 -0
  192. package/src/state/kv.ts +33 -0
  193. package/src/state/schema.ts +71 -0
  194. package/src/state/search-index.ts +245 -0
  195. package/src/state/stemmer.ts +104 -0
  196. package/src/state/synonyms.ts +63 -0
  197. package/src/state/vector-index.ts +130 -0
  198. package/src/telemetry/setup.ts +116 -0
  199. package/src/triggers/api.ts +1904 -0
  200. package/src/triggers/events.ts +71 -0
  201. package/src/types.ts +769 -0
  202. package/src/version.ts +1 -0
  203. package/src/viewer/index.html +2497 -0
  204. package/src/viewer/server.ts +207 -0
  205. package/src/xenova.d.ts +3 -0
  206. package/test/actions.test.ts +490 -0
  207. package/test/audit.test.ts +108 -0
  208. package/test/auto-forget.test.ts +188 -0
  209. package/test/cascade.test.ts +277 -0
  210. package/test/checkpoints.test.ts +493 -0
  211. package/test/circuit-breaker.test.ts +107 -0
  212. package/test/claude-bridge.test.ts +178 -0
  213. package/test/confidence.test.ts +247 -0
  214. package/test/consistency.test.ts +61 -0
  215. package/test/consolidation-pipeline.test.ts +251 -0
  216. package/test/crystallize.test.ts +521 -0
  217. package/test/diagnostics.test.ts +638 -0
  218. package/test/embedding-provider.test.ts +49 -0
  219. package/test/enrich.test.ts +209 -0
  220. package/test/eval.test.ts +300 -0
  221. package/test/export-import.test.ts +251 -0
  222. package/test/facets.test.ts +448 -0
  223. package/test/fallback-chain.test.ts +93 -0
  224. package/test/frontier.test.ts +485 -0
  225. package/test/governance.test.ts +147 -0
  226. package/test/graph-retrieval.test.ts +186 -0
  227. package/test/graph.test.ts +160 -0
  228. package/test/helpers/mocks.ts +40 -0
  229. package/test/hybrid-search.test.ts +145 -0
  230. package/test/index-persistence.test.ts +124 -0
  231. package/test/integration.test.ts +265 -0
  232. package/test/leases.test.ts +399 -0
  233. package/test/mcp-prompts.test.ts +218 -0
  234. package/test/mcp-resources.test.ts +286 -0
  235. package/test/mcp-standalone.test.ts +113 -0
  236. package/test/mesh.test.ts +700 -0
  237. package/test/privacy.test.ts +87 -0
  238. package/test/profile.test.ts +161 -0
  239. package/test/query-expansion.test.ts +154 -0
  240. package/test/relations.test.ts +198 -0
  241. package/test/retention.test.ts +245 -0
  242. package/test/routines.test.ts +497 -0
  243. package/test/schema-fingerprint.test.ts +81 -0
  244. package/test/schema.test.ts +42 -0
  245. package/test/search-index.test.ts +128 -0
  246. package/test/sentinels.test.ts +626 -0
  247. package/test/signals.test.ts +410 -0
  248. package/test/sketches.test.ts +549 -0
  249. package/test/sliding-window.test.ts +199 -0
  250. package/test/smart-search.test.ts +169 -0
  251. package/test/snapshot.test.ts +165 -0
  252. package/test/team.test.ts +156 -0
  253. package/test/temporal-graph.test.ts +378 -0
  254. package/test/timeline.test.ts +148 -0
  255. package/test/vector-index.test.ts +79 -0
  256. package/test/verify.test.ts +209 -0
  257. package/test/xml.test.ts +65 -0
  258. package/tsconfig.json +22 -0
  259. package/tsdown.config.ts +62 -0
@@ -0,0 +1,1455 @@
1
+ import type { ISdk, ApiRequest } from "iii-sdk";
2
+ import type { StateKV } from "../state/kv.js";
3
+ import { KV } from "../state/schema.js";
4
+ import type {
5
+ SessionSummary,
6
+ Memory,
7
+ Session,
8
+ GraphNode,
9
+ GraphEdge,
10
+ } from "../types.js";
11
+ import { getVisibleTools } from "./tools-registry.js";
12
+ import { timingSafeCompare } from "../auth.js";
13
+
14
+ type McpResponse = {
15
+ status_code: number;
16
+ headers?: Record<string, string>;
17
+ body: unknown;
18
+ };
19
+
20
+ export function registerMcpEndpoints(
21
+ sdk: ISdk,
22
+ kv: StateKV,
23
+ secret?: string,
24
+ ): void {
25
+ function checkAuth(
26
+ req: ApiRequest,
27
+ sec: string | undefined,
28
+ ): McpResponse | null {
29
+ if (!sec) return null;
30
+ const auth =
31
+ req.headers?.["authorization"] || req.headers?.["Authorization"];
32
+ if (typeof auth !== "string" || !timingSafeCompare(auth, `Bearer ${sec}`)) {
33
+ return { status_code: 401, body: { error: "unauthorized" } };
34
+ }
35
+ return null;
36
+ }
37
+
38
+ sdk.registerFunction(
39
+ { id: "mcp::tools::list" },
40
+ async (req: ApiRequest): Promise<McpResponse> => {
41
+ const authErr = checkAuth(req, secret);
42
+ if (authErr) return authErr;
43
+ return { status_code: 200, body: { tools: getVisibleTools() } };
44
+ },
45
+ );
46
+ sdk.registerTrigger({
47
+ type: "http",
48
+ function_id: "mcp::tools::list",
49
+ config: { api_path: "/agentmemory/mcp/tools", http_method: "GET" },
50
+ });
51
+
52
+ sdk.registerFunction(
53
+ { id: "mcp::tools::call" },
54
+ async (
55
+ req: ApiRequest<{ name: string; arguments: Record<string, unknown> }>,
56
+ ): Promise<McpResponse> => {
57
+ const authErr = checkAuth(req, secret);
58
+ if (authErr) return authErr;
59
+
60
+ if (!req.body || typeof req.body.name !== "string") {
61
+ return { status_code: 400, body: { error: "name is required" } };
62
+ }
63
+
64
+ const { name, arguments: args = {} } = req.body;
65
+
66
+ try {
67
+ switch (name) {
68
+ case "memory_recall": {
69
+ if (typeof args.query !== "string" || !args.query.trim()) {
70
+ return {
71
+ status_code: 400,
72
+ body: { error: "query is required for memory_recall" },
73
+ };
74
+ }
75
+ const result = await sdk.trigger("mem::search", {
76
+ query: args.query,
77
+ limit: (args.limit as number) || 10,
78
+ });
79
+ return {
80
+ status_code: 200,
81
+ body: {
82
+ content: [
83
+ { type: "text", text: JSON.stringify(result, null, 2) },
84
+ ],
85
+ },
86
+ };
87
+ }
88
+
89
+ case "memory_save": {
90
+ if (typeof args.content !== "string" || !args.content.trim()) {
91
+ return {
92
+ status_code: 400,
93
+ body: { error: "content is required for memory_save" },
94
+ };
95
+ }
96
+ const type = (args.type as string) || "fact";
97
+ const concepts =
98
+ typeof args.concepts === "string"
99
+ ? args.concepts.split(",").map((c: string) => c.trim()).filter(Boolean)
100
+ : [];
101
+ const files =
102
+ typeof args.files === "string"
103
+ ? args.files.split(",").map((f: string) => f.trim()).filter(Boolean)
104
+ : [];
105
+
106
+ const result = await sdk.trigger("mem::remember", {
107
+ content: args.content,
108
+ type,
109
+ concepts,
110
+ files,
111
+ });
112
+ return {
113
+ status_code: 200,
114
+ body: {
115
+ content: [{ type: "text", text: JSON.stringify(result) }],
116
+ },
117
+ };
118
+ }
119
+
120
+ case "memory_file_history": {
121
+ if (typeof args.files !== "string" || !args.files.trim()) {
122
+ return {
123
+ status_code: 400,
124
+ body: { error: "files is required for memory_file_history" },
125
+ };
126
+ }
127
+ const fileList = (args.files as string)
128
+ .split(",")
129
+ .map((f: string) => f.trim());
130
+ const result = await sdk.trigger("mem::file-context", {
131
+ sessionId: (args.sessionId as string) || "",
132
+ files: fileList,
133
+ });
134
+ return {
135
+ status_code: 200,
136
+ body: {
137
+ content: [
138
+ {
139
+ type: "text",
140
+ text:
141
+ (result as { context: string }).context ||
142
+ "No history found.",
143
+ },
144
+ ],
145
+ },
146
+ };
147
+ }
148
+
149
+ case "memory_patterns": {
150
+ const result = await sdk.trigger("mem::patterns", {
151
+ project: args.project as string,
152
+ });
153
+ return {
154
+ status_code: 200,
155
+ body: {
156
+ content: [
157
+ { type: "text", text: JSON.stringify(result, null, 2) },
158
+ ],
159
+ },
160
+ };
161
+ }
162
+
163
+ case "memory_sessions": {
164
+ const sessions = await kv.list(KV.sessions);
165
+ return {
166
+ status_code: 200,
167
+ body: {
168
+ content: [
169
+ { type: "text", text: JSON.stringify({ sessions }, null, 2) },
170
+ ],
171
+ },
172
+ };
173
+ }
174
+
175
+ case "memory_smart_search": {
176
+ if (typeof args.query !== "string" || !args.query.trim()) {
177
+ return {
178
+ status_code: 400,
179
+ body: { error: "query is required for memory_smart_search" },
180
+ };
181
+ }
182
+ const expandIds =
183
+ typeof args.expandIds === "string"
184
+ ? args.expandIds.split(",").map((id: string) => id.trim()).slice(0, 20)
185
+ : [];
186
+ const result = await sdk.trigger("mem::smart-search", {
187
+ query: args.query,
188
+ expandIds,
189
+ limit: (args.limit as number) || 10,
190
+ });
191
+ return {
192
+ status_code: 200,
193
+ body: {
194
+ content: [
195
+ { type: "text", text: JSON.stringify(result, null, 2) },
196
+ ],
197
+ },
198
+ };
199
+ }
200
+
201
+ case "memory_timeline": {
202
+ if (typeof args.anchor !== "string" || !args.anchor.trim()) {
203
+ return {
204
+ status_code: 400,
205
+ body: { error: "anchor is required for memory_timeline" },
206
+ };
207
+ }
208
+ const result = await sdk.trigger("mem::timeline", {
209
+ anchor: args.anchor,
210
+ project: (args.project as string) || undefined,
211
+ before: (args.before as number) || 5,
212
+ after: (args.after as number) || 5,
213
+ });
214
+ return {
215
+ status_code: 200,
216
+ body: {
217
+ content: [
218
+ { type: "text", text: JSON.stringify(result, null, 2) },
219
+ ],
220
+ },
221
+ };
222
+ }
223
+
224
+ case "memory_profile": {
225
+ if (typeof args.project !== "string" || !args.project.trim()) {
226
+ return {
227
+ status_code: 400,
228
+ body: { error: "project is required for memory_profile" },
229
+ };
230
+ }
231
+ const result = await sdk.trigger("mem::profile", {
232
+ project: args.project,
233
+ refresh: args.refresh === "true",
234
+ });
235
+ return {
236
+ status_code: 200,
237
+ body: {
238
+ content: [
239
+ { type: "text", text: JSON.stringify(result, null, 2) },
240
+ ],
241
+ },
242
+ };
243
+ }
244
+
245
+ case "memory_export": {
246
+ const result = await sdk.trigger("mem::export", {});
247
+ return {
248
+ status_code: 200,
249
+ body: {
250
+ content: [
251
+ { type: "text", text: JSON.stringify(result, null, 2) },
252
+ ],
253
+ },
254
+ };
255
+ }
256
+
257
+ case "memory_relations": {
258
+ if (typeof args.memoryId !== "string" || !args.memoryId.trim()) {
259
+ return {
260
+ status_code: 400,
261
+ body: { error: "memoryId is required for memory_relations" },
262
+ };
263
+ }
264
+ const rawMaxHops = Number(args.maxHops);
265
+ const rawMinConf = Number(args.minConfidence);
266
+ const result = await sdk.trigger("mem::get-related", {
267
+ memoryId: args.memoryId,
268
+ maxHops: Number.isFinite(rawMaxHops) ? rawMaxHops : 2,
269
+ minConfidence: Number.isFinite(rawMinConf)
270
+ ? Math.max(0, Math.min(1, rawMinConf))
271
+ : 0,
272
+ });
273
+ return {
274
+ status_code: 200,
275
+ body: {
276
+ content: [
277
+ { type: "text", text: JSON.stringify(result, null, 2) },
278
+ ],
279
+ },
280
+ };
281
+ }
282
+
283
+ case "memory_claude_bridge_sync": {
284
+ const direction = (args.direction as string) || "write";
285
+ const funcId =
286
+ direction === "read"
287
+ ? "mem::claude-bridge-read"
288
+ : "mem::claude-bridge-sync";
289
+ try {
290
+ const result = await sdk.trigger(funcId, {});
291
+ return {
292
+ status_code: 200,
293
+ body: {
294
+ content: [
295
+ { type: "text", text: JSON.stringify(result, null, 2) },
296
+ ],
297
+ },
298
+ };
299
+ } catch {
300
+ return {
301
+ status_code: 200,
302
+ body: {
303
+ content: [
304
+ {
305
+ type: "text",
306
+ text: "Claude bridge not enabled. Set CLAUDE_MEMORY_BRIDGE=true",
307
+ },
308
+ ],
309
+ },
310
+ };
311
+ }
312
+ }
313
+
314
+ case "memory_graph_query": {
315
+ try {
316
+ const result = await sdk.trigger("mem::graph-query", {
317
+ startNodeId: args.startNodeId as string,
318
+ nodeType: args.nodeType as string,
319
+ maxDepth: args.maxDepth as number,
320
+ query: args.query as string,
321
+ });
322
+ return {
323
+ status_code: 200,
324
+ body: {
325
+ content: [
326
+ { type: "text", text: JSON.stringify(result, null, 2) },
327
+ ],
328
+ },
329
+ };
330
+ } catch {
331
+ return {
332
+ status_code: 200,
333
+ body: {
334
+ content: [
335
+ {
336
+ type: "text",
337
+ text: "Knowledge graph not enabled. Set GRAPH_EXTRACTION_ENABLED=true",
338
+ },
339
+ ],
340
+ },
341
+ };
342
+ }
343
+ }
344
+
345
+ case "memory_consolidate": {
346
+ try {
347
+ const result = await sdk.trigger("mem::consolidate-pipeline", {
348
+ tier: args.tier as string,
349
+ });
350
+ return {
351
+ status_code: 200,
352
+ body: {
353
+ content: [
354
+ { type: "text", text: JSON.stringify(result, null, 2) },
355
+ ],
356
+ },
357
+ };
358
+ } catch {
359
+ return {
360
+ status_code: 200,
361
+ body: {
362
+ content: [
363
+ {
364
+ type: "text",
365
+ text: "Consolidation not enabled. Set CONSOLIDATION_ENABLED=true",
366
+ },
367
+ ],
368
+ },
369
+ };
370
+ }
371
+ }
372
+
373
+ case "memory_team_share": {
374
+ if (
375
+ typeof args.itemId !== "string" ||
376
+ typeof args.itemType !== "string"
377
+ ) {
378
+ return {
379
+ status_code: 400,
380
+ body: { error: "itemId and itemType are required" },
381
+ };
382
+ }
383
+ try {
384
+ const result = await sdk.trigger("mem::team-share", {
385
+ itemId: args.itemId,
386
+ itemType: args.itemType,
387
+ });
388
+ return {
389
+ status_code: 200,
390
+ body: {
391
+ content: [
392
+ { type: "text", text: JSON.stringify(result, null, 2) },
393
+ ],
394
+ },
395
+ };
396
+ } catch {
397
+ return {
398
+ status_code: 200,
399
+ body: {
400
+ content: [
401
+ {
402
+ type: "text",
403
+ text: "Team memory not enabled. Set TEAM_ID and USER_ID",
404
+ },
405
+ ],
406
+ },
407
+ };
408
+ }
409
+ }
410
+
411
+ case "memory_team_feed": {
412
+ try {
413
+ const result = await sdk.trigger("mem::team-feed", {
414
+ limit: (args.limit as number) || 20,
415
+ });
416
+ return {
417
+ status_code: 200,
418
+ body: {
419
+ content: [
420
+ { type: "text", text: JSON.stringify(result, null, 2) },
421
+ ],
422
+ },
423
+ };
424
+ } catch {
425
+ return {
426
+ status_code: 200,
427
+ body: {
428
+ content: [
429
+ {
430
+ type: "text",
431
+ text: "Team memory not enabled. Set TEAM_ID and USER_ID",
432
+ },
433
+ ],
434
+ },
435
+ };
436
+ }
437
+ }
438
+
439
+ case "memory_audit": {
440
+ try {
441
+ const result = await sdk.trigger("mem::audit-query", {
442
+ operation: args.operation as string,
443
+ limit: (args.limit as number) || 50,
444
+ });
445
+ return {
446
+ status_code: 200,
447
+ body: {
448
+ content: [
449
+ { type: "text", text: JSON.stringify(result, null, 2) },
450
+ ],
451
+ },
452
+ };
453
+ } catch {
454
+ return {
455
+ status_code: 200,
456
+ body: {
457
+ content: [{ type: "text", text: "Audit query failed" }],
458
+ isError: true,
459
+ },
460
+ };
461
+ }
462
+ }
463
+
464
+ case "memory_governance_delete": {
465
+ if (typeof args.memoryIds !== "string") {
466
+ return {
467
+ status_code: 400,
468
+ body: { error: "memoryIds is required" },
469
+ };
470
+ }
471
+ const ids = (args.memoryIds as string)
472
+ .split(",")
473
+ .map((id) => id.trim())
474
+ .filter(Boolean);
475
+ try {
476
+ const result = await sdk.trigger("mem::governance-delete", {
477
+ memoryIds: ids,
478
+ reason: args.reason as string,
479
+ });
480
+ return {
481
+ status_code: 200,
482
+ body: {
483
+ content: [
484
+ { type: "text", text: JSON.stringify(result, null, 2) },
485
+ ],
486
+ },
487
+ };
488
+ } catch {
489
+ return {
490
+ status_code: 200,
491
+ body: {
492
+ content: [{ type: "text", text: "Governance delete failed" }],
493
+ isError: true,
494
+ },
495
+ };
496
+ }
497
+ }
498
+
499
+ case "memory_snapshot_create": {
500
+ try {
501
+ const result = await sdk.trigger("mem::snapshot-create", {
502
+ message: args.message as string,
503
+ });
504
+ return {
505
+ status_code: 200,
506
+ body: {
507
+ content: [
508
+ { type: "text", text: JSON.stringify(result, null, 2) },
509
+ ],
510
+ },
511
+ };
512
+ } catch {
513
+ return {
514
+ status_code: 200,
515
+ body: {
516
+ content: [
517
+ {
518
+ type: "text",
519
+ text: "Snapshots not enabled. Set SNAPSHOT_ENABLED=true",
520
+ },
521
+ ],
522
+ },
523
+ };
524
+ }
525
+ }
526
+
527
+ case "memory_action_create": {
528
+ if (typeof args.title !== "string" || !args.title.trim()) {
529
+ return {
530
+ status_code: 400,
531
+ body: { error: "title is required" },
532
+ };
533
+ }
534
+ const edges: Array<{ type: string; targetActionId: string }> = [];
535
+ if (typeof args.requires === "string" && args.requires.trim()) {
536
+ for (const id of args.requires.split(",").map((s: string) => s.trim()).filter(Boolean)) {
537
+ edges.push({ type: "requires", targetActionId: id });
538
+ }
539
+ }
540
+ const tags = typeof args.tags === "string" && args.tags.trim()
541
+ ? args.tags.split(",").map((t: string) => t.trim()).filter(Boolean)
542
+ : [];
543
+ const actionResult = await sdk.trigger("mem::action-create", {
544
+ title: args.title,
545
+ description: args.description,
546
+ priority: args.priority,
547
+ project: args.project,
548
+ tags,
549
+ parentId: args.parentId,
550
+ edges: edges.length > 0 ? edges : undefined,
551
+ });
552
+ return {
553
+ status_code: 200,
554
+ body: {
555
+ content: [
556
+ { type: "text", text: JSON.stringify(actionResult, null, 2) },
557
+ ],
558
+ },
559
+ };
560
+ }
561
+
562
+ case "memory_action_update": {
563
+ if (typeof args.actionId !== "string" || !args.actionId.trim()) {
564
+ return {
565
+ status_code: 400,
566
+ body: { error: "actionId is required" },
567
+ };
568
+ }
569
+ const updateResult = await sdk.trigger("mem::action-update", {
570
+ actionId: args.actionId,
571
+ status: args.status,
572
+ result: args.result,
573
+ priority: args.priority,
574
+ });
575
+ return {
576
+ status_code: 200,
577
+ body: {
578
+ content: [
579
+ { type: "text", text: JSON.stringify(updateResult, null, 2) },
580
+ ],
581
+ },
582
+ };
583
+ }
584
+
585
+ case "memory_frontier": {
586
+ const frontierResult = await sdk.trigger("mem::frontier", {
587
+ project: args.project,
588
+ agentId: args.agentId,
589
+ limit: args.limit,
590
+ });
591
+ return {
592
+ status_code: 200,
593
+ body: {
594
+ content: [
595
+ { type: "text", text: JSON.stringify(frontierResult, null, 2) },
596
+ ],
597
+ },
598
+ };
599
+ }
600
+
601
+ case "memory_next": {
602
+ const nextResult = await sdk.trigger("mem::next", {
603
+ project: args.project,
604
+ agentId: args.agentId,
605
+ });
606
+ return {
607
+ status_code: 200,
608
+ body: {
609
+ content: [
610
+ { type: "text", text: JSON.stringify(nextResult, null, 2) },
611
+ ],
612
+ },
613
+ };
614
+ }
615
+
616
+ case "memory_lease": {
617
+ if (
618
+ typeof args.actionId !== "string" ||
619
+ typeof args.agentId !== "string" ||
620
+ typeof args.operation !== "string"
621
+ ) {
622
+ return {
623
+ status_code: 400,
624
+ body: { error: "actionId, agentId, and operation are required" },
625
+ };
626
+ }
627
+ const op = args.operation as string;
628
+ let leaseResult;
629
+ if (op === "acquire") {
630
+ leaseResult = await sdk.trigger("mem::lease-acquire", {
631
+ actionId: args.actionId,
632
+ agentId: args.agentId,
633
+ ttlMs: args.ttlMs,
634
+ });
635
+ } else if (op === "release") {
636
+ leaseResult = await sdk.trigger("mem::lease-release", {
637
+ actionId: args.actionId,
638
+ agentId: args.agentId,
639
+ result: args.result,
640
+ });
641
+ } else if (op === "renew") {
642
+ leaseResult = await sdk.trigger("mem::lease-renew", {
643
+ actionId: args.actionId,
644
+ agentId: args.agentId,
645
+ ttlMs: args.ttlMs,
646
+ });
647
+ } else {
648
+ return {
649
+ status_code: 400,
650
+ body: { error: "operation must be acquire, release, or renew" },
651
+ };
652
+ }
653
+ return {
654
+ status_code: 200,
655
+ body: {
656
+ content: [
657
+ { type: "text", text: JSON.stringify(leaseResult, null, 2) },
658
+ ],
659
+ },
660
+ };
661
+ }
662
+
663
+ case "memory_routine_run": {
664
+ if (typeof args.routineId !== "string") {
665
+ return {
666
+ status_code: 400,
667
+ body: { error: "routineId is required" },
668
+ };
669
+ }
670
+ const runResult = await sdk.trigger("mem::routine-run", {
671
+ routineId: args.routineId,
672
+ project: args.project,
673
+ initiatedBy: args.initiatedBy,
674
+ });
675
+ return {
676
+ status_code: 200,
677
+ body: {
678
+ content: [
679
+ { type: "text", text: JSON.stringify(runResult, null, 2) },
680
+ ],
681
+ },
682
+ };
683
+ }
684
+
685
+ case "memory_signal_send": {
686
+ if (
687
+ typeof args.from !== "string" ||
688
+ typeof args.content !== "string"
689
+ ) {
690
+ return {
691
+ status_code: 400,
692
+ body: { error: "from and content are required" },
693
+ };
694
+ }
695
+ const sigResult = await sdk.trigger("mem::signal-send", {
696
+ from: args.from,
697
+ to: args.to,
698
+ content: args.content,
699
+ type: args.type,
700
+ replyTo: args.replyTo,
701
+ });
702
+ return {
703
+ status_code: 200,
704
+ body: {
705
+ content: [
706
+ { type: "text", text: JSON.stringify(sigResult, null, 2) },
707
+ ],
708
+ },
709
+ };
710
+ }
711
+
712
+ case "memory_signal_read": {
713
+ if (typeof args.agentId !== "string") {
714
+ return {
715
+ status_code: 400,
716
+ body: { error: "agentId is required" },
717
+ };
718
+ }
719
+ const readResult = await sdk.trigger("mem::signal-read", {
720
+ agentId: args.agentId,
721
+ unreadOnly: args.unreadOnly === true || args.unreadOnly === "true",
722
+ threadId: args.threadId,
723
+ limit: args.limit,
724
+ });
725
+ return {
726
+ status_code: 200,
727
+ body: {
728
+ content: [
729
+ { type: "text", text: JSON.stringify(readResult, null, 2) },
730
+ ],
731
+ },
732
+ };
733
+ }
734
+
735
+ case "memory_checkpoint": {
736
+ const cpOp = args.operation as string;
737
+ if (!cpOp) {
738
+ return {
739
+ status_code: 400,
740
+ body: { error: "operation is required" },
741
+ };
742
+ }
743
+ let cpResult;
744
+ if (cpOp === "create") {
745
+ const linkedIds = typeof args.linkedActionIds === "string" && args.linkedActionIds.trim()
746
+ ? args.linkedActionIds.split(",").map((s: string) => s.trim())
747
+ : [];
748
+ cpResult = await sdk.trigger("mem::checkpoint-create", {
749
+ name: args.name,
750
+ description: args.description,
751
+ type: args.type,
752
+ linkedActionIds: linkedIds,
753
+ });
754
+ } else if (cpOp === "resolve") {
755
+ if (typeof args.checkpointId !== "string" || !args.checkpointId.trim()) {
756
+ return {
757
+ status_code: 400,
758
+ body: { error: "checkpointId is required for resolve operation" },
759
+ };
760
+ }
761
+ cpResult = await sdk.trigger("mem::checkpoint-resolve", {
762
+ checkpointId: args.checkpointId,
763
+ status: args.status,
764
+ });
765
+ } else if (cpOp === "list") {
766
+ cpResult = await sdk.trigger("mem::checkpoint-list", {
767
+ status: args.status,
768
+ type: args.type,
769
+ });
770
+ } else {
771
+ return {
772
+ status_code: 400,
773
+ body: { error: "operation must be create, resolve, or list" },
774
+ };
775
+ }
776
+ return {
777
+ status_code: 200,
778
+ body: {
779
+ content: [
780
+ { type: "text", text: JSON.stringify(cpResult, null, 2) },
781
+ ],
782
+ },
783
+ };
784
+ }
785
+
786
+ case "memory_mesh_sync": {
787
+ const meshResult = await sdk.trigger("mem::mesh-sync", {
788
+ peerId: args.peerId,
789
+ direction: args.direction,
790
+ });
791
+ return {
792
+ status_code: 200,
793
+ body: {
794
+ content: [
795
+ { type: "text", text: JSON.stringify(meshResult, null, 2) },
796
+ ],
797
+ },
798
+ };
799
+ }
800
+
801
+ case "memory_sentinel_create": {
802
+ let snlConfig: Record<string, unknown> = {};
803
+ if (typeof args.config === "object" && args.config !== null) {
804
+ snlConfig = args.config as Record<string, unknown>;
805
+ } else if (typeof args.config === "string" && args.config.trim()) {
806
+ try { snlConfig = JSON.parse(args.config); } catch { return { status_code: 400, body: { error: "invalid config JSON" } }; }
807
+ }
808
+ const snlLinked = typeof args.linkedActionIds === "string" && args.linkedActionIds.trim()
809
+ ? args.linkedActionIds.split(",").map((s: string) => s.trim()).filter(Boolean)
810
+ : undefined;
811
+ const snlResult = await sdk.trigger("mem::sentinel-create", {
812
+ name: args.name,
813
+ type: args.type,
814
+ config: snlConfig,
815
+ linkedActionIds: snlLinked,
816
+ expiresInMs: args.expiresInMs,
817
+ });
818
+ return { status_code: 200, body: { content: [{ type: "text", text: JSON.stringify(snlResult, null, 2) }] } };
819
+ }
820
+
821
+ case "memory_sentinel_trigger": {
822
+ let snlTrigPayload: unknown;
823
+ if (args.result !== undefined && args.result !== null) {
824
+ if (typeof args.result === "string") {
825
+ try { snlTrigPayload = JSON.parse(args.result); } catch { return { status_code: 400, body: { error: "invalid result JSON" } }; }
826
+ } else {
827
+ snlTrigPayload = args.result;
828
+ }
829
+ }
830
+ const snlTrigResult = await sdk.trigger("mem::sentinel-trigger", {
831
+ sentinelId: args.sentinelId,
832
+ result: snlTrigPayload,
833
+ });
834
+ return { status_code: 200, body: { content: [{ type: "text", text: JSON.stringify(snlTrigResult, null, 2) }] } };
835
+ }
836
+
837
+ case "memory_sketch_create": {
838
+ const skResult = await sdk.trigger("mem::sketch-create", {
839
+ title: args.title,
840
+ description: args.description,
841
+ expiresInMs: args.expiresInMs,
842
+ project: args.project,
843
+ });
844
+ return { status_code: 200, body: { content: [{ type: "text", text: JSON.stringify(skResult, null, 2) }] } };
845
+ }
846
+
847
+ case "memory_sketch_promote": {
848
+ const skpResult = await sdk.trigger("mem::sketch-promote", {
849
+ sketchId: args.sketchId,
850
+ project: args.project,
851
+ });
852
+ return { status_code: 200, body: { content: [{ type: "text", text: JSON.stringify(skpResult, null, 2) }] } };
853
+ }
854
+
855
+ case "memory_crystallize": {
856
+ if (typeof args.actionIds !== "string" || !args.actionIds.trim()) {
857
+ return { status_code: 400, body: { error: "actionIds is required" } };
858
+ }
859
+ const crysIds = args.actionIds.split(",").map((s: string) => s.trim()).filter(Boolean);
860
+ const crysResult = await sdk.trigger("mem::crystallize", {
861
+ actionIds: crysIds,
862
+ project: args.project,
863
+ sessionId: args.sessionId,
864
+ });
865
+ return { status_code: 200, body: { content: [{ type: "text", text: JSON.stringify(crysResult, null, 2) }] } };
866
+ }
867
+
868
+ case "memory_diagnose": {
869
+ const diagCats = typeof args.categories === "string" && args.categories.trim()
870
+ ? args.categories.split(",").map((s: string) => s.trim()).filter(Boolean)
871
+ : undefined;
872
+ const diagResult = await sdk.trigger("mem::diagnose", { categories: diagCats });
873
+ return { status_code: 200, body: { content: [{ type: "text", text: JSON.stringify(diagResult, null, 2) }] } };
874
+ }
875
+
876
+ case "memory_heal": {
877
+ const healCats = typeof args.categories === "string" && args.categories.trim()
878
+ ? args.categories.split(",").map((s: string) => s.trim()).filter(Boolean)
879
+ : undefined;
880
+ const healResult = await sdk.trigger("mem::heal", {
881
+ categories: healCats,
882
+ dryRun: args.dryRun === true || args.dryRun === "true",
883
+ });
884
+ return { status_code: 200, body: { content: [{ type: "text", text: JSON.stringify(healResult, null, 2) }] } };
885
+ }
886
+
887
+ case "memory_facet_tag": {
888
+ const fctResult = await sdk.trigger("mem::facet-tag", {
889
+ targetId: args.targetId,
890
+ targetType: args.targetType,
891
+ dimension: args.dimension,
892
+ value: args.value,
893
+ });
894
+ return { status_code: 200, body: { content: [{ type: "text", text: JSON.stringify(fctResult, null, 2) }] } };
895
+ }
896
+
897
+ case "memory_facet_query": {
898
+ if (args.matchAll !== undefined && typeof args.matchAll !== "string") {
899
+ return { status_code: 400, body: { error: "matchAll must be a string" } };
900
+ }
901
+ if (args.matchAny !== undefined && typeof args.matchAny !== "string") {
902
+ return { status_code: 400, body: { error: "matchAny must be a string" } };
903
+ }
904
+ const fqAll = typeof args.matchAll === "string" && args.matchAll.trim()
905
+ ? args.matchAll.split(",").map((s: string) => s.trim()).filter(Boolean)
906
+ : undefined;
907
+ const fqAny = typeof args.matchAny === "string" && args.matchAny.trim()
908
+ ? args.matchAny.split(",").map((s: string) => s.trim()).filter(Boolean)
909
+ : undefined;
910
+ const fqResult = await sdk.trigger("mem::facet-query", {
911
+ matchAll: fqAll,
912
+ matchAny: fqAny,
913
+ targetType: args.targetType,
914
+ });
915
+ return { status_code: 200, body: { content: [{ type: "text", text: JSON.stringify(fqResult, null, 2) }] } };
916
+ }
917
+
918
+ case "memory_verify": {
919
+ if (!args.id || typeof args.id !== "string") {
920
+ return { status_code: 400, body: { error: "id is required" } };
921
+ }
922
+ const verifyResult = await sdk.trigger("mem::verify", { id: args.id });
923
+ return { status_code: 200, body: { content: [{ type: "text", text: JSON.stringify(verifyResult, null, 2) }] } };
924
+ }
925
+
926
+ case "memory_lesson_save": {
927
+ if (typeof args.content !== "string" || !args.content.trim()) {
928
+ return { status_code: 400, body: { error: "content is required" } };
929
+ }
930
+ const lessonTags = typeof args.tags === "string" && args.tags.trim()
931
+ ? args.tags.split(",").map((t: string) => t.trim()).filter(Boolean)
932
+ : [];
933
+ const lessonSaveResult = await sdk.trigger("mem::lesson-save", {
934
+ content: args.content,
935
+ context: args.context || "",
936
+ confidence: args.confidence,
937
+ project: args.project,
938
+ tags: lessonTags,
939
+ source: "manual",
940
+ });
941
+ return { status_code: 200, body: { content: [{ type: "text", text: JSON.stringify(lessonSaveResult, null, 2) }] } };
942
+ }
943
+
944
+ case "memory_lesson_recall": {
945
+ if (typeof args.query !== "string" || !args.query.trim()) {
946
+ return { status_code: 400, body: { error: "query is required" } };
947
+ }
948
+ const lessonRecallResult = await sdk.trigger("mem::lesson-recall", {
949
+ query: args.query,
950
+ project: args.project,
951
+ minConfidence: args.minConfidence,
952
+ limit: args.limit,
953
+ });
954
+ return { status_code: 200, body: { content: [{ type: "text", text: JSON.stringify(lessonRecallResult, null, 2) }] } };
955
+ }
956
+
957
+ case "memory_obsidian_export": {
958
+ const exportTypes = typeof args.types === "string" && args.types.trim()
959
+ ? args.types.split(",").map((t: string) => t.trim()).filter(Boolean)
960
+ : undefined;
961
+ const obsidianResult = await sdk.trigger("mem::obsidian-export", {
962
+ vaultDir: args.vaultDir,
963
+ types: exportTypes,
964
+ });
965
+ return { status_code: 200, body: { content: [{ type: "text", text: JSON.stringify(obsidianResult, null, 2) }] } };
966
+ }
967
+
968
+ default:
969
+ return {
970
+ status_code: 400,
971
+ body: { error: `Unknown tool: ${name}` },
972
+ };
973
+ }
974
+ } catch (err) {
975
+ return {
976
+ status_code: 500,
977
+ body: {
978
+ error: "Internal error",
979
+ },
980
+ };
981
+ }
982
+ },
983
+ );
984
+ sdk.registerTrigger({
985
+ type: "http",
986
+ function_id: "mcp::tools::call",
987
+ config: { api_path: "/agentmemory/mcp/call", http_method: "POST" },
988
+ });
989
+
990
+ const MCP_RESOURCES = [
991
+ {
992
+ uri: "agentmemory://status",
993
+ name: "Agent Memory Status",
994
+ description: "Current session count, memory count, and health status",
995
+ mimeType: "application/json",
996
+ },
997
+ {
998
+ uri: "agentmemory://project/{name}/profile",
999
+ name: "Project Profile",
1000
+ description:
1001
+ "Top concepts, frequently modified files, and conventions for a project",
1002
+ mimeType: "application/json",
1003
+ },
1004
+ {
1005
+ uri: "agentmemory://project/{name}/recent",
1006
+ name: "Recent Sessions",
1007
+ description: "Last 5 session summaries for a project",
1008
+ mimeType: "application/json",
1009
+ },
1010
+ {
1011
+ uri: "agentmemory://memories/latest",
1012
+ name: "Latest Memories",
1013
+ description: "Top 10 latest memories with their type and strength",
1014
+ mimeType: "application/json",
1015
+ },
1016
+ {
1017
+ uri: "agentmemory://graph/stats",
1018
+ name: "Knowledge Graph Stats",
1019
+ description: "Node and edge counts by type in the knowledge graph",
1020
+ mimeType: "application/json",
1021
+ },
1022
+ {
1023
+ uri: "agentmemory://team/{id}/profile",
1024
+ name: "Team Profile",
1025
+ description: "Team memory profile with shared concepts and patterns",
1026
+ mimeType: "application/json",
1027
+ },
1028
+ ];
1029
+
1030
+ sdk.registerFunction(
1031
+ { id: "mcp::resources::list" },
1032
+ async (req: ApiRequest): Promise<McpResponse> => {
1033
+ const authErr = checkAuth(req, secret);
1034
+ if (authErr) return authErr;
1035
+ return { status_code: 200, body: { resources: MCP_RESOURCES } };
1036
+ },
1037
+ );
1038
+ sdk.registerTrigger({
1039
+ type: "http",
1040
+ function_id: "mcp::resources::list",
1041
+ config: { api_path: "/agentmemory/mcp/resources", http_method: "GET" },
1042
+ });
1043
+
1044
+ sdk.registerFunction(
1045
+ { id: "mcp::resources::read" },
1046
+ async (req: ApiRequest<{ uri: string }>): Promise<McpResponse> => {
1047
+ const authErr = checkAuth(req, secret);
1048
+ if (authErr) return authErr;
1049
+
1050
+ const uri = req.body?.uri;
1051
+ if (!uri || typeof uri !== "string") {
1052
+ return { status_code: 400, body: { error: "uri is required" } };
1053
+ }
1054
+
1055
+ try {
1056
+ if (uri === "agentmemory://status") {
1057
+ const sessions = await kv.list<Session>(KV.sessions);
1058
+ const memories = await kv.list<Memory>(KV.memories);
1059
+ const healthData = await kv.list(KV.health).catch(() => []);
1060
+ return {
1061
+ status_code: 200,
1062
+ body: {
1063
+ contents: [
1064
+ {
1065
+ uri,
1066
+ mimeType: "application/json",
1067
+ text: JSON.stringify({
1068
+ sessionCount: sessions.length,
1069
+ memoryCount: memories.length,
1070
+ healthStatus:
1071
+ healthData.length > 0 ? "available" : "no-data",
1072
+ }),
1073
+ },
1074
+ ],
1075
+ },
1076
+ };
1077
+ }
1078
+
1079
+ const projectProfileMatch = uri.match(
1080
+ /^agentmemory:\/\/project\/(.+)\/profile$/,
1081
+ );
1082
+ if (projectProfileMatch) {
1083
+ let projectName: string;
1084
+ try {
1085
+ projectName = decodeURIComponent(projectProfileMatch[1]);
1086
+ } catch {
1087
+ return {
1088
+ status_code: 400,
1089
+ body: { error: "Invalid percent-encoding in URI" },
1090
+ };
1091
+ }
1092
+ const profile = await sdk.trigger("mem::profile", {
1093
+ project: projectName,
1094
+ });
1095
+ return {
1096
+ status_code: 200,
1097
+ body: {
1098
+ contents: [
1099
+ {
1100
+ uri,
1101
+ mimeType: "application/json",
1102
+ text: JSON.stringify(profile),
1103
+ },
1104
+ ],
1105
+ },
1106
+ };
1107
+ }
1108
+
1109
+ const projectRecentMatch = uri.match(
1110
+ /^agentmemory:\/\/project\/(.+)\/recent$/,
1111
+ );
1112
+ if (projectRecentMatch) {
1113
+ let projectName: string;
1114
+ try {
1115
+ projectName = decodeURIComponent(projectRecentMatch[1]);
1116
+ } catch {
1117
+ return {
1118
+ status_code: 400,
1119
+ body: { error: "Invalid percent-encoding in URI" },
1120
+ };
1121
+ }
1122
+ const summaries = await kv.list<SessionSummary>(KV.summaries);
1123
+ const filtered = summaries
1124
+ .filter((s) => s.project === projectName)
1125
+ .sort(
1126
+ (a, b) =>
1127
+ new Date(b.createdAt).getTime() -
1128
+ new Date(a.createdAt).getTime(),
1129
+ )
1130
+ .slice(0, 5);
1131
+ return {
1132
+ status_code: 200,
1133
+ body: {
1134
+ contents: [
1135
+ {
1136
+ uri,
1137
+ mimeType: "application/json",
1138
+ text: JSON.stringify(filtered),
1139
+ },
1140
+ ],
1141
+ },
1142
+ };
1143
+ }
1144
+
1145
+ if (uri === "agentmemory://memories/latest") {
1146
+ const memories = await kv.list<Memory>(KV.memories);
1147
+ const latest = memories
1148
+ .filter((m) => m.isLatest)
1149
+ .sort(
1150
+ (a, b) =>
1151
+ new Date(b.updatedAt).getTime() -
1152
+ new Date(a.updatedAt).getTime(),
1153
+ )
1154
+ .slice(0, 10)
1155
+ .map((m) => ({
1156
+ id: m.id,
1157
+ title: m.title,
1158
+ type: m.type,
1159
+ strength: m.strength,
1160
+ }));
1161
+ return {
1162
+ status_code: 200,
1163
+ body: {
1164
+ contents: [
1165
+ {
1166
+ uri,
1167
+ mimeType: "application/json",
1168
+ text: JSON.stringify(latest),
1169
+ },
1170
+ ],
1171
+ },
1172
+ };
1173
+ }
1174
+
1175
+ if (uri === "agentmemory://graph/stats") {
1176
+ try {
1177
+ const nodes = await kv.list<GraphNode>(KV.graphNodes);
1178
+ const edges = await kv.list<GraphEdge>(KV.graphEdges);
1179
+ const nodesByType: Record<string, number> = {};
1180
+ for (const n of nodes)
1181
+ nodesByType[n.type] = (nodesByType[n.type] || 0) + 1;
1182
+ const edgesByType: Record<string, number> = {};
1183
+ for (const e of edges)
1184
+ edgesByType[e.type] = (edgesByType[e.type] || 0) + 1;
1185
+ return {
1186
+ status_code: 200,
1187
+ body: {
1188
+ contents: [
1189
+ {
1190
+ uri,
1191
+ mimeType: "application/json",
1192
+ text: JSON.stringify({
1193
+ totalNodes: nodes.length,
1194
+ totalEdges: edges.length,
1195
+ nodesByType,
1196
+ edgesByType,
1197
+ }),
1198
+ },
1199
+ ],
1200
+ },
1201
+ };
1202
+ } catch {
1203
+ return {
1204
+ status_code: 200,
1205
+ body: {
1206
+ contents: [
1207
+ {
1208
+ uri,
1209
+ mimeType: "application/json",
1210
+ text: JSON.stringify({
1211
+ totalNodes: 0,
1212
+ totalEdges: 0,
1213
+ }),
1214
+ },
1215
+ ],
1216
+ },
1217
+ };
1218
+ }
1219
+ }
1220
+
1221
+ const teamProfileMatch = uri.match(
1222
+ /^agentmemory:\/\/team\/(.+)\/profile$/,
1223
+ );
1224
+ if (teamProfileMatch) {
1225
+ try {
1226
+ const teamId = decodeURIComponent(teamProfileMatch[1]);
1227
+ const items = await kv.list(KV.teamShared(teamId));
1228
+ return {
1229
+ status_code: 200,
1230
+ body: {
1231
+ contents: [
1232
+ {
1233
+ uri,
1234
+ mimeType: "application/json",
1235
+ text: JSON.stringify({
1236
+ teamId,
1237
+ sharedItems: items.length,
1238
+ }),
1239
+ },
1240
+ ],
1241
+ },
1242
+ };
1243
+ } catch {
1244
+ return {
1245
+ status_code: 200,
1246
+ body: {
1247
+ contents: [
1248
+ {
1249
+ uri,
1250
+ mimeType: "application/json",
1251
+ text: JSON.stringify({
1252
+ teamId: teamProfileMatch[1],
1253
+ sharedItems: 0,
1254
+ }),
1255
+ },
1256
+ ],
1257
+ },
1258
+ };
1259
+ }
1260
+ }
1261
+
1262
+ return {
1263
+ status_code: 404,
1264
+ body: { error: `Unknown resource: ${uri}` },
1265
+ };
1266
+ } catch {
1267
+ return { status_code: 500, body: { error: "Internal error" } };
1268
+ }
1269
+ },
1270
+ );
1271
+ sdk.registerTrigger({
1272
+ type: "http",
1273
+ function_id: "mcp::resources::read",
1274
+ config: {
1275
+ api_path: "/agentmemory/mcp/resources/read",
1276
+ http_method: "POST",
1277
+ },
1278
+ });
1279
+
1280
+ const MCP_PROMPTS = [
1281
+ {
1282
+ name: "recall_context",
1283
+ description:
1284
+ "Search observations and memories to build context for a task",
1285
+ arguments: [
1286
+ {
1287
+ name: "task_description",
1288
+ description: "What you are working on",
1289
+ required: true,
1290
+ },
1291
+ ],
1292
+ },
1293
+ {
1294
+ name: "session_handoff",
1295
+ description:
1296
+ "Generate a handoff summary for continuing work in a new session",
1297
+ arguments: [
1298
+ {
1299
+ name: "session_id",
1300
+ description: "Session ID to hand off from",
1301
+ required: true,
1302
+ },
1303
+ ],
1304
+ },
1305
+ {
1306
+ name: "detect_patterns",
1307
+ description: "Detect recurring patterns across sessions for a project",
1308
+ arguments: [
1309
+ {
1310
+ name: "project",
1311
+ description: "Project path to analyze (optional)",
1312
+ required: false,
1313
+ },
1314
+ ],
1315
+ },
1316
+ ];
1317
+
1318
+ sdk.registerFunction(
1319
+ { id: "mcp::prompts::list" },
1320
+ async (req: ApiRequest): Promise<McpResponse> => {
1321
+ const authErr = checkAuth(req, secret);
1322
+ if (authErr) return authErr;
1323
+ return { status_code: 200, body: { prompts: MCP_PROMPTS } };
1324
+ },
1325
+ );
1326
+ sdk.registerTrigger({
1327
+ type: "http",
1328
+ function_id: "mcp::prompts::list",
1329
+ config: { api_path: "/agentmemory/mcp/prompts", http_method: "GET" },
1330
+ });
1331
+
1332
+ sdk.registerFunction(
1333
+ { id: "mcp::prompts::get" },
1334
+ async (
1335
+ req: ApiRequest<{ name: string; arguments?: Record<string, string> }>,
1336
+ ): Promise<McpResponse> => {
1337
+ const authErr = checkAuth(req, secret);
1338
+ if (authErr) return authErr;
1339
+
1340
+ const promptName = req.body?.name;
1341
+ if (!promptName || typeof promptName !== "string") {
1342
+ return { status_code: 400, body: { error: "name is required" } };
1343
+ }
1344
+
1345
+ const promptArgs = req.body?.arguments || {};
1346
+
1347
+ try {
1348
+ switch (promptName) {
1349
+ case "recall_context": {
1350
+ const taskDesc = promptArgs.task_description;
1351
+ if (typeof taskDesc !== "string" || !taskDesc.trim()) {
1352
+ return {
1353
+ status_code: 400,
1354
+ body: {
1355
+ error:
1356
+ "task_description argument is required and must be a string",
1357
+ },
1358
+ };
1359
+ }
1360
+ const searchResult = await sdk
1361
+ .trigger("mem::search", { query: taskDesc, limit: 10 })
1362
+ .catch(() => ({ results: [] }));
1363
+ const memories = await kv.list<Memory>(KV.memories);
1364
+ const relevant = memories.filter((m) => m.isLatest).slice(0, 5);
1365
+ return {
1366
+ status_code: 200,
1367
+ body: {
1368
+ messages: [
1369
+ {
1370
+ role: "user",
1371
+ content: {
1372
+ type: "text",
1373
+ text: `Here is relevant context from past sessions for the task: "${taskDesc}"\n\n## Past Observations\n${JSON.stringify(searchResult, null, 2)}\n\n## Relevant Memories\n${JSON.stringify(relevant, null, 2)}`,
1374
+ },
1375
+ },
1376
+ ],
1377
+ },
1378
+ };
1379
+ }
1380
+
1381
+ case "session_handoff": {
1382
+ const sessionId = promptArgs.session_id;
1383
+ if (typeof sessionId !== "string" || !sessionId.trim()) {
1384
+ return {
1385
+ status_code: 400,
1386
+ body: {
1387
+ error: "session_id argument is required and must be a string",
1388
+ },
1389
+ };
1390
+ }
1391
+ const session = await kv.get<Session>(KV.sessions, sessionId);
1392
+ const summaries = await kv.list<SessionSummary>(KV.summaries);
1393
+ const summary = summaries.find((s) => s.sessionId === sessionId);
1394
+ return {
1395
+ status_code: 200,
1396
+ body: {
1397
+ messages: [
1398
+ {
1399
+ role: "user",
1400
+ content: {
1401
+ type: "text",
1402
+ text: `## Session Handoff\n\n### Session\n${JSON.stringify(session, null, 2)}\n\n### Summary\n${JSON.stringify(summary || "No summary available", null, 2)}`,
1403
+ },
1404
+ },
1405
+ ],
1406
+ },
1407
+ };
1408
+ }
1409
+
1410
+ case "detect_patterns": {
1411
+ if (
1412
+ promptArgs.project !== undefined &&
1413
+ typeof promptArgs.project !== "string"
1414
+ ) {
1415
+ return {
1416
+ status_code: 400,
1417
+ body: { error: "project argument must be a string" },
1418
+ };
1419
+ }
1420
+ const result = await sdk.trigger("mem::patterns", {
1421
+ project: promptArgs.project || undefined,
1422
+ });
1423
+ return {
1424
+ status_code: 200,
1425
+ body: {
1426
+ messages: [
1427
+ {
1428
+ role: "user",
1429
+ content: {
1430
+ type: "text",
1431
+ text: `## Pattern Analysis\n\n${JSON.stringify(result, null, 2)}`,
1432
+ },
1433
+ },
1434
+ ],
1435
+ },
1436
+ };
1437
+ }
1438
+
1439
+ default:
1440
+ return {
1441
+ status_code: 400,
1442
+ body: { error: `Unknown prompt: ${promptName}` },
1443
+ };
1444
+ }
1445
+ } catch {
1446
+ return { status_code: 500, body: { error: "Internal error" } };
1447
+ }
1448
+ },
1449
+ );
1450
+ sdk.registerTrigger({
1451
+ type: "http",
1452
+ function_id: "mcp::prompts::get",
1453
+ config: { api_path: "/agentmemory/mcp/prompts/get", http_method: "POST" },
1454
+ });
1455
+ }