@agentmemory/agentmemory 0.8.11 → 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.
@@ -154,11 +154,31 @@ const CORE_TOOLS = [
154
154
  limit: {
155
155
  type: "number",
156
156
  description: "Max results to return (default 10)"
157
+ },
158
+ format: {
159
+ type: "string",
160
+ description: "Result format: full, compact, or narrative (default full)"
161
+ },
162
+ token_budget: {
163
+ type: "number",
164
+ description: "Optional token budget to trim returned results"
157
165
  }
158
166
  },
159
167
  required: ["query"]
160
168
  }
161
169
  },
170
+ {
171
+ name: "memory_compress_file",
172
+ description: "Compress a markdown file to reduce token usage while preserving headings, URLs, and code blocks. Creates a .original.md backup before writing.",
173
+ inputSchema: {
174
+ type: "object",
175
+ properties: { filePath: {
176
+ type: "string",
177
+ description: "Path to the markdown file to compress"
178
+ } },
179
+ required: ["filePath"]
180
+ }
181
+ },
162
182
  {
163
183
  name: "memory_save",
164
184
  description: "Explicitly save an important insight, decision, or pattern to long-term memory.",
@@ -1090,7 +1110,7 @@ function getStandalonePersistPath() {
1090
1110
 
1091
1111
  //#endregion
1092
1112
  //#region src/version.ts
1093
- const VERSION = "0.8.11";
1113
+ const VERSION = "0.9.0";
1094
1114
 
1095
1115
  //#endregion
1096
1116
  //#region src/state/schema.ts
@@ -1098,6 +1118,81 @@ function generateId(prefix) {
1098
1118
  return `${prefix}_${Date.now().toString(36)}_${crypto.randomUUID().replace(/-/g, "").slice(0, 12)}`;
1099
1119
  }
1100
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
+
1101
1196
  //#endregion
1102
1197
  //#region src/mcp/standalone.ts
1103
1198
  const IMPLEMENTED_TOOLS = new Set([
@@ -1115,6 +1210,13 @@ const SERVER_INFO = {
1115
1210
  protocolVersion: "2024-11-05"
1116
1211
  };
1117
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
+ }
1118
1220
  function normalizeList(value) {
1119
1221
  if (!value) return [];
1120
1222
  if (Array.isArray(value)) return value.map((v) => typeof v === "string" ? v.trim() : "").filter((v) => v.length > 0);
@@ -1129,21 +1231,94 @@ function parseLimit(raw, fallback = DEFAULT_LIMIT) {
1129
1231
  if (!Number.isFinite(n) || n <= 0) return fallback;
1130
1232
  return Math.min(Math.floor(n), MAX_LIMIT);
1131
1233
  }
1132
- 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 };
1133
1243
  switch (toolName) {
1134
1244
  case "memory_save": {
1135
- const rawContent = args.content;
1136
- if (typeof rawContent !== "string" || !rawContent.trim()) throw new Error("content is required");
1137
- 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": {
1138
1313
  const id = generateId("mem");
1139
1314
  const isoNow = (/* @__PURE__ */ new Date()).toISOString();
1140
1315
  await kvInstance.set("mem:memories", id, {
1141
1316
  id,
1142
- type: args.type || "fact",
1143
- title: content.slice(0, 80),
1144
- content,
1145
- concepts: normalizeList(args.concepts),
1146
- 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,
1147
1322
  createdAt: isoNow,
1148
1323
  updatedAt: isoNow,
1149
1324
  strength: 7,
@@ -1152,80 +1327,69 @@ async function handleToolCall(toolName, args, kvInstance = kv) {
1152
1327
  sessionIds: []
1153
1328
  });
1154
1329
  kvInstance.persist();
1155
- return { content: [{
1156
- type: "text",
1157
- text: JSON.stringify({ saved: id })
1158
- }] };
1330
+ return textResponse({ saved: id });
1159
1331
  }
1160
1332
  case "memory_recall":
1161
1333
  case "memory_smart_search": {
1162
- const rawQuery = args.query;
1163
- if (typeof rawQuery !== "string" || !rawQuery.trim()) throw new Error("query is required");
1164
- const query = rawQuery.trim().toLowerCase();
1165
- const limit = parseLimit(args.limit);
1166
- const results = (await kvInstance.list("mem:memories")).filter((m) => {
1167
- return [
1168
- typeof m["title"] === "string" ? m["title"] : "",
1169
- typeof m["content"] === "string" ? m["content"] : "",
1170
- Array.isArray(m["files"]) ? m["files"].join(" ") : "",
1171
- Array.isArray(m["concepts"]) ? m["concepts"].join(" ") : "",
1172
- Array.isArray(m["sessionIds"]) ? m["sessionIds"].join(" ") : "",
1173
- typeof m["id"] === "string" ? m["id"] : ""
1174
- ].join(" ").toLowerCase().includes(query);
1175
- }).slice(0, limit);
1176
- return { content: [{
1177
- type: "text",
1178
- text: JSON.stringify(results, null, 2)
1179
- }] };
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);
1180
1350
  }
1181
1351
  case "memory_sessions": {
1182
1352
  const sessions = await kvInstance.list("mem:sessions");
1183
- const limit = parseLimit(args.limit, 20);
1184
- return { content: [{
1185
- type: "text",
1186
- text: JSON.stringify({ sessions: sessions.slice(0, limit) }, null, 2)
1187
- }] };
1353
+ const limit = v.limit ?? 20;
1354
+ return textResponse({ sessions: sessions.slice(0, limit) }, true);
1188
1355
  }
1189
1356
  case "memory_governance_delete": {
1190
- const ids = normalizeList(args.memoryIds);
1191
- if (ids.length === 0) throw new Error("memoryIds is required");
1192
1357
  let deleted = 0;
1193
- 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)) {
1194
1359
  await kvInstance.delete("mem:memories", id);
1195
1360
  deleted++;
1196
1361
  }
1197
1362
  kvInstance.persist();
1198
- return { content: [{
1199
- type: "text",
1200
- text: JSON.stringify({
1201
- deleted,
1202
- requested: ids.length,
1203
- reason: args.reason || "plugin skill request"
1204
- })
1205
- }] };
1206
- }
1207
- case "memory_export": {
1208
- const memories = await kvInstance.list("mem:memories");
1209
- const sessions = await kvInstance.list("mem:sessions");
1210
- return { content: [{
1211
- type: "text",
1212
- text: JSON.stringify({
1213
- version: VERSION,
1214
- memories,
1215
- sessions
1216
- }, null, 2)
1217
- }] };
1363
+ return textResponse({
1364
+ deleted,
1365
+ requested: (v.memoryIds || []).length,
1366
+ reason: v.reason
1367
+ });
1218
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);
1219
1374
  case "memory_audit": {
1220
1375
  const entries = await kvInstance.list("mem:audit");
1221
- const limit = parseLimit(args.limit, 50);
1222
- return { content: [{
1223
- type: "text",
1224
- text: JSON.stringify(entries.slice(0, limit), null, 2)
1225
- }] };
1376
+ const limit = v.limit ?? 50;
1377
+ return textResponse({ entries: entries.slice(0, limit) }, true);
1226
1378
  }
1227
- 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();
1228
1391
  }
1392
+ return handleLocal(validated, kvInstance);
1229
1393
  }
1230
1394
  const transport = createStdioTransport(async (method, params) => {
1231
1395
  switch (method) {