@xultrax-web/agent-memory-mcp 0.11.1 → 0.11.2
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.
- package/README.md +28 -4
- package/dist/index.js +150 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -43,11 +43,35 @@ Companion file targets (v0.11.1):
|
|
|
43
43
|
|
|
44
44
|
Set `AGENT_MEMORY_AUTO_EMIT_DIR=/path/to/project` to auto-regenerate all companions on every rule save.
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
### Compliance Receipts (v0.11.2 · primitive · tool wiring in v0.11.3)
|
|
47
|
+
|
|
48
|
+
Receipts are short-lived, HMAC-signed bearer tokens with caveats (Macaroon pattern · [Birgisson et al., NDSS 2014](https://research.google/pubs/pub41892/)). The novel protocol primitive in agent-memory-mcp: server-issued tokens that bind to action + session + rules-version-hash + expiry. Tampering breaks the HMAC. Rule changes invalidate stale receipts (because `rules_version` is part of the signed payload).
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { issueReceipt, validateReceipt } from "@xultrax-web/agent-memory-mcp";
|
|
52
|
+
|
|
53
|
+
// Server-internal: issue a receipt for a destructive action
|
|
54
|
+
const r = issueReceipt({
|
|
55
|
+
caveats: [
|
|
56
|
+
{ type: "action", value: "delete_memory" },
|
|
57
|
+
{ type: "session", value: "sess_abc123" },
|
|
58
|
+
],
|
|
59
|
+
ttl_seconds: 60,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Later: validate before executing the destructive op
|
|
63
|
+
const v = validateReceipt(r, {
|
|
64
|
+
required_caveats: [{ type: "action", value: "delete_memory" }],
|
|
65
|
+
});
|
|
66
|
+
if (!v.valid) throw new Error(v.reason);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
HMAC key lives at `<MEMORY_DIR>/.keyring/hmac-key` · 32 random bytes · mode `0600`. v0.11.3 wires receipts into `delete_memory` + other destructive tools and adds the `check_action` MCP tool.
|
|
70
|
+
|
|
71
|
+
### Roadmap for the v0.11.x series:
|
|
47
72
|
|
|
48
|
-
-
|
|
49
|
-
- `
|
|
50
|
-
- `audit` command (rule conflicts · staleness · receipt-denial log)
|
|
73
|
+
- `check_action` tool · deterministic rule matching · optional Sampling enrichment where clients support it · issues Compliance Receipts when proposed action passes
|
|
74
|
+
- `audit` command · rule conflicts · staleness · receipt-denial log
|
|
51
75
|
|
|
52
76
|
---
|
|
53
77
|
|
package/dist/index.js
CHANGED
|
@@ -27,6 +27,7 @@ import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema
|
|
|
27
27
|
import Fuse from "fuse.js";
|
|
28
28
|
import matter from "gray-matter";
|
|
29
29
|
import { spawnSync } from "node:child_process";
|
|
30
|
+
import { createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
30
31
|
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync, } from "node:fs";
|
|
31
32
|
import { homedir } from "node:os";
|
|
32
33
|
import { join, resolve } from "node:path";
|
|
@@ -1270,6 +1271,154 @@ function toolSaveRule(args) {
|
|
|
1270
1271
|
});
|
|
1271
1272
|
}
|
|
1272
1273
|
// -------------------------------------------------------------
|
|
1274
|
+
// Compliance Receipts · v0.11.2 · the novel protocol primitive
|
|
1275
|
+
// -------------------------------------------------------------
|
|
1276
|
+
//
|
|
1277
|
+
// Receipts are short-lived, HMAC-signed bearer tokens with caveats
|
|
1278
|
+
// (attenuations). Macaroon-style. Issued by `check_action` (v0.11.3),
|
|
1279
|
+
// validated before our own destructive tools execute. Prior art:
|
|
1280
|
+
//
|
|
1281
|
+
// Birgisson et al · "Macaroons: Cookies with Contextual Caveats for
|
|
1282
|
+
// Decentralized Authorization in the Cloud" · Google Research,
|
|
1283
|
+
// NDSS 2014 · https://research.google/pubs/pub41892/
|
|
1284
|
+
//
|
|
1285
|
+
// Why receipts work where MCP Sampling doesn't:
|
|
1286
|
+
// - MCP Sampling is unsupported on Claude Code / Cursor / Cline /
|
|
1287
|
+
// Codex CLI (the primary coding clients) per the MCP client matrix.
|
|
1288
|
+
// - Receipts are server-issued protocol artifacts — they work on every
|
|
1289
|
+
// client because the server controls both ends (issue + validate).
|
|
1290
|
+
// - Receipts bind to: action + session + rules-version-hash + expiry.
|
|
1291
|
+
// Tampering breaks the HMAC. Rule changes invalidate stale receipts.
|
|
1292
|
+
//
|
|
1293
|
+
// Storage:
|
|
1294
|
+
// HMAC key lives at <MEMORY_DIR>/.keyring/hmac-key · 32 random bytes
|
|
1295
|
+
// created on first use with mode 0o600 (owner read/write only).
|
|
1296
|
+
// Caller-rotatable via `agent-memory rotate-key` (a v0.11.x follow-up).
|
|
1297
|
+
//
|
|
1298
|
+
// v0.11.2 ships the PRIMITIVE only — issuance + validation +
|
|
1299
|
+
// canonicalization. Tool wiring (delete_memory + check_action) lands
|
|
1300
|
+
// in v0.11.3.
|
|
1301
|
+
const KEYRING_DIR = join(MEMORY_DIR, ".keyring");
|
|
1302
|
+
const HMAC_KEY_FILE = join(KEYRING_DIR, "hmac-key");
|
|
1303
|
+
const RECEIPT_DEFAULT_TTL_SECONDS = 60;
|
|
1304
|
+
function loadOrCreateHmacKey() {
|
|
1305
|
+
if (existsSync(HMAC_KEY_FILE)) {
|
|
1306
|
+
return readFileSync(HMAC_KEY_FILE);
|
|
1307
|
+
}
|
|
1308
|
+
if (!existsSync(KEYRING_DIR)) {
|
|
1309
|
+
mkdirSync(KEYRING_DIR, { recursive: true });
|
|
1310
|
+
}
|
|
1311
|
+
const key = randomBytes(32); // 256 bits · plenty for HMAC-SHA256
|
|
1312
|
+
// mode 0o600 is owner-only on POSIX; Windows ignores mode but ACLs
|
|
1313
|
+
// default to the user, so practically equivalent for our threat model.
|
|
1314
|
+
writeFileSync(HMAC_KEY_FILE, key, { mode: 0o600 });
|
|
1315
|
+
return key;
|
|
1316
|
+
}
|
|
1317
|
+
/**
|
|
1318
|
+
* Compute the rules-version hash · first 16 hex chars of SHA-256 over
|
|
1319
|
+
* the concatenated bytes of every type=rule memory file, in sorted
|
|
1320
|
+
* filename order. Any rule add / edit / remove changes this hash,
|
|
1321
|
+
* which invalidates outstanding receipts (they were issued against a
|
|
1322
|
+
* different rule set).
|
|
1323
|
+
*/
|
|
1324
|
+
function computeRulesVersion() {
|
|
1325
|
+
const rules = loadAllRules();
|
|
1326
|
+
const sortedPaths = rules.map((r) => r.filePath).sort();
|
|
1327
|
+
const hash = createHash("sha256");
|
|
1328
|
+
for (const fp of sortedPaths) {
|
|
1329
|
+
try {
|
|
1330
|
+
hash.update(readFileSync(fp));
|
|
1331
|
+
}
|
|
1332
|
+
catch {
|
|
1333
|
+
// File disappeared between listMemoryFiles + read; skip it.
|
|
1334
|
+
// Next computation will reflect the change.
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
return hash.digest("hex").slice(0, 16);
|
|
1338
|
+
}
|
|
1339
|
+
/**
|
|
1340
|
+
* Deterministic canonical form for HMAC input · caveats sorted by
|
|
1341
|
+
* (type, value) so the order in which the caller listed them doesn't
|
|
1342
|
+
* change the signature. JSON with no whitespace (single line) so the
|
|
1343
|
+
* exact byte sequence is reproducible across platforms.
|
|
1344
|
+
*/
|
|
1345
|
+
function canonicalizeReceipt(r) {
|
|
1346
|
+
const sortedCaveats = [...r.caveats].sort((a, b) => a.type === b.type ? a.value.localeCompare(b.value) : a.type.localeCompare(b.type));
|
|
1347
|
+
return JSON.stringify({
|
|
1348
|
+
id: r.id,
|
|
1349
|
+
issued_at: r.issued_at,
|
|
1350
|
+
expires_at: r.expires_at,
|
|
1351
|
+
rules_version: r.rules_version,
|
|
1352
|
+
caveats: sortedCaveats,
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
function signReceipt(r) {
|
|
1356
|
+
const key = loadOrCreateHmacKey();
|
|
1357
|
+
return createHmac("sha256", key).update(canonicalizeReceipt(r)).digest("hex");
|
|
1358
|
+
}
|
|
1359
|
+
/**
|
|
1360
|
+
* Issue a fresh Compliance Receipt with the given caveats. The receipt
|
|
1361
|
+
* is bound to the current rule-store hash; any rule edit invalidates it.
|
|
1362
|
+
*/
|
|
1363
|
+
export function issueReceipt(opts) {
|
|
1364
|
+
const now = Math.floor(Date.now() / 1000);
|
|
1365
|
+
const ttl = Math.max(1, opts.ttl_seconds ?? RECEIPT_DEFAULT_TTL_SECONDS);
|
|
1366
|
+
const base = {
|
|
1367
|
+
id: "rcpt_" + randomBytes(8).toString("hex"),
|
|
1368
|
+
issued_at: now,
|
|
1369
|
+
expires_at: now + ttl,
|
|
1370
|
+
rules_version: computeRulesVersion(),
|
|
1371
|
+
caveats: opts.caveats,
|
|
1372
|
+
};
|
|
1373
|
+
return { ...base, signature: signReceipt(base) };
|
|
1374
|
+
}
|
|
1375
|
+
/**
|
|
1376
|
+
* Validate a Compliance Receipt against the current rule store + caller's
|
|
1377
|
+
* required caveats. Returns {valid: true} on success, otherwise
|
|
1378
|
+
* {valid: false, reason: <human-readable>}.
|
|
1379
|
+
*/
|
|
1380
|
+
export function validateReceipt(receipt, opts = {}) {
|
|
1381
|
+
// 1. HMAC verification · constant-time compare to avoid timing leaks.
|
|
1382
|
+
const expected = signReceipt({
|
|
1383
|
+
id: receipt.id,
|
|
1384
|
+
issued_at: receipt.issued_at,
|
|
1385
|
+
expires_at: receipt.expires_at,
|
|
1386
|
+
rules_version: receipt.rules_version,
|
|
1387
|
+
caveats: receipt.caveats,
|
|
1388
|
+
});
|
|
1389
|
+
const expectedBuf = Buffer.from(expected, "hex");
|
|
1390
|
+
const actualBuf = Buffer.from(receipt.signature, "hex");
|
|
1391
|
+
if (expectedBuf.length !== actualBuf.length || !timingSafeEqual(expectedBuf, actualBuf)) {
|
|
1392
|
+
return { valid: false, reason: "invalid signature" };
|
|
1393
|
+
}
|
|
1394
|
+
// 2. Expiry · receipts past their expires_at are dead.
|
|
1395
|
+
const now = Math.floor(Date.now() / 1000);
|
|
1396
|
+
if (now > receipt.expires_at) {
|
|
1397
|
+
return { valid: false, reason: "receipt expired" };
|
|
1398
|
+
}
|
|
1399
|
+
// 3. Rules-version binding · any rule edit since issuance invalidates.
|
|
1400
|
+
const currentRulesVersion = opts.current_rules_version ?? computeRulesVersion();
|
|
1401
|
+
if (receipt.rules_version !== currentRulesVersion) {
|
|
1402
|
+
return {
|
|
1403
|
+
valid: false,
|
|
1404
|
+
reason: `rules changed since receipt issued (was ${receipt.rules_version}, now ${currentRulesVersion})`,
|
|
1405
|
+
};
|
|
1406
|
+
}
|
|
1407
|
+
// 4. Required-caveat check · each required pair must appear on the receipt.
|
|
1408
|
+
if (opts.required_caveats) {
|
|
1409
|
+
for (const required of opts.required_caveats) {
|
|
1410
|
+
const found = receipt.caveats.find((c) => c.type === required.type && c.value === required.value);
|
|
1411
|
+
if (!found) {
|
|
1412
|
+
return {
|
|
1413
|
+
valid: false,
|
|
1414
|
+
reason: `missing required caveat: ${required.type}=${required.value}`,
|
|
1415
|
+
};
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
return { valid: true };
|
|
1420
|
+
}
|
|
1421
|
+
// -------------------------------------------------------------
|
|
1273
1422
|
// Git sync · multi-machine memory via git remote
|
|
1274
1423
|
// -------------------------------------------------------------
|
|
1275
1424
|
//
|
|
@@ -1591,7 +1740,7 @@ function actionColor(action) {
|
|
|
1591
1740
|
// -------------------------------------------------------------
|
|
1592
1741
|
// Server wiring
|
|
1593
1742
|
// -------------------------------------------------------------
|
|
1594
|
-
const server = new Server({ name: "agent-memory", version: "0.11.
|
|
1743
|
+
const server = new Server({ name: "agent-memory", version: "0.11.2" }, { capabilities: { tools: {}, resources: {}, prompts: {} } });
|
|
1595
1744
|
// -------------------------------------------------------------
|
|
1596
1745
|
// Resource URI scheme
|
|
1597
1746
|
// -------------------------------------------------------------
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xultrax-web/agent-memory-mcp",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.2",
|
|
4
4
|
"mcpName": "io.github.xultrax-web/agent-memory-mcp",
|
|
5
5
|
"description": "Markdown memory for AI agents. Plain files you can read, edit, grep, and commit. Operator-grade storage with atomic writes, file locking, tags, [[wiki-links]], find_related, git-backed multi-machine sync, and an Ink-based TUI.",
|
|
6
6
|
"type": "module",
|