@agentmemory/agentmemory 0.8.12 → 0.9.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.
@@ -1110,7 +1110,7 @@ function getStandalonePersistPath() {
1110
1110
 
1111
1111
  //#endregion
1112
1112
  //#region src/version.ts
1113
- const VERSION = "0.8.12";
1113
+ const VERSION = "0.9.0";
1114
1114
 
1115
1115
  //#endregion
1116
1116
  //#region src/state/schema.ts
@@ -1118,6 +1118,81 @@ function generateId(prefix) {
1118
1118
  return `${prefix}_${Date.now().toString(36)}_${crypto.randomUUID().replace(/-/g, "").slice(0, 12)}`;
1119
1119
  }
1120
1120
 
1121
+ //#endregion
1122
+ //#region src/mcp/rest-proxy.ts
1123
+ const DEFAULT_URL = "http://localhost:3111";
1124
+ const HEALTH_PROBE_TIMEOUT_MS = 500;
1125
+ const CALL_TIMEOUT_MS = 15e3;
1126
+ const LOCAL_MODE_TTL_MS = 3e4;
1127
+ let cached = null;
1128
+ let cachedAt = 0;
1129
+ let probeInFlight = null;
1130
+ function baseUrl() {
1131
+ return (process.env["AGENTMEMORY_URL"] || DEFAULT_URL).replace(/\/+$/, "");
1132
+ }
1133
+ function authHeader() {
1134
+ const secret = process.env["AGENTMEMORY_SECRET"];
1135
+ return secret ? { authorization: `Bearer ${secret}` } : {};
1136
+ }
1137
+ async function probe(url) {
1138
+ try {
1139
+ return (await fetch(`${url}/agentmemory/livez`, {
1140
+ method: "GET",
1141
+ headers: authHeader(),
1142
+ signal: AbortSignal.timeout(HEALTH_PROBE_TIMEOUT_MS)
1143
+ })).ok;
1144
+ } catch {
1145
+ return false;
1146
+ }
1147
+ }
1148
+ function invalidateHandle() {
1149
+ cached = null;
1150
+ cachedAt = 0;
1151
+ }
1152
+ async function resolveHandle() {
1153
+ const now = Date.now();
1154
+ if (cached) if (cached.mode === "local" && now - cachedAt >= LOCAL_MODE_TTL_MS) {
1155
+ cached = null;
1156
+ cachedAt = 0;
1157
+ } else return cached;
1158
+ if (probeInFlight) return probeInFlight;
1159
+ const url = baseUrl();
1160
+ probeInFlight = (async () => {
1161
+ if (await probe(url)) {
1162
+ const handle = {
1163
+ mode: "proxy",
1164
+ baseUrl: url,
1165
+ call: async (path, init) => {
1166
+ const res = await fetch(`${url}${path}`, {
1167
+ ...init,
1168
+ headers: {
1169
+ "content-type": "application/json",
1170
+ ...authHeader(),
1171
+ ...init?.headers
1172
+ },
1173
+ signal: AbortSignal.timeout(CALL_TIMEOUT_MS)
1174
+ });
1175
+ if (!res.ok) throw new Error(`${init?.method || "GET"} ${path} -> ${res.status} ${res.statusText}`);
1176
+ const text = await res.text();
1177
+ return text ? JSON.parse(text) : null;
1178
+ }
1179
+ };
1180
+ cached = handle;
1181
+ cachedAt = Date.now();
1182
+ return handle;
1183
+ }
1184
+ const local = { mode: "local" };
1185
+ cached = local;
1186
+ cachedAt = Date.now();
1187
+ return local;
1188
+ })();
1189
+ try {
1190
+ return await probeInFlight;
1191
+ } finally {
1192
+ probeInFlight = null;
1193
+ }
1194
+ }
1195
+
1121
1196
  //#endregion
1122
1197
  //#region src/mcp/standalone.ts
1123
1198
  const IMPLEMENTED_TOOLS = new Set([
@@ -1135,6 +1210,13 @@ const SERVER_INFO = {
1135
1210
  protocolVersion: "2024-11-05"
1136
1211
  };
1137
1212
  const kv = new InMemoryKV(getStandalonePersistPath());
1213
+ let modeAnnounced = false;
1214
+ function announceMode(handle) {
1215
+ if (modeAnnounced) return;
1216
+ modeAnnounced = true;
1217
+ if (handle.mode === "proxy") process.stderr.write(`[@agentmemory/mcp] proxying to agentmemory server at ${handle.baseUrl}\n`);
1218
+ else process.stderr.write(`[@agentmemory/mcp] no server reachable at ${process.env["AGENTMEMORY_URL"] || "http://localhost:3111"}; falling back to local InMemoryKV\n`);
1219
+ }
1138
1220
  function normalizeList(value) {
1139
1221
  if (!value) return [];
1140
1222
  if (Array.isArray(value)) return value.map((v) => typeof v === "string" ? v.trim() : "").filter((v) => v.length > 0);
@@ -1149,21 +1231,94 @@ function parseLimit(raw, fallback = DEFAULT_LIMIT) {
1149
1231
  if (!Number.isFinite(n) || n <= 0) return fallback;
1150
1232
  return Math.min(Math.floor(n), MAX_LIMIT);
1151
1233
  }
1152
- async function handleToolCall(toolName, args, kvInstance = kv) {
1234
+ function textResponse(payload, pretty = false) {
1235
+ return { content: [{
1236
+ type: "text",
1237
+ text: JSON.stringify(payload, null, pretty ? 2 : 0)
1238
+ }] };
1239
+ }
1240
+ function validate(toolName, args) {
1241
+ if (!IMPLEMENTED_TOOLS.has(toolName)) throw new Error(`Unknown tool: ${toolName}`);
1242
+ const v = { tool: toolName };
1153
1243
  switch (toolName) {
1154
1244
  case "memory_save": {
1155
- const rawContent = args.content;
1156
- if (typeof rawContent !== "string" || !rawContent.trim()) throw new Error("content is required");
1157
- const content = rawContent;
1245
+ const content = args["content"];
1246
+ if (typeof content !== "string" || !content.trim()) throw new Error("content is required");
1247
+ v.content = content;
1248
+ v.type = args["type"] || "fact";
1249
+ v.concepts = normalizeList(args["concepts"]);
1250
+ v.files = normalizeList(args["files"]);
1251
+ return v;
1252
+ }
1253
+ case "memory_recall":
1254
+ case "memory_smart_search": {
1255
+ const query = args["query"];
1256
+ if (typeof query !== "string" || !query.trim()) throw new Error("query is required");
1257
+ v.query = query.trim();
1258
+ v.limit = parseLimit(args["limit"]);
1259
+ return v;
1260
+ }
1261
+ case "memory_sessions":
1262
+ v.limit = parseLimit(args["limit"], 20);
1263
+ return v;
1264
+ case "memory_governance_delete": {
1265
+ const ids = normalizeList(args["memoryIds"]);
1266
+ if (ids.length === 0) throw new Error("memoryIds is required");
1267
+ v.memoryIds = ids;
1268
+ v.reason = args["reason"] || "plugin skill request";
1269
+ return v;
1270
+ }
1271
+ case "memory_export": return v;
1272
+ case "memory_audit":
1273
+ v.limit = parseLimit(args["limit"], 50);
1274
+ return v;
1275
+ default: throw new Error(`Unknown tool: ${toolName}`);
1276
+ }
1277
+ }
1278
+ async function handleProxy(v, handle) {
1279
+ switch (v.tool) {
1280
+ case "memory_save": return textResponse(await handle.call("/agentmemory/remember", {
1281
+ method: "POST",
1282
+ body: JSON.stringify({
1283
+ content: v.content,
1284
+ type: v.type,
1285
+ concepts: v.concepts,
1286
+ files: v.files
1287
+ })
1288
+ }));
1289
+ case "memory_recall":
1290
+ case "memory_smart_search": return textResponse(await handle.call("/agentmemory/smart-search", {
1291
+ method: "POST",
1292
+ body: JSON.stringify({
1293
+ query: v.query,
1294
+ limit: v.limit
1295
+ })
1296
+ }), true);
1297
+ case "memory_sessions": return textResponse(await handle.call(`/agentmemory/sessions?limit=${v.limit}`, { method: "GET" }), true);
1298
+ case "memory_governance_delete": return textResponse(await handle.call("/agentmemory/governance/memories", {
1299
+ method: "POST",
1300
+ body: JSON.stringify({
1301
+ memoryIds: v.memoryIds,
1302
+ reason: v.reason
1303
+ })
1304
+ }));
1305
+ case "memory_export": return textResponse(await handle.call("/agentmemory/export", { method: "GET" }), true);
1306
+ case "memory_audit": return textResponse(await handle.call(`/agentmemory/audit?limit=${v.limit}`, { method: "GET" }), true);
1307
+ default: throw new Error(`Unknown tool: ${v.tool}`);
1308
+ }
1309
+ }
1310
+ async function handleLocal(v, kvInstance) {
1311
+ switch (v.tool) {
1312
+ case "memory_save": {
1158
1313
  const id = generateId("mem");
1159
1314
  const isoNow = (/* @__PURE__ */ new Date()).toISOString();
1160
1315
  await kvInstance.set("mem:memories", id, {
1161
1316
  id,
1162
- type: args.type || "fact",
1163
- title: content.slice(0, 80),
1164
- content,
1165
- concepts: normalizeList(args.concepts),
1166
- files: normalizeList(args.files),
1317
+ type: v.type,
1318
+ title: (v.content || "").slice(0, 80),
1319
+ content: v.content,
1320
+ concepts: v.concepts,
1321
+ files: v.files,
1167
1322
  createdAt: isoNow,
1168
1323
  updatedAt: isoNow,
1169
1324
  strength: 7,
@@ -1172,81 +1327,69 @@ async function handleToolCall(toolName, args, kvInstance = kv) {
1172
1327
  sessionIds: []
1173
1328
  });
1174
1329
  kvInstance.persist();
1175
- return { content: [{
1176
- type: "text",
1177
- text: JSON.stringify({ saved: id })
1178
- }] };
1330
+ return textResponse({ saved: id });
1179
1331
  }
1180
1332
  case "memory_recall":
1181
1333
  case "memory_smart_search": {
1182
- const rawQuery = args.query;
1183
- if (typeof rawQuery !== "string" || !rawQuery.trim()) throw new Error("query is required");
1184
- const query = rawQuery.trim().toLowerCase();
1185
- const limit = parseLimit(args.limit);
1186
- const results = (await kvInstance.list("mem:memories")).filter((m) => {
1187
- const text = [
1188
- typeof m["title"] === "string" ? m["title"] : "",
1189
- typeof m["content"] === "string" ? m["content"] : "",
1190
- Array.isArray(m["files"]) ? m["files"].join(" ") : "",
1191
- Array.isArray(m["concepts"]) ? m["concepts"].join(" ") : "",
1192
- Array.isArray(m["sessionIds"]) ? m["sessionIds"].join(" ") : "",
1193
- typeof m["id"] === "string" ? m["id"] : ""
1194
- ].join(" ").toLowerCase();
1195
- return query.split(/\s+/).every((word) => text.includes(word));
1196
- }).slice(0, limit);
1197
- return { content: [{
1198
- type: "text",
1199
- text: JSON.stringify(results, null, 2)
1200
- }] };
1334
+ const query = (v.query || "").toLowerCase();
1335
+ const limit = v.limit ?? DEFAULT_LIMIT;
1336
+ return textResponse({
1337
+ mode: "compact",
1338
+ results: (await kvInstance.list("mem:memories")).filter((m) => {
1339
+ const text = [
1340
+ typeof m["title"] === "string" ? m["title"] : "",
1341
+ typeof m["content"] === "string" ? m["content"] : "",
1342
+ Array.isArray(m["files"]) ? m["files"].join(" ") : "",
1343
+ Array.isArray(m["concepts"]) ? m["concepts"].join(" ") : "",
1344
+ Array.isArray(m["sessionIds"]) ? m["sessionIds"].join(" ") : "",
1345
+ typeof m["id"] === "string" ? m["id"] : ""
1346
+ ].join(" ").toLowerCase();
1347
+ return query.split(/\s+/).every((word) => text.includes(word));
1348
+ }).slice(0, limit)
1349
+ }, true);
1201
1350
  }
1202
1351
  case "memory_sessions": {
1203
1352
  const sessions = await kvInstance.list("mem:sessions");
1204
- const limit = parseLimit(args.limit, 20);
1205
- return { content: [{
1206
- type: "text",
1207
- text: JSON.stringify({ sessions: sessions.slice(0, limit) }, null, 2)
1208
- }] };
1353
+ const limit = v.limit ?? 20;
1354
+ return textResponse({ sessions: sessions.slice(0, limit) }, true);
1209
1355
  }
1210
1356
  case "memory_governance_delete": {
1211
- const ids = normalizeList(args.memoryIds);
1212
- if (ids.length === 0) throw new Error("memoryIds is required");
1213
1357
  let deleted = 0;
1214
- for (const id of ids) if (await kvInstance.get("mem:memories", id)) {
1358
+ for (const id of v.memoryIds || []) if (await kvInstance.get("mem:memories", id)) {
1215
1359
  await kvInstance.delete("mem:memories", id);
1216
1360
  deleted++;
1217
1361
  }
1218
1362
  kvInstance.persist();
1219
- return { content: [{
1220
- type: "text",
1221
- text: JSON.stringify({
1222
- deleted,
1223
- requested: ids.length,
1224
- reason: args.reason || "plugin skill request"
1225
- })
1226
- }] };
1227
- }
1228
- case "memory_export": {
1229
- const memories = await kvInstance.list("mem:memories");
1230
- const sessions = await kvInstance.list("mem:sessions");
1231
- return { content: [{
1232
- type: "text",
1233
- text: JSON.stringify({
1234
- version: VERSION,
1235
- memories,
1236
- sessions
1237
- }, null, 2)
1238
- }] };
1363
+ return textResponse({
1364
+ deleted,
1365
+ requested: (v.memoryIds || []).length,
1366
+ reason: v.reason
1367
+ });
1239
1368
  }
1369
+ case "memory_export": return textResponse({
1370
+ version: VERSION,
1371
+ memories: await kvInstance.list("mem:memories"),
1372
+ sessions: await kvInstance.list("mem:sessions")
1373
+ }, true);
1240
1374
  case "memory_audit": {
1241
1375
  const entries = await kvInstance.list("mem:audit");
1242
- const limit = parseLimit(args.limit, 50);
1243
- return { content: [{
1244
- type: "text",
1245
- text: JSON.stringify(entries.slice(0, limit), null, 2)
1246
- }] };
1376
+ const limit = v.limit ?? 50;
1377
+ return textResponse({ entries: entries.slice(0, limit) }, true);
1247
1378
  }
1248
- default: throw new Error(`Unknown tool: ${toolName}`);
1379
+ default: throw new Error(`Unknown tool: ${v.tool}`);
1380
+ }
1381
+ }
1382
+ async function handleToolCall(toolName, args, kvInstance = kv) {
1383
+ const validated = validate(toolName, args);
1384
+ const handle = await resolveHandle();
1385
+ announceMode(handle);
1386
+ if (handle.mode === "proxy") try {
1387
+ return await handleProxy(validated, handle);
1388
+ } catch (err) {
1389
+ process.stderr.write(`[@agentmemory/mcp] proxy call failed for ${toolName}: ${err instanceof Error ? err.message : String(err)}; invalidating handle and falling back to local KV\n`);
1390
+ invalidateHandle();
1249
1391
  }
1392
+ return handleLocal(validated, kvInstance);
1250
1393
  }
1251
1394
  const transport = createStdioTransport(async (method, params) => {
1252
1395
  switch (method) {